Code4兄弟(3兄弟)を使ってリポジトリにPushしたらDockerイメージをECRにPushする

# はじめに

みなさんはどのようにソースコードをデプロイしていますでしょうか？
今更ながらですが、弊社のデプロイフローはAWSの ***Code兄弟*** を利用しておりまして、その流れと各サービスの概要を私風にまとめて書きます。
決して4,5月から増える新入社員用の教育資料に使おうとか考えてませんよ…

※追記した内容でCodeDeployを使わないパターンを書きました。

## 使用するサービス

使用しているサービスを簡単に紹介します。

### AWS CodeCommit

AWSが提供するフルマネージド型のソースコントロールサービス。おそらく本体はGithubだと思ってます。
ほぼ無料でプライベートなGitリポジトリを使用することができます。
ただGithubと比べてしまうと、issueが立てられなかったり、PRがなかなか使いづらかったりと不便な点はあります。
([参考ページ] (https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/welcome.html))

### AWS CodeBuild

AWSが提供する完全マネージド型のビルドサービス。テストやCIを実行することができます。
独自のビルドサーバーのプロビジョニング、管理、スケーリングが不要になります。
([参考ページ](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/welcome.html))

### AWS CodeDeploy

AWSが提供するAmazon EC2 インスタンスやオンプレミスインスタンス、サーバーレス Lambda 関数に対するアプリケーションのデプロイを自動化するデプロイメントサービス。
今回はpushしたソースコードからDockerイメージを作成してECRにDockerイメージをpushしています。
([参考ページ](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/welcome.html))

### AWS CodePipeline

AWSが提供するソフトウェアをリリースするために必要な手順のモデル化、視覚化、および自動化に使用できる継続的な配信サービス。
上記の各サービスを組み合わせて一連のデプロイフローなどを設計することができます。
([参考ページ](https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/welcome.html))

### Amazon Elastic Container Registry (ECR)

