Flask を Google App Engine で動作させる
Google App Engine 上で Python の軽量 Web フレームワークである Flask を使うためのチュートリアルです。
TL;DR
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
上のリンクにもありますが、ディレクトリは下のようになります。
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)とは、ジェネリックなクラス、プロトタイプベース、テンプレート、アスペクト、コード生成などの技法を使ってソースコード作成を自動化し、プログラマの生産性を向上させるプログラミングのスタイルである。ソフトウェアコンポーネントなどのコード再利用の技法とも連携して使用される。
会場には Generative Programming を実施した方がほとんどいないようでしたが、 Generics も Compile 時のことを考えれば C++ のテンプレートと似ていて生成的プログラミングの一種となります。なので、知らず知らずのうちに Generative Programming は大体の Swift 経験者なら経験しています!
今回の LT ではコード生成 (Code Generation) の方をお話したかったので、自分が Swift でクライアントアプリを作るときに使用している ishkawa/APIKit を元にコード生成をするデモコードを前日の夜にちゃちゃっと作ってみました。
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 前に知りたかった…)
Swift Code Generators
ちなみに、他にも Generator は存在しています。
おわりに
別の LT で BaseViewController の話がありましたが、コード生成前提でコードを設計すると、神クラスのような煩雑なコードをつくり上げるようなことがなくなる気がしているので、コード生成はかなりおすすめです。 社内プロジェクトでもコード生成は結構使っているので、実装者はビジネスロジックを考えることに集中することができます。本当におすすめです。
buildersconではGo Python PerlのコードをJSON Schemaから作ってる。便利 #iosdc
— Daisuke Maki (@lestrrat) August 20, 2016
lestrrat さんも builderscon を作っているので、是非参考にすると良いかもです!
golang の channel を使って Dispatcher-Worker を作り goroutine 爆発させないようにする
golang で処理の高速化をするために goroutine/channel を使って並行処理にし、待ち時間を無駄にしないようにするのは言葉で表すのは簡単ですが、実際にパターンとして落としこむためには経験が必要だなと思うので、今回 Dispatcher-Worker として Job Queue を golang で実装する方法を紹介したいと思います。
この記事は mattn さんの Big Sky :: golang の channel を使ったテクニックあれこれ の次のステップとして読むことをオススメします。
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 にキューイングされているメッセージを渡します。
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分くらい)