ECSインスタンスがクラスターに登録されない

# 要約
ECSでWebサーバーを動かしてみようとしたら、表題の通りの問題が発生。
問題解決に至るまでに何を調べたか、何を考えたかを書いた。

（2020/12/02追記）
ちなみに、投稿から2年経ってますが、この記事の主眼はECSの知識と言うより原因調査の考え方なので、読む価値は無くなってないんじゃないかなと思います。
それに、ECSやVPCの仕様が根本的に変わったってことも無いと思いますし。

# 問題発生
いい加減にECS勉強しておかなきゃと思い、試しにウィザードに沿ってECSクラスターを作って見たところ、Run Taskでエラーになる。

> Unable to run task
> No Container Instances were found in your cluster.

確かに、ECS Instancesのタブには1つもインスタンスが表示されない。だが、EC2のダッシュボードには「ECS Instance〜」とかのインスタンスが表示されている。なぜECSのダッシュボード側には表示されないのか？

# 原因調査
## ざっくり原因絞り込み
そもそもECSインスタンス（＝container instance）が何なのかちゃんと分かってないので公式ドキュメントを読む
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_instances.html

> An Amazon ECS container instance is an Amazon EC2 instance that is running the Amazon ECS container agent and has been registered into a cluster.

なるほど〜

> Your container instance must be running the Amazon ECS container agent to register into one of your clusters. 

EC2インスタンスにSSHして `ps -A | grep ecs` してみると・・・

```
[ec2-user@ip-10-0-1-100 ~]$ ps -A | grep ecs
 2813 ?        00:00:00 amazon-ecs-init
```

エージェントが何かを初期化しようとしているっぽいので、たぶんECSエージェントは動いているんだろうと判断。

> Because the Amazon ECS container agent makes calls to Amazon ECS on your behalf, you must launch container instances with an IAM role that authenticates to your account and provides the required resource permissions.

ECSインスタンス（ECSエージェントが立ち上がってるEC2インスタンス）をECSクラスターに登録するのは、自分でやるのではなくECSエージェントがやってくれるらしい。
そのためにIAMロールが適切に設定されている必要があるらしいが、そのあたりはECSのウィザードに従って設定したので、たぶん問題ないだろうと判断。

むしろ気になるのはネットワークの方で、自分でイチから作ったVPC（Subnet, Route Table, Internet Gateway, Network ACL, Security Group）にクラスターを構成しようとしていたため、名前解決、経路、権限設定あたりに問題があり、EC2インスタンスからECSへのリクエストが到達していないのではと推測。

そこでまずは「どのECSクラスターに自分を登録するか」の情報はEC2インスタンス側で設定を持っているはずと思い、その辺りを調べると、ECSのコンフィグについてのページを発見
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-agent-config.html

> ECS_CLUSTER

> The cluster that this agent should check into. If this value is undefined, then the default cluster is assumed. If the default cluster does not exist, the Amazon ECS container agent attempts to create it. If a non-default cluster is specified and it does not exist, registration fails.

さっそくチェック

```
[ec2-user@ip-10-0-1-100 ~]$ cat /etc/ecs/ecs.config 
ECS_CLUSTER=the-name-of-the-cluster-i-expected
ECS_BACKEND_HOST=
```

というわけで、ここは問題なさそうなので、次はログを確認

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/logs.html

> The Amazon ECS container agent stores logs at /var/log/ecs/ecs-agent.log.timestamp on Linux instances, and C:\ProgramData\Amazon\ECS\log\ecs-agent.log.timestamp on Windows instances.

これも確認してみると・・・

```
2018-10-06T03:43:15Z [INFO] Loading configuration
2018-10-06T03:43:15Z [INFO] Amazon ECS agent Version: 1.20.3, Commit: e8b5229a
2018-10-06T03:43:15Z [INFO] Creating root ecs cgroup: /ecs
2018-10-06T03:43:15Z [INFO] Creating cgroup /ecs
2018-10-06T03:43:15Z [INFO] Loading state! module="statemanager"
2018-10-06T03:43:15Z [INFO] Event stream ContainerChange start listening...
2018-10-06T03:43:16Z [INFO] Registering Instance with ECS
2018-10-06T03:43:36Z [ERROR] Could not register: RequestError: send request failed
caused by: Post https://ecs.ap-northeast-1.amazonaws.com/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
2018-10-06T03:43:36Z [ERROR] Error registering: RequestError: send request failed
caused by: Post https://ecs.ap-northeast-1.amazonaws.com/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
```

おぉ〜　やっぱりネットワークの問題っぽい〜

## 詳しく見ていくと

試しに

```
# タイムアウト！
$ ping example.com

# これもタイムアウト！
$ curl example.com

# もちろんこれもタイムアウト！
$ sudo yum update
```

このEC2、外部にアクセスできない！　なんで！？

原因を調べるため、一つずつ確認して行った

### セキュリティグループ

セキュリティグループのアウトバウンドルールはご覧の通り（ちなみに、全部マネジメントコンソールで確認したが、この記事に載せるためだけにAWS CLIの使い方を調べた）
https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html

```
# IpPermissionsEgress はアウトバウンドルールのことで、IpProtocol が -1 の場合は任意のプロトコルを表す
$ aws ec2 describe-security-groups --group-ids sg-12345678 | jq ".SecurityGroups[0].IpPermissionsEgress"
[
  {
    "IpProtocol": "-1",
    "IpRanges": [
      {
        "CidrIp": "0.0.0.0/0"
      }
    ],
    "Ipv6Ranges": [],
    "PrefixListIds": [],
    "UserIdGroupPairs": []
  }
]
```

