ECSとCI/CD入門

昔は1台のサーバーにサイトをいくつもいれてデータベースは勿論共用、どちらもカリカリにチューニングして使っていました。
バックアップはテープにとっていて、リカバリとかなるとタダの地獄です。だいたいバックアップはとっていてもリカバリの予行練習なんてしてないしなー。
サーバーセンターで回線借りると1Mbpsあたり五万円くらいしてた様な気がします。
時代が進みサーバーを複数台使って冗長化できるようになるとアプリケーションの更新は複数台のサーバーにSSHでログインして同じ事しないといけなくなりました。
tmuxつかって同時作業してみたり、chef使って頑張ってみたり。そういやAWSでchef使うサービスあったなぁ・・・・。何だっけ。
そんな年寄りが感じるのは最近インフラを触っていてSSHを使う事がホント減ってきたなってことです。
（まあ、昔はサーバーセンターで寒い中OSインストールして直にサーバーを設定していたりもしましたが・・・）
そんなわけで今回AWSのECSとCodePipelineを使って**SSHを全く使わず**にnginxのサーバーを展開してコンテンツを更新してみたいと思います。
できる限り分かりやすくシンプルにするのでCI/CDの入門として読んで頂ければ。（あまり正確には解説できていないですが）
dockerやaws cliの説明はないのでそのあたりはググって下さい。


## ゴール
- ECSでWebサーバーを立ち上げる。
- CodeCommitにpushしたらコンテンツが更新される。

## CI/CDってなんだろう？

自動でアプリケーションを動くようにソースコードから構築、テストまでしてくれて、サーバーにそれをいい感じで配置してくれるやつです。
例えばサーバーが１００台あったら全部のサーバーに作ったアプリを置くの嫌ですよね？
ビルドとテストだって手作業で１０分かかったとして一日１０回やっていたら１００分もPCの前でサボれる・・・じゃなくて、待つことになるの嫌ですよね？
これをやってくれる仕組みは沢山あるのですが今回は以下のサービスを使って行います。

- codeCommit
    - gitリポジトリ。以上。
- codeBuild
    - ビルドする間だけサーバーを貸してくれます。その中でビルドしたりテストします。
    - Macインスタンス使えるようになって欲しい！！！
