ECS運用のノウハウ

### 関連記事

* [マイクロサービスを支えるインフラアーキテクチャ (AWS Dev Day 2019登壇資料)](https://speakerdeck.com/naomichi/maikurosahisuwozhi-eruinhuraakitekutiya?fbclid=IwAR3zX7JSda1kSgOt-iYSSUcZOWJSNf0YAC6f69rG__H96brx3_nY_l0aL_Q)
* [ECSデプロイツールを公開しました](https://qiita.com/naomichi-y/items/fee7720fdadd443a0aa0)
* [ECSにおけるログの取り扱いを別ページに移動させました](https://qiita.com/naomichi-y/items/c6765d2c70b7b51cead6)

## 設計

### 基本方針

基盤を設計する上で次のキーワードを意識した。

* Immutable infrastructure
  * 一度構築したサーバは設定の変更を行わない
* Infrastructure as Code
 * インフラの構成をコードで管理 (Terraformを採用)
* Serverless architecture
 * 無駄にサーバを増やさない

アプリケーションレイヤに関して言えば、[Twelve Factor App](https://12factor.net/ja/)が参考になる。コンテナ技術とも親和性が高い。

### ECSとBeanstalk Multi-container Dockerの違い

以前に記事を書いたので、詳しくは下記参照。

* [Dockerコンテナデプロイサービスの比較(Beanstalk Multi-container Docker/ECS)](http://qiita.com/naomichi-y/items/5830b851e8028b11cf4e)

Beanstalk Multi-container Dockerは、ECSを抽象化してRDSやログ管理の機能を合わせて提供してくれる。ボタンを何度か押すだけでRubyやNode.jsのアプリケーションが起動してしまう。
一見楽に見えるが、ブラックボックスな部分もありトラブルシュートでハマりやすいので、素直にECSを使った方が良いと思う。

### ECSクラスタの作り方

ECSクラスタにEC2を登録する方法は2パターンある。

1. クラスタ作成時にEC2を作成する (デフォルト)
2. クラスタ作成時にEC2を作成せず、手動登録する

1は裏でCloudFormationが働き、クラスタへのEC2の登録からオートスケーリング周りの設定まで自動で行ってくれる。
2は空のクラスタを作成し、ECSコンテナインスタンスを手動登録する必要がある。

インフラをコード管理する上でも、ここでは2の手法をお勧めしたい。実はクラスタ作成後のオートスケール周りで設定の違いが出てくる。
下図は1の手法でクラスタを作成した場合のコンテナインスタンスの設定ページ。`Scale ECS Instances`という項目が用意されている。インスタンス数を増減する場合はこのボタンから変更すれば良い。
<img width="512" alt="Screen Shot 2018-08-12 at 4.38.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/27454/8df1ccf9-6b61-1a13-0c3f-d7e0f35b2f5e.png">
※後述するが、インスタンスを減らす場合はこのボタンを押す前にインスタンスの保護設定が必要。でないと稼働中のアプリケーションが落ちてしまうことがある。

続いてこちらが手動登録時のUI。`Scale ECS Instances`ボタンが無い。インスタンスの増減はEC2(Auto Scaling Groups)から設定が必要となる。
<img width="296" alt="Screen Shot 2018-08-12 at 4.39.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/27454/d14b5857-fe94-b66f-d68f-f2225772a030.png">

以前、1のパターンで作成したクラスタには次のような問題が起きた。

* クラスタが所属するサブネットを `Auto Scaling Groups`から変更後、`Scale ECS Instances`からインスタンスを増やしても追加インスタンスは変更後のサブネットに所属しない。更にインスタンスを増やした時点でサブネットの設定が元に戻ってしまう。
  * 原因としてはCloudFormation側のテンプレートもサブネットの変更が必要だった
* `Auto Scaling Groups`でもインスタンス数を増減できるが、インスタンス数は`Scale ECS Instances`と同期していない
  * `Auto Scaling Groups`からインスタンス数の変更しない方が無難

2のパターンでコンテナインスタンスを登録するには、EC2作成時のイメージとしてAWS Marketplaceで公開されている`Amazon ECS-Optimized Amazon Linux AMI`を使うのが簡単である。

* [Amazon EC2 Container Service (ECS)を試してみた](https://dev.classmethod.jp/cloud/ecs-ataglance/)

### ALBを使う

ECSでロードバランサを利用する場合、CLB(Classic Load Balancer)かALB(Application Load Balancer)を選択できるが、特別な理由がない限りALBを利用するべきである。
ALBはURLベースのルーティングやHTTP/2のサポート、パフォーマンスの向上など様々なメリットが挙げられるが、ECSを使う上での最大のメリットは動的ポートマッピングがサポートされたことである。
動的ポートマッピングを使うことで、1ホストに対し複数のタスク(例えば複数のNginx)を稼働させることが可能となり、ECSクラスタのリソースを有効活用することが可能となる。

※1: ALBの監視方式はHTTP/HTTPSのため、TCPポートが必要となるミドルウェアは現状ALBを利用できない。
※2: 2017年9月、TCPによる高スループットを実現したNetwork Load Balancerが追加された。詳しくは[クラスメソッドの記事](http://dev.classmethod.jp/cloud/aws/elb-network-load-balancer-static-ip-adress/)辺りを参考に。HTTP/HTTPSはALB、TCPはNLBを利用することで、今後CLBを利用する機会は無くなってくるものと考えられる。

### アプリケーションの設定は環境変数で管理

[Twelve Factor App](https://12factor.net/ja/config)でも述べられてるが、アプリケーションの設定は環境変数で管理している。
ECSのタスク定義パラメータとして環境変数を定義し、パスワードやシークレットキーなど、秘匿化が必要な値に関してはKMSで暗号化。CIによってECSにデプロイが走るタイミングで復号化を行っている。

### ジョブスケジューリング

アプリケーションをコンテナで運用する際、スケジュールで定期実行したい処理はどのように実現するべきか。
いくつか方法はあるが、1つの手段としてLambdaのスケジュールイベントからタスクを叩く方法がある([Run task](http://docs.aws.amazon.com/cli/latest/reference/ecs/run-task.html))。この方法でも問題はないが、最近(2017年6月)になってECSにScheduled Taskという機能が追加されており、Lambdaに置き換えて利用可能となった。Cron形式もサポートしているので非常に使いやすい。

* [ECS Scheduled Tasksによるタスクの定期実行](http://qiita.com/naomichi-y/items/3b18a3986db68c895701)

### Service Discoveryの運用

Service Discoveryを使うことで、サービス間の名前解決が容易になった。サービス登録時に一意の名前を付けておくことで、自動でRoute 53にコンテナを指すレコードが登録される(更にサービスのスケールに合わせてレコードを自動管理してくれる)。サービス呼び出し元は事前に付けた名前でエンドポイントを叩くことでコンテナにアクセスすることができるという仕組み。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f32373435342f62343934326263392d636563382d663961632d373833662d6266393236623861356334652e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/a1dcdce1-9379-4a9c-4bb2-c392c9d73890.png)

名前解決には二通りパターンがある点に注意したい。

#### Aレコードによる名前解決

タスクのネットワークモードが`awsvpc`の場合に利用可能。サービス名が`foo.local`であれば、サービス呼び出し元では`foo.local`に対しリクエストを送ることで、Route 53経由でAレコードの名前解決が行われ、コンテナにアクセスすることができる。
コンテナごとにENIが割り振られるため、ECSの起動タイプとしてEC2を利用する場合はインスタンスが持てるENIの上限に注意が必要となる。

```
$ curl -I foo.local
HTTP/1.1 200 OK
...
```

尚、`awsvpc`モードでは動的ポートマッピングは利用できない。

* [各インスタンスタイプのネットワークインターフェイスあたりの IP アドレス数](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI)
* [ECSでEC2インスタンスを利用する際のネットワークモードについて調べてみた](https://dev.classmethod.jp/etc/ecs-networking-mode/)

#### SRVレコードによる名前解決

タスクのネットワークモードが`bridge`、あるいは`host`の場合に有効(つまりFargateでは利用不可)。コンテナ間でネットワークが共有されるため、動的ポートマッピングが利用可能。ただしRoute 53にはSRVレコードが登録されるため、サービス呼び出し元は(ライブラリにも依存するが)、SRVレコードの名前解決を行った上でホスト名・ポート番号を取得し、その上でリクエストを送る必要がある。

```
# SRVレコードを取得し、ホスト名とポート番号を取得
$ host -t srv foo.local
foo.local has SRV record 1 1 33753 xxx.foo.local.

# リクエストの送信
$ curl -I xxx.foo.local:33753
HTTP/1.1 200 OK
...
```

## 運用

### ECSで設定可能なパラメータ (起動タイプ: EC2)

ECSコンテナインスタンスにはコンテナエージェントが常駐しており、パラメータを変更することでECSの動作を調整できる。設定ファイルの場所は `/etc/ecs/ecs.config`。
変更する可能性が高いパラメータは下表の通り。他にも様々なパラメータが存在する。

| パラメータ名 | 説明 | デフォルト値 | 補足 |
|---|---|---|---|
| ECS_LOGLEVEL | ECSが出力するログのレベル | info | 障害調査時はdebug |
| ECS_AVAILABLE_LOGGING_DRIVERS | 有効なログドライバの一覧 | ["json-file","awslogs"] | 利用するログドライバに応じて追加が必要。Fluendを利用している場合は `fluentd` など |
| ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION | タスクが停止してからコンテナが削除されるまでの待機時間 | 3h | |
| ECS_IMAGE_CLEANUP_INTERVAL | イメージ自動クリーンアップの間隔 | 30m | イメージサイズによってはディスクを圧迫するので、Dockerボリュームサイズから適正値を算出 |
| ECS_IMAGE_MINIMUM_CLEANUP_AGE | イメージ取得から自動クリーンアップが始まるまでの間隔 | 1h | |
| ECS_LOG_MAX_ROLL_COUNT | ECSログ (/var/log/ecs) の保管期間 | 24 | デフォルトは1日でログが消えるので調整を推奨 |

パラメータ変更後はエージェントの再起動が必要。

```
# Amazon Linux 2の場合
$ sudo systemctl stop ecs
$ sudo systemctl start ecs

# Amazon Linux 1の場合
$ sudo stop ecs
$ sudo start ecs
```

クラスタのスケールアウトを考慮し、`ecs.config`はUserDataに定義しておくと良い。
以下は`fluentd`を有効にしたUserDataの記述例。

```
#!/bin/bash
echo ECS_CLUSTER=sandbox >> /etc/ecs/ecs.config
echo ECS_AVAILABLE_LOGGING_DRIVERS=["fluentd\"] >> /etc/ecs/ecs.config
```

* [Amazon ECS コンテナエージェントの設定](http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-agent-config.html)
* [Linuxインスタンスでの起動時のコマンドの実行](http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html)

### CPUリソースの制限 (起動タイプ: EC2)

現状ECSにおいてCPUリソースの制限を設定することはできない(docker runの`--cpu-quota`オプションがサポートされていない)。
タスク定義パラメータ`cpu`は、docker runの`--cpu-shares`にマッピングされるもので、CPUの優先度を決定するオプションである。従って、あるコンテナがCPUを食いつぶしてしまうと、他のコンテナにも影響が出てしまう。
尚、Docker 1.13からは直感的にCPUリソースを制限ができる`--cpus`オプションが追加されている。是非ECSにも取り入れて欲しい。

* [Docker、コンテナのCPUリソースの利用上限を簡単に設定可能に。正式版となったバージョン1.13で「--cpus」フラグを追加](http://www.publickey1.jp/blog/17/dockercpu113--cpus.html)

### ユーティリティ (起動タイプ: EC2)

実際に利用しているツールを紹介。

* [awslabs/ecs-logs-collector](https://github.com/awslabs/ecs-logs-collector)
  ECSに関する各種ログを集約して出力
* [ExpediaDotCom/c3vis](https://github.com/ExpediaDotCom/c3vis)
  ECSクラスタのリソース使用状況をビジュアライズ化

<img width="548" alt="graph.png" src="https://qiita-image-store.s3.amazonaws.com/0/27454/26c6c242-b7c0-4774-445d-122bb4a5efe1.png">

### ルートボリューム・Dockerボリュームのディスク拡張 (起動タイプ: EC2)

ECSコンテナインスタンスは自動で2つのボリュームを作成する。1つはOS領域(`/dev/xvda` 8GB)、もう1つがDocker領域（`/dev/xvdcz` 22GB)である。
クラスタ作成時にDocker領域のサイズを変更することはできるが、OS領域は項目が見当たらず変更が出来ないように見える。

![Screen_Shot_2017-08-31_at_11_00_03.png](https://qiita-image-store.s3.amazonaws.com/0/27454/18106705-bcde-a6eb-2e2f-61fb3f2381c4.png)

どこから設定するかというと、一度空のクラスタを作成し、EC2マネージメントコンソールからインスタンスを作成する必要がある。

また、既存ECSコンテナインスタンスのOS領域を拡張したい場合は、EC2マネージメントコンソールのEBS項目から変更可能。スケールアウトを考慮し、Auto scallingのLaunch Configurationも忘れずに更新しておく必要がある。

* [Docker volumes still stored on /dev/xvda, not /dev/xvdcz](https://github.com/aws/amazon-ecs-agent/issues/312)
* [Amazon ECS コンテナインスタンスの起動](http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/launch_container_instance.html)

補足となるが、Docker領域はOS上にマウントされていないため、ECSコンテナインスタンス上から`df`等のコマンドで領域を確認することはできない。

### デプロイ

ECSのデプロイツールは色々ある。

* [aws/amazon-ecs-cli](https://github.com/aws/amazon-ecs-cli)
* [openfresh/ecs-formation](https://github.com/openfresh/ecs-formation)
* [silinternational/ecs-deploy](https://github.com/silinternational/ecs-deploy)
* [eagletmt/hako](https://github.com/eagletmt/hako)
* [naomichi-y/ecs_deployer](https://github.com/naomichi-y/ecs_deployer)
* [metaps/genova](https://github.com/metaps/genova)

※下2つは私が開発したツールです。`genova` は `ecs_deployer` をベースとしたデプロイ管理マネージャとなってます。

#### デプロイ方式

  * コマンド実行形式のデプロイ
  * GitHubのPushを検知した自動デプロイ
  * Slackを利用したインタラクティブデプロイ

![Screen_Shot_2017-08-31_at_11_31_15.png](https://qiita-image-store.s3.amazonaws.com/0/27454/d7873180-c9c3-67e1-7014-1e7ec30ac4bb.png)

#### デプロイフロー

ECSへのデプロイフローは次の通り。

  1. リポジトリ・タスクの取得
  2. イメージのビルド
      * タグにGitHubのコミットID、デプロイ日時を追加
  3. ECRへのプッシュ
  4. タスクの更新
  5. 不要なイメージの削除
      * ECRは1リポジトリ辺り最大1,000のイメージを保管できる
  6. サービスの更新
  7. タスクの入れ替えを監視
      * コンテナの異常終了も検知
  8. Slackにデプロイ完了通知を送信

#### デプロイパフォーマンスの改善

ALBを利用している場合、デプロイ時のコンテナの入れ替えに時間がかかることがある。
これはELBが登録解除プロセスを実行する前に300秒待機して、リクエストの完了を待つために起きている。アプリケーションの特性によってはこの時間を短くすることで、デプロイのパフォーマンスを改善することができる。

<img width="421" alt="Screen Shot 2017-09-23 at 13.33.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/27454/07de0827-9382-4b38-4632-dee03d474fc7.png">

この設定の変更により、6〜7分かかっていたデプロイが2分程度に改善された。

* [Application Load Balancer のターゲットグループ - 登録解除の遅延](http://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/load-balancer-target-groups.html#deregistration-delay)

### ログの分類

ECSのログを分類してみた。

| ログの種別 | ログの場所 || 備考 |
|---|---|---|---|
| サービス | AWS ECSコンソール | サービス一覧ページのEventタブ | APIで取得可能 (※1) |
| タスク | AWS ECSコンソール | クラスタページのTasksタブから"Desired task status"が"Stopped"のタスクを選択。タスク名のリンクから停止した理由を確認できる | APIで取得可能 |
| Docker daemon | ECSコンテナインスタンス | /var/log/docker | (※2) |
| ecs-init upstart ジョブ | ECSコンテナインスタンス | /var/log/ecs/ecs-init.log | (※2) |
| ECSコンテナエージェント | ECSコンテナインスタンス | /var/log/ecs/ecs-agent.log | (※2) |
| IAMロール | ECSコンテナインスタンス | /var/log/ecs/audit.log | タスクに認証情報のIAM使用時のみ |
| アプリケーション | コンテナ    | /var/lib/docker/containers | ログドライバで変更可能 |

* ※1: イベントが発生したタイミングでログを取得することは出来ないため、Lambda等から定期的にイベントを取得する必要がある。
  * [How to programmatically get event "unable to place a task because the resources could not be found" · Issue #1221 · aws/amazon-ecs-agent](https://github.com/aws/amazon-ecs-agent/issues/1221#issuecomment-361668452)
* ※2: ECSコンテナインスタンス上の各種ログは、CloudWatch Logs Agentを使うことでCloudWatch Logsに転送することが可能(現状の運用ではログをFluentdサーバに集約させているので、ECSコンテナインスタンスにはFluentdクライアントを構築している)。
  * [コンテナインスタンスでのCloudWatch Logsの使用](http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/using_cloudwatch_logs.html)

### サーバレス化

ECSから少し話が逸れるが、インフラの運用・保守コストを下げるため、Lambda(Node.js)による監視の自動化を進めている。各種バックアップからシステムの異常検知・通知までをすべてコード化することで、サービスのスケールアウトに耐えうる構成が容易に構築できるようになる。
ECS+Lambdaを使ったコンテナ運用に切り替えてから、EC2の構築が必要となるのは踏み台くらいだった。

### インスタンスのドレイニング (起動タイプ: EC2)

クラスタから特定のインスタンスを外したい場合は、インスタンスのドレイニングが必要となる。ドレイニング状態となったインスタンスには新しいタスクが配置されなくなり、別のインスタンスにタスクが配置される。
ドレイニングを行わずインスタンスを削除した場合、インスタンス上のコンテナが削除され、アプリケーションへの接続が遮断される点に注意したい。

インスタンスのドレイニングはクラスタページの`ECS Instances`タブから設定できる。

![Screen_Shot_2017-09-25_at_15_28_54.png](https://qiita-image-store.s3.amazonaws.com/0/27454/8a70fdf6-e32e-53b5-48e1-eec6300b2b61.png)

ドレイニングが終わると、対象インスタンス上のタスク数は `0` となる。

![Screen_Shot_2017-09-25_at_15_35_36.png](https://qiita-image-store.s3.amazonaws.com/0/27454/d9213ff2-9e0b-875e-0393-2ccfb2ef79f2.png)

尚、インスタンスをドレイニングするに辺り、クラスタ上で利用できるリソースは減るため、タスクに割り当てるCPUやメモリの設定値、タスク数などを事前に確認しておいたほうが良い。インスタンスの削除については [クラスタにアタッチされているインスタンスを削除する (起動タイプ: EC2)](#%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%81%AB%E3%82%A2%E3%82%BF%E3%83%83%E3%83%81%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B-%E8%B5%B7%E5%8B%95%E3%82%BF%E3%82%A4%E3%83%97-ec2) を参照。

ドレイニングの自動化についてはAWSの記事が参考となる。

* [Amazon ECS におけるコンテナ インスタンス ドレイニングの自動化方法](https://aws.amazon.com/jp/blogs/news/how-to-automate-container-instance-draining-in-amazon-ecs/)

### インスタンスのスケールアウト (起動タイプ: EC2)

クラスタの作成方法によって作業手順が変わる。クラスタ作成時に `Create an empty cluster` にチェックを付けた場合は空のクラスタのみ作成されるが、チェックを外した場合はCloudFormationにより、クラスタと同時にEC2インスタンスやセキュリティグループなどが紐づく。

<img width="614" alt="Screen_Shot_2020-08-06_at_16_05_55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/7c1d134f-57dd-0044-36c7-3a7f144bc0af.png">

* 空のクラスタを作成した場合
    * EC2コンソールの `Auto Scaling Groups` からクラスタに紐づく設定を開き、`General` タブから `Edit` を選択
      ![Screen Shot 2021-02-08 at 12.44.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/7043bc7f-2cc5-ae36-4445-abb220aed40b.png)
    * `Desired capacity` にスケールするインスタンス数、`Maximum capacity` に並列でスケールするインスタンス数 (最大値はDesired capacity) を指定して `Update` を実行
* クラスタと同時にインスタンスを作成した場合
    * ECSコンソールの `ECS Instances` タブに `Scale ECS Instances` というボタンがある<br>
<img width="515" alt="Screen_Shot_2018-06-14_at_1_48_41.png" src="https://qiita-image-store.s3.amazonaws.com/0/27454/531dbd8a-9f5e-055b-e89f-5b32119224fb.png">
    * ボタンを押すと `Desired number of instances` という項目があるので、スケールアウト後の数を指定して `Scale` を実行

数分待つとインスタンスが起動するので、ECSのコンソールを開き、対象クラスタの `ECS Instances` にインスタンスが追加されていることを確認する。

![Screen_Shot_2021-02-08_at_12_56_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/fc240b25-4fbc-c92f-f473-9f1654c8129c.png)

尚、インスタンスを起動しただけではサービスはスケールアウトしないので、別途サービスのタスク数を調整する必要がある。

### インスタンスのスケールイン (起動タイプ: EC2)

インスタンスを減らす場合、初めに削除対象インスタンス上で稼働しているサービスやスケジュールタスクを別のインスタンスに退避させる必要がある (詳しくは [インスタンスのドレイニング](#インスタンスのドレイニング-起動タイプ-ec2) を参照)。

インスタンスのスケールイン自体はコンソールから変更 (後述) となるが、スケールインの対象となるインスタンスは利用者側では決められない (ドレイニングしたインスタンスが削除対象となる訳ではない) ので、予め削除保護しておきたいインスタンスをオートスケーリンググループに登録しておく必要がある。

* EC2コンソールの `Auto Scaling Groups` から対象クラスタを選択。`Instance Management` タブを開く
* スケールインから保護したいインスタンスを選択して `Actions` から `Set scale-in protection` を選択

削除保護が有効になると、対象インスタンスの `Protected from` が `Scale in` の表示に切り替わる。
![Screen_Shot_2021-02-08_at_13_16_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/334ef3b5-10ec-1e79-f717-4fc004fb969f.png)

続いてインスタンスのスケールインを行う。

* 空のクラスタを作成した場合
    * EC2の `Auto Scaling Groups` から対象クラスタの設定を開き、`Details` から `Edit` を選択。`Desired capacity` と `Maximum capacity` にスケールイン後の数を指定して `Update` を押す
* クラスタと同時にインスタンスを作成した場合
    * ECSコンソールの `ECS Instances` タブに `Scale ECS Instances` というボタンがある<br>
    <img width="515" alt="Screen_Shot_2018-06-14_at_1_48_41.png" src="https://qiita-image-store.s3.amazonaws.com/0/27454/531dbd8a-9f5e-055b-e89f-5b32119224fb.png">
    * ボタンを押すと `Desired number of instances` という項目があるので、スケールイン後の数を指定して `Scale` を実行

## トラブルシュート

### コンテナの起動が失敗し続け、ディスクフルが発生する

ECSはタスクの起動が失敗すると数十秒間隔でリトライを実施する。この時コンテナがDockerボリュームを使用していると、ECSコンテナエージェントによるクリーンアップが間に合わず、ディスクフルが発生することがあった(ECSコンテナインスタンスの`/var/lib/docker/volumes`にボリュームが残り続けてしまう)。
この問題を回避するには、ECSコンテナインスタンスのOS領域(※1)を拡張するか、コンテナクリーンアップの間隔を調整する必要がある。
コンテナを削除する間隔は`ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION`パラメータを使うと良い。

※1: DockerボリュームはDocker領域ではなく、OS領域に保存される。OS領域の容量はデフォルトで8GBなので注意が必要。

* [ルートボリューム・Dockerボリュームのディスク拡張](#ルートボリューム・Dockerボリュームのディスク拡張)
* [自動化タスクとイメージのクリーンアップ](http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/automated_image_cleanup.html)

また、どういう訳か稀に古いボリュームが削除されず残り続けてしまうことがあった。そんな時は次のコマンドでボリュームを削除しておく。

```
# コンテナから参照されていないボリュームの確認
docker volume ls -f dangling=true

# 未参照ボリュームの削除
docker volume rm $(docker volume ls -q -f dangling=true)
```

### 古いコンテナが削除されない

`ecs.config`に`ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION`を指定を指定しているにも関わらず、古いコンテナが削除されない場合がある。

```
$ docker ps -a
xxx xxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxx "bundle exec rails..."11 days ago          Dead xxx
```
削除されていないコンテナのステータスを見ると`Dead`となっていた。
サポートに問い合わせたところ、`Dead`ステータスのコンテナは`ECS_ENGINE_TASK_CLEANUP_WAIT_DURATION`を過ぎてもクリーンアップされない仕様らしい。
従って現段階(2019年1月現在)で`Dead`コンテナを削除するにはホスト側でCronを回すしかない。

```
$ docker rm $(docker ps --all -q -f status=dead)
または
$ docker rm -f $(docker ps --all -q -f status=dead)
```

改善して欲しい。。

### ECSがELBに紐付くタイミング

Dockerfileの`CMD`でスクリプトを実行するケースは多々あると思うが、コンテナは`CMD`が実行された直後にELBに紐付いてしまうので注意が必要となる。

```
bundle exec rake assets:precompile
```

このようなコマンドをスクリプトで実行する場合、アセットがコンパイル中であろうがお構いなしにELBに紐付いてしまう。
時間のかかる処理は素直にDockerfile内で実行した方が良い。

### ecs-agent.logに"WARN messages when no Tasks are scheduled"という警告が出力される

ECSコンテナインスタンスの`/var/log/ecs-agent.log`に`WARN messages when no Tasks are scheduled`という警告が頻出していた。[Issue](https://github.com/aws/amazon-ecs-agent/issues/506)にも症例が報告されている。
この問題は既知の不具合のようで、ECSのAMIに`amzn-ami-2017.03.d-amazon-ecs-optimized - ami-e4657283`を使うことで解決した(AMI `amzn-ami-2016.09.f-amazon-ecs-optimized - ami-c393d6a4`で問題が起こることを確認済み）。

### コンテナ起動時にディスク容量不足のエラーが出てコンテナごと落ちる

```
CannotPullContainerError: failed to register layer: devmapper: Thin Pool has 2862 free data blocks which is less than minimum required 4449 free data blocks. Create more free space in thin pool or use dm.min_free_space option to change behavior
```
`docker info` でDockerの情報を確認する。

```
$ docker info
Containers: 5
 Running: 5
 Paused: 0
 Stopped: 0
Images: 6
Server Version: 17.12.0-ce
Storage Driver: devicemapper
 Pool Name: docker-docker--pool
 Pool Blocksize: 524.3kB
 Base Device Size: 10.74GB
 Backing Filesystem: ext4
 Udev Sync Supported: true
 Data Space Used: 21.84GB
 Data Space Total: 23.33GB
 Data Space Available: 1.491GB
...
```

ECSはデフォルトで`22GB`のDockerイメージ格納領域を作るが、`Data Space Available` の値を見ると `1.49GB` しかない。

次のコマンドを実行して容量を増やす。

```
# 実行中でないコンテナを削除
$ docker rm $(docker ps -aq)

# 未使用のイメージを削除
$ docker rmi $(docker images -q)

# 確認
$ docker info
Containers: 5
 Running: 5
 Paused: 0
 Stopped: 0
Images: 3
Server Version: 17.12.0-ce
Storage Driver: devicemapper
 Pool Name: docker-docker--pool
 Pool Blocksize: 524.3kB
 Base Device Size: 10.74GB
 Backing Filesystem: ext4
 Udev Sync Supported: true
 Data Space Used: 20.54GB
 Data Space Total: 23.33GB
 Data Space Available: 2.79GB
```

`1.49GB`→`2.79GB` と少し増えた。
続けてコンテナ内で使用されていないデータブロックを削除してみる。

```
$ sudo sh -c "docker ps -q | xargs docker inspect --format='{{ .State.Pid }}' | xargs -IZ fstrim /proc/Z/root/"

$ docker info
Containers: 5
 Running: 5
 Paused: 0
 Stopped: 0
Images: 3
Server Version: 17.12.0-ce
Storage Driver: devicemapper
 Pool Name: docker-docker--pool
 Pool Blocksize: 524.3kB
 Base Device Size: 10.74GB
 Backing Filesystem: ext4
 Udev Sync Supported: true
 Data Space Used: 10.43GB
 Data Space Total: 23.33GB
 Data Space Available: 12.9GB
```

`2.79GB`→`12.9GB` まで増えた。
根本解決としては、Dockerに割り当てるストレージ容量を増やすか、古いイメージの削除間隔を短くすれば良さそう。

* [ストレージ設定](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-ami-storage-config.html)

### Scheduled task (CloudWarch Events)が多重実行される

Scheduled taskの実行タスク数を`1`にしてるのに、タスクが複数回実行される場合がある。これ**仕様**です。マニュアルに書いてます。

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/CWE_Troubleshooting.html#RuleTriggeredMoreThanOnce

> 1 つのイベントに応じてルールが複数回トリガーされました。CloudWatch イベント で、ルールのトリガーまたはターゲットへのイベントの提供で何が保証されますか。
>> まれに、単一のイベントまたはスケジュールされた期間に対して同じルールを複数回トリガーしたり、特定のトリガーされたルールに対して同じターゲットを複数回起動したりする場合があります。

現時点(2019年5月)ではアプリケーション側で排他制御するしかなさそうです。マニュアルそこまで読まないし絶対ハマると思う。
ちなみにサポートからの回答では、冗長性や可用性を担保する関係で、**LambdaやSQSもまれに複数回実行される可能性がある**とのことです。ご注意ください。

### ecs-agentが落ちる (起動タイプ: EC2)

それまで安定稼働していたサービスが突然落ちることがありました。よくよく調べてみるとクラスタにアタッチしているインスタンスが落ちて、コンテナエージェントの状態が `false` に変わっていました。

<img width="729" alt="Screen_Shot_2020-08-02_at_13_42_48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/ae340c31-6795-c89b-7926-5540d553f993.png">

ホストインスタンスに接続してDockerの状態を見ようとするも、起動が失敗している様子。

```
[root@development-web developer]# docker ps
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

[root@development-web developer]# docker -v
Docker version 19.03.6-ce, build 369ce74
```

状況によっては `Out of memory` のエラーが出ることもありました。

```
$ docker -v
fatal error: runtime: out of memory
```

この問題は弊社プロダクトで同時期に一斉に起こり悩まされたのですが、原因はDockerのバージョンを19に上げると起きることが再現できました。movyプロジェクトで報告されているメモリが多く消費される問題に起因してるようです。

* dockerd: high memory usage 
    * https://github.com/docker/for-linux/issues/848

弊社ではホストインスタンスを立ち上げる際に

* Terraform経由で常に最新かつ安定版のAMIを取得していた
* cloud-init経由でパッケージの最新版へ更新を行っていた (`repo_upgrade: all`)

この2つを行っていたがために、Dockerが最新版に更新されサービスの動作が不安定に陥っていました。

参考までにAMIとDockerの組み合わせで動作が安定した環境は下表の通りです。

<img width="1066" alt="5f0e995af8a1990048862123.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27454/ea627d60-5310-33c3-d261-27f6ecce5197.png">

尚、検証したOSは `Amazon Linux 1` ですが、`Amazon Linux 2` であれば `ami-032b1a02e6610214e` が最新の安定したAMIとなります (2020年7月現在)。

この問題が解消されるまでの間は、AMIを固定にし、cloud-initを利用している場合は `repo_upgrade` の設定を `none` (Amazon Linux 2の場合は `security` でも問題なし) に設定しておいたほうが良さそうです。

### コンテナでOOMが発生、あるいはEC2の自動停止が発生する (起動タイプ: EC2)

前述の問題に類似する問題ですが、コンテナが正常に起動せず、以下のようなエラーが発生することがありました。

* `OutOfMemoryError: Container killed due to memory usage`
* `service *** was unable to place a task because no container instance met all of its requirements. The closest matching container-instance *** has insufficient memory available. For more information, see the Troubleshooting section.`
* `Host EC2 (instance i-***) terminated.`

原因はコンテナ実行時のメモリ使用率がタスクに割り当てられている上限値を超えている可能性があります。
あるプロジェクトではRailsコンテナのメモリ平均使用率が170MB、タスク定義におけるハード制限が386MB指定されていましたが、コンテナの動作が不安定になることがりました。
その後、コンテナ起動直後からプロセスの状態を確認してみると、ENTRYPOINTで `asset:precompile` が実行されていることが確認でき、瞬間的に400MB以上のメモリが使用され、コンテナが落ちていることが分かりました。
この問題に関しては一次対策としてソフト制限を256MB、ハード制限を768MB割り当てることで解消しています。
恒久対応として、アセットプリコンパイルはビルド時に行う形が良さそうです。