任意のIPへのアクセスを許可しているため、ここも問題なし

### ルートテーブル

ルートテーブルはご覧の通り

```
$ aws ec2 describe-route-tables --route-table-ids rtb-12345678 | jq ".RouteTables[0].Routes"
[
  {
    "DestinationCidrBlock": "10.0.0.0/16",
    "GatewayId": "local",
    "Origin": "CreateRouteTable",
    "State": "active"
  },
  {
    "DestinationCidrBlock": "0.0.0.0/0",
    "GatewayId": "igw-12345678",
    "Origin": "CreateRoute",
    "State": "active"
  }
]
```

ここも問題なし

じゃあ怪しいのはネットワークACLですねと言うことで・・・

### ネットワークACL

```
$ aws ec2 describe-network-acls --network-acl-ids acl-12345678 | jq ".NetworkAcls[0].Entries"
[
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": true,
    "Protocol": "-1",
    "RuleAction": "allow",
    "RuleNumber": 100
  },
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": true,
    "Protocol": "-1",
    "RuleAction": "deny",
    "RuleNumber": 32767
  },
  {
    "CidrBlock": "xxx.xxx.xxx.xxx/32",
    "Egress": false,
    "PortRange": {
      "From": 22,
      "To": 22
    },
    "Protocol": "6",
    "RuleAction": "allow",
    "RuleNumber": 100
  },
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "PortRange": {
      "From": 22,
      "To": 22
    },
    "Protocol": "6",
    "RuleAction": "deny",
    "RuleNumber": 110
  },
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "PortRange": {
      "From": 80,
      "To": 80
    },
    "Protocol": "6",
    "RuleAction": "allow",
    "RuleNumber": 120
  },
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "PortRange": {
      "From": 443,
      "To": 443
    },
    "Protocol": "6",
    "RuleAction": "allow",
    "RuleNumber": 130
  },
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "Protocol": "-1",
    "RuleAction": "deny",
    "RuleNumber": 32767
  }
]
```

`"Egress": true` になっているのがアウトバウンドのルール。こちらはフルオープン状態。

```
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": true,
    "Protocol": "-1",
    "RuleAction": "allow",
    "RuleNumber": 100
  },
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": true,
    "Protocol": "-1",
    "RuleAction": "deny",
    "RuleNumber": 32767
  },
```

`"Egress": false` になっているのがインバウンドのルール。順番に見ていく。

この部分では決まったIP以外からのSSH接続を禁止している。

```
  {
    "CidrBlock": "xxx.xxx.xxx.xxx/32",
    "Egress": false,
    "PortRange": {
      "From": 22,
      "To": 22
    },
    "Protocol": "6",
    "RuleAction": "allow",
    "RuleNumber": 100
  },
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "PortRange": {
      "From": 22,
      "To": 22
    },
    "Protocol": "6",
    "RuleAction": "deny",
    "RuleNumber": 110
  }
```

この部分ではHTTP, HTTPSでのアクセスを許可している。

```
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "PortRange": {
      "From": 80,
      "To": 80
    },
    "Protocol": "6",
    "RuleAction": "allow",
    "RuleNumber": 120
  },
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "PortRange": {
      "From": 443,
      "To": 443
    },
    "Protocol": "6",
    "RuleAction": "allow",
    "RuleNumber": 130
  }
```

最後のここでそれ以外のアクセスを弾いている。

```
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "Protocol": "-1",
    "RuleAction": "deny",
    "RuleNumber": 32767
  }
```

ここまで確認し、試しにネットワークACLのインバウンドルールを変更し、任意のIPアドレスからの任意のプロトコルのアクセスを許可したところ、ECSインスタンスがクラスターに登録された。
よって、ネットワークACLの設定が原因だったことは分かったが、何が悪いのかが分からなかった。

## 結局何が問題だったのか？

AWS VPCに用意されているアクセス制御機構はネットワークACLとセキュリティグループがあり、下記のような違いがある。

| サービス           | 設定単位                   | 状態           |
|-------------------|--------------------------|----------------|
| ネットワークACL     | サブネット単位             | ステートレス     |
| セキュリティグループ | インスタンス単位（EC2, ELB） | ステートフル    |

ポイントはネットワークACLがステートレスだと言うこと。
例えば `curl example.com` とかやると、アウトバウンドルールで任意のプロトコル、送信先が許可されているので、リクエストはexample.comに到達する。
だが、インバウンドルールでエフェメラルポートでのアクセスを許可していないため、example.comからのレスポンスが弾かれてEC2に到達せず、結果、タイムアウトとなっていた。

では、俺がなぜエフェメラルポートの穴空けをしていなかったかと言うと、単に知らなかったから。
参考： http://e-words.jp/w/エフェメラルポート.html

そこで、下記のドキュメントを参考にルールを追加した。
https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-network-acls.html#nacl-ephemeral-ports

```
  {
    "CidrBlock": "0.0.0.0/0",
    "Egress": false,
    "PortRange": {
      "From": 1024,
      "To": 65535
    },
    "Protocol": "6",
    "RuleAction": "allow",
    "RuleNumber": 140
  }
```

https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-recommended-nacl-rules.html:title で紹介されているシナリオ1から、
自宅からのRDPを除いたような設定に落ち着いた。

これでECSにコンテナインスタンスが登録され、タスクが作成できるようになった。良かった。

# 教訓

* AWSの公式ドキュメントはよく読むと有用な情報がたくさんある
* AWSは便利だが、基本的なネットワークやプロトコルの知識がないと扱いきれない部分がある。基礎はやはり大事
* この程度の分量の記事でも書くのに2〜3時間はかかった。人の記事を読むときはもっと感謝しよう