- 投稿日:2019-08-27T21:06:57+09:00
GithubにGoで書いたDockerをTravisでCIする
あまり他人のソフト使うのが上手くないので、色々とハマりポイントがあったもので
Git履歴よりハマりポイントのおさらい目指すもの
GithubにTravisCIを連携させることで、PushやPullRequestした時などに
自動的にTestやLintを行い、コードカバレッジを行う事ができる
大手の開発では一般的なスタイルではなかろうか?個人的に勉強ついでに作っていた SlackBotのプログラムを使って勉強してみた
https://github.com/YukiMiyatake/GOSICK
SlackBotである。プラグインを入れる事で機能拡張を行う事ができる
とりあえず勉強用テストプロジェクトである
これからリファクタリングしたり interface化して、LintなりTestなり色々な改造を行う予定
(現在 gomodule導入テストのためプラグインもecho以外動かないはず)dockerファイルを修正
今までDockerfileにて go buildして実行ファイルを起動させていたが
TravisやローカルでTestをまわすために
また、コード修正のたびに docker-compose buildしたくないので
Dockerでは何もせず、シェルスクリプトでビルド実行やテストを行うように変更したDockerファイル
dockerfileWORKDIR /build COPY src . CMD ./build.sh CMD ls CMD ./GOSICKこの辺りを削除した。ぶっちゃけ Goのイメージそのまま使うのでDockerfileは不要で
docker-composeにimage指定するだけでいいけど
今後なにかインストールする可能性もあるので Dockerファイルはのこしたbuild.sh
今回はプラグインのビルドなども入るので、ワンライナーで書かず
ビルドをシェルスクリプトでわけたかった
(最終的にはMakefile等にしたいなあ)build.shif [ $TEST = "test" ]; then cd plugins/echo go test -v ./... --buildmode=plugin cd ../.. go test -v ./... else cd plugins/echo go build --buildmode=plugin cd ../.. go build fiテストと通常ビルドを分けるため $TEST環境変数で分けることにした
Script
docker_app_builddocker-compose up -d docker-compose exec gosick_app bash -C '/app/build.sh'docker_app_testdocker-compose up -d docker-compose exec -e TEST=test gosick_app bash -C '/app/build.sh'テストと実行でファイルを分けたが、環境変数以外はかわらないので、まとめてもいいなあ
docker-compose up も別にすべきかなあ等色々考えているが、しばらく使って考える予定
GithubにTravisを連携させバッヂをつける
https://qiita.com/hoshimado/items/4090d8e64beb8a7f95e1
例えばこの辺を参考に、バッヂまでつける
Travisの設定
とっても苦労したTravisの設定
とりあえずはどのリポジトリをPushしても通知が入るようにしている
プロジェクトのルートに .travis.yml ファイルを作成してPush最初の設定
.travis.yamlsudo: required services: - docker before_install: # - docker-compose up -d install: before_script: script: - scripts/docker_app_run # - docker-compose exec golang go test -v ./... after_script: notifications:いろんなサイトをみて上記の設定ファイルを書いたが、色々なエラーが出てダメ
なので色々とlanguage設定
TravisのページをみるとRubyと判断されていたのでlanguageを設定
.travis.yamllanguage: goただ、Goのバージョンが古かったのでちゃんとバージョン指定してみた
.travis.yamllanguage: go go: - 1.12.xGO111MODULE
ログをみていると GO111MODULE=autoになっていた
今回はgo module使いたいので onにしたいので下記のようにかいてみた.travis.yamlscript: - env GO111MODULE=on scripts/docker_app_testところがログをみても GO111MODULE=autoのままだった
どうやらenvセクションに書く必要があった
.travis.yamlenv: - GO111MODULE=ongo_import_path
githubのアドレスを加えた
.travis.yamlgo_import_path: github.com/YukiMiyatake/GOSICKスクリプトパーミッション
Windowsで開発を行っていたため、ファイルのパーミッションがおかしくなっていた
Travis内でパーミッションを設定してもいいが、根本的な解決としてスクリプトファイルにパーミッションをあたえPushしたhttps://qiita.com/maosanhioro/items/aaade68cdca591232159
この辺をみて、パーミッションを正しく設定したら動いた
DockerComposeのバージョンが古い
docker-composeのバージョンが古かったので念のためバージョンアップを行った
まず、TravisのDockerやDockerComposeバージョンは例えば下記で調べられる
.travis.ymlbefore_install: - docker-compose -v - docker -vこれによると
$ docker-compose -v
docker-compose version 1.17.1, build 6d101fb
before_install.3
$ docker -v
Docker version 17.09.0-ce, build afdb6d4Dockerはとりあえず今回はバージョンはいいとします
DockerComposeだけ 1.24.1にバージョン上げます.travis.ymlenv: - GO111MODULE=on - DOCKER_COMPOSE_VERSION=1.24.1 before_install: - sudo rm /usr/local/bin/docker-compose - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose - chmod +x docker-compose - sudo mv docker-compose /usr/local/binTravis公式のドキュメント通りです。
ところが色々不可解なエラーが出ます
Yamlの罠
yamlじたいほとんど書かないのでよくわかってませんが、気になるポイントをみつけた
.travis.ymlenv: - GO111MODULE=on - DOCKER_COMPOSE_VERSION=1.24.1結論からいうと
GO111MODULE:on
だとGO111MODULEは定義されずautoになるGO111MODULE: on
だとGO111MODULEはtrueが定義されてしまうGO111MODULE= on
だとGO111MODULEは空文字が定義されるよって
GO111MODULE=on以外はダメ
DOCKER_COMPOSE_VERSION = 1.24.1
DOCKER_COMPOSE_VERSION:1.24.1
DOCKER_COMPOSE_VERSION :1.24.1
は共に定義されないDOCKER_COMPOSE_VERSION: 1.24.1
DOCKER_COMPOSE_VERSION=1.24.1
はOK:の場合は前にスペースNG、後ろにスペース必須
=の場合は 前も後ろもスペースNG
GO111MODULEには=しか使えない
DOCKER_COMPOSE_VERSIONは上記ルールを守っているなら:でも=でもOK細かい事はおいといて
.travis.yml- GO111MODULE=on - DOCKER_COMPOSE_VERSION=1.24.1とするとちゃんと定義されるようになった
並列実行問題
.travis.ymlenv: - GO111MODULE=on - DOCKER_COMPOSE_VERSION=1.24.1で両方の環境変数は定義されたが、なぜか2つ実行されているっぽい
その2つの環境変数はGO111MODULE=on, DOCKER_COMPOSE_VERSION定義なし
GO111MODULE定義なし, DOCKER_COMPOSE_VERSION=1.24.1の2つ
どう考えても並列実行されている
ドキュメント見た感じだと globalに設定すればよさそう.travis.ymlenv: global: - GO111MODULE=on - DOCKER_COMPOSE_VERSION=1.24.1これで、すべての実行環境において上記のフラグが立つようになった
今回は使用していないが metrix: に環境変数配列を列挙すると並列で実行できるので
テストの高速化を行う時は使ってみよう
(ただし無料版だとたぶん2並列までしかできない)DBのbind問題
下記の謎エラーが出て結構悩まされた
ERROR: for gosick_mysql Cannot start service gosick_mysql: driver failed programming external connectivity on endpoint gosick_mysql (cb76f17f3761214c04ca3c4ca03fe4b993e65b0ca14b76b711606651d40c8a8c): Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in use ERROR: for gosick_mysql Cannot start service gosick_mysql: driver failed programming external connectivity on endpoint gosick_mysql (cb76f17f3761214c04ca3c4ca03fe4b993e65b0ca14b76b711606651d40c8a8c): Error starting userland proxy: listen tcp 0.0.0.0:3306: bind: address already in useBindAddressが使われている。3306なのでMySQLが既に立ち上がっているようだが、いくら見渡してもそれらしいものがない・・
ので困った
MySQLのDockerは今は使ってないので消せば解決はするが、ちゃんと解決させたいので調べたら、まさかの! TravisでMySQLが立ち上がっていた・・・ので、実行前にTravisのMySQLを落とす
.travis.ymlbefore_install: - sudo /etc/init.d/mysql stopやった!できた!
まとめ
色々な罠があったが、とりあえずTravisCIを動かすことができた
一通り自分の陥った罠をリストアップしたので、誰かの役にたてば幸いですなぜかすべてのテストが通ってないので問題は残ってるけど、TravisCIの問題は解決したということで。
最終的な設定ファイル(不要な設定もあるかもしれないけど)
.travis.ymllanguage: go go: - 1.12.x go_import_path: github.com/YukiMiyatake/GOSICK env: global: - GO111MODULE=on - DOCKER_COMPOSE_VERSION=1.24.1 services: - docker before_install: - sudo /etc/init.d/mysql stop - docker-compose -v - docker -v - sudo rm /usr/local/bin/docker-compose - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose - chmod +x docker-compose - sudo mv docker-compose /usr/local/bin - docker-compose build install: before_script: script: - scripts/docker_app_test after_script: notifications:
- 投稿日:2019-08-27T18:52:17+09:00
goで大食い大会
はじめに
ゴルーチンとチャネルの勉強として大食い大会を書いてみた。
1)コックはひたすらホットドッグを作る
2)ウェイターはひたすらホットドッグをフードファイターへ運ぶ
3)フードファイターはひたすらホットドッグを食べて、自分が食べたホットドッグをカウントする
4)制限時間になったら大会終了。その時点でのランキングを出す。この 1 => 2 => 3 の流れをチャネルを使い、4の制限時間の制御にcontextを使った。
コード
package main import ( "context" "fmt" "time" . "github.com/ahmetb/go-linq" ) func cook( done <-chan interface{}, fn func() interface{}, ) <-chan interface{} { foods := make(chan interface{}) go func() { defer close(foods) for { select { case <-done: return case foods <- fn(): } } }() return foods } func hotDog() interface{} { fmt.Printf("コックはホットドッグを作ります\n") return "ホットドッグ" } func waiter(foods <-chan interface{}) interface{} { food := <-foods fmt.Printf("ウェイターは%vをフードファイターへ持っていきます。\n", food) return food } func waitperson( done <-chan interface{}, foods <-chan interface{}, ) <-chan interface{} { toFoodFighter := make(chan interface{}) go func() { defer close(toFoodFighter) for { select { case <-done: return case toFoodFighter <- waiter(foods): } } }() return toFoodFighter } type FoodFighter struct { name string current int } type FoodFighters []*FoodFighter func (f *FoodFighter) eat(foods <-chan interface{}) *FoodFighter { food := <-foods f.current++ fmt.Printf("%vは%vを%v個食べた\n", f.name, food, f.current) return f } func (f *FoodFighter) startEat( done <-chan interface{}, foods <-chan interface{}, limit int, ) <-chan *FoodFighter { results := make(chan *FoodFighter) go func() { defer close(results) for i := 0; i <= limit; i++ { select { case <-done: return case results <- f.eat(foods): } } }() return results } func start(done <-chan interface{}, f *FoodFighter) <-chan *FoodFighter { return f.startEat(done, waitperson(done, cook(done, hotDog)), 99) } func competition(ctx context.Context, cancel context.CancelFunc, done <-chan interface{}) FoodFighters { defer cancel() var foodFighters FoodFighters var f1Result *FoodFighter var f2Result *FoodFighter var f3Result *FoodFighter f1 := &FoodFighter{name: "ボブ"} f2 := &FoodFighter{name: "ビル"} f3 := &FoodFighter{name: "マイケル"} for { select { case f1Result = <-start(done, f1): case f2Result = <-start(done, f2): case f3Result = <-start(done, f3): case <-ctx.Done(): fmt.Println("大会終了です\n") foodFighters = append(foodFighters, f1Result) foodFighters = append(foodFighters, f2Result) foodFighters = append(foodFighters, f3Result) return foodFighters } } } func displayResult(results FoodFighters) { time.Sleep(1 * time.Second) fmt.Println("大会結果発表\n") sortedResults := From(results).Sort(func(f1, f2 interface{}) bool { return f1.(*FoodFighter).current > f2.(*FoodFighter).current }).Results() for i, fighter := range sortedResults { fmt.Printf("%v位: %v %v個\n", i+1, fighter.(*FoodFighter).name, fighter.(*FoodFighter).current) } } func main() { done := make(chan interface{}) defer close(done) limitTime := 2 * time.Second ctx, cancel := context.WithTimeout(context.Background(), limitTime) results := competition(ctx, cancel, done) displayResult(results) }実行結果
: : マイケルはホットドッグを417個食べた ウェイターはホットドッグをフードファイターへ持っていきます。 ウェイターはホットドッグをフードファイターへ持っていきます。 コックはホットドッグを作ります コックはホットドッグを作ります ボブはホットドッグを447個食べた コックはホットドッグを作ります ビルはホットドッグを442個食べた ウェイターはホットドッグをフードファイターへ持っていきます。 大会結果発表 1位: ボブ 447個 2位: ビル 442個 3位: マイケル 418個goではselectのcase文全体に対して
擬似乱数による一様選択1 をしているらしい。
それぞれのcaseは等しく選択されていく。なので、結果は毎回、拮抗した感じになる。
Go言語による並行処理 p81 ↩
- 投稿日:2019-08-27T18:52:17+09:00
goroutineで大食い大会
はじめに
ゴルーチンとチャネルの勉強として大食い大会を書いてみた。
1)コックはひたすらホットドッグを作る
2)ウェイターはひたすらホットドッグをフードファイターへ運ぶ
3)フードファイターはひたすらホットドッグを食べて、自分が食べたホットドッグをカウントする
4)制限時間になったら大会終了。その時点でのランキングを出す。この 1 => 2 => 3 の流れをチャネルを使い、4の制限時間の制御にcontextを使った。
コード
package main import ( "context" "fmt" "time" . "github.com/ahmetb/go-linq" ) func cook( done <-chan interface{}, fn func() interface{}, ) <-chan interface{} { foods := make(chan interface{}) go func() { defer close(foods) for { select { case <-done: return case foods <- fn(): } } }() return foods } func hotDog() interface{} { fmt.Printf("コックはホットドッグを作ります\n") return "ホットドッグ" } func waiter(foods <-chan interface{}) interface{} { food := <-foods fmt.Printf("ウェイターは%vをフードファイターへ持っていきます。\n", food) return food } func waitperson( done <-chan interface{}, foods <-chan interface{}, ) <-chan interface{} { toFoodFighter := make(chan interface{}) go func() { defer close(toFoodFighter) for { select { case <-done: return case toFoodFighter <- waiter(foods): } } }() return toFoodFighter } type FoodFighter struct { name string current int } type FoodFighters []*FoodFighter func (f *FoodFighter) eat(foods <-chan interface{}) *FoodFighter { food := <-foods f.current++ fmt.Printf("%vは%vを%v個食べた\n", f.name, food, f.current) return f } func (f *FoodFighter) startEat( done <-chan interface{}, foods <-chan interface{}, ) <-chan *FoodFighter { results := make(chan *FoodFighter) go func() { defer close(results) for { select { case <-done: return case results <- f.eat(foods): } } }() return results } func start(done <-chan interface{}, f *FoodFighter) <-chan *FoodFighter { return f.startEat(done, waitperson(done, cook(done, hotDog))) } func competition(ctx context.Context, cancel context.CancelFunc, done <-chan interface{}) FoodFighters { defer cancel() var foodFighters FoodFighters var f1Result *FoodFighter var f2Result *FoodFighter var f3Result *FoodFighter f1 := &FoodFighter{name: "ボブ"} f2 := &FoodFighter{name: "ビル"} f3 := &FoodFighter{name: "マイケル"} for { select { case f1Result = <-start(done, f1): case f2Result = <-start(done, f2): case f3Result = <-start(done, f3): case <-ctx.Done(): fmt.Println("大会終了です\n") foodFighters = append(foodFighters, f1Result) foodFighters = append(foodFighters, f2Result) foodFighters = append(foodFighters, f3Result) return foodFighters } } } func displayResult(results FoodFighters) { time.Sleep(1 * time.Second) fmt.Println("大会結果発表\n") sortedResults := From(results).Sort(func(f1, f2 interface{}) bool { return f1.(*FoodFighter).current > f2.(*FoodFighter).current }).Results() for i, fighter := range sortedResults { fmt.Printf("%v位: %v %v個\n", i+1, fighter.(*FoodFighter).name, fighter.(*FoodFighter).current) } } func foodFight() { done := make(chan interface{}) defer close(done) limitTime := 2 * time.Second ctx, cancel := context.WithTimeout(context.Background(), limitTime) results := competition(ctx, cancel, done) displayResult(results) } func main() { foodFight() }実行結果
: : マイケルはホットドッグを417個食べた ウェイターはホットドッグをフードファイターへ持っていきます。 ウェイターはホットドッグをフードファイターへ持っていきます。 コックはホットドッグを作ります コックはホットドッグを作ります ボブはホットドッグを447個食べた コックはホットドッグを作ります ビルはホットドッグを442個食べた ウェイターはホットドッグをフードファイターへ持っていきます。 大会結果発表 1位: ボブ 447個 2位: ビル 442個 3位: マイケル 418個goではselectのcase文全体に対して
擬似乱数による一様選択1 をしているらしい。
それぞれのcaseは等しく選択されていく。なので、結果は毎回、拮抗した感じになる。
Go言語による並行処理 p81 ↩