at kaneshin

Free space for me.

Flask を Google App Engine で動作させる


f:id:laplus-knsn:20160905010354p:plain


Google App Engine 上で Python の軽量 Web フレームワークである Flask を使うためのチュートリアルです。

TL;DR

github.com

GAE For Python

とにかく、Flask を App Engine で使うためには App Engine の SDK をインストールしておく必要があります。

インストール

Download the Google App Engine SDK  |  App Engine Documentation  |  Google Cloud Platform

上のリンクにある Google App Engine SDK for Python からインストールすることができます。

Flask on GAE

GitHub - GoogleCloudPlatform/appengine-flask-skeleton: A skeleton for creating Python applications using the Flask framework on App Engine

上のリンクにもありますが、ディレクトリは下のようになります。

app
 |_ app.yaml
 |_ appengine_config.py
 |_ lib/
 |_ main.py

Flask の準備

Flask を App Engine で使うためには Flask をロードしておく必要があります。そのため、ロードできるとうに Flask を lib というディレクトリの中にインストールしておきます。

mkdir lib
pip install -t lib flask

ちなみに、GCPリポジトリでは requirements.txt からインストールするようになっています。

# https://github.com/GoogleCloudPlatform/appengine-flask-skeleton の場合
pip install -r requirements.txt -t lib

この lib ディレクトリを App Engine の実行時にロードするために appengine_config.py へ下記の記述をします。

appengine_config.py

from google.appengine.ext import vendor

# Third-party libraries are stored in "lib", vendoring will make
# sure that they are importable by the application.
vendor.add('lib')

App Engine

App Engine へのリクエストハンドラや静的ファイルへのパス・アプリケーションの設定を app.yaml に記述します。

app.yaml

runtime: python27
api_version: 1
threadsafe: yes

# Handlers define how to route requests to your application.
handlers:
- url: .*
  script: main.app

ルーティング

main.py に Flask をインポートし、簡単なルーティングをします。

main.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    """Return a friendly HTTP greeting."""
    return 'Hello World!'

@app.errorhandler(404)
def page_not_found(e):
    """Return a custom 404 error."""
    return 'Sorry, Nothing at this URL.', 404

@app.errorhandler(500)
def application_error(e):
    """Return a custom 500 error."""
    return 'Sorry, unexpected error: {}'.format(e), 500

動作確認

App Engine の SDK にの中にある dev_appserver.py を使用して実行します。

dev_appserver.py .

※dev_appserver.py が実行できない場合、App Engine の SDK が PATH に通っていない可能性が高いです。

実行後、http://localhost:8080 を叩けば "Hello World!" の文字が確認できると思います。

# 動作確認
curl -XGET localhost:8080

デプロイ

デプロイするプロジェクトを GCP の管理画面にて作成し、Project ID を用意し、下記コマンドの [your-project-id] に記述して実行してください。

appcfg.py update -A [your-project-id] -V v1 .

特にエラーがでなければ https://[your-project-id].appspot.com でアクセスすれば "Hello World!" が表示されていると思います。

iOSDC で LT してきました! - Generative Programming in Swift

Generative Programming in Swift // Speaker Deck

Generative Programming とは?

生成的プログラミング(英: Generative programming)とは、ジェネリックなクラス、プロトタイプベース、テンプレート、アスペクト、コード生成などの技法を使ってソースコード作成を自動化し、プログラマの生産性を向上させるプログラミングのスタイルである。ソフトウェアコンポーネントなどのコード再利用の技法とも連携して使用される。

自動プログラミング - Wikipedia

会場には Generative Programming を実施した方がほとんどいないようでしたが、 Generics も Compile 時のことを考えれば C++ のテンプレートと似ていて生成的プログラミングの一種となります。なので、知らず知らずのうちに Generative Programming は大体の Swift 経験者なら経験しています!

今回の LT ではコード生成 (Code Generation) の方をお話したかったので、自分が Swift でクライアントアプリを作るときに使用している ishkawa/APIKit を元にコード生成をするデモコードを前日の夜にちゃちゃっと作ってみました。

github.com

kaneshin/genkit

まだ、デモ程度の実装しかしていないです。

まず、JSON Schema を用意しておき、それを元に genkit を使用します。使用方法は generate.go に書いてありますが

go run /path/to/genkit/cmd/genkit/main.go /path/to/genkit/cmd/genkit/gen.go -path=./Sources -output=apikit_gen.swift ./api.json

の様に使用します。コマンドとしてインストールしててもOKです。

