Docker環境をECSにデプロイ

# はじめに
これまでは Vagrant + VirtualBox で作った仮想環境上で、Dockerコンテナを動かしていましたが、これがどのようにAmazon Elastic Container Service (ECS) にデプロイされるかの手順が良く分かっていませんでした。

そこで、簡単なWebアプリケーションコンテナをECSにデプロイして、AWS上で動作確認するところまでをやってみようと思います。

# 目次

 - Docker イメージを用意
 - ローカルでのDockerイメージの動作確認
 - AWS CLIをインストール
 - ECRにPUSHする為のIAMユーザを作成
 - AWS CLIに認証情報を設定
 - キーペアの作成
 - ECRにリポジトリを作成
 - ECRの該当リポジトリにPUSH
 - ECSのタスク定義を作成
 - ECSのクラスターを作成
 - ECSのタスクを実行
 - 動作確認

# Docker イメージを用意
今回はAWS上でのWebアプリケーションの動作確認が目的です。
Dockerイメージを一からビルドしてもいいのですが、それだと時間がかかるので、公開されているDockerイメージ（今回はtraining/webapp）を利用させてもらいます。

例によって、私の環境はWindows7 なので、Vagrant+VirtualBoxで新しい仮想環境を作って、そこにDockerイメージをダウンロードしようと思います。
今回は途中AWS CLIのインストールなどもある為、CentOSで仮想環境を作ります。

```
> vagrant box add centos/7
> vagrant init centos/7
```

ホストOS（Windows7）と、ゲストOS（centos/7）間のポートフォワーディング設定部分のコメントアウトを外します。

```lang:vagrantfile
26| config.vm.network "forwarded_port", guest: 80, host: 8080
```

仮想環境を起動します。

```
> vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'centos/7'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'centos/7' is up to date...
==> default: Setting the name of the VM: test_ecs_deploy_v2_default_1566893989169_14897
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 80 (guest) => 8080 (host) (adapter 1)
    default: 22 (guest) => 2222 (host) (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default:
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default:
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if it's present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: No guest additions were detected on the base box for this VM! Guest
    default: additions are required for forwarded ports, shared folders, host only
    default: networking, and more. If SSH fails on this machine, please install
    default: the guest additions and repackage the box to continue.
    default:
    default: This is not an error message; everything may continue to work properly,
    default: in which case you may ignore this message.
==> default: Rsyncing folder:
```

ゲストOS（CentOS）にSSHでログインします。

```
> vagrant ssh
```

Dockerをインストールします。

```
$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum makecache fast
$ sudo yum install docker-ce
```

Dockerデーモンを起動します。

```
$ sudo systemctl start docker
```

# ローカルでDockerイメージの動作確認

今回、ECSにデプロイするWebアプリケーションコンテナ（training/webapp）をpull（ダウンロード）します。

```
$ sudo docker pull training/webapp
Using default tag: latest
latest: Pulling from training/webapp
[DEPRECATION NOTICE] registry v2 schema1 support will be removed in an upcoming release. Please contact admins of the docker.io registry NOW to avoid future disruption.
e190868d63f8: Pull complete
909cd34c6fd7: Pull complete
0b9bfabab7c1: Pull complete
a3ed95caeb02: Pull complete
10bbbc0fc0ff: Pull complete
fca59b508e9f: Pull complete
e7ae2541b15b: Pull complete
9dd97ef58ce9: Pull complete
a4c1b0cb7af7: Pull complete
Digest: sha256:06e9c1983bd6d5db5fba376ccd63bfa529e8d02f23d5079b8f74a616308fb11d
Status: Downloaded newer image for training/webapp:latest
docker.io/training/webapp:latest
```

コンテナを起動して、動作確認をしましょう。

```
$ sudo docker run -d -p 80:5000 training/webapp python app.py
103772d31387e0dc150dcb2ef8916c2c6f554a4f1fbb670a7b96560a7e219b2f
$
```

ブラウザで http://localhost:8080/ へアクセスして Hello world! の文字が表示されればOKです。

# AWS CLI をインストール
AWS CLI は、Pythonのパッケージ管理ツール pip 経由でインストールします。
その為、まずは pip をインストールします。

```
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
$ sudo python get-pip.py
Successfully installed pip-19.2.3 setuptools-41.2.0 wheel-0.33.6
```

AWS CLI をインストールします。

```
$ sudo pip install awscli
Successfully installed PyYAML-5.1.2 awscli-1.16.226 botocore-1.12.216 colorama-0.3.9 docutils-0.15.2 futures-3.3.0 jmespath-0.9.4 pyasn1-0.4.6 python-dateutil-2.8.0 rsa-3.4.2 s3transfer-0.2.1 six-1.12.0 urllib3-1.25.3
```

バージョンを確認します。

```
$ aws --version
aws-cli/1.16.226 Python/2.7.5 Linux/3.10.0-957.12.2.el7.x86_64 botocore/1.12.216
```

# IAMユーザを作成
以下の手順で作成します。

 - AWS のコンソールから IAM サービスにアクセスします。
 - サイドバーにある[ユーザー]をクリックします。
 - ユーザー名を入力し、[プログラムによるアクセス]をチェックします。
 - [次のステップ:アクセス権限]をクリックします。
![aws_iam_user_create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477633/eb064d0b-9fcc-d74e-fb9a-24fea6dcb4e9.png)
 - [既存のポリシーをアタッチ]をクリックします。
 - [フィルター: ポリシータイプ]の検索窓に’EC2’と入力
 - 以下のポリシーの項目をチェックします。  
