【AWS】GithubからCodePipelineでECS/Fargateにデプロイする方法

#【AWS】GithubからCodePipelineでECS/Fargateにデプロイする方法

CodePipelineを使ってECS/Fargateにnginx,phpコンテナをデプロイする方法をまとめます。

## 目次
 * [構築イメージ](#構築イメージ)
 * [動作環境](#動作環境)
 * [手順](#手順)
   * [STEP1. ECRの作成とイメージのプッシュ](#step1-ecrの作成とイメージのプッシュ)
   * [STEP2. タスク定義ファイルとAppspecファイルの作成](#step2-タスク定義ファイルとappspecファイルの作成)
   * [STEP3. アプリケーションロードバランサーとターゲットグループを作成する](#step3-アプリケーションロードバランサーとターゲットグループを作成する)
   * [STEP4. ECSクラスターとサービスを作成する](#step4-ECSクラスターとサービスを作成する)
   * [STEP5. CodeDeployアプリケーションとデプロイグループを作成](#step5-codedeployアプリケーションとデプロイグループを作成)
   * [STEP6. CodeBuildを作成](#step6-codebuildを作成)
   * [ポイント1. CodeBuildにポリシーを付与](ポイント1-codebuildにポリシーを付与)
   * [ポイント2. 特権を付与](ポイント2-特権を付与)
   * [STEP7. CodePipelineを作成](#step7-codepipelineを作成)
 * [動作確認](#動作確認)

---------------------------------------
## 構築イメージ

![ZAC_Infra_Diagram-Qiita_20200624_Infra.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/0b8c752e-dbfe-7d05-fb95-0dd63ad5273d.png)


## 動作環境・前提条件

【動作環境】
`OS : macOS 10.14.6`

【前提条件】
VPC,サブネットは作成済


## STEP1. ECRの作成とイメージのプッシュ

ECR(Amazon Elastic Container Registry)とはDocker Hubのようなコンテナイメージを保管するAWS上のレジストリのことです.

### ECRの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/c6f9cf7b-6d37-d1ea-e203-5d1cb4e60b26.png)

複数のイメージコンテナをデプロイするため、レジストリも複数作成します.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/c4afd7bf-5d60-a3f5-3da8-59ab592ebd6b.png)


リポジトリ名は任意で構いませんが、後ほどdockerイメージとタグ付けを行うため,管理しやすいようdockerfileで定義したコンテナ名と同じ名前をつけています.

### イメージのプッシュ

次にそれぞれのリポジトリ名をクリックし、リポジトリに入り、`プッシュコマンドの表示`を押します.　

表示されたコマンドを手元のMACのターミナルで実行します.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/51a59fe6-9455-f273-420c-4129cfb2ef10.png)

実行後、ECRにlatestタグが付いたイメージがアップロードされます.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/27ec02f0-a037-b1b8-75ef-4b6eba0c93cb.png)

## STEP2. タスク定義ファイルとAppspecファイルの作成

### タスク定義ファイル

AWSのチュートリアルでは`taskdef.json`と記載されていますが、
複数コンテナを同じタスクに登録する場合,この時点ではテンプレートを作成し、codebuildの際にtaskdef.jsonを作成するように設定します.

```json:taskdef-template.json
{
    "executionRoleArn": "arn:aws:iam::アカウントID:role/ecsTaskExecutionRole",
    "containerDefinitions": [{
            "name": "nginx",
            "image": "<IMAGE_NGINX_NAME>",
            "essential": true,
            "portMappings": [{
                "hostPort": 80,
                "protocol": "tcp",
                "containerPort": 80
            }]
        },
        {
            "name": "php",
            "image": "<IMAGE_PHP_NAME>",
            "essential": true,
            "portMappings": [{
                "hostPort": 9000,
                "protocol": "tcp",
                "containerPort": 9000
            }]
        }
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "networkMode": "awsvpc",
    "cpu": "256",
    "memory": "512",
    "family": "ecs-task"
}
```
### Appspecファイル

AppspecファイルはCodeDeployの際に実行されます.

```yml:appspec.yml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION> 
        #<TASK_DEFINITION>は変えずにそのままにする
        LoadBalancerInfo:
          ContainerName: "nginx"
          ContainerPort: 80
```

作成したタスク定義ファイルとAppspecファイルをプロジェクト直下のフォルダに保存し、githubにpushします.

## STEP3. アプリケーションロードバランサーとターゲットグループを作成する

アプリケーションロードバランサとターゲットグループをAWSチュートリアルに沿って作成します.
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-ecs-ecr-codedeploy.html#tutorials-ecs-ecr-codedeploy-loadbal


## STEP4. ECSクラスターとサービスを作成する

ECSクラスターとサービスも上記リンクの`STEP4`と同じように作成します.

参考までに作成したサービス作成ファイルを掲示します.

```json:create-service.json
{
    "taskDefinition": "ecs-task",
    "cluster": "ecs-cluster01",
    "loadBalancers": [{
        "targetGroupArn": "AWSのサイトで確認したtargetgroupのArnを入力",
        "containerName": "nginx",
        "containerPort": 80
    }],
    "desiredCount": 1, #タスク実行数を定義しています.
    "launchType": "FARGATE",
    "schedulingStrategy": "REPLICA",
    "deploymentController": {
        "type": "CODE_DEPLOY"
    },
    "networkConfiguration": {
        "awsvpcConfiguration": {
            "subnets": [
                "subnet-0fXXXXXのようなsubnet IDを記載",
                "subnet-0fXXXXXのような2つ目のsubnet IDを記載"
            ],
            "securityGroups": [
                "security gropuのIDを記載"
            ],
            "assignPublicIp": "ENABLED"
        }
    }
}
```

こちらもプロジェクトフォルダに保存し、ローカルPCのターミナルからコマンドを実行,サービスを作成します.
*ecs-test-serviceはサービス名のため任意の名前を付けられます

```bash
aws ecs create-service --service-name ecs-test-service --cli-input-json file://create-service.json
```

## STEP5. CodeDeployアプリケーションとデプロイグループを作成

こちらもAWSチュートリアルに従って作成していきます
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-ecs-ecr-codedeploy.html#tutorials-ecs-ecr-codedeploy-cluster

###注釈
まずは動かして動作を確かめる場合はチュートリアル通りで問題ないです。

ただチュートリアルだと`すぐにトラフィックを再ルーティング`となっておりデプロイした新しいターゲットグループに対してすぐにユーザがアクセスできるようになっています。

またデフォルトでは1時間後に、古いリビジョンが消去されるようになっているため、こちらも運用ポリシーに沿って変更します。

こちらではどちらも1日と設定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/dfeeabd6-c91e-0af1-4cfa-f8119c87af96.png)

## STEP6. CodeBuildを作成

CodeBuildを実行するためにBuildspecファイルを作成し、プロジェクトフォルダのルートに保存します.そして、GithubにPushします.

```yml:buildspec.yml
version: 0.2

phases:
  pre_build:
    commands:
      - IMAGE_URI_WEB=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME_WEB
      - IMAGE_URI_PHP=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME_PHP
      - $(aws ecr get-login --no-include-email --region ${AWS_DEFAULT_REGION})
  build:
    commands:
      - docker-compose -f docker-compose-production.yml build
      - docker tag src_web:$IMAGE_TAG $IMAGE_URI_WEB:$IMAGE_TAG
      - docker tag src_php:$IMAGE_TAG $IMAGE_URI_PHP:$IMAGE_TAG
  post_build:
    commands:
      - docker push $IMAGE_URI_WEB:$IMAGE_TAG
      - docker push $IMAGE_URI_PHP:$IMAGE_TAG
      #taskdef-templateで定義したIMAGE_URIを環境変数で置き換えています.
      - cat taskdef-template.json | sed -e s@\<IMAGE_WEB_NAME\>@$IMAGE_URI_WEB:$IMAGE_TAG@ -e s@\<IMAGE_PHP_NAME\>@$IMAGE_URI_PHP:$IMAGE_TAG@ > taskdef.json
artifacts:
  files:
    - appspec.yaml
    - taskdef.json
```

その後、AWSの管理画面からCodeBuildの作成を選択します。
作成時に環境変数を設定する画面があるためにそこにリポジトリ名などを入れます。
そして、ソースとしてGithubを選択します.

アーティファクトも不要です.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/5a78a420-027f-b5c1-69bb-c12331939983.png)

### ポイント1. 特権を付与

こちらを必ずチェックします.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/02c378a3-15e2-b1d7-70c5-ac5d549f5b92.png)

そしてBuildの作成を実行します.

### ポイント2. CodeBuildにポリシーを付与

codebuild作成後、CodeBuildがECRにアクセスできるようにCodeBuildのサービスロールに`AmazonEC2ContainerRegistryPowerUser`ポリシーをアタッチします.


## STEP7. CodePipelineを作成

最後にCodePipelineの作成を選択し、ソースにはGithub, ビルドステップではSTEP5で作成したCodeBuildを選択します。

デプロイステップではAmazon ECS(ブルー/グリーン)を選択し、
Source Artifactでtaskdef.jsonとappspec.ymlを選択します。

`タスク定義の動的な更新イメージ - オプショナル`は入力不要です。

##　動作確認

作成が完了するとCodePipelineがスタートします。

ここでDeployのところでステータスが処理中になります。

そのため、ロードバランサのDNS:8080(テスト用のリスナー)にアクセスに新しくデプロイしたシステムが問題なく動くか確認します。

問題なさそうであればDeploy画面からトラフィックの再ルーティングをクリックすると、
新システムにトラフィックが流れ始めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/54283058-6082-668c-e7c2-32014767aa85.png)

その後、CodeDeployで設定した待機時間をすぎると古いタスクが停止されCodeDeployが成功となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/434ba320-0b14-899a-cf20-d36e1dd35a42.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/d30378c0-6700-c4fb-b189-d487512eb55e.png)


実際にAWSの管理画面からECSを選択し、タスクが実行されていることを確認します。

そしてブラウザからロードバランサが持つ、パブリックDNSに対してアクセスし、PHPの画面が表示されれば成功です。

その後、githubに対して新たなバージョンをpushするとcodepiplineが実行されます.