EC2 Container Serviceを使ってみる

# Amazon EC2 Container Service
**内容はあくまで現在のPreviewを元にしているので、今後も仕様などが変更になる可能性が大きくありますので、ご利用の際は必ずドキュメントをご覧ください。**

ECSはDockerベースのアプリケーションのビルド、実行とそしてスケールを助けるためのサービスです。

利点として、より簡単なクラスターマネージメント、ハイパフォーマンス、柔軟なスケジューリング、拡張性、ポータビリティ、そしてAWSの他サービスや機能との統合があります。ECSは論理的なEC2インスタンスのグループを作成し、EC2インスタンスグループ内から、Dockerコンテナの実行に必要なリソースを提供出来るEC2インスタンス上でDockerコンテナを起動するように制御を行います。ECSはDockerベースのため既存のDockerコンテナをそのまま使用可能で、Docker Hubや任意のプライベートレポジトリに対応しています。もちろん、ECSで起動するコンテナはAWSの他のサービスと同様にVPC内で起動可能なので、システムを安全に管理することが出来ます。ECSでは1 EC2インスタンスのリソースを複数のDockerコンテナでシェア出来るため効率的にリソースを使用することが可能です。

Dockerコンテナを配置するEC2インスタンスの使用中・残りCPU/メモリやポート番号のようなリソース管理はECSが行うため、どのEC2インスタンスに残っているリソースをユーザが管理する必要はありません。リソースが不足した場合は、EC2インスタンスをインスタンスプールに追加するだけで自動的にリソースが追加されます。

Dockerコンテナを自前でEC2上で起動し、管理するためには、各EC2インスタンスで現在使えるCPUやメモリ、ポート番号といったハードウエアリソースの空き状況やDockerコンテナ同士の接続、環境変数の設定など、Dockerコンテナ内で動作するアプリケーションなどに必要な情報を管理する必要がありました。ECSを使用することによって、これらのハードウエアリソースや設定情報の管理をECSに任せることが出来るようになります。

その他の特徴として、既存のDockerイメージをそのまま利用可能なため、オンプレミスとクラウドの間でシームレスにイメージをやり取りすることが可能になっています。ECS特有の機能としてAWSのサービスや機能との連携が行い易くECS APIを使用してプログラマブルにECS環境を扱うことが可能です。

## 用語の解説
- Task Definition: Task Definitionはコンテナの集合の定義。1つ以上のコンテナを定義し、CPUやメモリ割当やContainer間のLinkなどを設定する。1つのTask Definition内で定義されたコンテナは同一のContainerインスタンスで稼働する
- Task - Task Definitionの実体
- Containerインスタンス: ECSエージェントが稼働しているEC2インスタンスで、クラスタに登録されているもの。クラスタ内で動いているインスタンスの集合はTaskを実行するためのリソースプールとして扱われる。Multi-AZにも対応しているため複数のアベイラビリティゾーンにContainerインスタンスを起動して障害耐性を高めることも可能
- Cluster: Taskを実行するためのContainerインスタンスの論理的なグループ
- Container: Taskにより生成されたDockerコンテナ

## ECS Container Agent
ECS Container AgentはContainerインスタンスの中で動くエージェントです。このエージェントを通してContainerインスタンス内で稼働するコンテナの開始や停止などの管理を行います。他の機能として、エージェントが起動しているContainerインスタンスが、どのECS Clusterに所属しているか、インスタンスのCPUやメモリの情報、Containerインスタンスの起動状況などもECSの管理システムへ通知を行っています。ECS Container AgentがECS API経由でデータを送信する際にはContainerインスタンスであるEC2インスタンスに付与されているIAM Role(認証情報)を使用してアクセスを行うため、コンテナ内に認証情報を渡す必要が無く、ECS APIへのアクセスもAgentから行うためポートをEXPOSEを設定する必要もありません。

