AWS Fargateをコマンドベースで作る(少しGUI)

こんにちわ、Loco ParntersでSREチームのエンジニアです。

ECSをCodepiplelineの中で使うことによってCI/CDが実現できます。
今回はECSのFargateでコンテナを簡単に構築する方法を書いてみました。

##1.ECSの概要
Docker(コンテナ)をローカル端末で使用しているユーザは多いと思います。 `docker-compose` 使ったり `Dockerfile` を使用して Apache や nginx 、 MySQLなどを構築する。

ところが、AWSのEC2でDockerを使用したい場合、以下のような問題に直面すると思います。

* WEBのDocker(同じコンテンツを持ったDocker)が5台、起動した際にALBに自動で接続したい
* WEBのDockerを常に5台起動させておきたい
* EC2をまたいでもWEBとMySQLを通信させたい(RDS使わない前提で)

このようなエンタープライズ向けのコンテナサービス展開をECSで実現できます。

##2.ECSの概念
ECSを最初に起動したときに以下のような説明の画面が表示されます。こちらをベースに説明します。
![スクリーンショット 2018-12-05 14.44.27.png](https://qiita-image-store.s3.amazonaws.com/0/79473/edb6e196-a70e-e744-8fb4-49b0ed7aee39.png)

+ ContainerDefinition
これはそのままコンテナの定義です。具体的には｢コンテナのイメージはnginxを使う｣
と言ったコンテナの基本的な設定を指します。

+ Task definition
次にTask definitionですが、これは
｢WEBのコンテナとRedisのコンテナを1つのグループにしたい｣
｢タスク全体(コンテナ全体)といてどのくらいのCPUやメモリを割り当てたい｣
｢EC2上で稼働させるEC2、サーバレスで稼働させるFargate｣
のようなグループを定義するものです。

+ Service
｢上記で定義したTask definitionをいくつ動かすか｣
｢アタッチするALBはどれか?｣
｢Dockerはどこのsubnetに属するか｣
｢AutoScalingを実施するか?｣
････
と言った内容を設定できます。

+ Cluster
上記のタスクとサービスを稼働させる箱です。

今回は以下の条件でECSを作成します。
+ ECRよりイメージを作成するようにする
+ nginxとredisの2つのコンテナを一つのタスクにする
+ タイプはサーバレスの｢Fargate｣で作成
+ ALBにnginxだけアタッチする

##3.ECRの作成

###1.aws cliでの作成
`aws cli` が既に使える前提で書いておきます。
まだの人は [AWS Command Line Interface のインストール](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/installing.html) と
[設定ファイルと認証情報ファイル](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-config-files.html)を手順を踏んで使える状態にしてください

###2.Dockerfileの作成
コンテナはDockerhubよりプルして構築してもいいのですが、折角なのでDockerhubのAWS版(?)であるECRを使用します。

nginxでは特定の設定を行っていません。必要に応じて設定を行ってください。

```Dockerfile
FROM amazonlinux:2.0.20180827

ADD nginx/nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum --enablerepo=nginx install -y nginx

RUN yum update -y
RUN yum --enablerepo=nginx install -y nginx
RUN yum install -y zip \
                   procps \
                   iputils \
                   awscli \
                   unzip \
                   libtool-ltdl \
                   libtool \
                   libXpm \
                   mysql \
                   httpd \
                   libtiff \
                   autoconf \
                   gcc \
                   gcc-c++ \
                   automake \
                   git \
                   systemd-sysv \
                   libxslt \
                   https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm \
                   http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

RUN yum install -y --enablerepo=amzn2-core gd-last
RUN yum install -y supervisor

## Install PHP7.2
RUN yum install --enablerepo=remi-php72 --disablerepo=amzn2-core -y php \
                                           php-fpm \
                                           php-devel \
                                           php-mbstring \
                                           php-pdo \
                                           php-mysqlnd \
                                           php-gd \
                                           php-xml \
                                           php-mcrypt

## Modify TimeZone
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

## Add supervisor
ADD supervisor/supervisord.conf /etc/supervisord.d/supervisord.conf

## Start nginx settings
EXPOSE 80
CMD /usr/bin/supervisord -c /etc/supervisord.d/supervisord.conf
```
上記で必要なファイルは `nginx.repo` と `supervisord.conf` ですが、それぞれ


```nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=1
```

```supervisord.conf
[supervisord]
nodaemon=true

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"

[program:php-fpm]
command=/usr/sbin/php-fpm --nodaemonize

```
###2.イメージリポジトリの作成
ECRにイメージリポジトリを作成します。

```
$ aws ecr create-repository --repository-name myrepo
{
    "repository": {
        "registryId": "353XXXXXXXXX",
        "repositoryName": "myrepo",
        "repositoryArn": "arn:aws:ecr:ap-northeast-1:[AWSのID]:repository/myrepo",
        "createdAt": 1543991375.0,
        "repositoryUri": "[AWSのID].dkr.ecr.ap-northeast-1.amazonaws.com/myrepo"
    }
}
```

###3.ECRへの登録
レジストリに対して Docker クライアントを認証するために使用するログインコマンドを取得

```
$ aws ecr get-login --no-include-email --region ap-northeast-1
(以下のreponse)
docker login -u AWS -p eyJwYXlsb2FkIjoiWlRla0xlNlUw
････
････
････ap-northeast-1.amazonaws.com
```
返ってきたresponseをそのままコピペしてコンソールに貼り付けてEnter

```
･･･････
RGxJb3BDNnpzMGJNZFJyWVNIYS9DMzlrQ3JjUDhrVnByRTlmK2tRQUFBSDR3ZkFZSktvWklodmNOQVFjR29HOHdiUUlCQURCb0Jna3Foa2lHOXcwQkJ3RXdIZ1lKWUlaSUFXVURCQUV1TUJFRURLUVJQeUVvbHZmZDUvL1FBUUlCRUlBNyt3SEV2Q2haeXNaQldhSkRoNmo5UDE2U0JUQlZVQUo1YU5uM0Z4elNhSThrdmdyZDYwWHBpQ0dpWm9NWUhoTXQ4MnJ5VlVRWkppU05VQlk9IiwidmVyc2lvbiI6IjIiLCJ0eXBlIjoiREFUQV9LRVkiLCJleHBpcmF0aW9uIjoxNTQ0MDM0NzQ0fQ== XXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded
````
上記のように `Login Succeeded` と出たら成功です。

あとは登録するために以下のコマンドを順番に入力します。

```
(Dockerfileが存在するディレクトリ内で)
$ docker build -t myrepo .

$ docker tag myrepo:latest [AWSのID].dkr.ecr.ap-northeast-1.amazonaws.com/myrepo:latest

$ docker push [AWSのID].dkr.ecr.ap-northeast-1.amazonaws.com/myrepo:latest

```
イメージの確認を行います。

```
$ aws ecr describe-images --repository-name myrepo
{
    "imageDetails": [
        {
            "imageSizeInBytes": 571860528,
            "imageDigest": "sha256:cb1a28b1eXXXXXXXXXXXXXx",
            "imageTags": [
                "latest"
            ],
            "registryId": "35363XXXXXXXXX",
            "repositoryName": "myrepo",
            "imagePushedAt": 1543307109.0
        }
    ]
}
```

以上で、WEBのイメージがECRに登録されました。

##4.clusterの作成
最初にECSの大外から作成していきます。

```
$ aws ecs create-cluster --cluster-name mycluster
[以下のresponseが返る]
{
    "cluster": {
        "status": "ACTIVE",
        "statistics": [],
        "clusterName": "mycluster",
        "registeredContainerInstancesCount": 0,
        "pendingTasksCount": 0,
        "runningTasksCount": 0,
        "activeServicesCount": 0,
        "clusterArn": "arn:aws:ecs:ap-northeast-1:111111111111:cluster/mycluster"
    }
}
```

##5. Task definitionの作成
今回は、Fargateで作成したので以下の制限やお決まりごとがあります。

| パラメータ | おきまりごと |
|:-----------|:------------|
| networkMode      | awsvpcのみ対応        | 
| links     | 同じタスク定義内のコンテナはlocalhostを共有しているので、localhost+ポート番号でアクセスさせる      |
| イメージ     | ECRとDocker Hubのパブリックリポジトリのみサポート|
| Task Size         | タスク全体に割り与えるメモリとCPUで、Fargateでは必須パラメータ。値は組み合わせから選ぶ必要がある          |

上記の条件を加味して以下のtask.jsonを作成します。
最初にTaskのRoleとexecutionのRoleをIAMで作成します。
[参考URL]
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-iam-roles.html#create_task_iam_policy_and_role
Roleができたら(今回はAmazonECSTaskS3BucketRoleという名前で作りました)下記のtask.jsonを作成します。
このRoleについてはもう少し詳しく+詳細に設定しなければならないのですが今回は長くなるので一つのRoleで作成しました。

```task.json
{
    "family": "mytask",
    "taskRoleArn": "arn:aws:iam::[AWSのID]:role/AmazonECSTaskS3BucketRole",
    "executionRoleArn": "arn:aws:iam::[AWSのID]:role/AmazonECSTaskS3BucketRole",
    "networkMode": "awsvpc",
    "containerDefinitions": [
        {
            "name": "web",
            "image": "[AWSのID].dkr.ecr.ap-northeast-1.amazonaws.com/myrepo",
            "portMappings": [
                {
                    "containerPort": 80,
                    "hostPort": 80,
                    "protocol": "tcp"
                }
            ]
        },
        {
            "name": "redis",
            "image": "redis:latest",
            "portMappings": [
                {
                    "containerPort": 6379,
                    "hostPort": 6379,
                    "protocol": "tcp"
                }
            ]
        }
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "512",
    "memory": "2048"
}
```

上記で

```
 "taskRoleArn": "arn:aws:iam::[AWSのID]:role/AmazonECSTaskS3BucketRole",
 "executionRoleArn": "arn:aws:iam::[AWSのID]:role/AmazonECSTaskS3BucketRole",
```

の部分は[タスク用の IAM ロール](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-iam-roles.html)を参考にIAMでRoleを作成します。

タスクの登録を行います。

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

##6.ALBの作成
ALBを作成します。これはGUIで作成しても構いません。
サービス登録にあたりALBを使用しますが、ALBを作成する際にターゲットグループの部分で、｢ターゲットの種類｣を選択するが｢インスタンス｣と｢IP｣が選べます。

* Fargateはインスタンスタイプでは無いので<font color="DeepPink">｢IP｣でロードバランサを作成</font>してください。
* ヘルスチェックは[index.html]を指定してください。


##7.サービスの登録

```
$ aws ecs create-service \
--cluster cluster-api \
--service-name cluster-service \
--launch-type "FARGATE" \
--task-definition cluster-task \
--network-configuration "awsvpcConfiguration={subnets=[subnet-XXXXXX,subnet-YYY],securityGroups=[sg-ZZZ],assignPublicIp=ENABLED}" \
--deployment-configuration  maximumPercent=200,minimumHealthyPercent=100 \
--load-balancer targetGroupArn=arn:aws:elasticloadbalancing:ap-northeast-1:111111:targetgroup/web-pre-target/23614c822222222f272,containerName=web_pre_nginx,containerPort=80  \
--desired-count 1
```

以下、説明します。
 *  --network-configurationには配置するSubnetを指定することができます。
 * ｢containerName=web｣の部分は、上記のtask.jsonの｢"name": "web"｣の文言と合わせます。
 * 最後の `--desired-count` は タスクをいくつ起動させるかになりますので必要数設定してください

##8.確認
あとは、ALBのDNS経由でコンテンツが見れればOKです。

<img width="660" alt="スクリーンショット 2018-12-05 16.10.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/79473/f4ec0f64-f032-589c-54ce-bc7bb602d7c3.png">