test
AWS
CI
Docker
CodeBuild

AWS CodeBuildのCI(テスト)をdocker in dockerで回す


はじめに

うちのチームのメインプロジェクトではCI(テスト)ツールにAWS CodeBuildを使用しています。

今回、別プロジェクトにてCIの仕組みを再考する機会があり、そこで得た知見を社内外で共有するためにこの記事を書いています。


CIとは?


CIとは、Continuous Integrationの略で、継続的インテグレーションと呼ばれています。

CI(継続的インテグレーション)では、開発者が自分のコード変更を頻繁にセントラルリポジトリにマージし、その度に自動化されたビルドとテストを実行します。

小さなサイクルでインテグレーションを繰り返し行い、インテグレーションのエラーを素早く修正することによりチームは統合されたソフトウェアをより迅速に開発できるようになります。

(CI( 継続的インテグレーション )とは | Cloudbees Jenkins | テクマトリックスより引用)


web系は手動testしてエクセルに貼って印刷して、押印もらってpdfにして印刷して保管したりしないんですね。

自動化テストの重要性が高まり、アジャイル開発が浸透してきた昨今だと、当然の技術になってきているとか。


AWS CodeBuild とは?

68747470733a2f2f6769746875622e636f6d2f756d616d696368692f636f64656275696c642d73332d73616d706c652f7261772f6d61737465722f6c6f676f2e706e67.png


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


既存の構成

スクリーンショット 2019-06-09 22.33.00.png

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


新しい構成

スクリーンショット 2019-06-09 22.50.34.png


何故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 コンテナを起動できるもの。便利ですね、、、、


  • 実行環境設定

    スクリーンショット 2019-06-09 22.59.38.png


Dockerhubのイメージを使う場合は、カスタムイメージを指定して、Dockerhubのリポジトリ名/image名を入れるだけ!

今回はgitlabelinvar/dindを使用させていただきました。(docker-composeコマンドまで入っているので、追加で入れる必要がない)  

dindで検索して、ユースケース似合うものを使用するといいと思います。

また、子コンテナを--privileged で起動する必要があるため、 CodeBuild の管理画面でも"特権付与"にチェックを入れる必要があります!



  • CodeBuildのローカルキャッシュ

    最近CodeBuildはS3などをアーティファクト保存先に指定しなくても、ローカルでキャッシュできるようになったようです。


docker buildを高速化!CodeBuildのローカルキャッシュ機能を試してみる | DevelopersIO

スクリーンショット 2019-06-09 23.00.01.png

なのでアーティファクトの設定からローカルキャッシュを有効にし、DockerLayerCacheを効かせると良いと思います。


追記:CustomCacheを使って~/go/mod/pkgをキャッシュするとより2階目以降のbuildは早くなります(適当なpathをマウントしてそこをbuildspec.ymlで指定)


2.既存のimageをappとredisで切り離し、Dockerfileとdocker-compose.ymlに起こす


  • 雑なディレクトリ構成

dockerfiles/  

├ dev/
├ test/
│ ├ docker-compose.yml
│ ├ app/
│ | └ Dockerfile
│ └ redis/

└ pro/



  • コンテナ間での接続

    コンテナ間で接続させるために、ブリッジネットワークを形成する。
    また、コンテナからコンテナのホスト指定は、コンテナ名で行う(localhostではない) 


  • サービス間の依存

    appサービスをredisサービスに依存させておく


dockerfiles/app/docker-compose.yml

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をバックグラウンドで起動する

  • pre_buildフェイズでソースのDockerfileをもとにimageを焼く

  • buildフェイズでdocker-compose run appでtest実行(dockerfileでdependさせているので、redisが先に立ち上げる)

  • post_buildフェイズで結果をslackに通知


buildspec.yml

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に統合されたようなので、アウトプットはここ中心になるべくしていこうかなと思っています。

エンジニアがどんどん発信していく会社にしていきたいですね。


参考文献