go get github.com/kaneshin/genkit/cmd/genkit
genkit -path=./Sources -output=apikit_gen.swift ./api.json

これを実行すると、 apikit_gen.swift が生成されます。ただ、 Swift には gofmt に相当するものが無いのでコードのインデントが崩れています。

(swiftformat で探してみると案外あるんですね。LT 前に知りたかった…)

github.com

github.com

Swift Code Generators

ちなみに、他にも Generator は存在しています。

github.com

おわりに

別の LT で BaseViewController の話がありましたが、コード生成前提でコードを設計すると、神クラスのような煩雑なコードをつくり上げるようなことがなくなる気がしているので、コード生成はかなりおすすめです。 社内プロジェクトでもコード生成は結構使っているので、実装者はビジネスロジックを考えることに集中することができます。本当におすすめです。


lestrrat さんも builderscon を作っているので、是非参考にすると良いかもです!

golang の channel を使って Dispatcher-Worker を作り goroutine 爆発させないようにする

golang で処理の高速化をするために goroutine/channel を使って並行処理にし、待ち時間を無駄にしないようにするのは言葉で表すのは簡単ですが、実際にパターンとして落としこむためには経験が必要だなと思うので、今回 Dispatcher-Worker として Job Queue を golang で実装する方法を紹介したいと思います。

この記事は mattn さんの Big Sky :: golang の channel を使ったテクニックあれこれ の次のステップとして読むことをオススメします。

mattn.kaoriya.net

golang で作成したアプリケーションで多くのリクエストをアプリケーションが送受信する必要がある場合、高速に捌くために並行処理にして非同期化を図る場合を想定しています。

今回は get という関数でHTTPリクエストを実行して取得したデータのサイズとそのときの goroutine の数を出力するようにしています。

func get(url string) {
    resp, err := http.DefaultClient.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("Goroutine:%d, URL:%s (%d bytes)", runtime.NumGoroutine(), url, len(body))
}

非同期処理への単純アプローチ

TL;DR https://gist.github.com/kaneshin/c589b958592b4b685accc48249bf4b41

「並行処理するためには go をつければいいんですよね?」という感覚で下記のように実装してしまうと、大規模アプリケーションでは即座に破綻します。 goroutine の数が処理の数に応じて上昇するためです。

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            get(fmt.Sprintf("http://placehold.it/%dx%d", i, i))
        }(i)
    }
    wg.Wait()
}

これのログを見てみると、for文のカウンターに依存して goroutine の数が増えているのがわかります。

...
2016/08/18 17:05:27 Goroutine:104, URL:http://placehold.it/31x31 (2843 bytes)
2016/08/18 17:05:27 Goroutine:101, URL:http://placehold.it/32x32 (2901 bytes)
2016/08/18 17:05:27 Goroutine:98, URL:http://placehold.it/17x17 (2879 bytes) 
2016/08/18 17:05:27 Goroutine:95, URL:http://placehold.it/83x83 (3495 bytes) 
2016/08/18 17:05:27 Goroutine:92, URL:http://placehold.it/57x57 (3197 bytes) 
2016/08/18 17:05:27 Goroutine:89, URL:http://placehold.it/25x25 (2923 bytes) 
2016/08/18 17:05:27 Goroutine:86, URL:http://placehold.it/95x95 (3586 bytes) 
...

今回の Dispatcher-Worker ではこの goroutine を worker の数で制限できるような方針です。

Dispatcher-Worker でのアプローチ

TL;DR: https://gist.github.com/kaneshin/69bd13c7b57ba8bac84fb4de0098b5fc

Dispatcher-Worker のイメージは下記の通りで、dispatcher の方で Idle 状態の worker にキューイングされているメッセージを渡します。

f:id:laplus-knsn:20160818181940p:plain

Dispatcher と Worker

dispatcher ではメッセージのキューイングと Idle 状態の worker を管理できるようにします。

type (
    // Dispatcher represents a management workers.
    Dispatcher struct {
        pool    chan *worker     // Idle 状態の worker の受け入れ先
        queue   chan interface{} // メッセージの受け入れ先
        workers []*worker
        wg      sync.WaitGroup   // 非同期処理の待機用
        quit    chan struct{}
    }
)

worker は処理するメッセージを受け取れるようにします。

type (
    // worker represents the worker that executes the job.
    worker struct {
        dispatcher *Dispatcher
        data       chan interface{} // 受け取ったメッセージの受信先
        quit       chan struct{}
    }
)

Dispatcher と Worker の起動

