はじめに
こんにちは。Wanoのエンジニアのnarikawaと申します。
うちのチームのメインプロジェクトではCI(テスト)ツールにAWS CodeBuildを使用しています。
今回、別プロジェクトにてCIの仕組みを再考する機会があり、そこで得た知見を社内外で共有するためにこの記事を書いています。
CIとは?
CIとは、Continuous Integrationの略で、継続的インテグレーションと呼ばれています。
CI(継続的インテグレーション)では、開発者が自分のコード変更を頻繁にセントラルリポジトリにマージし、その度に自動化されたビルドとテストを実行します。
小さなサイクルでインテグレーションを繰り返し行い、インテグレーションのエラーを素早く修正することによりチームは統合されたソフトウェアをより迅速に開発できるようになります。
(CI( 継続的インテグレーション )とは | Cloudbees Jenkins | テクマトリックスより引用)
web系は手動testしてエクセルに貼って印刷して、押印もらってpdfにして印刷して保管したりしないんですね。
自動化テストの重要性が高まり、アジャイル開発が浸透してきた昨今だと、当然の技術になってきているとか。
AWS CodeBuild とは?

AWS CodeBuild は、クラウドで動作する完全マネージド型のビルドサービスです。CodeBuild はソースコードをコンパイルし、ユニットテストを実行して、すぐにデプロイできるアーティファクトを生成します。CodeBuild により、独自のビルドサーバーのプロビジョニング、管理、スケーリングが不要になります。Apache Maven、Gradle などの最も一般的なプログラミング言語とビルドツール用のパッケージ済みのビルド環境を提供します。CodeBuild のビルド環境をカスタマイズして、独自のビルドツールを使用することもできます。CodeBuild はピーク時のビルドリクエストに合わせて自動的にスケーリングします。
(AWS CodeBuild とは - AWS CodeBuildより引用)
要は完全マネージドなCIのサービスです。
jenkinsなどのツールは自前でサーバーを管理する必要がありますが、その必要がないです(同じようなSaaSとして、CircleCIやTravisCI などがある)
チームが主にAWSの各種サービスを使用しているため、CI as a ServiceとしてCodeBuildを選択しています。
(CircleCIやTravisCIなどはお高いという話も)
Codebuildで安く楽する話 - Qiita
既存の構成

うちのチームのテックリードが一人で構築してくれたものがあったので、それに手を入れる形で進めました(自分一人では絶対に無理だった。。大感謝)
新しい構成

何故docker in dockerに変更しようと思ったか
大きく分けて以下の2つです。
- 今回のprojectはこちらのチームで開発し、運用は別チームに引き継ぐ形になっているため、運用はよりわかりやすい方がいい(test環境を変更するたびにECRにpushする手間を減らしたい)
- ECSへのデプロイパイプライン構築を考えたときに、testの時からimageを分割し、デプロイフローに近づけてdindしといた方が良い(どうせdeployの時にdindでimageを焼いてECRにぶちこむことになるので)
どうやったか
1.テスト環境のimageをdindのものに変える
-
dindとは
dind は Docker in Docker の略。
Docker コンテナ上で、 Docker のデーモンを起動して、別の(複数の) Docker コンテナを起動できるもの。便利ですね、、、、 -
実行環境設定

Dockerhubのイメージを使う場合は、カスタムイメージを指定して、Dockerhubのリポジトリ名/image名を入れるだけ!
今回はgitlabelinvar/dindを使用させていただきました。(docker-composeコマンドまで入っているので、追加で入れる必要がない)
※訂正(2019/7/5) codebuild公式のstandard2.0にdocker-composeまで入っているようで、かつdockerdも立ち上げてくれるようです)
dindで検索して、ユースケース似合うものを使用するといいと思います。
また、子コンテナを--privileged で起動する必要があるため、 CodeBuild の管理画面でも"特権付与"にチェックを入れる必要があります!
-
CodeBuildのローカルキャッシュ
最近CodeBuildはS3などをアーティファクト保存先に指定しなくても、ローカルでキャッシュできるようになったようです。
docker buildを高速化!CodeBuildのローカルキャッシュ機能を試してみる | DevelopersIO

