t2.micro 1台でEKSを起動してみた

AWSの無料枠といえばt2.microです。
ということで、EKSをWorkerがt2.micro 1台という構成で構築してみました。[^1]
[^1]: GKEやAKSと違い、EKSはMasterノードが有料 ($0.20/hr。t2.xlargeよりも高額) なので、Workerを無料枠に入れても仕方ないというのは別のお話です。

(2018/06/12) CloudFormationテンプレートのMaxPodsの設定が影響している可能性を考慮していなかったため、追記しました。

# 結論
EKSのWorkerは、t2.microでも特に問題なく構築でき、Podの起動も可能でした。
ただし、IPアドレス数の制限により、Podは2つ以上起動できませんでした。
~~IPアドレスが枯渇するとWorkerもPodもスケールできなくなると思われるので、IPアドレス数の制限には注意が必要そうです。~~
インスタンスタイプごとに、IPアドレス数の制限と、MaxPodsによる制限と、他にも何らかの制限があり、Podの起動数が制限されているようです。
スケールを考えるうえで、Podの起動数の制限には注意が必要そうです。

# やったこと

## Workerがt2.micro 1台構成なEKSの構築

### Workerの構築の手前まで

[Amazon EKS の使用開始](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started.html) に従い、「ステップ 3: Amazon EKS ワーカーノードを起動して設定する」の手前まで構築を進めます。

### CloudFormationテンプレートの修正

[Amazon EKS の使用開始](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started.html) に掲載されているテンプレートを、以下のように2箇所書き換えます。


```yaml
---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Amazon EKS - Node Group'

Parameters:

  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instances
    Type: AWS::EC2::KeyPair::KeyName
    :
    :
    :
  NodeInstanceType:
    Description: EC2 instance type for the node instances
    Type: String
    Default: t2.medium
    AllowedValues:
    # t2.microを追加
    - t2.micro
    - t2.small
    - t2.medium
    - t2.large
    - t2.xlarge
    - t2.2xlarge
    :
    :
    :
      MaxPods: 234
    r4.8xlarge:
      MaxPods: 234
    r4.16xlarge:
      MaxPods: 737
    # t2.microを追加
    t2.micro:
      # MaxPodsは適当に設定
      MaxPods: 4
    t2.small:
      MaxPods: 8
    t2.medium:
      MaxPods: 17
    t2.large:
    :
    :
    :
```

### 修正したテンプレートでWorkerを構築

変更したテンプレートを使ってWorkerを構築します。
NodeAutoScalingGroupMaxSizeは1、NodeInstanceTypeはt2.microとします。