Dispatcher と Worker は起動させてからはメッセージを受け取り次第勝手に処理を実行するようになります。非同期処理の終了を待機できるようにしておくように sync.WaitGroup もうまくハンドリングします。

Dispatcher はメッセージがキューイングされたとき、 Idle になっている worker にメッセージを送信します。

// Start starts the specified dispatcher but does not wait for it to complete.
func (d *Dispatcher) Start() {
    for _, w := range d.workers {
        w.start()
    }

    go func() {
        for {
            select {
            // メッセージがキューイングされた場合、 v にメッセージを設定
            case v := <-d.queue:
                (<-d.pool).data <- v // 下記の2行と同じ意味
                // worker := <-d.pool // d.pool から Idle の worker がpoolingされるまで待機
                // worker.data <- v // worker.data に メッセージ v を送信

            case <-d.quit:
                return
            }
        }
    }()
}

worker は自身が Idle になり次第自身をプーリングすることと、メッセージを受信したら処理を実施します。

func (w *worker) start() {
    go func() {
        for {
            // dispatcher の pool に自身を送信する(Idle状態を示す)
            w.dispatcher.pool <- w

            select {
            // メッセージがキューイングされた場合、 v にメッセージを設定
            case v := <-w.data:
                if str, ok := v.(string); ok {
                    // get 関数でHTTPリクエスト
                    get(str)
                }

                // WaitGroupのカウントダウン
                w.dispatcher.wg.Done()

            case <-w.quit:
                return
            }
        }
    }()
}

メッセージのキューイング

外からのメッセージをキューイングするのは Dispatcher だけですので、 Dispatcher にエンキュー用の関数を用意します。

// Add adds a given value to the queue of the dispatcher.
func (d *Dispatcher) Add(v interface{}) {
    // キューイングされた場合に処理を待機するために WaitGroup をカウントアップ
    d.wg.Add(1)
    d.queue <- v
}

Dispatcher の待機

非同期処理が終わる前に main の実行を終了してしまった場合、キューイングしたメッセージが処理されないで終了してしまいます。そうならないように Wait 関数を用意します。

// Wait waits for the dispatcher to exit. It must have been started by Start.
func (d *Dispatcher) Wait() {
    d.wg.Wait()
}

単純に sync.WaitGroup の Wait 関数で待機するだけです。

Dispatcher と Worker の初期化と実行

ここまで実装が完了すれば後は初期化を行い、実行するのみです。

const (
    maxWorkers = 3
    maxQueues  = 10000
)

// NewDispatcher returns a pointer of Dispatcher.
func NewDispatcher() *Dispatcher {
    // dispatcher の初期化
    d := &Dispatcher{
        pool:  make(chan *worker, maxWorkers),    // capacity は用意する worker の数
        queue: make(chan interface{}, maxQueues), // capacity はメッセージをキューイングする数
        quit:  make(chan struct{}),
    }

    // worker の初期化
    d.workers = make([]*worker, cap(d.pool))
    for i := 0; i < cap(d.pool); i++ {
        w := worker{
            dispatcher: d,
            data:       make(chan interface{}), // worker でキューイングする場合は capacity を2以上
            quit:       make(chan struct{}),
        }
        d.workers[i] = &w
    }
    return d
}

func main() {
    d := NewDispatcher()

    d.Start()
    for i := 0; i < 100; i++ {
        url := fmt.Sprintf("http://placehold.it/%dx%d", i, i)
        d.Add(url)
    }
    d.Wait()
}

これを実行してみると、 goroutine の数が for文のカウンターではなく、 worker の数に依存しているのがわかります。

...
2016/08/18 18:54:06 Goroutine:22, URL:http://placehold.it/77x77 (3321 bytes)   
2016/08/18 18:54:06 Goroutine:22, URL:http://placehold.it/78x78 (3431 bytes)   
2016/08/18 18:54:06 Goroutine:20, URL:http://placehold.it/79x79 (3427 bytes)   
2016/08/18 18:54:06 Goroutine:16, URL:http://placehold.it/81x81 (3296 bytes)   
2016/08/18 18:54:06 Goroutine:16, URL:http://placehold.it/80x80 (3478 bytes)   
...

コード全体はこちらにあります。

今回の worker はシンプルに実装しているので、worker 側でキューイングすることはしていないですが、延長で実装が可能なので是非試してみて下さい。

おわりに

mattn さんも Big Sky :: golang の channel を使ったテクニックあれこれ の中で話していますが、 goroutine と channel の可能性はかなりあると思っており、今回の一例も単なるベースに過ぎずこれ以上の実装はまだまだ可能です。慣れないと使い処に困る goroutine と channel かもしれませんが、使いこなすことが出来れば実装の視野も広がると思います。是非、この夏に goroutine/channel を使いこなせるようになりましょう。

