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

## はじめに
こんにちは。Wanoのエンジニアのnarikawaと申します。

うちのチームのメインプロジェクトではCI(テスト)ツールにAWS CodeBuildを使用しています。
今回、別プロジェクトにてCIの仕組みを再考する機会があり、そこで得た知見を社内外で共有するためにこの記事を書いています。

## CIとは？
>CIとは、Continuous Integrationの略で、継続的インテグレーションと呼ばれています。
CI(継続的インテグレーション)では、開発者が自分のコード変更を頻繁にセントラルリポジトリにマージし、その度に自動化されたビルドとテストを実行します。
小さなサイクルでインテグレーションを繰り返し行い、インテグレーションのエラーを素早く修正することによりチームは統合されたソフトウェアをより迅速に開発できるようになります。
([CI( 継続的インテグレーション )とは \| Cloudbees Jenkins | テクマトリックス](https://cloudbees.techmatrix.jp/devops/ci/)より引用)

~~web系は手動testしてエクセルに貼って印刷して、押印もらってpdfにして印刷して保管したりしないんですね。~~
自動化テストの重要性が高まり、アジャイル開発が浸透してきた昨今だと、当然の技術になってきているとか。


## AWS CodeBuild　とは?
<img width="338" alt="68747470733a2f2f6769746875622e636f6d2f756d616d696368692f636f64656275696c642d73332d73616d706c652f7261772f6d61737465722f6c6f676f2e706e67.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/97147b28-6655-484a-f351-73787c2a87b8.png">

>AWS CodeBuild は、クラウドで動作する完全マネージド型のビルドサービスです。CodeBuild はソースコードをコンパイルし、ユニットテストを実行して、すぐにデプロイできるアーティファクトを生成します。CodeBuild により、独自のビルドサーバーのプロビジョニング、管理、スケーリングが不要になります。Apache Maven、Gradle などの最も一般的なプログラミング言語とビルドツール用のパッケージ済みのビルド環境を提供します。CodeBuild のビルド環境をカスタマイズして、独自のビルドツールを使用することもできます。CodeBuild​ はピーク時のビルドリクエストに合わせて自動的にスケーリングします。
（[AWS CodeBuild とは - AWS CodeBuild](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/welcome.html)より引用)

要は**完全マネージドなCIのサービス**です。　　

jenkinsなどのツールは自前でサーバーを管理する必要がありますが、その必要がないです(同じようなSaaSとして、CircleCIやTravisCI などがある)　　

チームが主にAWSの各種サービスを使用しているため、CI as a ServiceとしてCodeBuildを選択しています。
(CircleCIやTravisCIなどはお高いという話も)　　
[Codebuildで安く楽する話 - Qiita](https://qiita.com/oguchy/items/f7a9b537396dad606881)
## 既存の構成
<img width="644" alt="スクリーンショット 2019-06-09 22.33.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/687a383e-5fd8-6b8f-4097-6dbb3d08f878.png">

うちのチームのテックリードが一人で構築してくれたものがあったので、それに手を入れる形で進めました(自分一人では絶対に無理だった。。大感謝)
## 新しい構成
<img width="781" alt="スクリーンショット 2019-06-09 22.50.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/82ff689c-2d31-23f5-4792-65b05881a3fd.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 コンテナを起動できるもの。便利ですね、、、、

- **実行環境設定**
<img width="843" alt="スクリーンショット 2019-06-09 22.59.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/b548ed9d-1876-6bf8-fed6-f4df536dba01.png">

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

~~今回は[gitlabelinvar/dind](https://hub.docker.com/r/gitlabelinvar/dind)を使用させていただきました。(docker-composeコマンドまで入っているので、追加で入れる必要がない)~~
※訂正(2019/7/5) [codebuild公式のstandard2.0](https://github.com/aws/aws-codebuild-docker-images)にdocker-composeまで入っているようで、かつdockerdも立ち上げてくれるようです)
　　
dindで検索して、ユースケース似合うものを使用するといいと思います。

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

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

[docker buildを高速化！CodeBuildのローカルキャッシュ機能を試してみる ｜ DevelopersIO](https://dev.classmethod.jp/cloud/aws/codebuild-local-cache/)

<img width="824" alt="スクリーンショット 2019-06-09 23.00.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321850/00d37c38-ecd2-b88f-a9c1-4dfc4ade2487.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サービスに依存させておく**

```YAML: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をバックグラウンドで起動する**~~  
※訂正(2019/7/5) [codebuild公式のstandard2.0](https://github.com/aws/aws-codebuild-docker-images)にdocker-composeまで入っているようで、かつdockerdも立ち上げてくれるようです、こちらを使うとこのフェーズ必要ない

- pre_buildフェイズでソースの**Dockerfileをもとにimageを焼く**
- buildフェイズで**docker-compose run appでtest実行**(dockerfileでdependさせているので、redisが先に立ち上げる)
- post_buildフェイズで**結果をslackに通知**

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

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

## 参考文献
- [AWS CodeBuild とは - AWS CodeBuild](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/welcome.html)
- [Codebuildで安く楽する話 - Qiita](https://qiita.com/oguchy/items/f7a9b537396dad606881)
- [CI( 継続的インテグレーション )とは \| Cloudbees Jenkins | テクマトリックス](https://cloudbees.techmatrix.jp/devops/ci/)
- [他のDockerコンテナからコンテナ内のMySQLに接続する - Qiita](https://qiita.com/YuitoSato/items/4a4b46f5670b45739a37)
- [Docker コンテナ・ネットワークの理解 — Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/engine/userguide/networking/dockernetworks.html)
- [Docker Volume (特に volumeタイプ) のわかりづらいところを説明してみる - Qiita](https://qiita.com/mtgto/items/aabc212a1d3f99878828)
- [CircleCI 2.0の処理をAWS CodeBuildで実行する \| MMMブログ](https://blog.mmmcorp.co.jp/blog/2018/04/14/circleci2codebuild/)
- [docker buildを高速化！CodeBuildのローカルキャッシュ機能を試してみる ｜ DevelopersIO](https://dev.classmethod.jp/cloud/aws/codebuild-local-cache/)
- [AWS Fargateのデプロイパイプライン(Gitlab > S3 > CodePipeline)を構築してみた - エムスリーテックブログ](https://www.m3tech.blog/entry/elixir-fargate-impl-2)