![eks-worker-stack-parameter.png](https://qiita-image-store.s3.amazonaws.com/0/216010/08359e93-624b-344b-6a67-5360a72cda87.png)

### ConfigMapの作成

[Amazon EKS の使用開始](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started.html) に記載されている「ワーカーノードをクラスターと結合するには」に従って、ConfigMapを作成します。

### Worker構築完了の確認

kubectlでワーカーが1台表示されれば構築完了です。

```console
$ kubectl get nodes
NAME                                           STATUS    ROLES     AGE       VERSION
ip-192-168-157-38.us-west-2.compute.internal   Ready     <none>    1m        v1.10.3
```

## Podの起動

### まずはPodを1つ起動してみる

NginxのPodを1つ起動してみます。

```console
$ kubectl run my-nginx --image=nginx:alpine
deployment.apps "my-nginx" created
```

無事起動しました。

```console
$ kubectl get all
NAME                            READY     STATUS    RESTARTS   AGE
pod/my-nginx-6947c57698-tjt6p   1/1       Running   0          25s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   24m

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   1         1         1            1           25s

NAME                                  DESIRED   CURRENT   READY     AGE
replicaset.apps/my-nginx-6947c57698   1         1         1         25s
```

ポートフォワードして動作確認します。

```console
$ kubectl port-forward deployment.apps/my-nginx 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
```

ブラウザから http://localhost:8080 に接続すると、画面が表示されました。

![nginx.png](https://qiita-image-store.s3.amazonaws.com/0/216010/184d863b-3e8b-9878-662d-94fb6b289778.png)

t2.microでもPodを起動できることが確認できました。

### Podを増やしてみる

Podを2つに増やしてみます。

```console
$ kubectl scale deployment.apps/my-nginx --replicas=2
deployment.apps "my-nginx" scaled
```

以下の通り、2つ目のPodが起動していません。

```console
$ kubectl get all
NAME                            READY     STATUS    RESTARTS   AGE
pod/my-nginx-6947c57698-5lnxj   0/1       Pending   0          16s
pod/my-nginx-6947c57698-tjt6p   1/1       Running   0          6m

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   29m

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   2         2         2            1           6m

NAME                                  DESIRED   CURRENT   READY     AGE
replicaset.apps/my-nginx-6947c57698   2         2         1         6m
```

詳細を確認します。

```console
$ kubectl describe pod/my-nginx-6947c57698-5lnxj
Name:           my-nginx-6947c57698-5lnxj
Namespace:      default
Node:           <none>
Labels:         pod-template-hash=2503713254
                run=my-nginx
Annotations:    <none>
Status:         Pending
IP:
Controlled By:  ReplicaSet/my-nginx-6947c57698
Containers:
  my-nginx:
    Image:        nginx:alpine
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-8xb9r (ro)
Conditions:
  Type           Status
  PodScheduled   False
Volumes:
  default-token-8xb9r:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-8xb9r
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age               From               Message
  ----     ------            ----              ----               -------
  Warning  FailedScheduling  19s (x8 over 1m)  default-scheduler  0/1 nodes are available: 1 Insufficient pods.
```

何かの不足でPodが起動できていないようです。

# 考察

## 2つ目のPodが起動できなかった理由

[ポッドネットワーキング](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/pod-networking.html) によると...

> Amazon EKS は、Kubernetes の Amazon VPC CNI プラグインを使用した従来の VPC ネットワーキングをサポートしています。この CNI プラグインを使用すると、Kubernetes ポッドは VPC ネットワーク上と同じ IP アドレスをポッド内に持つことができます。この CNI プラグインは、GitHub で管理されているオープンソースのプロジェクトです。
>     :
>     :
>     :
> Elastic Network Interface と、Amazon EC2 インスタンスタイプによるセカンダリ IP アドレス制限が適用されます。一般的に、インスタンスが大きいほど、より多くの IP アドレスをサポートできます。詳細については、Linux インスタンス用 Amazon EC2 ユーザーガイドの「各インスタンスタイプのネットワークインターフェイスごとの IP アドレス」を参照してください。

[各インスタンスタイプのネットワークインターフェイスごとの IP アドレス](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-eni.html#AvailableIpPerENI) を参照すると...

> |インスタンスタイプ|ネットワークインターフェイスの最大数|インターフェイスあたりの IPv4 アドレス|インターフェイスあたりの IPv6 アドレス|
> |---|---|---|---|
> |t2.micro|2|2|2|
> |t2.small|2|4|4|
> |t2.midium|3|6|6|

ということで、t2.microは 2 × 2 = 4 つまでしかIPアドレスを持てないようです。

他のNamespaceでどれだけIPアドレスを使っているのか確認すると...

```console
$ kubectl get pod --namespace kube-system -o wide
NAME                       READY     STATUS    RESTARTS   AGE       IP                NODE
aws-node-2tqc2             1/1       Running   0          30m       192.168.157.38    ip-192-168-157-38.us-west-2.compute.internal
kube-dns-7cc87d595-bqdjk   3/3       Running   0          52m       192.168.170.125   ip-192-168-157-38.us-west-2.compute.internal
kube-proxy-zkd2n           1/1       Running   0          30m       192.168.157.38    ip-192-168-157-38.us-west-2.compute.internal
```

上記のように、kube-systemでIPアドレスを2つ使っています。

インスタンスがそもそも1つIPアドレスを使っているので、各ノードに対して自動的に3個のIPアドレスが消費されてしまうようです。

その結果、合計で4つまでしかIPアドレスを持てないt2.microは、Podを1つしか起動できなかったようです。

## t2.smallでも試してみる

t2.smallでも実験してみました。

t2.smallはIPアドレスを 2 × 4 = 8 個持てるはずです。
そのうち3個が消費されてしまうはずなので、5個だけPodが起動して、6個目は起動できないはずです。

実際にPodを6個起動しようとすると...

```console
$ kubectl scale deployment.apps/my-nginx --replicas=6
deployment.apps "my-nginx" scaled
$ kubectl get all
NAME                            READY     STATUS    RESTARTS   AGE
pod/my-nginx-6947c57698-2cq6s   1/1       Running   0          34s
pod/my-nginx-6947c57698-6kdmb   1/1       Running   0          34s
pod/my-nginx-6947c57698-6xnfc   0/1       Pending   0          34s
pod/my-nginx-6947c57698-gl29s   1/1       Running   0          34s
pod/my-nginx-6947c57698-jfk58   1/1       Running   0          34s
pod/my-nginx-6947c57698-z5fh4   1/1       Running   0          34s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   1h

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   6         6         6            5           50m

NAME                                  DESIRED   CURRENT   READY     AGE
replicaset.apps/my-nginx-6947c57698   6         6         5         50m
```

たしかに5つまでしか起動できませんでした。

# (2018/06/12) 追加調査

## t2.mediumでも試してみる

実は、ここまでの確認方法では、t2.micro、t2.smallのPod起動制限がCloudFormationテンプレートで指定したMaxPodsに依存している可能性を排除できていませんでした。
そこで、t2.mediumでも試してみました。

t2.mediumはMaxPodsが17なので、この設定の影響があるならNginxのPodは14個まで起動できるはずです。
ENIの面では、 3 × 6 = 18 なので、こちらの設定が影響するならNginxのPodは15個まで起動できるはずです。

では、Podを16個起動しようとしてみます。

```console
$ kubectl scale deployment.apps/my-nginx --replicas=16
deployment.apps "my-nginx" scaled
$ kubectl get all
NAME                            READY     STATUS    RESTARTS   AGE
pod/my-nginx-6947c57698-2dm68   1/1       Running   0          1m
pod/my-nginx-6947c57698-5zdkr   1/1       Running   0          1m
pod/my-nginx-6947c57698-6kx6c   1/1       Running   0          2m
pod/my-nginx-6947c57698-8mt2x   0/1       Pending   0          1m
pod/my-nginx-6947c57698-9lqmk   1/1       Running   0          1m
pod/my-nginx-6947c57698-dwpcz   1/1       Running   0          1m
pod/my-nginx-6947c57698-gxzft   1/1       Running   0          1m
pod/my-nginx-6947c57698-jf94r   1/1       Running   0          1m
pod/my-nginx-6947c57698-jnzd9   1/1       Running   0          1m
pod/my-nginx-6947c57698-k69fp   1/1       Running   0          1m
pod/my-nginx-6947c57698-nntk4   1/1       Running   0          1m
pod/my-nginx-6947c57698-rhdn6   1/1       Running   0          1m
pod/my-nginx-6947c57698-s47mq   1/1       Running   0          1m
pod/my-nginx-6947c57698-thc85   0/1       Pending   0          1m
pod/my-nginx-6947c57698-vkdpb   1/1       Running   0          1m
pod/my-nginx-6947c57698-vwvd6   1/1       Running   0          1m

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   14m

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   16        16        16           14          2m

NAME                                  DESIRED   CURRENT   READY     AGE
replicaset.apps/my-nginx-6947c57698   16        16        14        2m
```

念のため詳細を見ておくと...

```console
$ kubectl describe pod/my-nginx-6947c57698-8mt2x
Name:           my-nginx-6947c57698-8mt2x
Namespace:      default
Node:           <none>
Labels:         pod-template-hash=2503713254
                run=my-nginx
Annotations:    <none>
Status:         Pending
IP:
Controlled By:  ReplicaSet/my-nginx-6947c57698
Containers:
  my-nginx:
    Image:        nginx:alpine
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-t664j (ro)
Conditions:
  Type           Status
  PodScheduled   False
Volumes:
  default-token-t664j:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-t664j
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age               From               Message
  ----     ------            ----              ----               -------
  Warning  FailedScheduling  48s (x8 over 1m)  default-scheduler  0/1 nodes are available: 1 Insufficient pods.
```

ということで、Podは14個までしか起動できませんでした。
したがって、Podの起動数の制限はMaxPodsにも依存していたように思えます。

## MaxPodsを変更して試してみる

ENIによる制限とMaxPodsによる制限のうち小さいほうに依存している可能性があるので、さらに検証します。

t2.mediumのMaxPodsを19に書き換えます。

```yaml
      MaxPods: 737
    t2.micro:
      MaxPods: 4
    t2.small:
      MaxPods: 8
    t2.medium:
      # 17から19に書き換える
      MaxPods: 19
    t2.large:
      MaxPods: 35
    t2.xlarge:
      MaxPods: 44
    t2.2xlarge:
```

これでもMaxPodsが影響するなら 19 - 3 = 16 個のPodが起動するはずです。
ENIが影響するなら 18 - 3 = 15 個のPodが起動するはずです。

```console
$ kubectl get all
NAME                            READY     STATUS              RESTARTS   AGE
pod/my-nginx-6947c57698-2k9kz   1/1       Running             0          6m
pod/my-nginx-6947c57698-4q8j7   0/1       ContainerCreating   0          6m
pod/my-nginx-6947c57698-5n4hn   1/1       Running             0          6m
pod/my-nginx-6947c57698-7ntlc   1/1       Running             0          6m
pod/my-nginx-6947c57698-96p94   1/1       Running             0          6m
pod/my-nginx-6947c57698-bxqfz   0/1       Pending             0          6m
pod/my-nginx-6947c57698-d4rt8   1/1       Running             0          6m
pod/my-nginx-6947c57698-f2bv7   1/1       Running             0          6m
pod/my-nginx-6947c57698-g2krs   0/1       ContainerCreating   0          6m
pod/my-nginx-6947c57698-kw2dq   1/1       Running             0          6m
pod/my-nginx-6947c57698-kzc4k   1/1       Running             0          6m
pod/my-nginx-6947c57698-mrv8m   1/1       Running             0          6m
pod/my-nginx-6947c57698-sq9td   1/1       Running             0          6m
pod/my-nginx-6947c57698-t9kfq   1/1       Running             0          6m
pod/my-nginx-6947c57698-wnvjf   1/1       Running             0          6m
pod/my-nginx-6947c57698-x8lkg   1/1       Running             0          6m
pod/my-nginx-6947c57698-zbkt7   1/1       Running             0          6m

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   44m

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   17        17        17           14          31m

NAME                                  DESIRED   CURRENT   READY     AGE
replicaset.apps/my-nginx-6947c57698   17        17        14        31m
```

14個という、予想外の結果になりました。。。

PendingになっているPodの詳細は...

```console
$ kubectl describe pod/my-nginx-6947c57698-bxqfz
Name:           my-nginx-6947c57698-bxqfz
Namespace:      default
Node:           <none>
Labels:         pod-template-hash=2503713254
                run=my-nginx
Annotations:    <none>
Status:         Pending
IP:
Controlled By:  ReplicaSet/my-nginx-6947c57698
Containers:
  my-nginx:
    Image:        nginx:alpine
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-t664j (ro)
Conditions:
  Type           Status
  PodScheduled   False
Volumes:
  default-token-t664j:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-t664j
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age               From               Message
  ----     ------            ----              ----               -------
  Warning  FailedScheduling  2m (x26 over 8m)  default-scheduler  0/1 nodes are available: 1 Insufficient pods.
```

今まで表示されていたのと同様のエラーです。

ContainerCreatingになっているPodの詳細は...

```console
$ kubectl describe pod/my-nginx-6947c57698-4q8j7
Name:           my-nginx-6947c57698-4q8j7
Namespace:      default
Node:           ip-192-168-144-73.us-west-2.compute.internal/192.168.144.73
Start Time:     Tue, 12 Jun 2018 11:25:42 +0900
Labels:         pod-template-hash=2503713254
                run=my-nginx
Annotations:    <none>
Status:         Pending
IP:
Controlled By:  ReplicaSet/my-nginx-6947c57698
Containers:
  my-nginx:
    Container ID:
    Image:          nginx:alpine
    Image ID:
    Port:           <none>
    Host Port:      <none>
    State:          Waiting
      Reason:       ContainerCreating
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-t664j (ro)
Conditions:
  Type           Status
  Initialized    True
  Ready          False
  PodScheduled   True
Volumes:
  default-token-t664j:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-t664j
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason                  Age                From                                                   Message
  ----     ------                  ----               ----                                                   -------
  Normal   Scheduled               9m                 default-scheduler                                      Successfully assigned my-nginx-6947c57698-4q8j7 to ip-192-168-144-73.us-west-2.compute.internal
  Normal   SuccessfulMountVolume   9m                 kubelet, ip-192-168-144-73.us-west-2.compute.internal  MountVolume.SetUp succeeded for volume "default-token-t664j"
  Warning  FailedCreatePodSandBox  8m (x12 over 9m)   kubelet, ip-192-168-144-73.us-west-2.compute.internal  Failed create pod sandbox: rpc error: code = Unknown desc = NetworkPlugin cni failed to set up pod "my-nginx-6947c57698-4q8j7_default" network: add cmd: failed to assign an IP address to container
  Normal   SandboxChanged          4m (x271 over 9m)  kubelet, ip-192-168-144-73.us-west-2.compute.internal  Pod sandbox changed, it will be killed and re-created.
```

IPアドレスを与えることに失敗しています。

どうやら、MaxPodsとENIの制限と、他にも何らかの制限があるようです。。。
これ以上は未調査です。

# 結論 (再掲)
EKSのWorkerは、t2.microでも特に問題なく構築でき、Podの起動も可能でした。
ただし、IPアドレス数の制限により、Podは2つ以上起動できませんでした。
~~IPアドレスが枯渇するとWorkerもPodもスケールできなくなると思われるので、IPアドレス数の制限には注意が必要そうです。~~
インスタンスタイプごとに、IPアドレス数の制限と、MaxPodsによる制限と、他にも何らかの制限があり、Podの起動数が制限されているようです。
スケールを考えるうえで、Podの起動数の制限には注意が必要そうです。