Golangの標準入力をインタラクティブかパイプで入力を受け取る

Go言語でコマンドラインツールを作るときに入力を受け取るインターフェースでオプションや標準入力で受け付けることはあると思いますが、パイプで渡すことも考慮されているとクールなツールになるなと思っています。

標準入力の受け取り

それぞれの実装方法は簡単です。

インタラクティブ

var stdin string
fmt.Scan(&stdin)
fmt.Println(stdin)

インタラクティブに標準入力からデータを受け取るには fmt.Scan で入力待ちをします。このとき入力した値が渡した変数に格納されます。

パイプ

body, err := ioutil.ReadAll(os.Stdin)
fmt.Println(string(body))

パイプで渡ってきたものは os.Stdin というファイルディスクリプタにデータが入っているので、ここから取得します。

インタラクティブかパイプを判定する

パイプでファイルディスクリプタが渡ってきた場合はそのままそのファイルディスクリプタからデータを取得すれば良いので、インタラクティブな入力待ちは必要ありません。 そんなときは syscall パッケージを利用します。

syscallパッケージ

const ioctlReadTermios = 0x5401  // syscall.TCGETS

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
    fd := syscall.Stdin
    var termios syscall.Termios
    _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
    return err == 0
}

上記の IsTerminal 関数が true で返ってくる場合、インタラクティブにデータを受け取る場合になります。処理としては、/dev/stdin のファイルディスクリプタ値である syscall.Stdin が読み込みをしているかを判定しています。

ただ、この実装は Linux に依存しており、他のPlatformで利用するには ioctlReadTermios の値を適宜変更しなければいけません。 これを自分で実装するのはちょい面倒なので、既に実装がされているものを利用します。

terminalパッケージ

golang.orgに golang.org/x/crypto/ssh/terminal というパッケージが存在していて、ここにあるIsTerminalという関数が先ほどの各Platformの要件を満たしています。

go get -u golang.org/x/crypto/ssh/terminal
import "golang.org/x/crypto/ssh/terminal"

func main() {
    // ...
    if terminal.IsTerminal(syscall.Stdin) {
        // Do something ...
    }
    // ...
}

全体の実装

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "syscall"

    "golang.org/x/crypto/ssh/terminal"
)

func main() {
    if terminal.IsTerminal(syscall.Stdin) {
        // Execute: go run main.go
        fmt.Print("Type something then press the enter key: ")
        var stdin string
        fmt.Scan(&stdin)
        fmt.Printf("Result: %s\n", stdin)
        return
    }

    // Execute: echo "foo" | go run main.go
    body, err := ioutil.ReadAll(os.Stdin)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Result: %s\n", string(body))
}

Ansible: "unsupported parameter for module: gather_subset"

Ansible 2.1を動かしたところ、下記のようなエラーが発生…

[kaneshin@casper-01] ~/local/src/github.com/kaneshin/kamimai (master)
 ( ^o^) < ansible-playbook -C misc/playbook/localhost.yml

PLAY [127.0.0.1] ***************************************************************

TASK [setup] *******************************************************************
fatal: [127.0.0.1]: FAILED! => {"changed": false, "failed": true, "msg": "unsupported parameter for module: gather_subset"}

NO MORE HOSTS LEFT *************************************************************
        to retry, use: --limit @misc/playbook/localhost.retry

PLAY RECAP *********************************************************************
127.0.0.1                  : ok=0    changed=0    unreachable=0    failed=1   

Pythonが2.7.9だとダメなのかと思い、2.7.11にしてみたり、pipでansibleを再インストールしてみたりしたが一向に直らず。 諦めたくなってきたころにGoogle グループにあった回答をしっかり読んでみたら

「aptでインストールしたansbileがあったからそれを削除した。そしたら直った。」とあったので、実際にaptでもインストールしていたことが確認できたのでaptからremoveしたら直った。

[kaneshin@casper-01] ~/local/src/github.com/kaneshin/kamimai (master)
 ( ^o^) < ansible-playbook -C misc/playbook/localhost.yml

PLAY [127.0.0.1] ***************************************************************

TASK [setup] *******************************************************************
ok: [127.0.0.1]

TASK [golang/lint : install golint] ********************************************
skipping: [127.0.0.1]

PLAY RECAP *********************************************************************
127.0.0.1                  : ok=1    changed=0    unreachable=0    failed=0   

地味にハマった…(40分くらい)