[AmazonEC2ContainerServiceFullAccess],  
[AmazonEC2ContainerRegistryFullAccess]  
※管理者権限による Amazon ECR へのフルアクセスを許可するものです。
![aws_iam_user_create_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477633/35b27f59-7427-01d9-db9a-178bb7010724.png)
 - [次のステップ:タグ]を選択します。  
※タグの追加は今回は行わないのでスキップします。
 - [次のステップ:確認]を選択します。
 - [ユーザーの作成]をクリックします。
 - [.csvのダウンロード]から認証情報が記載されたファイルをダウンロードします。

# AWS CLIに認証情報を設定
先ほどダウンロードした認証情報ファイルを開き、aws cli に設定していきます。

```
$ aws configure                 #AWS 認証情報の設定
$ AWS Access Key ID [None]:    #取得したアクセスキーID を入力
$ AWS Secret Access Key [None]: #取得したシークレットアクセスキーIDを入力
$ Default region name [None]:   ap-northeast-1
$ Default output format [None]: json
$ aws configure list            #設定した内容の確認
```

# キーペアの作成
AWSコンソールにて、EC2 > キーペア で「キーペアの作成」をクリックします。
![aws_ec2_key_pairs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477633/bf1a09c0-dfc7-7937-0ec3-b18ba2acf45e.png)
キーペア名は何でもいいですが、とりあえず「ecs-deploy」としましょう。

作成すると、秘密鍵ファイルがダウンロードされます。

# ECRにリポジトリを作成
AWSコンソールにて、ECR をクリックし、「リポジトリの作成」をクリックします。
リポジトリ名を入力する画面になるので、「python」とでもしておきましょう。

# ECRの「python」リポジトリにPUSH
早速作成したリポジトリにtraining/webappイメージをpythonという名前でAWS CLIにて、PUSH（アップロード）しましょう。

AWS コンソールにて、作成した「python」リポジトリをクリックすると、右上に「プッシュコマンドの表示」というボタンがありますので、これをクリックします。
表示されたコマンドで、DockerイメージのPUSHができます。

まずはaws cli でAWSにログインします。

```
$ sudo $(aws ecr get-login --no-include-email --region ap-northeast-1)
```
まだパスワード設定していないので、上記のコマンドだけでいけます。

AWSコンソールの案内では、Dockerイメージの構築を docker buildコマンドで行うように記載されていますが、今回はpullしたtraining/webappイメージをpushしますので、この手順はスキップします。

手元のtraining/webappイメージに、タグ付けしていきます。

```
$ sudo docker tag training/webapp:latest XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/python:latest
```

XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com の部分は、ECRのイメージURIです。

では、イメージをpushします。

```
$ sudo docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/python:latest
```

AWSコンソールからECRサービスに移動し、pythonリポジトリにイメージがあるか確認しましょう。

# ECSのタスク定義を作成
以下の手順でタスク定義を作成します。

 - AWSコンソールからECSサービスに移動します。
 - 左メニューのタスク定義をクリックします。
 - 「新しいタスク定義の作成」をクリックします。
 - 「起動タイプの互換性の選択」にて「EC2」を選択し（Fargateはまだ使ったことがないので。。。）、「次のステップ」をクリックします。
 - 「タスク定義名」に「python」と入力します。
 - 「コンテナ定義」の「コンテナの追加」をクリックします。
 - 「コンテナ名」に「python」と入力します。
 - 「イメージ」には先ほど`docker tag`コマンドで設定したイメージURIとイメージ名、タグを入力します。  
（例：XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/python:latest）
 - 「メモリ制限」には「128」と入力します。
 - 「ポートマッピング」には「80」（ホスト）「5000」（コンテナ）と入力し、「追加」ボタンをクリックします。
 - 「詳細コンテナ設定」の「環境」で、「CPUユニット数」を「1」、「コマンド」に「python, app.py」を入力します。（※）
 - 最後に、「作成」ボタンをクリックします。

※コマンドに設定している「python」と「app.py」の間は「,」で区切ることに注意してください。（私はスペースで区切っていたため、コンテナが起動せずはまりました。）

# ECSのクラスターを作成
以下の手順でクラスターを作成します。
 - 左メニューの「クラスター」をクリックします。
 - 「クラスターの作成」ボタンをクリックします。
 - 「EC2＋ネットワーキング」を選択し、「次のステップ」ボタンをクリックします。
 - クラスター名に「ecs-deploy-cluster」を入力します。
 - EC2インスタンスタイプで「t2.micro」を選択します。
 - インスタンス数で「1」を入力します。
 - キーペアで先ほど作成した「ecs-deploy」を選択します。
 - 「作成」ボタンをクリックします。
 - 起動ステータス画面になるので、「クラスターの表示」ボタンが活性化されるまで待ちます。
 - 「クラスターの表示」ボタンが活性化されたら、クリックします。

# ECSのタスクを実行
以下の手順でタスクを実行します。

 - [タスク]の項目から[新しいタスクの実行]をクリックします。
 - [起動タイプ]は「EC2」を選択します。
 - [タスク定義]は先ほど作成した、「python:1」を選択します。
 - [タスクの実行]ボタンをクリックします。
 - 状態が[RUNNING]になるまでしばし待機します。
 - [RUNNING]になったら、コンテナインスタンスのリンクをクリックします。
 - [パブリックDNS」に記載されているURIにブラウザからアクセスします。  
「Hello World!」が表示されればOKです。

# おわりに
実際にデプロイを一通りやってみると、どのような仕組みで動いているのかが良く分かると思います。

# 参考
https://techblog.istyle.co.jp/archives/1652
http://yng.hatenablog.jp/entry/2016/08/22/232807