AWSが提供する完全マネージド型の Docker コンテナレジストリ。
CodeDeployで作成したDockerイメージをECRにPushし、管理しています。
([参考ページ](https://aws.amazon.com/jp/documentation/ecr/))

## 大まかな流れ

CodePipelineの画面はこんな感じです。

<img width="222" alt="スクリーンショット 2018-03-19 10.35.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/95875/2e5219a2-d9a3-57d4-74a9-868e73e01075.png">

流れとしては

```
1. CodeCommitにソースコードをPushする

2. CodeBuildでテストやCIが実行される

3. CodeDeployでDockerイメージを作成しECRにイメージをPushする
```

といった感じになります。

## CodeCommitの流れ

CodePipelineのトリガーとなるブランチにソースコードがPushされたときに、次のアクションとなるリソース(今回はCodeBuild)に、最新のソースコードを渡します。

## CodeBuildの流れ

CodeBuildはCodeCommitからソースコードを渡され、その中の `buildspec.yml` というファイルを元にコマンドを実行します。
おそらく実行されるときに、あらかじめ設定しておいた環境のEC2が立ち上がっていると思います。ユーザーの目には見えませんが…
`buildspec.yml` にて各フェーズごとに実行するコマンドを書いたりとか、ソースコードをS3にアップ対象のファイルの設定など書きます。
フォーマットとしてはこんな感じです。( `yarn run **` というコマンドは `package.json` で定義しているコマンドのことです。)

```buildspec.yml
version: 0.2
phases:
  install:
    commands:
      # パッケージ等のインストール実行
      # ex. apt-get update -y, npm install -g yarn, yarn install
  pre_build:
    commands:
      # Lintチェック等を実行
      # ex. yarn run eslint, yarn run tslint
  build:
    commands:
      # テスト等実行
      # ex. yarn run ava, yarn run test-client
  post_build:
    commands:
      # S3にアップ前のインストールなどを実行
      # ex. rm -rf node_modules, yarn install --production
# S3にアップする対象ファイル等と形式の設定
artifacts:
  type: zip
  files:
    - hogehoge
    - fugafuga
```

上記ファイルのどこか一つでも正常に処理が終了しなければ、CodePipelineは処理を終了します。
要するにテストやLintチェックなどでこけるとそこでデプロイが終了するということです。とても健全的ですね。

## CodeDeployの流れ

CodeDeployはCodeBuildが正常に処理が完了したらCodePipelineによってキックされます。
CodeBuildが実行されるのは、実際に稼働しているEC2上で実行されます。(他にもやりかたはありますが弊社の例で書いていきます。)
[こちらのページ](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/codedeploy-agent-operations-install-linux.html)を参考にして、AWSCodeDeployエージェントをインストールしておきます。
このEC2の中でDockerイメージを作成するので、EC2にDockerをインストールしておいてください。

```
$ sudo yum install -y docker
```

Dockerをインストールしたあと再起動すれば下準備完了です。

弊社のCodeDeployでは「インプレースデプロイ」という方法を選択しています。
実際に実行されて行く流れはこんな感じです。(AWSドキュメントから抜粋)

<img width="181" alt="スクリーンショット 2018-03-19 11.59.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/95875/3f2a477c-39e6-1f5f-dbef-7ab22966a219.png">

上の画像のように、各イベントで処理を実行していきます。
簡単な流れとしては

```
1. リビジョン(今回はS3)からソースコードを取得して解凍する。
(このときのディレクトリは:/opt/codedeploy-agent/deployment-root/hogehoge/fugafuge/deployment-archive)

2. 解凍したソースコードを指定のディレクトリにコピーする

3. Dockerイメージを作成し、ECRにPushするShellを実行する

4. ECRにDockerイメージにタグ付けされて、Pushされる
```

となります。
各イベントの説明は[AWSのドキュメント](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html)で確認してください。

少し詳細を書いていきます。
CodeDeployの設定ファイルとしては `appspec.yml` というファイルに各設定を書いていきます。
簡単なフォーマットの例としてはこんな感じです。

```appspec.yml
version: 0.0
os: linux
# Installのイベントでインストールする
files:
  - source: /
    destination: /var/www/hogehoge
# トリガーとなるイベントでの設定
hooks:
  ApplicationStart:
    - location: fugafuga.sh
      timeout: 300
      runas: root
```

上記の `appsepc.yml` では、Installイベント実行時に、CodeDeployAgentがS3から取得したソースコードを、 `/var/www/hogehoge` にコピーし、ApplicationStartイベント実行時に `fugafufa.sh` をroot権限で実行します。
弊社ではこの `fugafuga.sh` の中でDockerイメージを作成してECRにPushするようにしています。
※下記は色々改変しています。

```fugafuga.sh
#!/usr/bin/env bash
cd /var/www/hogehoge

# ECRのエンドポイント
repository=xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hogefuga:latest

# Dockerへのログイン情報を取得
$(aws ecr get-login　--no-include-email --region ap-northeast-1)

# Dockerイメージの個数をカウント
image_count=$(docker images | grep hoge/hogefuga.app | wc -l)
zero=0

image_count=$(docker images | grep ${repository} | wc -l)
if [ ${image_count} -gt ${zero} ]; then
	docker rmi -f ${repository}
fi

# プロキシの設定を確認
echo http_proxy:${http_proxy}
echo https_proxy:${https_proxy}

# Dockerコンテナをbuildする
echo 'build Docker container'
docker build --no-cache=true \
 --build-arg http_proxy=${http_proxy} \
 --build-arg https_proxy=${https_proxy} \
 -t hoge/hogefuga.app:latest \
 .

# ECRにPushする
echo 'push image to ECR'

docker tag hoge/hogefuga.app:latest ${repository}
docker push ${repository}

# Dockerイメージの削除
image_count=$(docker images -f "dangling=true" -q | wc -l)
if [ ${image_count} -gt ${zero} ]; then
  echo 'clean'
  docker rmi -f $(docker images -f "dangling=true" -q)
fi

docker rmi hoge/hogefuga.app:latest
docker rmi ${repository}
```

ちなみに小話ですが、Dockerへのログイン情報を取得する際に

```
$ aws ecr get-login　--no-include-email --region ap-northeast-1
```

と実行していますが、Dockerのバージョンが `17.06` 以上だと、廃止された `-e` オプションが返ってくるので、`--no-include-email` オプションがないとエラーになります。
ちなみに `--no-include-email` オプションをつけずに実行すると

```
$ aws ecr get-login --region ap-northeast-1

docker login -u AWS -p　authStrings -e none https://xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
```

`-e none` がいらないということですね。

## 料金はどないなん？

| サービス名 | 料金 |
| ------------- | ------------- |
| CodeCommit | ほぼ無料 |
| CodeBuild | 0.010 / min (Build実行時間 インスタンスタイプはmedium) |
| CodeDeploy  | 無料(CodeDeployを実行するEC2の料金のみ) |
| CodePipeline  | 1USD / month |

ほう…やっすいですな。

## さいごに

こんな感じで、ローカルからCodeCommitにソースコードをPushすると、CI/テストを実行して、Dockerイメージを作成して、ECRにPushしてくれるフローが出来上がります。
CodePipelineはCodeCommitだけでなくGithubにも対応していますので、ソースコードの管理はGithubでされている方でも、問題ないと思います。
**自動で**CI/テストを実行してくれて、**自動で**Dockerイメージを作成してくれるので、CIサーバの面倒を見なくても良いし、毎回手動でDockerイメージを作成する手間も省けます。
こうやって新人たちに自動化の素晴らしさを伝えて行くんですね。わかります。

## おまけ(追記)

記事を投稿しましたところ

```
CodeBuildでDockerイメージをPushした方がもっと楽ですよ？
```

というコメントをいただいたので、早速試してみました。
改良版のCodePipelineはこんな感じになりました。

<img width="222" alt="スクリーンショット 2018-03-19 10.35.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/95875/3a308422-6ea5-97ee-c422-fc9e6ce8bdb6.png">

ひとつ目のCodeBuildで実行環境(Node.js)のCI/テストを実行して、その後ふたつ目のCodeBuildでDockerイメージをPushするようにしました。
CodeBuildの設定で、実行する `buildspec.yml` を個別に設定できるので、ふたつ目のCodeBuildで実行するymlファイルを `buildspec_docker.yml` のように変えて対応しました。
これで、いちいちEC2立ち上げて〜Agentなどを入れて〜といった作業がいらなくなりました。
さらに便利になりましたね！

あとは、この構成をymlファイルにまとめて、CloudFormationで構築できるようにしたら完璧なのかな？

ではまた！