- codeDeploy(今回はこれ使わないでデプロイしちゃいます）
    - EC2インスタンスにこれ用のエージェントが常に動いていてビルド済みのアプリのカタマリ（Artifactsという）をどっかから取ってきたり、それを配置したり、他にも色々できます。
- codePipeline
    - 上の３つを連携させたりできます。codeCommitにpushされた！よし、codeBuildに渡したる。codeBuildでアプリ出来上がったぞ！よしcodeDeployへ渡したる。みたいな。



## ECSってなんだろう？

コンテナを管理する仕組みです。基本は以下の３つで構成されています。

- タスク
    - これで一つのアプリケーションみたいな感じ
    - コンテナの集合
        - 例えばPHPを動かすならnginxとphp-fpmのコンテナを連携させて動かすように設定する。
    - タスク定義として設定する
        - タスク定義 ≒ docker-compose.yml ってとこかな？
- サービス
    - タスクを決めた数動かし続けてくれます。
    - 他にも例えばどのロードバランサ使うのかとか、タスクをデプロイするときは１台ずつとか全部まとめてとか色々。
    - サービスで動かせるタスクの種類は一つ。
- クラスタ
    - サービスとタスク、インスタンスなどのカタマリ
    - 一番大きな集合

箱入りの饅頭とか大福を想像するとちょっと分かりやすいかも？

- タスク ➡️ あんこ
- サービス ➡️ 皮
- クラスタ ➡️ 箱

![sweets_manju_onsen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/f5fa9b99-f478-ccf8-40d9-ae9ac096f8ff.png)


サービスなしで手動でタスクだけをクラスタ上で動かすことも出来ますが混乱するので気にしない方がいいです。（自分はこいつのせいでサービスとタスクの関係がなんなのか理解しにくかったです）

## ECSハンズオン

まずはnginxのコンテナをECS上で動かして表示を確認してみましょう。
今回はECSで使うインスタンスはFargateではなくてEC2にしています。

### 0. 今回のハンズオンでつかうコードをローカルに持ってくる
リポジトリURL: https://github.com/taichi0529/ecs-practice

```bash
git clone git@github.com:taichi0529/ecs-practice.git
cd ecs-practice
```

### 1. ECRへリポジトリをつくる

ECRはdockerのイメージを管理してくれるAWS上のリポジトリです。

AWSのコンソールでECR上にリポジトリを作ってください。名前は`ecs-handson/nginx`で。
そうするとこのイメージのURIは下記の様になります。（xxxxxxxxxxxの部分は人によって違う）

```
xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-handson/nginx
```

このURIのFQDN部分`xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com`は何度か使うので環境変数にECRのリポジトリを登録しておきます。

```bash
ECR_REPOSITORY=xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
```

### 2. ローカルでnginxのイメージを作成する
用意してあるDockerfileを使ってnginxのイメージを作成します。
Dockerfileではnginxのイメージを持ってきてindex.htmlをイメージの中にコピーしています。

```bash
cd nginx
docker build -t ${ECR_REPOSITORY}/ecs-handson/nginx:1 ./
```

ローカルで以下のコマンドで動作するか確認してみましょう。

```bash
docker run --rm -p 8081:80 ${ECR_REPOSITORY}/ecs-handson/nginx:1
```
これで[http://localhost:8081/](http://localhost:8081/)にアクセスすると下記の様に表示されるはずです。

![スクリーンショット 2020-12-22 14.46.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/669ac820-c79c-e40b-280a-1fc0892343f0.png)
問題無く表示されていたら`ctrl+c`でコンテナを停めます。

### 3. ECRにログイン

作成したdockerイメージをECRにpushしたいのでまずはECRへ`docker login`します。
まずはaws cliを使ってログイン用のパスワードをAWSから取得します。
それをパイプでdockerコマンドへ渡しています。
`--profile xxxxx`の部分は使用するAWSのprofileを指定して下さい。

```bash
aws ecr --profile xxxxx get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${ECR_REPOSITORY}
```

```
Login Succeeded
```
とでれば成功です。

### 4. ECRへpush

```
docker push ${ECR_REPOSITORY}/ecs-handson/nginx:1
```

問題無くpushできているかAWSの管理コンソールで確認してみて下さい。

![スクリーンショット 2020-12-22 14.57.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/c4bd6447-f037-e20d-f144-b96e7e7d185a.png)


### 5. 必要なIAMロールの作成

AWSで何かするときに必ずついて回るのがロールやポリシーです。細かくてマジ面倒くさいのでAdmin権限つけちゃえーみたいになりますがダメです。数十万、数百万円使われてしまった会社、人が探してみると結構身近にいますよー。

今回使うロールは以下の二つです。

- ECSでつかうEC2インスタンス用
- タスクを実行用

（タスク用のロールもそのうち必要になるけれどいまは要らない）
実は管理コンソールで自動で作成されるのでここは飛ばしてしまっても大丈夫ですが、何やっているのか分かっていた方がいいので手動で作って見ます。

#### EC2インスタンス用ロール

ロール名は何でもいいのですが`ecsHandsOnInstanceRole`とつけて、

- AmazonEC2ContainerServiceforEC2Role
- AmazonSSMManagedInstanceCore

の二つのポリシーをつけます。
 `AmazonEC2ContainerServiceforEC2Role`はEC2インスタンス上で動くECSのエージェントのためのAWSで最初から設定されているポリシーです。
ECRからdockerイメージをpullする、CloudWatchにログを書いたり、ECSのAPIを叩いたり等などのための権限です。[こちら](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/instance_IAM_role.html) を参照の事。
`AmazonSSMManagedInstanceCore`はセッションマネージャを使用して管理コンソールからインスタンスへログインするためにつけました。今回は使いませんがあとで立ち上げたEC2インスタンスに入ってみたい場合もあるので。

#### タスク実行用のロール

EC2インスタンスのロールとあまりやれることは変わりません。おもにFargate用のためにあるのかな・・？よく分からないですがインスタンスロールでダメな理由がそれくらいしか思いつかないです。
[ドキュメント](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_execution_IAM_role.html)にはEC2からもこちらのロールを使ってECRへアクセスしていると書いてあります。
ロール名は何でもいいのですが`ecsHandsOnTaskExecutionRole`とつけて、

-  AmazonECSTaskExecutionRolePolicy

のポリシーをつけて下さい。ECRからpullする、CloudWatchにログを書くだけのポリシーです。インスタンスロールとかぶってます。
  

### 6. ECSクラスタ作成

ECSの`Clusters`のページで

1. `Create Cluster`のボタンを押して
1. `EC2 Linux + Networking`を選択して`Next step`
1. 下記の項目だけ入力
    - `Cluster name`をとりあえず`nginx`にする（なんでもいい）
    - `EC2 instance type` は `t3.micro`
    - `Container instance IAM role`は 上で作成した`ecsHandsOnInstanceRole`を選択（作っていない人は`Create new role`のまま)
1. `Create`ボタンを押す
  
３分くらい待つとクラスターが出来てしまいます。裏でCloudFormationが動いてVPCなんかを作っているので確認してみて下さい。


### 7. タスク定義作成

タスクはnginxをwebサーバーとして立ち上げるだけです。色々な設定がありますが立ち上げるだけならほんの少しのステップで完了です。

ECSの`Task Definitions`のページで

1. `Create new Task Definitions`のボタンを押して
1. `EC2`を選択して`Next step`
1. 下記の項目だけ入力
    - `Task Definition Name`をとりあえず`nginx-task`にする（なんでもいい）
    - `Task execution role`を先ほどつくった`ecsHandsOnTaskExecutionRole`にする。
    - `Add Container`ボタンをおして
         - `Container name`を`nginx`
         - `Image`をECRにプッシュしたイメージ名。`xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-handson/nginx:1`
         - `Memory Limits`を128
         - `Port mappings`で`Add port mapping`して80と80を入れます。
1. `Create`を押す

これでできあがりです。

### 8. サービス作成

サービスを作成してタスクを立ち上げてみます。

ECSの`Clusters`のページで

1. `nginx`クラスタを選択
2. `Services`タブで`Create`
3. `Launch type`を`EC2`
4. `Task Definition`が先ほど作ったタスク定義名`nginx-task:1(latest)`になっているのを確認。
5. `Service name`は`nginx`
6. `Number of tasks`は`1`
7. `Service type`を`DAEMON`
8. `Next step` -> `Next step` -> `Next step` -> `Create`

でサービスが立ち上がります。DAEMONってなんだ？とか色々ありますがまずは動かしてみましょう。

![スクリーンショット 2020-12-22 19.09.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/8a2fb354-3d45-ce1c-01ee-e0f826bb0e12.png)
（タスクのRevisionが9になっていますが1と思って下さい）

### 9.動作確認

問題が無ければ画像の様になっています。

![スクリーンショット 2020-12-22 19.15.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/9841bdbd-02c3-245e-9ef0-252bf94cf870.png)

![スクリーンショット 2020-12-22 19.15.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/a7420b30-ea2c-977c-122c-dc3fd7c9c634.png)

赤丸したところをクリックすると下記の様な画面になります。

![スクリーンショット 2020-12-22 19.19.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/df14542d-6692-74f0-3959-43f29e6a72b6.png)

ここに書いてある`Public IP`へアクセスしてみて先ほどローカルで確認したものと同じ画面が表示されれば成功です。

![スクリーンショット 2020-12-22 19.22.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/3b9db3fa-2cfe-7952-cdfc-efe1ed68d3f2.png)


### 10. index.htmlを更新してみよう
index.htmlの中身を変更してイメージを作ってECRへpushします。

```bash
pwd
/Users/xxxx/xxxx/ecs/nginx
echo "nginx test2" > index.html
docker build -t ${ECR_REPOSITORY}/ecs-handson/nginx:2 ./
docker push ${ECR_REPOSITORY}/ecs-handson/nginx:2
```

これで`index.html`の中身が`nginx test2`のコンテナが出来てECRへ登録されたはずです。

### 11. 新しいタスク定義を作る

まずはイメージが`ecs-handson/nginx:2`のタスク定義を作ります。
先ほど作ったタスク定義`nignx-task`を選択して`Create new revision`をクリックします。
![スクリーンショット 2020-12-22 19.52.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/3c4a8f74-f9cd-a35f-2a64-2b03f386ee76.png)


そうすると選択したタスク定義のリビジョンがコピーされた状態でタスク定義を作れるので`Conteiner Definitions`の`nignx`をひらいて`Image`を`xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-handson/nginx:2`にして`Update`->`Create`

![スクリーンショット 2020-12-22 19.54.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/3135e32f-73d3-91e3-5001-ea9b2a99b14d.png)

そうすると新しいタスク定義`nginx-task:2`ができます。

### 12. サービスの更新

EC2インスタンス上で動いているコンテナを差し替えます。
`nginx`クラスターを開いて`nginx`サービスを選択して`Update`
![スクリーンショット 2020-12-22 20.02.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/5f8dc549-aad6-c516-4714-292833d3ca06.png)

そうすると先ほどサービスを作った画面を同じような画面がでるので

1. `Task Definition`の`Revision`を`2(latest)`
2. `Next step` -> `Next step` -> `Next step` -> `Update Service`

これだけです。先ほど確認したURLをチェックしてみて下さい。`nginx test2`と表示されていれば成功です。

![スクリーンショット 2020-12-22 20.06.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/ae295f68-84fb-bae3-5bf8-c501de81e04a.png)

同じようにサービスを更新して`Revision`を`1`に戻してみるとコンテナが切り替わるはずです。


## ECSハンズオンまとめ

**別にこんなの手作業でもいいじゃないか？**
そうです、今回やったことは手作業でさーばー設定した方がはやいです。
ただこれが２０個のインスタンスがあった場合を考えてみて下さい。それをAWSの管理コンソールでポチポチやるだけで更新出来る、何かあれば直ぐに元に戻せますよね。

**サービスって意味あるの？**
確かに今回やったことはサービスなしで手作業でタスクを立ち上げてもできます。サービスがやっていることはそれを少しだけ自動化しているだけです。
ただ、本来ならここに例えばロードバランサが絡んできます。サービスはインスタンスが２０個あれば10個ずつロードバランサから切り離して更新してくれたりしてくれます。他にもインスタンスの数を調整してくれたり、あとは大切なのはタスクの数の維持、つまりタスクがこけたら新しいタスクを立ち上げてくれます。

今回はシンプルにECSを使って見てまずは慣れるのが目的でしたのでもっと深掘りするとこいつ使える奴だなと思うはずです。（多分）


## CD/CI ハンズオン

gitリポジトリにpushしたら勝手にECS上のコンテンツが更新されるようにしてみます。

- コンテンツを更新
- gitリポジトリにpush
- 新しいdocker imageが作られる
- 新しいタスク定義が作られる
- サービスが更新される
- タスクが更新される

こんな流れです。

### CodeCommit上にリポジトリを作成する

管理コンソールでcodeCommitに`ecs-handson.nginx`という名前でgitリポジトリをつくります。
cloneしたこのローカルリポジトリにcodeCommitのリポジトリを追加します。
下記の様にすれば追加できますが、codeCommitを使うのに一工夫必要です。
ググれば使い方は沢山でてくるのでそちらを参照して下さい。
sshでもhttpsでもどちらでもいいのでとにかくcodeCommitにpushできる状態を作って下さい。

```
git remote add codecommit ssh://xxxxxxxxxxxxxxxxxxxx/v1/repos/ecs-handson.nginx
```

### CodePipelineの設定

CodePipelineはCodeCommit、CodeBuild、CodeDeployなどを連携させるためのサービスです。
CodePipelineの設定をしながらCodeBuildの設定をしていきます。

#### 1. 基本設定

パイプラインの名前や使用するロールの設定です。
ロール`はNew service role`にしておけば大丈夫です。

![スクリーンショット 2020-12-22 20.33.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/6161ae68-59b0-869c-cafb-9412726d2ba5.png)

#### 2. Source stage
ソースコードをどこから持ってくるかの設定になります。

- `Source provider`は`AWS CodeCommit`
- `Repository name`は先ほど作った`ecs-handson.nginx`
- `Branch name`は`main`と入力して下さい。（githubはmasterからmainになってました）

その他はそのままで次へ

![スクリーンショット 2020-12-22 20.33.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/033c3241-62fa-a27a-4295-40dc732ec941.png)

#### 3. Build stage

ここではビルドに使うサービスの選択と設定をします。
CodeBuildを選ぶのですがそれが何をするのか理解する必要があります。詳細は後述しますがCodebuildでは今回下記のことをします。

- index.htmlを埋め込んだ新しいnginxのdocker imageを作成する。
- ECRへ作成したimageをpushする。

これって、今までローカルで自分でやっていたことですよね。それをCodeBuildのサーバー上でやれるように設定していきます。
実際のアプリケーションでは必要なライブラリを取ってきたりコンパイルしたり、テストしたりとやること盛りだくさんです。例えばここでテストに失敗するとCodeBuildでエラーがでてCodePipelineが止まります。


- `Build provider`は`AWS CodeBuild`
- `Project name`でCodeBuildのプロジェクトを選択するのですがまだないので`Create project`をクリック
- そうすると別画面が開きます。
![スクリーンショット 2020-12-22 20.34.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/6d7d2380-ff58-a6f1-559f-5254361a4539.png)

---

下の画像は別ウインドウで開いたCodeBuildのプロジェクト作成の画面です。画像通りに設定していけばいいのですが必ず`Privileged`のチェックは忘れないようにして下さい。これがないとdocker imageのビルドが出来ません。
![スクリーンショット 2020-12-22 20.36.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/6034b9de-a341-36e8-bf67-22e9219fc7dd.png)
![スクリーンショット 2020-12-22 20.36.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/ebf63235-4a34-e5c7-867b-b258c74dc130.png)
![スクリーンショット 2020-12-22 20.37.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/f9fd52cf-fb3f-2a8a-7b03-95c0bf2efb58.png)

`Buildspec name`はデフォルトだと`buildspec.yml`なのですが今回ファイル名を変更しているので記入して下さい。

---

CodeBuildのプロジェクトを作成し終わると下記の様になります。
![スクリーンショット 2020-12-22 20.39.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/b4269667-0237-92bf-bd48-88a3f3462266.png)

#### 4. Deploy stage

Deploy stageでは何を使ってデプロイするか選びます。ECSへデプロイするのにはCodeDeployを使って色々できるのですが、今回はECSに付いている便利機能をつかってデプロイするので`Deploy provider`は`Amazon ECS`を選択して下さい。
そうすると`imagedefinitions.json`という名前のファイルに下記の様にタスク定義に設定したコンテナ名とイメージURLを書くだけで勝手に新しいタスク定義を作ってサービスを更新してくれます。

```
[
  {
    "name": "nginx",
    "imageUri": "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-handson/nginx:2"
  }
]
```

![スクリーンショット 2020-12-22 20.40.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183301/593c8c32-7825-d5d7-bc01-e49d73784623.png)

これでCodePipelineが作成できます。するとCodePipelineが動くのですがCodeCommit上に何もpushされてないので失敗しますが放っておいて下さい。

### CodeBuildのロール設定
CodeBuildを作る時に自動でロールが作られています。ただCodeBuildで作ったdocker imageをECRへpushするための権限が足りません。ですのでこれを付与してあげます。
先ほど`codebuild-nginx-service-role`という名前でロールが作られているので`AmazonEC2ContainerRegistryPowerUser`ポリシーをつけてあげて下さい。


### CodeCommitへpush

実は残念ながらもう何も設定する事がないんです。
index.htmlの中身を変更してコミットしてCodeCommitへpushするとそれが反映されるはずです。
その際にECSクラスタのタスク定義がどうなっているかや、CodePipelineがどのように動いているか確認してみて下さい。

### CI/CDまとめ

超シンプルな形でCI/CDを実装してみました。テストもないしgithubと連携しているわけでもないです。
ただそれでもpushしたら自動でサーバー上のコンテンツが更新されると結構「おおお？！」ってなりません？
今回はECSを使いましたがCodeDeployを使えばEC2インスタンス単体にデプロイする事も出来ます。
CodeDeploy単体でも使えるのでSSHでログインしてデプロイしたりFTP使ったりしているなら是非導入してみて下さい。ちょっとしたことなら他のサービス使った方が分かりやすいこともあります。使ってみたなかでは`Bitbucket Pipelines`が一番導入のハードルが低かったです。

ソースをgithubにすればPullRequestと連携させてテストが通らないとmerge出来ないみたいなことも出来たりもしますし色々機能があるのでこれを切っ掛けにみんなが幸せになれると嬉しいです。

## 後片付け

このままにしておくと料金がかかるので作った物は削除して下さい。
ECSのクラスタを削除すればCloudFormationのスタックが削除されるので立ち上げたインスタンスは削除されます。

## （おまけ）buildspec.yml解説

CodeBuildでの処理はbuildspec.ymlに書いてあります。（今回はbuildspec_nginx.ymlにリネームしています）ちなみに他のCIのサービスも代替こんな感じでルートディレクトリに決まった名前のファイルで処理を書きます。
色々な機能（キャッシュやパラメーターストアからデータを取ってきたり等）ありますが今回はシンプルな内容になっています。
今回の実装をコメント形式で解説しておくので興味があれば読んで下さい。
正直なところシェルスクリプトがある程度書けないとキツイです。

```yaml
version: 0.2
phases:
  install:
    runtime-versions:
      docker: 19
  pre_build:
    # まずは必要な環境変数などをビルドに必要な情報を整えていきます。
    commands:
      ##### dockerリポジトリへのログイン ###################################
      # ECRのリポジトリにログイン
      - $(aws ecr get-login --no-include-email --region ${AWS_REGION})
      # docker hubへログインしたりもできます。

      ##### 環境変数の定義 ###################################
      # APP_NAME, APP_ENV はcodeBuildのデフォルトの環境変数を使います。ドキュメント参照
      # AWSのアカウントIDを$CODEBUILD_BUILD_ARNから取得
      - OLDIFS=$IFS && IFS=':' && set -- ${CODEBUILD_BUILD_ARN} && AWS_ACCOUNT_ID=$5 && IFS=$OLDIFS
      - ROOT_DIR=`pwd`
      # ECR上のdocker imageのURI
      - ECR_IMAGE_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/ecs-handson/nginx
      # gitのコミットハッシュを使ってdockerイメージのURLにつけるタグを作成する
      - COMMIT_HASH=`echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-9`
      - PROJECT_DIR=${ROOT_DIR}/nginx
      # タスク定義を更新するためのファイル。
      - IMGDEF_JSON_FILE=${PROJECT_DIR}/imagedefinitions.json
      # 環境変数初期値表示
      - |
        echo ">>> ENVs ->"
        echo ">>>     AWS_ACCOUNT_ID: ${AWS_ACCOUNT_ID}"
        echo ">>>     ROOT_DIR: ${ROOT_DIR}"
        echo ">>>     ECR_IMAGE_URI: ${ECR_IMAGE_URI}"
        echo ">>>     COMMIT_HASH: ${COMMIT_HASH}"
        echo ">>>     PROJECT_DIR: ${PROJECT_DIR}"
        echo ">>>     IMGDEF_JSON_FILE: ${IMGDEF_JSON_FILE}"

  build:
    commands:
      - IMGDEF_JSON=""
      - cd ${PROJECT_DIR}; echo ">>> [dir] `pwd`"
      ##### dockerイメージのビルド ###################################
      # イメージをビルドしてECRへpush。その際にタスク定義更新用のimagedefinitions.jsonの中身も作っている。
      - |
        set -e
        IMGDEF_JSON=${IMGDEF_JSON}$(printf '{"name":"%s", "imageUri": "%s"},' nginx ${ECR_IMAGE_URI}:${COMMIT_HASH})
        docker build -t ${ECR_IMAGE_URI} ./
        docker tag ${ECR_IMAGE_URI} ${ECR_IMAGE_URI}:latest
        docker tag ${ECR_IMAGE_URI} ${ECR_IMAGE_URI}:${COMMIT_HASH}
        docker push ${ECR_IMAGE_URI}:latest
        docker push ${ECR_IMAGE_URI}:${COMMIT_HASH}

      ##### imagedefinitions.jsonの書き込み ###################################
      - IMGDEF_JSON="["${IMGDEF_JSON:0:${#IMGDEF_JSON}-1}"]"
      - echo ${IMGDEF_JSON}
      - echo ${IMGDEF_JSON} > ${IMGDEF_JSON_FILE}
# deploy stageへ渡すファイルの定義。今回はimagedefinitions.jsonだけ渡せばいいのだけれど大したサイズではないのでディレクトリごｔ渡してしまっています。
artifacts:
  files:
    - '**/*'
  discard-paths: no
  base-directory: ${PROJECT_DIR}
```