実はこのエージェント自身もDockerコンテナとして稼働しており、[Docker Hub](https://registry.hub.docker.com/u/amazon/amazon-ecs-agent/)で公開されています。またエージェントで使用されているソースコードなどは[github](https://github.com/aws/amazon-ecs-agent)にてApache 2.0ライセンスで公開されているので是非ご覧ください。
![ecs-image](https://qiita-image-store.s3.amazonaws.com/0/4769/3a5d2adf-7dfa-2dcb-8436-2fdc27154782.png)

# ECSを動かしてみる
それでは早速ECSを使ってみましょう。現在のところPreview版のECSではAWS Management Consoleは用意されていないためコマンドラインツールを使用して操作を行います。

ECSはLinux AMI(マシンイメージ)にECS Container Agentを導入することで利用が可能になりますが、AWSでは現在Amazon LinuxをベースとしたECSに最適化された軽量なAMIを用意しています。このAMIには既にECS Container AgentがインストールされているためすぐにECSを利用できます。他にも、CoreOSもサポートしており[公式サイト](https://coreos.com/docs/running-coreos/cloud-providers/ecs/)に掲載されているcloud-initを実行することで簡単にECS Container Agentを導入可能です。

## ECSセットアップ
今回はAWSで提供している、ECS用のAMIを利用してECSがどのようなサービスなのかを紹介します。

### CLIツールのインストール
ECSを操作するために、AWS CLIツールを[公式サイト](http://aws.amazon.com/cli/)からダウンロードしてインストールします。また、CLI操作用にIAMユーザを作成しておきます。IAMユーザにECSの操作権限を付与することで、ユーザ毎にAWSリソースに対して細かくアクセス制限を行うことが出来ます。IAMユーザ作成の詳細は[こちら](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/get-set-up-for-amazon-ecs.html#create-an-iam-user)をご覧ください。ECSではクラスタの作成・Taskの登録やコンテナの実行・停止など細かく権限を割り当てることが可能で、特定のIAMユーザに特定のクラスタの操作や特定タスクの操作のみを割り当てることが可能です。細かく権限を割り当てることで権限が分離できECSの運用を安全に行うことが出来ます。[詳細](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/IAM_policies.html)をご覧ください。

今回は、IAMユーザはCLI操作に利用して、IAM RoleはECS Container Agentが使用します。

```shell-session
$ pip install awscli
```
で行えます。 インストール後は認証情報を設定するため `aws configure`を実行し、IAMユーザの情報を登録します。今回は、Default RegionをVirginiaリージョンで設定しておきます。

また、`--profile`オプションを使用して、ECS Preview用のProfileを作成します。--profileオプションを使用することで一時的にdefaultのProfileとProfile情報を切り替えることが出来ます。

```shell-session
$ aws configure --profile ecs-preview
AWS Access Key ID []:****************
AWS Secret Access Key []:****************
Default region name []:us-east-1
Default output format []:
```

既に、awscliをお使いの場合は`--region`オプションを使用することで、一時的に使用するリージョンを切り替えることが出来ます。Virginiaリージョンを使用する場合は、`--region us-east-1`と指定します。

```shell-session
$ aws ecs aws ecs list-container-instances --cluster default --region us-east-1 --profile ecs-preview
```

### Containerインスタンスの起動
本章ではECS Clusterインスタンスの起動から設定までを行います。ECS Clusterに所属させるEC2インスタンスの起動を行う前にEC2インスタンス内のECS Container AgentがECS APIにアクセスするための権限(IAM Role)付与やSecurity Group(アクセスコントロール)を設定する必要がありますが、詳細は[ドキュメント](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_GetStarted.html)をご参照ください。

事前設定が完了したらClusterインスタンスを起動します。AWSが用意している2015年4月現在最新の **preview用AMI** を使用するには、Management Console → EC2 → Launch Instance → Community AMIsから「ami-801544e8」で検索してヒットしたものを使用してください。こちらはVirginiaリージョンのAMIですので、Oregonは「ami-801544e8」、Irelandは「ami-519a0a26」をご利用下さい。
 **こちらのAMIはpreview用ですので、正式リリース時はリリース時の案内にしたがって適切なAMIをご利用下さい**

![ecs-ami](https://qiita-image-store.s3.amazonaws.com/0/4769/e6c37f1c-9c96-b455-f120-33ef396a7596.png)


使用するAMIを選択後、使用するEC2インスタンスタイプを選択します。こちらで指定したインスタンスのリソースを各Dockerコンテナでシェアして使用します。後からクラスタにEC2インスタンスを追加して使用できるリソースを拡張することも可能なので、リソースの利用状況を見ながら最初は小さいインスタンスタイプを使用し、用途が増えてきた段階でスペックの高いEC2インスタンスをクラスタに参加させることでコストを抑えることも可能です。

### ECSインスタンスをClusterに参加させる
ECSはClusterという単位でEC2インスタンスのリソースプールを使用します。EC2インスタンスをどのECS Clusterに参加させるかはEC2インスタンスの起動時に指定します。指定しない場合はdefault Clusterに所属することになります。

クラスタ名を明示的に指定する場合は、EC2インスタンス起動時の設定でAdvanced Details内のUser Dataに以下の情報を設定します。

```shell-session
#!/bin/bash
echo ECS_CLUSTER=クラスタ名 >> /etc/ecs/ecs.config
```

![ecs-cluster-setup](https://qiita-image-store.s3.amazonaws.com/0/4769/068c61a1-11e9-52b7-442e-cb61fd1f5449.png)

ECSでは、ECS_CLUSTERに指定したクラスタ名が同一のEC2インスタンスを1つのECSクラスタとして扱い、コンテナ起動時にコンテナを実行するために必要なCPUやメモリなどのインスタンスリソースを各コンテナに割り当てていくことになります。


## Containerインスタンス起動の確認
Containerインスタンスが正しく起動すると、起動時に指定した(指定がなければdefault)クラスタに自動で登録されます。

```shell-session
$ aws ecs list-container-instances --cluster クラスタ名 --profile ecs-preview
```
Clusterインスタンスの確認

```json
{
  "containerInstanceArns": [
    "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:container-instance/29f000f6-73ed-4b2f-9a79-4fc7209d6730",
    "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:container-instance/d79fbdc9-7099-4c5c-863f-62ebea76cdba"
  ]
}
```
Containerインスタンスの確認

2インスタンスが指定したクラスタに登録されていることが確認出来ます。

実行結果中のarnはAmazon Resource Nameの略称で、ContainerインスタンスやTaskなどのAWSのリソースを一意に識別するためのIDです。

各クラスタの詳細情報は

```shell-session
$ aws ecs describe-container-instances --cluster default --container-instances 29f000f6-73ed-4b2f-9a79-4fc7209d6730 d79fbdc9-7099-4c5c-863f-62ebea76cdba --profile ecs-preview
```
Clusterインスタンス詳細

```json
{
  "failures": [],
  "containerInstances": [
    {
      "status": "ACTIVE",
      "registeredResources": [
       {
          "integerValue": 1024,
          "longValue": 0,
          "type": "INTEGER",
          "name": "CPU",
          "doubleValue": 0.0
       },
       {
          "integerValue": 2004,
          "longValue": 0,
          "type": "INTEGER",
          "name": "MEMORY",
          "doubleValue": 0.0
       },
       {
          "name": "PORTS",
          "longValue": 0,
          "doubleValue": 0.0,
          "stringSetValue": [
            "2376",
            "22",
            "51678",
            "2375"
          ],
          "type": "STRINGSET",
          "integerValue": 0
       }
  ],
  "ec2InstanceId": "i-a9b8f552",
  "agentConnected": true,
  "containerInstanceArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:container-instance/29f000f6-73ed-4b2f-9a79-4fc7209d6730",
      "remainingResources": [
       {
          "integerValue": 1024,
          "longValue": 0,
          "type": "INTEGER",
          "name": "CPU",
          "doubleValue": 0.0
       },
       {
          "integerValue": 2004,
          "longValue": 0,
          "type": "INTEGER",
          "name": "MEMORY",
          "doubleValue": 0.0
       },
       {
          "name": "PORTS",
          "longValue": 0,
          "doubleValue": 0.0,
          "stringSetValue": [
            "2376",
            "22",
            "51678",
            "2375"
          ],
          "type": "STRINGSET",
          "integerValue": 0
       }
  ],
〜〜〜〜〜 インスタンス数分 〜〜〜〜〜
```
Clusterインスタンス詳細

| Value        | 説明                                                              |
|--------------|------------------------------------------------------------------|
| status       | Containerインスタンスの状態         |
| agentConnected       | ECS Container AgentがECS APIと通信出来ているか。trueになっているContainerインスタンスのみ使用可能         |
| registeredResources       | Containerインスタンスで使用できる全てのリソース(CPU/メモリ/ネットワークポート)         |
| remainingResources       | Containerインスタンスで使用できる全リソース(CPU/メモリ/ネットワークポート)         |
| ec2InstanceId       | ContainerインスタンスのEC2インスタンスID         |
| containerInstanceArn       | Containerインスタンスを一意に表すAWSのリソースID        |
Clusterインスタンス詳細


`describe-container-instances` APIを使用して表示します。各Containerインスタンスが所持している、CPU Unit・メモリー・ポートのリソース状況と現在残っているリソース、Instance IDが表示されています。まだ何もコンテナを起動していない場合、所持リソースと残りリソースが同一になっています。`Instance ID: i-a9b8f552`のインスタンスはCPUが1 CoreなのでCPU Unitが1024になっています。

Containerインスタンスにログインし、ECS Container Agentのmetadata APIを使用してインスタンスが所属しているクラスタやContainerInstanceArnを確認出来ます。

```shell-session
$ curl http://localhost:51678/v1/metadata
```

```json
{
  "ClusterArn":"arn:aws:ecs:us-east-1:xxxxxxxxxxxx:cluster/default",
  "ContainerInstanceArn":"arn:aws:ecs:us-east-1:xxxxxxxxxxxx:container-instance/db2cc682-a63c-47b7-9d66-fd9269fc8f56"
}
```
ECS Container Agent metadata取得

## ECSでコンテナを起動する
### Taskの記述
TaskはJson形式で記述し、各Dockerコンテナを起動するために必要なCPUやメモリ、ポート番号などのリソース割当や環境変数、Dockerコンテナ同士の接続などの情報を記述します。

また、Taskはリソースの割り当てだけではなく、Task内で実際に起動するDockerコンテナを指定し、このファイルに記述されたリソースが全て提供可能なClusterインスタンスをECSが自動的に選択し、そのClusterインスタンス内でDockerコンテナをダウンロードし起動します。その後、ポートマッピングやDockerコンテナ同士の接続・環境変数の設定なども行われます。

| Value        | Type         | 説明                                                                                                                                                                                          |
|--------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| name         | string       | コンテナを識別する名前。コンテナ同士を接続するlink機能で指定される                                                                                                                          |
| image        | string       | Dockerイメージを指定。標準ではDocker Hubを使用しますが、repository-url/image:tagと記述することで任意のレポジトリを使用可能                                                                  |
| cpu          | integer      | コンテナに割り当てるCPU Unitを指定。各EC2インスタンスは1コア当たり1024 CPU Unitsを持っている                                                                                                |
| memory       | integer      | コンテナに割り当てるメモリをMiB単位で指定。Dockerは最低4MiBを割当てる                                                                                                                       |
| links        | string array | linksで指定したコンテナ同士を接続。linksパラメータで指定することでポートマッピングなどの設定が不要。詳細: https://docs.docker.com/userguide/dockerlinks/                                      |
| portMappings | object array | EXPOSEするポートを指定。                                                                                                                                                                      |
| essential    | boolean      | trueに設定されたコンテナの実行が失敗するとTaskが失敗するが、falseに設定するとコンテナの実行が失敗してもTaskの処理を継続。1つのTask Definishonには最低でも1つessentialがtrueなコンテナが必要 |
| entryPoint   | string       | ENTRYPOINTとしてDockerコンテナに渡される。詳細: https://docs.docker.com/reference/builder/#entrypoint                                                                                         |
| command      | string       | CMDとしてDockerコンテナに渡される。詳細: https://docs.docker.com/reference/builder/#cmd                                                                                                       |
| environment  | object array | 環境変数としてDockerコンテナに渡される。形式は、{ "name" : "環境変数", "value" : "値" }                                                                                                       |
Taskで指定できる項目

linksで指定できるコンテナは現在、同一のTask Definishon内に記述されている必要があり、同一のEC2インスタンス内で稼働していなければいけないという制約がありますので注意して下さい。

### 使用するTask Definition
今回はWordPressとMySQLを別々のコンテナで起動し、Linksパラメータを使用して2つのコンテナを接続するTaskを作成しECSで起動します。

WordPressとMySQLのimageは公式レポジトリの https://registry.hub.docker.com/_/wordpress/ https://registry.hub.docker.com/_/mysql/ をそれぞれ使用します。

```json
[
  {
    "environment": [
      { "name" : "WORDPRESS_DB_USER", "value" : "wpuser" },
      { "name" : "WORDPRESS_DB_PASSWORD", "value" : "wppass" },
      { "name" : "WORDPRESS_DB_NAME", "value" : "wpdb" }
    ],
    "name": "wp",
    "image": "wordpress:latest",
    "cpu": 100,
    "portMappings": [
      {
        "containerPort": 80,
        "hostPort": 80
      }
    ],
    "links": ["mysql:mysql"],
    "memory": 1024,
    "essential": false
  },
  {
    "environment": [
      { "name" : "MYSQL_ROOT_PASSWORD", "value" : "rootwppass" },
      { "name" : "MYSQL_USER", "value" : "wpuser" },
      { "name" : "MYSQL_PASSWORD", "value" : "wppass" },
      { "name" : "MYSQL_DATABASE", "value" : "wpdb" }
    ],
    "name": "mysql",
    "image": "mysql",
    "cpu": 200,
    "memory": 2048,
    "essential": true
  }
]
```

このTaskでは、WordPressのコンテナを`wp`という名前でCPU 100Units、Memory 1024MiBを割当、`links`でmysqlとname属性で指定されたコンテナにLinkするようにしています。また、essentialがfalseに指定されているため、WordPressコンテナの起動が失敗してもTaskは失敗せず継続して動作します。MySQLのコンテナは、CPU 200Units、Memory 2048MiBを割り当てています。こちらは、`essential`をtrueに指定しているので、こちらのコンテナの起動が失敗するとTask全体が失敗します。それぞれのコンテナで`enviroment`により環境を渡してデータベースのユーザやパスワードを指定しています。

## Taskの登録
Task Definishonを作成したら、ECSにTaskの登録を行います。Task DefinishonからContainerインスタンス内でDockerコンテナを起動するためには、ECSに登録したTaskを指定して起動することになります。

この先の例では、先ほどのTaskを`wordpress.json`として保存してECSへ登録します。

```shell-session
$ aws ecs register-task-definition --family wordpress --container-definitions file://./wordpress.json --profile ecs-preview
```

```json
{
  "taskDefinition": {
    "taskDefinitionArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task-definition/wordpress:1",
    "containerDefinitions": [
      {
        "environment": [
          {
            "name": "WORDPRESS_DB_USER",
            "value": "wpuser"
          },
          {
            "name": "WORDPRESS_DB_PASSWORD",
            "value": "wppass"
          },
          {
            "name": "WORDPRESS_DB_NAME",
            "value": "wpdb"
           }
       ],
       "name": "wp",
       "links": [
         "mysql:mysql"
       ],
       "image": "wordpress:latest",
       "essential": false,
       "portMappings": [
         {
           "containerPort": 80,
           "hostPort": 80
         }
       ],
       "memory": 1024,
       "cpu": 100
     },
     {
       "environment": [
         {
           "name": "MYSQL_DATABASE",
           "value": "wpdb"
         },
         {
           "name": "MYSQL_PASSWORD",
           "value": "wppass"
          },
          {
            "name": "MYSQL_USER",
            "value": "wpuser"
          },
          {
            "name": "MYSQL_ROOT_PASSWORD",
            "value": "rootwppass"
          }
        ],
        "name": "mysql",
        "image": "mysql",
        "cpu": 200,
        "portMappings": [],
        "memory": 2048,
        "essential": true
        }
   ],
   "family": "wordpress",
   "revision": 1
   }
 }
```

`container-definitions`にはJSONをそのまま渡す事が出来ますが、今回はファイルを指定しました。コマンドで指定されている`family`はTaskを識別するためのもので、Task Definitionの定義を変更し、同一のfamilyでTaskを登録すると`revision`が更新されます。Taskは`wordpress:1`や`wordpress:2`のようにTask名とrevisionの組み合わせでECS内で識別されるため、起動時にrevisionを指定し、CPUやメモリの割当を変えたTaskを複数用意して用途に合わせて使用するといったことも可能です。また、登録されているTask一覧も参照できます。

```shell-session
$ aws ecs list-task-definitions　--profile ecs-preview
```
登録されているTask一覧

```json
{
  "taskDefinitionArns": [
    "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task-definition/wordpress:1",
    "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task-definition/wordpress:2",
    "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task-definition/redis:1",
    ......
  ]
}
```
登録されているTask一覧

## Taskの実行
では実際に先ほど登録したTaskを実行してコンテナを起動します。

ECSでは、Taskの実行方法が現在2種類存在します。

- クラスタ内でコンテナを起動するリソースを持っているContainerインスタンスからランダム (`run-task`コマンド)
- Task実行時に指定した特定のContainerインスタンスを使用 (`start-task`コマンド)

`start-task`コマンドは独自のECSのカスタムスケジューラを作成することによりContainerインスタンスの負荷状況やContainerインスタンスのメンテナンスにより特定のインスタンスでコンテナを起動させることが出来ます。この場合、ECSとの連携部分を独自に作成する必要があります。負荷状況に影響を受けにくいコンテナを起動する場合は`run-task`コマンドを使用して、ハードウェアリソースの空いているContainerインスタンスを自動的にECSがクラスタ内から探してコンテナを起動させることでリソース管理が楽になります。

```shell-session
$ aws ecs run-task --cluster クラスタ名 --task-definition wordpress:1 --count 1　--profile ecs-preview
```

```json
{
  "failures": [],
  "tasks": [
    {
     "taskArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/3f638959-cea0-402a-a1f6-017d3bd324fb",
     "overrides": {
       "containerOverrides": [
         {
           "name": "wp"
         },
         {
            "name": "mysql"
         }
       ]
     },
     "lastStatus": "PENDING",
     "containerInstanceArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:container-instance/d79fbdc9-7099-4c5c-863f-62ebea76cdba",
     "desiredStatus": "RUNNING",
     "taskDefinitionArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task-definition/wordpress:1",
     "containers": [
       {
         "containerArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:container/fbb9f38f-45b5-460b-921d-459f3fd05c31",
         "taskArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/3f638959-cea0-402a-a1f6-017d3bd324fb",
          "lastStatus": "PENDING",
          "name": "wp"
       },
       {
          "containerArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:container/7d7a9f81-9201-49b5-8379-6bba6ce905ae",
          "taskArn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/3f638959-cea0-402a-a1f6-017d3bd324fb",
          "lastStatus": "PENDING",
          "name": "mysql"
         }
       ]
     }
  ]
}
```

```shell-session
$ aws ecs start-task --cluster クラスタ名 --task-definition wordpress:1 --container-instances　29f000f6-73ed-4b2f-9a79-4fc7209d6730 d79fbdc9-7099-4c5c-863f-62ebea76cdba --profile ecs-preview
```

コマンド実行直後は`"desiredStatus": "RUNNING"`・`"lastStatus": "PENDING"`となっており、コンテナのダウンロードから起動を待っている状態です。`describe-tasks`コマンドを使用して現在の起動状況を確認することが可能です(コマンド7)。`"lastStatus": "RUNNING"`に変わっているとTaskの起動が成功しています。

```shell-session
(--tasksオプションで指定しているIDは今回の場合の例です)
$ aws ecs describe-tasks --cluster default --tasks 3f638959-cea0-402a-a1f6-017d3bd324fb --profile ecs-preview
```

WordPress・MySQLのTaskの起動が正常に完了したことを確認したら、ContainerインスタンスのPublic IPアドレスにアクセスするとWordPressの画面が表示されます。

既に起動しているコンテナをさらに起動して負荷分散を行う場合などは、新規に起動したいコンテナ数を指定して、コマンドを実行します。指定したクラスタに空きリソースが存在しない場合はコマンドの実行は失敗してしまうので、その場合は新たにContainerインスタンスを起動してクラスタに参加させることでリソースを増加させます。コンテナを起動している、Containerインスタンスに問題が発生した場合は、start-taskコマンドを使用して健全なContainerインスタンス上でコンテナを明示的に起動することで障害への対応が可能です。

各コンテナの起動状況や停止処理などはECS Container Agentを通してECSのシステムとやり取りが行われ、コンテナが正常もしくは何らかの理由で異常終了した場合も検知しECSのシステムへ通知されるので、 describe-tasksコマンドで状況を確認し対処することが可能です。

Task実行時にコンテナを要求数起動するためのリソースが不足しており、コンテナを起動出来ない場合は`failures`としてAPIレスポンスが返却され、レスポンス中に不足しているリソースが含まれています。

```json
{
    "failures": [
        {
            "reason": "AGENT",　//ECS Container Agentと通信が出来ていない
            "arn": "〜〜〜コンテナインスタンスarn〜〜〜〜"
        },
        {
            "reason": "RESOURCE:MEMORY",  //メモリが不足
            "arn": "〜〜〜コンテナインスタンスarn〜〜〜〜"
        },
        {
            "reason": "RESOURCE:CPU",  //CPUが不足
            "arn": "〜〜〜コンテナインスタンスarn〜〜〜〜"
        }
    ],
    "tasks": []
}
```

## Taskの停止
使用が終了したコンテナを停止してリソースを開放するには、起動しているコンテナを`stop-task`コマンドを使用して停止させます。

```shell-session
$ aws ecs stop-task --cluster default --task 3f638959-cea0-402a-a1f6-017d3bd324fb
```

Taskの終了を実行すると、即座にコンテナが終了されるため、終了前の処理が必要であれば考慮しておく必要があります。


## Containerインスタンスをクラスタから外す
クラスタに登録されたContainerインスタンスは、インスタンスを停止・ターミネートしてもクラスタから自動で登録は解除されません。

コンテナ起動時に自動的に有効なインスタンス(ECS Container Agentと通信が出来ている)のみ使用されるためコンテナの起動に問題はありませんが、コンテナが起動可能なインスタンスかどうかは`describe-container-instances`APIの結果で判断出来ます。レスポンス中の"agentConnected": false`となっている場合はECS Container Agentと通信が出来ないインスタンスなためコンテナの配置は行われなくなっています。

インスタンスをクラスタから登録を解除する場合は`deregister-container-instance`APIを使用してクラスタからContainerインスタンスを取り除きます。この時、取り除こうとしているContainerインスタンス中でコンテナが動作している場合は実行が失敗します。強制的に取り外す場合は`--force`オプションを使用します。

```shell-session
$ aws ecs deregister-container-instance --container-instance db2cc682-a63c-47b7-9d66-fd9269fc8f56
```

```shell-session
$ aws ecs deregister-container-instance --container-instance db2cc682-a63c-47b7-9d66-fd9269fc8f56 --force --profile ecs-preview
```

## log
ECS Container Agentのログは `/var/log/ecs/`以下に出力されているため、動作の確認やトラブルシューティングなどには、EC2インスタンスにログインして確認することが出来ます。ECS Container AgentとECS APIとのハートビート・制御情報のやりとりのログも出力させることが可能なので、どのようなデータがECS APIとやりとりされているか、Dockerコンテナのダウンロード進行状況・起動・停止処理で出力されたメッセージやエラー情報もここで確認出来ます。

```shell-session
t=2015-03-02T01:30:01+0000 lvl=info msg="Task change event" module=TaskEngine state=3
t=2015-03-02T01:30:01+0000 lvl=info msg="Container change event" module=TaskEngine event="{TaskArn:arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/3f638959-cea0-402a-a1f6-017d3bd324fb ContainerName:mysql Status:3 Reason: ExitCode:<nil> PortBindings:[] TaskStatus:3}"
t=2015-03-02T01:30:01+0000 lvl=info msg="Top of ApplyTaskState" module=TaskEngine task="wp-5 arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/3f638959-cea0-402a-a1f6-017d3bd324fb, Overrides: {} Status: CREATED(R
UNNING) Containers: mysql,wp,"
t=2015-03-02T01:30:02+0000 lvl=info msg="MinContainerStatus is RUNNING" module=TaskEngine
```

ECSを使用したシステムでは以下の2点を考慮する必要があります。

- クラスタ内のContainerインスタンスリソース(CPU/メモリ/ポート)
- コンテナ内のログの管理

クラスタ内で使用できるContainerインスタンスのリソースの総量を監視することによって新規コンテナを起動して負荷分散をする場合や新規コンテナを起動しサービスを拡充させる場合にリソース不足で追加のコンテナが起動出来ないといったことを回避出来ます。AWS Command Line Interfaceのdescribe-container-instancesコマンドを使用して各ContainerインスタンスのCPUやメモリのリソース状況を取得し監視ツールやAmazon CloudWatchのカスタムメトリックスを使用しリソースの残量が閾値を下回った特にアラートの発報や、Auto Scalingを使用しContainerインスタンスを自動で起動することで、クラスタ内の残リソースを一定に保ちTaskをいつでも実行出来る状態に保てます。以下の例はAWS Command Line Interfaceを使用して、クラスタリソースの取得からCoudWatchへメトリクスを登録するサンプルコードです。この様なスクリプトをCRONで定期実行することでCloudWatchでクラスタが現在持っているCPUとメモリリソースの状況を監視することが出来ます。

Taskで起動したコンテナは、stop-taskコマンドを実行すると即座にコンテナが終了されるため、コンテナ内で生成され取得いておきたいログはAmazon CloudWatch logsや[Fluentd](http://www.fluentd.org/)などを使用しContainerインスタンス外部に収集しておくことで、ログの保全やコンテナの終了後もログの確認が可能です。

ECSの管理では、Cluster内に存在するContainerインスタンスのリソース状況を把握し、いつでもTaskが実行出来るようにしておくようにしておくこと、コンテナが終了したあとも大切なログやファイルはコンテナ起動中にコンテナ外部に保存しておくことが重要です。

```bash
#!/bin/sh

profile=ecs-preview
ecs_clusters=($(aws ecs list-clusters --profile ${profile} | jq -r '.clusterArns[]'| sed -e 's/^.\+\/\(.\+\)$/\1/'))

for cluster in ${ecs_clusters[@]}; do
  remaining_cpu=0
  remaining_memory=0
  cluster_instances=($(aws ecs list-container-instances --cluster ${cluster} --profile ${profile} | jq -r '.containerInstanceArns[]'))

  for instance_arn in ${cluster_instances[@]}; do
    cpu=($(aws ecs describe-container-instances --cluster ${cluster}  --container-instances ${instance_arn} --profile ${profile} | jq -r '.containerInstances[].remainingResources[]|select(.name == "CPU")|.integerValue'))
    remaining_cpu=$((remaining_cpu + cpu))

    memory=($(aws ecs describe-container-instances --cluster ${cluster} --container-instances ${instance_arn} --profile ${profile} | jq -r '.containerInstances[].remainingResources[]|select(.name == "MEMORY")|.integerValue'))
    remaining_memory=$((remaining_memory + memory))
  done

  ($(aws cloudwatch put-metric-data --namespace 'ECS Cluster/CPU' --dimensions ClusterName=${cluster} --metric-name 'Remaining CPU' --value ${remaining_cpu} --unit Count))

  ($(aws cloudwatch put-metric-data --namespace 'ECS Cluster/MEMORY' --dimensions ClusterName=${cluster} --metric-name 'Remaining MEMORY' --value ${remaining_memory} --unit Megabits))
done
```

![rcpu](https://qiita-image-store.s3.amazonaws.com/0/4769/4ca0342a-5a0a-70ee-5120-953e9a14fb0d.png)

![rmemory](https://qiita-image-store.s3.amazonaws.com/0/4769/021afb76-4584-2a84-8bdc-1673b583fa32.png)


# まとめ
AWSのDockerコンテナを使用した新しサービスであるAmazon EC2 Container Serviceについて概要をお伝えしました。Amazon EC2 Container Serviceの登場によってEC2インスタンスのリソースを今までよりも柔軟に使用することが可能になりました。サービスや環境ごとにDockerコンテナを作成し、用途に合わせたクラスタで起動することで、開発・テスト環境では集約度をあげてインスタンスコストを削減し、本番環境では潤沢なリソースをコンテナに割り当ててサービスを行うなどもAWSで容易に実現可能です。AWS CLIツールを活用し、Dockerコンテナのビルド・CIが通過したら自動的にTask・クラスタの登録やコンテナの起動停止も簡単に実装できます。

# おまけ
ECS Container Agentにはいくつか設定出来る項目があります。設定は`ecs.config`に環境変数を記述することで変更可能です。ecs.configへの環境変数の記述はContainerインスタンスの起動時にUser Dataでプロビジョニングすると簡単です。

認証情報は基本的にはEC2メタデータサービスから自動的に取得するので、ECS APIアクセス用の`AWS_ACCESS_KEY_ID`や`AWS_SECRET_ACCESS_KEY`などは、設定ファイルで記述するのではなくIAM Roleで設定することで安全に認証情報をECS Container Agentに渡せるためIAM Roleの使用を推薦します。`ECS_RESERVED_PORTS`はContainerインスタンス中に監視ツールなどの独自ソフトウェアを導入するような場合に事前に使用ポートを予約しておくことで、Taskで起動するコンテナとポートが重複してTaskが失敗するということを防ぐことが出来ます。

| 環境変数              | 説明                                                                                                           | Default値                         |
|-----------------------|----------------------------------------------------------------------------------------------------------------|-----------------------------------|
| ECS_CLUSTER           | Clusterインスタンスを所属させるクラスタ名                                                                      | default                           |
| ECS_RESERVED_PORTS    | Containerインスタンスで予め使用予定のポート番号一覧                                                            | [22, 2375, 2376, 51678]           |
| AWS_DEFAULT_REGION    | ECS Container AgentがアクセスするECS APIのリージョン                                                           | EC2メタデータサービスから自動取得 |
| AWS_ACCESS_KEY_ID     | ECS APIにアクセスするためのアクセスキー                                                                        | EC2メタデータサービスから自動取得 |
| AWS_SECRET_ACCESS_KEY | ECS APIにアクセスするためのシークレットキー                                                                    | EC2メタデータサービスから自動取得 |
| DOCKER_HOST           | Dockerデーモンとクライアントがやりとりするために使用されるホスト                                               | unix:///var/run/docker.sock       |
| ECS_LOGLEVEL          | ログレベル (<crit>,<error>,<warn>,<info>,<debug>\)                                                             | warn                              |
| ECS_LOGFILE           | デバッグログを書き出すパスとファイルを指定。この値が設定されるとECS_LOGLEVELを無視してデバッグログが出力される | blank                             |
| ECS_BACKEND_HOST      | ECS APIエンドポイントホスト名                                                                                  | ecs.REGION.amazonaws.com          |
| ECS_BACKEND_PORT      | ECS APIエンドポイントポート番号                                                                                | 443                               |
| AWS_SESSION_TOKEN     | 一時的な認証に使用するセッショントークン                                                                       | EC2メタデータサービスから自動取得 |

## 最新アップデート
Preview中のECSですが、今回触れなかった機能で以下の様な機能があります

- Mesos Scheduler対応
    - https://aws.amazon.com/blogs/compute/cluster-management-with-amazon-ecs/
    - https://github.com/awslabs/ecs-mesos-scheduler-driver

- Private repository対応 / Volume mount対応
    - https://aws.amazon.com/blogs/aws/ec2-container-service-ecs-update-access-private-docker-repos-mount-volumes-in-containers/

# お約束
こちらは個人の意見で会社とは関係ありません。お約束です。