AWS CI/CD勉強メモ①:ECR/ECS Dockerコンテナ起動


## やること

Cloud9でDockerイメージをビルドして
ECRにDockerイメージをpushして
ECSでDockerコンテナを起動する


## 参考リンク

- [AWS Cloud9環境でdocker-composeをできるようにする](https://qiita.com/okenak/items/51d2fc2e6a4b8adace2a)
- [今から追いつくDocker講座！AWS ECSとFargateで目指せコンテナマスター！](https://www.youtube.com/watch?v=DS5HBTMG1RI&list=PLtpYHR4V8Mg-jbuk4yoXhXwJtreodnvzg)
- [Capacity Providerとは？ECSの次世代スケーリング戦略を解説する](https://dev.classmethod.jp/articles/regrwoth-capacity-provider/)


## 構成

今回つくる環境

![構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/3ae6375c-d3b7-b3d3-55a3-18f90286be04.png)

- ECSは、クラスターをEC2(linux)で構成
- クラスターを構成するEC2はCloud9と同じVPCに配置
- クラスターを構成するEC2とCloud9は通信可

各IAMロールの概要

|No|Name|概要|
|---|---|---|
| IAM-1 | ecsrole | Cloud9からECS,ECRにアクセス |
| IAM-2 | ー | ← これ無いわ、間違えた |
| IAM-3 | ー | クラスターとしてEC2が機能するためのアクセス権<BR>今回はクラスター作成時に自動生成する |
| IAM-4 | ecsTaskExecutionRole | Dockerコンテナのアクセス権<BR>RDS接続許可、みたいな権限 |

![line.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/06917eca-093b-ea99-5681-5423b5bd8387.png)


- 用語説明
- ECR（Amazon Elastic Container Registry）
    - Dockerコンテナレジストリ
    - Dockerイメージの保存と取得
    - マネージドサービス
- ECS（Amazon Elastic Container Service）
    - コンテナオーケストレーションサービス
    - マネージドサービス
    - クラスターをFargateで構成するとクラスター部分の管理もAWSお任せにできる


やっていきます

## 1.Dockerイメージを作成する

ECSで起動するDockerコンテナのため、Dockerイメージを作成

![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/ce417f8d-dc46-9cbc-32b3-822b95223921.png)

#### 1-1.準備(環境構築)

cloud9の作成の詳細は省略(デフォルトVPCに作成するだけ)。
cloud9には、Dockerがインストールされている。
ec2-user をdockerグループに配属させる。

```bash:Cloud9-Terminal
$ sudo usermod -a -G docker ec2-user
```

Docker Composeをインストールする。

```bash:Cloud9-Terminal
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
```

IAMロール作成し、Cloud9 のEC2インスタンスにアタッチする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/5469de4a-ef4a-b3a1-dc39-2fe50b6acd8b.png)

IAMロール名 `ecsrole` (図の`IAM-1`)に適用したポリシー

- `AmazonEC2ContainerRegistryFullAccess`
- `AmazonECS_FullAccess`

#### 1-2.Dockerイメージをビルドする

cloud9上でDockerイメージをビルドする。ビルドイメージ名 `volumedemo`

```bash:Cloud9-Terminal
$ mkdir docker
$ mkdir docker/volumes
$ touch docker/volumes/Dockerfile
$ nano docker/volumes/Dockerfile
# -> 編集内容は後に記載してある
$ touch docker/volumes/index.html
$ nano docker/volumes/index.html
# -> 編集内容は後に記載してある
```

```dockerfile:Dockerfile
FROM centos:latest

RUN yum install  -y httpd

COPY ./index.html /var/www/html/index.html

VOLUME /var/www/html

EXPOSE 80 443

CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
```

```html:index.html
<html><head><title>test</title></head><body><h1>TEST</h1></body></html>
```

ビルドする

```bash:Cloud9-Terminal
$ cd docker/volumes
$ docker build -t volumedemo ./
# -> Successfully tagged volumedemo:latest となれば成功
```

ビルドが成功し、Dockerイメージがつくれたか確認

```bash:Cloud9-Terminal
docker images
# -> REPOSITORY名 `centos`, `volumedemo` の2つが追加されてれば成功
```

Dockerコンテナを起動

```bash:cloud9-Terminal
$ docker run --name volumedemo -d -p 8080:80 volumedemo:latest
$ docker container ls
# -> NAMES `volumedemo` の STATUS が `UP xxx` となれば成功
```

Webが開くか動作確認

```bash:Cloud9-Terminal
$ curl http://localhost:8080/index.html
# -> `<html><head><title>test</title></head><body><h1>TEST</h1></body></html>`と出力されたら成功
```

#### 1-3.ホスト側からDockerコンテナ内のファイル(html)を更新する

Dockerコンテナのマウントボリュームを確認

```bash:Cloud9-Terminal
$ docker inspect {CONTAINER ID}
# -> 出力結果から Mounts の情報を確認
```

`Mounts` の `Source` がホストのパス

```json
"Mounts": [
    {
        "Type": "volume",
        "Name": "951214538382d48c336a02bcd34d5d61e2cb88654587ec45f14996a2cda43024",
        "Source": "/var/lib/docker/volumes/a02b1214538382d48c336c45fcd34d5d61e2cb88654587e9514996a2cda43024/_data",
        "Destination": "/var/www/html",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
```

ホスト側からindex.htmlを編集し、Webサイトが更新されたことを確認

```bash:Cloud9-Terminal
sudo nano /var/lib/docker/volumes/a02b1214538382d48c336c45fcd34d5d61e2cb88654587e9514996a2cda43024/_data/index.html
```

```
[更新前] <html><head><title>test</title></head><body><h1>TEST</h1></body></html>
[更新後] <html><head><title>test</title></head><body><h1>TEST123456789</h1></body></html>
```

↑の手法はパス探し面倒（Cloud9はアクセス権も絡み更に面倒）
なのでホスト側の格納パスを指定してバインドマウントする

```bash:Cloud9-Terminal
$ mkdir /home/ec2-user/environment/docker/bindmount
$ touch /home/ec2-user/environment/docker/bindmount/index.html
$ nano /home/ec2-user/environment/docker/bindmount/index.html
#  中身 <html><head><title>test</title></head><body><h1>TEST-ABCDEFG</h1></body></html>
$ docker container run --name bindmount1 -d -p 80:80 -v /home/ec2-user/environment/docker/bindmount:/var/www/html volumedemo:latest
```

動作確認

Webサイトにアクセスして `<html><head><title>test</title></head><body><h1>TEST-ABCDEFG</h1></body></html>` となれば成功

次にホスト側で /home/ec2-user/environment/docker/bindmount/index.html を編集し、Webサイトにアクセスして更新されてたら成功

## 2.DockerイメージをECRにpushする

作成したDockerイメージにECRの規則に沿ったタグを付与しECRにpushする

![2.DockerイメージをECRにpush](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/4bde8c2d-61a5-30c7-77a4-7dd721a58227.png)


ECRでリポジトリを作成する ( リポジトリ名前 `blackriverrepo` ) 他の設定はデフォルト値を使用

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/e91db936-3ff7-7f61-29d7-db26e65ecee1.png)


Cloud9からECSにアクセス

```bash:Cloud9-Terminal
$ aws ecr get-login --no-include-email --region ap-northeast-1
# -> Dockerログインコマンドでてくる（長くてビビる）
```

取得したDockerログインコマンドをCloud9上のターミナルで実行し、`login Succeeded` と表示でれば成功。

ECRで作成したリポジトリ `blackriverrepo` を選択し、`プッシュコマンドの通知` ボタンをクリックすると、基本コマンドでてくる ※ 手順1,2は実施済み

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/4b74d3af-2d16-b678-8191-289c3a983811.png)