2.既存のimageをappとredisで切り離し、Dockerfileとdocker-compose.ymlに起こす
- 雑なディレクトリ構成
dockerfiles/
├ dev/
├ test/
│ ├ docker-compose.yml
│ ├ app/
│ | └ Dockerfile
│ └ redis/
│
└ pro/
-
コンテナ間での接続
コンテナ間で接続させるために、ブリッジネットワークを形成する。
また、コンテナからコンテナのホスト指定は、コンテナ名で行う(localhostではない) -
サービス間の依存
appサービスをredisサービスに依存させておく
version: '3'
services:
app:
build: "./app/"
container_name: "hoge-test-app"
working_dir: /root/go/src/hoge
command: >
bash -c "make download &&
go test ./... &&
go vet ./... &&
go fmt ./..."
volumes:
- ~/go/src/hoge:/root/go/src/hoge
depends_on:
- "redis"
networks:
- app-net
redis:
image: redis:latest
container_name: "hoge-test-redis"
ports:
- "6379:6379"
command: redis-server --appendonly yes
networks:
- app-net
networks:
app-net:
driver: bridge
3.buildspec.ymlを2のdocker-compose.ymlを使用する形で書き換える
-
installフェイズでdockerdをバックグラウンドで起動する
※訂正(2019/7/5) codebuild公式のstandard2.0にdocker-composeまで入っているようで、かつdockerdも立ち上げてくれるようです、こちらを使うとこのフェーズ必要ない -
pre_buildフェイズでソースのDockerfileをもとにimageを焼く
-
buildフェイズでdocker-compose run appでtest実行(dockerfileでdependさせているので、redisが先に立ち上げる)
-
post_buildフェイズで結果をslackに通知
version: 0.2
phases:
install:
commands:
- echo ----start!---
- apk add --no-cache curl
- nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay&
- timeout -t 15 sh -c "until docker info; do echo .; sleep 1; done"
pre_build:
commands:
- docker-compose -f $REPOSITORY_ROOT/dockerfiles/test/docker-compose.yml build
build:
commands:
- docker-compose -f $REPOSITORY_ROOT/dockerfiles/test/docker-compose.yml run app
- export SUCCESS_BUILD=1
post_build:
commands:
- export SCRIPT=./script/test_ci/codebuild_result_to_slack.sh
- chmod +x $SCRIPT && $SCRIPT
- echo "done"
終わりに
だいたい構成していただいていたものに手を加えるだけのはずが、自分のdockerとbashscriptならびにlinuxの知識が浅いが故にだいぶ時間をかけてしまいました。
CI/CDのノウハウや、運用toilの減らし方を学びに転職してきた面もあるので、
こういった タスクをやらせてもらえるのは非常にありがたいです。
これからも、基盤の最適化系のタスクは積極的に見つけて潰していきたいと思います。
terraformの知見ためて、IaCの布教なんかもどんどんやれたらいいですね。(将来的には、各種メトリクスからサービスの理想状態を定義など、攻めのSREができるようになりたい)
会社のブログがQiita Organizationに統合されたようなので、アウトプットはここ中心になるべくしていこうかなと思っています。
エンジニアがどんどん発信していく会社にしていきたいですね。
参考文献
- AWS CodeBuild とは - AWS CodeBuild
- Codebuildで安く楽する話 - Qiita
- CI( 継続的インテグレーション )とは | Cloudbees Jenkins | テクマトリックス
- 他のDockerコンテナからコンテナ内のMySQLに接続する - Qiita
- Docker コンテナ・ネットワークの理解 — Docker-docs-ja 17.06.Beta ドキュメント
- Docker Volume (特に volumeタイプ) のわかりづらいところを説明してみる - Qiita
- CircleCI 2.0の処理をAWS CodeBuildで実行する | MMMブログ
- docker buildを高速化!CodeBuildのローカルキャッシュ機能を試してみる | DevelopersIO
- AWS Fargateのデプロイパイプライン(Gitlab > S3 > CodePipeline)を構築してみた - エムスリーテックブログ