AWS CodePipelineがECSへのデプロイをサポートしたのでやってみる

AWS CodePipeline が Amazon ECSへのデプロイをサポートしました。(先日発表されたばかりのFargateも)
https://aws.amazon.com/jp/about-aws/whats-new/2017/12/aws-codepipeline-adds-support-for-amazon-ecs-and-aws-fargate/
これによりコンテナ上で稼働するアプリケーションの継続的デリバリーがより簡単に実装できるようになります。

早速チャレンジしてみました。
# ECSでテスト用のコンテナを起動する
## ECR の設定
Elastic Container Registry に今回のテスト用のリポジトリを作成します。
後ほどリポジトリのURIを使用しますので、メモしておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/995a609f-2819-7227-7dc0-d566e1575d5c.png)

## Dockerfileの作成
今回は公式ドキュメントのhello-worldを流用しました。
基本的には[こちら](http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/docker-basics.html)の手順を踏襲します。

```Dockerfile
FROM ubuntu:12.04

# Install dependencies
RUN apt-get update -y
RUN apt-get install -y apache2

# Install apache and write hello world message
RUN echo "Hello World!" > /var/www/index.html

# Configure apache
RUN a2enmod rewrite
RUN chown -R www-data:www-data /var/www
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2

EXPOSE 80

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

## イメージをビルド
Dockerfileをビルドし、ECRにプッシュします

```
$ docker build -t ecs-test-repo .
$ docker tag ecs-test-repo:latest 012345678910.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-test-repo:latest
$ docker push 012345678910.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-test-repo:latest
```

## ECSへのデプロイ
以下のようなタスク定義ファイルを作成します。

```json:imagedefinitions.json
{
    "family": "ecs-hello-world",
    "containerDefinitions": [
        {
            "name": "ecs-hello-world",
            "image": "012345678910.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-test-repo:latest",
            "cpu": 10,
            "memory": 128,
            "portMappings": [
                {
                    "containerPort": 80,
                    "hostPort": 80
                }
            ],
            "entryPoint": [
                "/usr/sbin/apache2",
                "-D",
                "FOREGROUND"
            ],
            "essential": true
        }
    ]
}
```

AWS CLIでタスク定義の登録を行います。

```
$ aws ecs register-task-definition --cli-input-json ./imagedefinitions.json
```

コンソールでタスク定義が登録されていることを確認します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/767e8814-89fa-0fda-ab3d-73b3edd1b009.png)

新規サービスを作成し、登録したタスクを起動します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/964ad2c3-9ac9-0fa0-e23c-d86e5ff4982e.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/be0a7247-004e-9216-a50e-a38b510fe8eb.png)

クラスタインスタンスのパブリックIPにアクセスすると、Hello Worldが表示されます。
長くなりましたが、以上で事前準備が完了しました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/b7058e74-9aba-9e7b-eaf8-823b530950b9.png)

# AWS CodeCommitの設定
DockerイメージをECRにプッシュするための buildspec.yml を作成し、CodeCommitのリポジトリに追加します。
## リポジトリの作成
リポジトリ名を指定して、新規作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/e344c2a7-943f-c4b1-69d4-b9e045909b50.png)

今回、Eメール通知の設定はスキップします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/2bb50794-8ce3-f650-c4bb-feaedf4deb65.png)

## リポジトリへの接続
ローカルに作成したリポジトリをcloneします。
リポジトリへの接続には適切な権限をもったIAMユーザおよびGit認証情報が必要ですので
あらかじめ[作成](http://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/setting-up-gc.html)しておきます。

```
$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/ecs-test-repo
Cloning into 'ecs-test-repo'...
warning: You appear to have cloned an empty repository.
````

## buildspec.ymlの作成
REPOSITORY_URIにはECRのURIを設定します。
またイメージタグにソースコードのコミットIDを追加するようにします。

```yaml:buildspec.yml

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - $(aws ecr get-login --region $AWS_DEFAULT_REGION)
      - REPOSITORY_URI=012345678910.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-test-repo
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...          
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo Writing image definitions file...
      - printf '[{"name":"ecs-hello-world","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
    files: imagedefinitions.json
```
artifacts として事前準備で作成したタスク定義ファイルを指定します。
パイプラインの中でタスク定義の新しいリビジョンが作成され、サービスが更新されます。

## CodeCommitへプッシュ
Dockerfile, buildspec.yml, imagedefinitions.json をCodeCommitへプッシュします。
Dockerfileの中身を少しだけ編集しています。

```
RUN echo "Hello World! by CodePipeline" > /var/www/index.html
```

```
$ git add.
$ git commit -m "Adding build specification."
$ git push
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/42b9fc36-3342-d90e-9eee-9f6563a984a6.png)

# Piepelineの設定
## Pipelineの作成
マネージドコンソールの AWS CodePipelineから新規パイプラインを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/f8321ada-7049-b27c-7e4d-5b3c8807fe09.png)

ソースの設定でソースプロバイダを CodeCommitに設定し、先程作成したリポジトリを指定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/54ae6f1a-7b26-5e25-926e-a9b5a01a5c5d.png)

ビルドの設定で以下のように、選択/入力し、ビルドプロジェクトを保存します。

* ビルドプロパイダでAWS CodeBuildを選択
* 新しいビルドプロジェクトを作成 を選択し、任意のプロジェクト名を入力
* 環境イメージで AWS CodeBuildマネージド型イメージの使用を選択し、OS:Ubuntu、ランタイム:Dockerとする

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/432c80fd-647a-d217-4a52-5f81642e4432.png)

デプロイの設定で、プロバイダに Amazon ECS を選択し、展開先のクラスタ名、サービス名を指定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/bb7c79f1-f732-0247-940e-23116e6695a9.png)

AWS サービスロールを選択します。必要に応じて新規作成もできます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/f49e28e1-e840-3cd8-481b-7d18b87c9839.png)

レビュー画面で設定値を確認し、パイプラインを作成します。

## パイプラインの実行
作成直後にパイプラインが起動します。
パイプライン作成ウィザードの中で、CodeBuildのビルドプロジェクト用のサービスロールを作成していますが、
初期の状態ではECRに対する権限が不足しているため、以下のようにビルドが失敗します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/fb0d36b4-df89-9717-4ae0-8d9b6c5252a2.png)

そのためIAM管理画面で、作成したサービスロールに対し、AmazonEC2ContainerRegistryPowerUserポリシーを追加します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/0c04241b-e522-a1e9-8db2-ac2e95b2b384.png)


Buildステージを再実行すると今度はステージングまで処理が完了します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/cafc711f-5897-a7b5-cd05-a310da82ed2c.png)

## 結果確認
タスク定義を確認すると新しいリビジョンが作成されています。
※途中メモリの設定などを誤り複数回デプロイしているため、以下の画面ショットではリビジョンが4まで増えています。。。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/fa32133a-ed03-a9c3-cf1d-1ed26e01366b.png)

同様にサービスも新しいタスク定義のリビジョンで更新されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/2ae8078a-ae75-83e9-eb9e-19be6e7cf342.png)


最後にブラウザで確認します。Hello worldのメッセージが更新されていることを確認できました！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/125105/69ca775c-19ba-65e6-753b-6c4aacffeeb9.png)

# 最後に
以上の手順でCodePipelineでECSへのデプロイを行うことができました。
参考になれば幸いです。