ECS上のリポジトリにイメージをプッシュするために、Dockerイメージにタグを付ける

```bash:Cloud9-Terminal
$ docker tag volumedemo:latest xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/blackriverrepo:latest
# タグが付いたか確認
$ docker image ls
```

ECRで作成したリポジトリ `blackriverrepo` にDockerイメージをpushする

```bash:Cloud9-Terminal
$ docker push xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/blackriverrepo:latest
```

ECR のリポジトリを確認し、イメージURI `xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/blackriverrepo:latest` があれば成功

```bash:Cloud9-Terminal
# ECR のリポジトリはコマンドでも確認できる
$ aws ecr describe-repositories --region ap-northeast-1
# コマンドでリポジトリの内容確認もできる
$ aws ecr describe-repositories --region ap-northeast-1 --repository-name blackriverrepo
```

AWSコンソールで確認するとこんな感じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/45c53963-7d2a-5f1e-de15-35f47d7c2975.png)


（おまけ）ECRからCloud9にDockerイメージをpullできるか確認

```bash:Cloud9-Terminal
# Cloud9からのDockerイメージを消す
$ docker rmi xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/blackriverrepo
# Cloud9でpullしてECRからDcokerイメージ取得
$ docker pull xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/blackriverrepo
# -> キャッシュに残ってる場合はすぐ取得できる
# -> docker images で xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/blackriverrepo があれば成功
```

