Dockerを使っているプロジェクトのCI環境としてShippableを使ってる話
まえがき
みなさん突然ですけどDockerつかってます?
この前とある勉強会でそういう話題になったとき、ぶっちゃけ本番では全然使ってないって感じの反応が多くてそんなもんなのかなあと思ってしょんぼりしてます。
いま僕は開発環境も本番環境もDockerなプロジェクトをやってるんですけど、Docker化するとそれはそれでいろいろ面倒なことがあってまあ面倒です。はい。
この記事はそんな面倒なDockerの中でもCIとCDのところでShippableを使ってみているのでそれについて書きます。
本文
サービスについて
CIサービス業界での立ち位置
公式サイトにもおもいっきり with Docker
って書いてありますね。CIサービスでDockerをウリにしてるのはShippableとCodeShipの2つという印象です。*1
CodeShipはサイトにホワイトペーパー*2があったり、Dockerの何か使うには別途申込が必要だったり、登録後のメールがちょっとめんどくさかったりという理由でいったん放置しています。
特に優れているなあと思う点は、Dockerのキャッシュが残るところでしょうか。
別プロジェクトで使っているCircleCIではbuild + pushで15分くらい待たされるのでなかなかつらいところがありますが、いまShippableを使っていると、pullしないで済んだなーとかbuildここまで使いまわせてるなーなどが確認できて嬉しいです。
あとマスコットがgopherくんの次にかわいいです。
価格
プライベートレポジトリでの利用を含め基本的に無料でいけます。
「並列実行をさせたい!」とか「複数箇所にデプロイしたい!」とかでお金がかかるように見えます。*3
Docker registryにpushするだけなら特にお金がかからないので今ところはタダで問題なく使えています。
複数プロジェクトでガンガンビルドするようになったら並列実行でお金が必要になるかも。
サポート + 情報量
公式のドキュメント(英語)とサポート用GitHubレポジトリ(英語)があります。
日本語情報はみた範囲ではなさそうでした。あってもDocker時代のではなかったので今は参考にならない気がしています。
だからいま書いてます。みんなももっと情報書いてくれー。ツッコミくれー。
GitHubのレポジトリは割と事例が多く、活発に動いているみたいです。参考になります。
いろいろ調べていく中でissue立て逃げの人が多いということも気付きました。解決したなら自分でクローズしようず……
ハマったところ
CircleCIを初めて使ったときとかはなんも気にしないでサンプルのymlをコピペしてそれっぽく直せば動いたんですけどShippableだと全然そんなことないです。
Shippableでは以下のビルドステップを順番にやっています。
役割は名前通りなんですが実行されるコンテナが違います。
CIコンテナで実行されるコンテナは自分で選択できます。
そのコンテナはpost_ci, on_*ステップまで使われ続けます。
そのCIコンテナを準備するためにpre_ciステップがあります。
単なるdocker pullやoptionsだけでCIコンテナの設定が終わるなら不要ですが、ちょっと込み入ったことをしようとするとここでやることになります。
このpre_ci環境もci環境もdockerコンテナとして動いているのが特徴です。というかハマりどころです。
各ステップ内でのdockerコマンド
pre_ciステップでたちあがるコンテナもciステップで立ち上がるコンテナも /var/run/docker.sock
をおおもとのマシン(以下shippable host)と共有しています。
これが何を意味するかというと、ciステップ内でdockerコマンドを実行した場合、実際にはコンテナは「ciステップのコンテナ上ではなく」「ciステップのコンテナを実行しているホスト上に」立ち上がります。
こんな感じのイメージです。
$ docker run --rm hkdnet/foo 誤 | hkdnet/foo | | ---------------| | ci container | | ---------------| | shippable host | 正 | ci container | hkdnet/foo | | ---------------+----------------| | shippable host |
ホストがci containerでないと何が困るのでしょうか。
volume問題
課題
volumeの指定方法は --volume host_path:container_path
です。host_pathなので、ciコンテナ内のパスを指定してもダメです。
この仕様は、こういうストーリーのときに困ります(というか実際これで困った)
- CI対象のレポジトリをとってくる
- docker image化してあるツールとCI対象のレポジトリのファイルをつかって設定ファイルを生成する
- 設定ファイルをもとにdocker buildする
- buildしたimageでdocker runしてテストする
ここで2のときにdocker imageに対してレポジトリをマウントする必要がでてきます。
でも CI対象のレポジトリはci container内にしかなくshippable hostにない のでマウントできません。
こまった。
解決策
ci containerのrunオプションは pre_ci_boot
で指定できます。
なのでci containerの特定のディレクトリをDataVolume化し、DataVolumeをマウントすればOKです。
build: pre_ci_boot: # ci containerの/dataDirをDataVolume化 options: -v /dataDir post_ci: - cp $SHIPPABLE_BUILD_DIR/settings.yml /dataDir/settings.yml # --volumes-fromでci containerを指定 # ci containerは$SHIPPABLE_CONTAINER_NAMEという名前で動いている - docker run --volumes-from $SHIPPABLE_CONTAINER_NAME mycontainer
detachedで残る
CI環境なんだから毎回cleanになってるでしょーって思ったらshippable hostの部分は全然クリーンになってません。
まあキャッシュ残したいんだから仕方ない感じはあります。はい。
で、これでどういうときに困るかっていうとdetachedで実行したコンテナが次回も残り続けます。
そんでportとかのリソース競合起こしたりします。こまった。
on_successかon_failureでstopするか、実行前にstopするか、まあ何かしらの対応が必要でしょう。
テスト実行自体はdocker-composeしているので2つ目のプロジェクトを作った後にようやく気付きました。
プライベートレポジトリ
これよくわかってないのですが、ssh-keyのintegrationを通して/tmp/ssh/hogeに置いた鍵をssh-agent使いつつ bash -e 'git clone private-repogitory' 的なことをやっています。
正しいやりかたわかんないのでだれか助けて。
環境変数
globalとそうでないのがあるのでくせ者。
そもそもShippableでは環境変数毎に並列にテスト実行ができるのだけど、globalと明示していない環境変数はそれぞれのテスト実行時の値になってしまう。
env: - FOO=FOO - BAR=BAR # -> FOO=FOOのときとBAR=BARのときの2回走る。FOO=FOOかつBAR=BARでは走らない # globalならFOO=FOOかつBAR=BARにできる env: global: - FOO=FOO - BAR=BAR # あるいは1行に書けばよいみたい(試したことない env: - FOO=FOO BAR=BAR
あとtokenとかを環境変数にいれるときはブラウザ側からencryptする必要があるのだけど、そのときVALUEを "
でくくらないとエラーになるので注意。
# NG FOO=FOO BAR=BAR # OK FOO="FOO" BAR="BAR"
参考
- Can't mount volume on docker run. · Issue #2353 · Shippable/support
- このissueにかぎらず基本的に本家のdocumentよりissueのほうが事例が載っててわかりやすい印象
だらだらした記事になってしまった。
紹介部分とハマりどころ部分は別記事にしたほうがよかったのかもしれない。