Docker イメージを作って AWS EC2 Container Registory (ECR) にプッシュし、ElasticBeanstalk でデプロイする

Amazon Web Service の EC2 Container Registory (ECR) は、フルマネージドな Docker コンテナリポジトリです。プライベートな Docker イメージをプッシュし、EC2にデプロイすることができます。

### 今回の構成

* AWS EC2 Container Registory (ECR)
* AWS ElasticBeanstalk
* Docker
* Python3
* Django1.10
* uwsgi
* nginx
* supervisor
* Fabric

手元で、Python3 + Django の Docker イメージを作り、EC2 Container Registory (ECR) にプッシュし、ElasticBeanstalk (EB) でデプロイするまで書きます。

## サンプルコード

こちらです。 https://github.com/ytyng/aws-eb-docker-django-skeleton

## Fabric の準備
fabric は必須ではありませんが、私はコマンドをすぐ忘れてしまうため fabric のコマンド化してあります。

とはいえ、サーバに SSH でデプロイするわけではないので、fabric の本来の使い方ではないですね。(make や invoke なんかで良さそうです)

[fabfile](https://github.com/ytyng/aws-eb-docker-django-skeleton/blob/master/fabfile/__init__.py)

fabric は、あらかじめローカル環境にインストールしておく必要があります。
おそらく、Python3 では動作しないため、ローカルの Python2 環境に
インストールする必要があります。

```
$ python
Python 2.7.10
...

$ sudo pip install fabric
```

## Dockerイメージのビルド


```
$ fab build
```
(もしくは $ docker build -t aws-eb-docker-django-skeleton )

```Dockerfile:Dockerfile
FROM ubuntu:16.04

MAINTAINER ytyng

RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip \
    python3-dev \
    python3-setuptools \
    nginx \
    supervisor \
  && rm -rf /var/lib/apt/lists/*

# for pil
# apt-get install libjpeg-dev
# RUN ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /usr/lib/libjpeg.so

# for ipython
# apt-get install lib32ncurses5-dev

# set locale
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

# Python packages
COPY requirements/base.txt /tmp/requirements/base.txt
COPY requirements/production.txt /tmp/requirements/production.txt
RUN pip3 install -r /tmp/requirements/base.txt
RUN pip3 install -r /tmp/requirements/production.txt

# setup all the configfiles
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
COPY conf/nginx-app.conf /etc/nginx/sites-available/default
COPY conf/supervisor-app.conf /etc/supervisor/conf.d/

RUN mkdir /var/log/django

COPY . /var/django/aws-eb-docker-django-skeleton

EXPOSE 80
EXPOSE 443
CMD ["supervisord", "-n"]
```

[Dockerfile](https://github.com/ytyng/aws-eb-docker-django-skeleton/blob/master/Dockerfile)

## DBマイグレーション

```
$ fab manage:migrate
```

↑コマンドが何をしているかは [fabfile](https://github.com/ytyng/aws-eb-docker-django-skeleton/blob/master/fabfile/__init__.py) を見てみてください

##  サーバ起動

```
$ fab up
```

(ポート 80, 443 をバインドします)

```yaml:docker-compose.yml
version: '2'
services:
  aws-eb-docker-django-skeleton:
    build: .
    image: aws-eb-docker-django-skeleton
    container_name: aws-eb-docker-django-skeleton
    ports:
      - 80:80
      - 443:443
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - .:/var/django/aws-eb-docker-django-skeleton
    environment:
      - UWSGI_PROCESSES=1
      - UWSGI_THREADS=1
      - APP_NAME=aws-eb-docker-django-skeleton
      - DJANGO_APP_NAME=aws_eb_docker_django_skeleton
      - DJANGO_SETTINGS_MODULE=aws_eb_docker_django_skeleton.settings.local
```

[docker-compose.yml](https://github.com/ytyng/aws-eb-docker-django-skeleton/blob/master/docker-compose.yml)

`$ docker-machine ls` でIPアドレスを調べ(例: 192.168.99.100 )、
ブラウザで開くと起動ページが見れます

```
$ open "http://192.168.99.100/"
```

![スクリーンショット 2016-10-03 18.06.14.png](https://qiita-image-store.s3.amazonaws.com/0/68302/fe268bf7-d566-5eb0-2440-82df3200a05b.png)

## Docker リポジトリを、AWS EC2コンテナサービス (ECS) に登録する

プライベートリポジトリとして登録できます。

### 1. ローカルPCの AWS CLI を設定 (既に行っていれば不要)

1-1. IAM より、ローカルコンピュータに保存するログインクレデンシャルを作っておきます。
既に作ってある場合は不要です。

[IAMページ](https://console.aws.amazon.com/iam/home?region=ap-northeast-1#)

ユーザー → 新規ユーザーの作成

ユーザー名 適当に (例: aws-cli )

![スクリーンショット 2016-10-03 16.48.17.png](https://qiita-image-store.s3.amazonaws.com/0/68302/5250928a-64ee-8bf5-0a9a-ccb611ad05b7.png)

ユーザーのセキュリティ認証情報を表示 をクリックし、アクセスキー ID と シークレットアクセスキー を保存しておく。

もしくは「認証情報のダウンロード」を押して、CSVを保存しておく。

閉じる をクリック

![スクリーンショット 2016-10-03 17.02.24.png](https://qiita-image-store.s3.amazonaws.com/0/68302/faf85e53-55b1-dbd9-69e4-cbb558a9b1ab.png)


作成したユーザーを選択し、「ポリシーのアタッチ」

![スクリーンショット 2016-10-03 17.02.42.png](https://qiita-image-store.s3.amazonaws.com/0/68302/14922d9f-a7ed-c436-1c3a-df309c0c050e.png)

**AmazonEC2ContainerRegistryFullAccess** をアタッチしておきましょう

![スクリーンショット 2016-10-03 17.04.02.png](https://qiita-image-store.s3.amazonaws.com/0/68302/77b8d028-564e-07f5-0046-33b27a2cf531.png)

![スクリーンショット 2016-10-03 17.04.19.png](https://qiita-image-store.s3.amazonaws.com/0/68302/2f05f555-5cbc-8c13-625a-50e0713fa484.png)


1-2. aws cli のインストール

```
$ sudo pip install awscli --upgrade --ignore-installed six
```

1-3. aws を設定

```
$ aws configure
```

さきほどのクレデンシャル情報を入力しておく

```
AWS Access Key ID [None]: AK................
AWS Secret Access Key [None]: ********************************
Default region name [None]: ap-northeast-1
Default output format [None]:
```

### 2. AWS EC2 コンテナリポジトリの設定とイメージのプッシュ

2-1. AWS コンソールの ECSページを表示

[ECSページ](https://ap-northeast-1.console.aws.amazon.com/ecs/home?region=ap-northeast-1#/firstRun)

初回起動時、チェックボックスが出ます。

Store container images securely with Amazon ECR にチェックON して Continue。

![スクリーンショット 2016-10-03 16.39.16.png](https://qiita-image-store.s3.amazonaws.com/0/68302/9ed0b2a4-3ced-c7af-7979-46836b9e620e.png)

Repository name にリポジトリ名を入力して、Next step。

すると、コンソールでの手順が表示されます。

![スクリーンショット 2016-10-03 16.42.15.png](https://qiita-image-store.s3.amazonaws.com/0/68302/4d4d179e-4c10-bf08-8ef5-acc10798d1bc.png)

2-2. ローカルでコンテナ登録コマンド実行

表示されたコマンドにそって

```
$ aws ecr get-login --region ap-northeast-1
```

ターミナルから入力すべきコマンドが表示されます。つまり、やるべきことは

```
$ $(aws ecr get-login --region ap-northeast-1)
```

です。

```
$ $(aws ecr get-login --region ap-northeast-1)
Flag --email has been deprecated, will be removed in 1.13.
Login Succeeded
```

fab のコマンドも用意してあります。 `$ fab login_ecr`

引き続き、AWSのページの手順に沿って続けます。

```
$ docker tag aws-eb-docker-django-skeleton:latest 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/aws-eb-docker-django-skeleton:latest
```

```
$ docker push 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/aws-eb-docker-django-skeleton:latest
```

fab のコマンドも用意してあります `$ fab push`

### 3. プッシュしたイメージから ElasticBeanstalk でインスタンスを作成

3-1. EBを作成

[EB のページを表示](https://ap-northeast-1.console.aws.amazon.com/elasticbeanstalk/home?region=ap-northeast-1#/getting_started)

プラットフォームの選択 は、**Multi-container Docker** にして、「今すぐ起動」

![スクリーンショット 2016-10-03 17.19.59.png](https://qiita-image-store.s3.amazonaws.com/0/68302/ba9075e8-169d-0847-f958-637a155979ec.png)

(※起動する Docker コンテナは1つですが、単一Docker コンテナの設定ファイルと互換性が無いため、あとからコンテナを増やしたい場合等に困ります。マルチコンテナDockerで全く問題無いでしょう。(メモリ設定が必須で少し面倒なぐらい?))

そしたら、「初めての Elastic Beanstalk アプリケーション」という変なアプリケーションが
出来たので、これは無視して右上の「新しいアプリケーションの作成」をクリック

![スクリーンショット 2016-10-03 17.23.29.png](https://qiita-image-store.s3.amazonaws.com/0/68302/a472d972-2e30-18cc-03c9-63b1a7ddc8c9.png)

アプリケーション名は適当に、例: **aws-eb-docker-django-skeleton**

→ 次へ → ウェブサーバーの作成 →

![スクリーンショット 2016-10-03 17.24.35.png](https://qiita-image-store.s3.amazonaws.com/0/68302/a86c0e6f-a04b-3573-ea60-6cb720e3dfe7.png)


プラットフォームの選択: **Multi-container Docker**

環境タイプ: 単一インスタンス

→ 次へ

送信元: **独自のアップロード**

今回は、手作業で Json ファイルをアップロードします。

本来は、S3経由でコマンドでアップロードなどした方が良いと思うのですが、
ここの ベストプラクティスはまだ確立できていません。(ebコマンドでできるか等もまだ不明…)

EC2コンテナサービス (ECS) を使う場合、*Dockerrun.aws.json* だけアップロードすれば良いです。

```json:Dockerrun.aws.json
{
  "AWSEBDockerrunVersion": 2,
  "containerDefinitions": [
    {
      "name": "aws-eb-docker-django-skeleton",
      "image": "000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/aws-eb-docker-django-skeleton:latest",
      "essential": true,
      "environment": [
        {
          "name": "UWSGI_PROCESSES",
          "value": "2"
        },
        {
          "name": "UWSGI_THREADS",
          "value": "2"
        },
        {
          "name": "APP_NAME",
          "value": "aws-eb-docker-django-skeleton"
        },
        {
          "name": "DJANGO_APP_NAME",
          "value": "aws_eb_docker_django_skeleton"
        },
        {
          "name": "DJANGO_SETTINGS_MODULE",
          "value": "aws_eb_docker_django_skeleton.settings.production"
        }
      ],
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 80
        },
        {
          "hostPort": 443,
          "containerPort": 443
        }
      ],
      "memory": 256,
      "memoryReservation": 128
    }
  ]
}
```

[Dockerrun.aws.json](https://github.com/ytyng/aws-eb-docker-django-skeleton/blob/master/Dockerrun.aws.json)

image の箇所を修正した json ファイルを、「独自のアップロード」のファイルに指定します。

環境名、環境URL は、例: aws-eb-docker-django-skeleton を入力

![スクリーンショット 2016-10-03 17.30.59.png](https://qiita-image-store.s3.amazonaws.com/0/68302/70e0a06d-fb4c-5285-5150-ba5601d1e63c.png)


その他のリソース は、今回は使わないのでチェック無しで「次へ」

![スクリーンショット 2016-10-03 17.31.17.png](https://qiita-image-store.s3.amazonaws.com/0/68302/58ba5e2c-9ff5-1b9a-f2ad-6c2594bfd369.png)


構成の詳細は適当に。今回はテストなので t1.micro で充分です

![スクリーンショット 2016-10-03 17.32.26.png](https://qiita-image-store.s3.amazonaws.com/0/68302/b4016dd1-c63c-a222-c7dd-41537c59e50f.png)

環境タグ も不要なので入力せずに「次へ」

アクセス制限 も、そのままで『次へ」

![スクリーンショット 2016-10-03 17.33.08.png](https://qiita-image-store.s3.amazonaws.com/0/68302/eab3b80a-60bc-4aa0-45d0-07ccad529fb3.png)

内容がプレビューされますので、「起動」

![スクリーンショット 2016-10-03 17.33.38.png](https://qiita-image-store.s3.amazonaws.com/0/68302/956c2cd0-e6da-679c-ed8e-0554b0596f0f.png)

しばらく待つとインスタンスが作られるのですが、そのままでは下記のようにエラーになります。

ECS task stopped due to: Essential container in task exited. (aws-eb-docker-django-skeleton: CannotPullContainerError: AccessDeniedException: User: arn:aws:sts::000000000000:assumed-role/aws-elasticbeanstalk-ec2-role/i-00000000 is not authorized to perform: ecr:GetAuthorizationToken on resource: * status code: 400, request id: 00000000-0000-0000)

![スクリーンショット 2016-10-03 17.53.13.png](https://qiita-image-store.s3.amazonaws.com/0/68302/980c4667-c764-bf5c-b4b7-82fd0bbb3239.png)

![スクリーンショット 2016-10-03 17.53.01.png](https://qiita-image-store.s3.amazonaws.com/0/68302/df0cecf6-e85a-1f22-3570-06714db9bcf5.png)

3-2. ロールにポリシーを設定

エラーの原因は、EB のロールが ECS (コンテナレジストリ) の **GetAuthorizationToken** の
権限が無いためです。付与しましょう。

[IAM のロールページ](https://console.aws.amazon.com/iam/home?region=ap-northeast-1#roles)

から、**aws-elasticbeanstalk-ec2-role** を選択し、「**ポリシーのアタッチ**」→ container で検索して、

**AmazonEC2ContainerRegistryReadOnly** をアタッチします。

![スクリーンショット 2016-10-03 17.58.30.png](https://qiita-image-store.s3.amazonaws.com/0/68302/650b3e94-850d-d73e-7d30-c4dce4233cd9.png)

![スクリーンショット 2016-10-03 17.59.16.png](https://qiita-image-store.s3.amazonaws.com/0/68302/558a85d3-7232-62d1-6fed-45dbabcadc7f.png)


3-3. 再デプロイ

EB のアプリケーションのページの、アップロードとデプロイ → アプリケーションバージョン ページ

![スクリーンショット 2016-10-03 18.02.26.png](https://qiita-image-store.s3.amazonaws.com/0/68302/4bddd6bb-55de-0270-5ec0-498025b83a22.png)

「最初のリリース」を選択して「デプロイ」

Environment update completed successfully. になりました。

![スクリーンショット 2016-10-03 18.04.49.png](https://qiita-image-store.s3.amazonaws.com/0/68302/de71c9b4-9427-3950-f531-d4736a94b857.png)


### 4. ページ確認

EB には1つの URL が割り当てられます。例: aws-eb-docker-django-skeleton.ap-northeast-1.elasticbeanstalk.com

ブラウザでEBのURLにアクセスするとページが表示されます。

![スクリーンショット 2016-10-03 18.06.14.png](https://qiita-image-store.s3.amazonaws.com/0/68302/210884f5-e453-1f1d-26f3-7e76afd4c552.png)


### 追記

EB でデプロイすると、遅いですしダウンタイムもあったので少し使いづらいですね。

複数台構成にしてローリングデプロイなんかで対応する必要がありそうですが、 uwsgi や gunicorn は無停止リロードができるのでせっかくなら活用したい所です。