```bash:Cloud9-Terminal
# pullしたイメージでDockerコンテナ起動してみる
# コンテナ起動
$ docker run --name blackriver -d -p 8080:80 xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/blackriverrepo:latest
# コンテナ確認
$ docker ps -ls
# Webサイト確認
$ curl http://localhost:8080/index.html
```

## 3.クラスターを作成する

ECSにクラスターを作成。今回はFargate使わずEC2(linux)にする

![3.クラスターを作成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/e7ff1a57-52e3-6dc3-f4d4-20038ddcdd9b.png)


ECSでクラスターを作成する（今回は EC2(linux) で作る） ※cloud9と同じVPCにする

- クラスターの構成
    - クラスター名：cluster01
    - プロビジョニングモデル：オンデマンドインスタンス
    - EC2インスタンスタイプ：t2.small
    - インスタンス数：2
    - EC2 Ami Id：Amazon Linux 2 AMI
    - EBSストレージ：22
    - キーペア：{既存のキーペア指定}　<- コンテナに入るなら設定しておく
    - VPCはcloud9と同じデフォルトVPC
    - セキュリティグループ：`新しいセキュリティグループ`
    - コンテナインスタンスIAMロール：`新しいロールの作成`
    - Tags：`Name`, `ECSInstance01`

設定時の画面　*クリックで拡大

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/4431597a-45b3-0c51-a019-f5fa3f769f4a.png" width="250">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/c04cb717-db7b-c6f9-b760-0f81e56e245c.png" height="300" width="450">

作成がはじまる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/5c387d34-a037-6eee-31a9-998c1a0ecb57.png)

ECSインスタンスが2つ`ACTIVE`になると成功

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/1ff25e40-b31d-215d-f167-439469d04a7e.png)


## 4.(オプション)キャパシティープロバイダーを設定する

キャパシティープロバイダーは設定しなくてもDockerコンテナを起動できる
なので、この章(4章) はやらなくてもよい。（←これに気が付かず苦労した）

![キャパシティープロバイダー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/f31d2f20-16b8-db4d-1bbf-281a4e3469db.png)

キャパシティープロバイダーとは、ECSに作ったクラスターに対する設定
前提として、ECSに紐づくAutoScalling側でスケールイン保護が必要

#### 4-1.AutoScalling `スケールインから保護`

ECSに紐づくAutoScallingを編集

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/3f1623a8-7c2d-5df9-0f1f-683ffc46cde2.png)

インスタンス保護を設定
キャパシティの最小値は `0` になっているが 1 以上にすることをオススメ
`0`だと、EC2インスタンスが0になる可能性がある
EC2インスタンスが0の状態で、ECSでタスク実行すると『キャパシティーが無い』旨のメッセージがでる ※本記事のトラブルシュート参照

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/29f1620e-b78d-c4d7-1bcf-2c4b6512dc52.png)


#### 4-2.AutoScalling `スケールイン保護の設定`

所属するEC2インスタンスを選択し、`スケールイン保護の設定`を設定する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/45318093-7b00-2372-95d9-a9ba43523911.png)

#### 4-3.ECS のクラスター `clustre01` にキャパシティー作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/b4f709ee-5438-8c90-402f-c9f32931649d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/2c8df0fe-663c-f9ee-1d39-ff59591ca5a6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/5123735c-c161-df75-7c32-6079f334dfe2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/499cfdeb-4a01-1e17-e71a-79ca1cec1007.png)



## 5.タスク定義を作成->実行する

タスク定義を作り実行すれば、Dockerコンテナが起動する。

- タスク定義とは？を粗く理解
    - json形式
    - コンテナ起動の設計書

![5.タスク定義を作成->実行](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/d2445209-5e04-9997-e2e1-baed823d162e.png)


#### 5-1.タスク定義を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/9ee86f66-a983-a9b5-210a-8b9e67dafc79.png)

起動タイプ EC2 を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/33484b1e-0e54-fcd2-70fd-2a3af49ad5f3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/0e394e07-e69a-4c9c-da1f-7887bf5c15ea.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/5fd51c06-ca44-a291-5e3c-9088a82a9e98.png)

コンテナ追加
`コンテナ名`, `リポジトリのURI`, `ポートマッピング` を設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/66a0ee30-0791-f7a2-451c-afdc71f7b39e.png)

こんな感じで登録される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/22c28a95-2b78-67fd-2028-627315879183.png)

作成する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/b48117f8-822d-40fb-2fff-ade1e1482d41.png)


#### 5-2.タスク定義を実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/223df212-117d-3b3b-6729-0a2cf0385ab4.png)

`4.(オプション)キャパシティープロバイダーを設定する`を実施していない場合は、このような画面になる。赤枠のメッセージは、`4`を実施していないため、その下の`起動タイプへ切り替える`をクリックし、(今回であれば)EC2を選択すればよい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/0976b59a-a3de-0353-1ad0-f1354e2f94cf.png)

※おまけ※ `4.(オプション)キャパシティープロバイダーを設定する`を実施した場合は、このように赤枠のメッセージが出ない。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/c11ddf57-f92a-5572-ca8c-906fa5ee8dd1.png" width="350">

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/ea448c55-ea83-2ac3-3d8e-f8ca6f1b1e63.png)


#### 5-3.動作確認

Webサイトが開けば成功。
※ Cloud9(Webサイトにアクセスする環境)とクラスターに所属するEC2インスタンスが通信できるようSG(セキュリティグループ)を設定する。

![動作確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/e71c1921-ac13-087c-bdd6-707ac09e398d.png)

Dockerコンテナが起動しているEC2インスタンスのローカルIPを確認するとアクセスするURLがわかる。

```bash:Cloud9-Terminal
$ curl http://{Dockerコンテナが起動しているEC2インスタンスのローカルIP}/index.html
# -> 以下のような結果になれば成功
# <html><head><title>test</title></head><body><h1>TEST</h1></body></html>
```

今回はこれでおわり

![line.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/06917eca-093b-ea99-5681-5423b5bd8387.png)
 
## トラブルシュート

タスク実行したときにキャパシティープロバイダー戦略のエラー発生

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/c2bfada4-7b9b-a6e3-dc02-353573d8d028.png)

↓赤枠のメッセージ↓

>キャパシティープロバイダー戦略
クラスターのデフォルト戦略
指定されたクラスターのデフォルトのキャパシティープロバイダー戦略にキャパシティープロバイダーが含まれていません。デフォルトのキャパシティープロバイダー戦略を更新して、1 つ以上のキャパシティープロバイダーを関連付けてから、もう一度お試しください。

- ＜考えられる原因＞
    - (1) ECSに作成したクラスターにキャパシティープロバイダーの設定がない
    - (2) キャパシティープロバイダーに設定したAuto Scalling に`スケールイン保護の設定`されたEC2インスタンスがない
- ＜考えられる対策＞
    - (1)の対策案-1：本記事【4.(オプション)キャパシティープロバイダーを設定する】を実施しキャパシティープロバイダーを設定する
    - (1)の対策案-2：`キャパシティープロバイダー`を使わない。`起動タイプへ切り替える`をクリックする。本記事【5-2.タスク定義を実行】参照
    - (2)の対策案：本記事【4-1.AutoScalling スケールインから保護】を参照し、Auto Scalling に最低1つ`スケールイン保護の設定`されたEC2インスタンスが含まれるようにする。