【AWS】Amazon EKS で Security Groups for Pods を導入して Pod 数の上限を検証してみた

## はじめに

Amazon EKS (Elastic Kubernetes Service) における Security Groups for Pods 機能は、Pod レベルでセキュリティグループを適用できる機能です。この機能を使うことで、Kubernetes の Pod に対して AWS のセキュリティグループを直接割り当て、きめ細かなネットワークセキュリティ制御が可能になります。

本記事では、EKS クラスターに Security Groups for Pods を導入し、インスタンスタイプごとに設定されている Pod 数の上限に達した場合の挙動を検証します。特に、Pod が Pending 状態になる条件と、その際のエラーメッセージを確認します。

### 対象読者
- Amazon EKS を運用している方
- Kubernetes の Pod セキュリティについて学びたい方
- EKS の Security Groups for Pods 機能に興味がある方
- インスタンスタイプごとの Pod 数制限について知りたい方

### 検証環境
- AWS リージョン: ap-northeast-1
- EKS バージョン: 1.30
- インスタンスタイプ: m6a.xlarge
- VPC CNI プラグインバージョン: v1.19.0-eksbuild.1

## Security Groups for Pods の概要

### 機能の説明

Security Groups for Pods は、Amazon EKS の機能で、Kubernetes の Pod に AWS セキュリティグループを直接適用できます。通常の Kubernetes 環境では Pod レベルでのセキュリティグループ適用はできませんが、EKS ではこの機能を使うことで、Pod ごとに異なるセキュリティポリシーを適用できます。

この機能は Amazon VPC CNI プラグインの拡張機能として提供されており、Pod ごとに Branch ENI (Elastic Network Interface) を作成することで実現しています。

### 仕組み

Security Groups for Pods を有効にすると、以下のような仕組みで動作します：

1. クラスターの各ノードに Trunk ENI が作成される
2. Pod が作成されると、その Pod 用に Branch ENI が作成される
3. Branch ENI にセキュリティグループが適用される
4. Pod のネットワークトラフィックは Branch ENI を通過する

[参考: Amazon EKS Pod のセキュリティグループ用に Amazon VPC CNI Plugin for Kubernetes を設定する - Amazon EKS](
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/security-groups-pods-deployment.html)
> トランクネットワークインターフェイスが作成されると、Pod にはトランクネットワークインターフェイスまたは標準ネットワークインターフェイスのセカンダリ IP アドレスが割り当てられます。ノードが削除されると、トランクインターフェイスは自動的に削除されます。
> 後のステップで Pod のセキュリティグループをデプロイすると、VPC リソースコントローラーはブランチネットワークインターフェイスと呼ばれる特別なインターフェイスを aws-k8s-branch-eni の説明と共に作成し、セキュリティグループを関連付けます。ノードにアタッチされた標準ネットワークインターフェイスとトランクネットワークインターフェイスに加えて、ブランチネットワークインターフェイスが作成されます。



### インスタンスタイプによる制限

重要なポイントとして、インスタンスタイプによって以下の制限があります：

- 対応するインスタンスタイプが限られている（Nitro ベースのインスタンスのみ）
- インスタンスタイプごとに作成できる Branch ENI（つまり SG for Pods を適用できる Pod）の数に上限がある

対応しているインスタンスタイプおよび Branch ENI の上限は [amazon-vpc-resource-controller-k8s の limits.go](
https://github.com/aws/amazon-vpc-resource-controller-k8s/blob/master/pkg/aws/vpc/limits.go) から確認できます。

[参考：Amazon EKS Pod のセキュリティグループポリシーを使用する - Amazon EKS](
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/sg-pods-example-deployment.html)
> Pod が Pending 状態でスタックした場合、ノードのインスタンスタイプが limits.go のリストに含まれていることを確認します。

今回検証に使用する m6a.xlarge の場合、以下の制限があります：
- 最大 18 個の Branch ENI（= 18 個の Pod に SG を適用可能）



## 前提条件

### 必要な権限とリソース

- AWS アカウントと管理者権限
- `eksctl` コマンドラインツール
- `kubectl` コマンドラインツール
- AWS CLI

### インスタンスタイプの選択

Security Groups for Pods は全てのインスタンスタイプでサポートされているわけではありません。特に t2/t3 などの旧世代インスタンスでは使用できません。サポートされているのは主に Nitro ベースのインスタンスです。

サポートされているインスタンスは [limits.go](
https://github.com/aws/amazon-vpc-resource-controller-k8s/blob/master/pkg/aws/vpc/limits.go) より確認できます。

limits.go より m6a.xlarge の記載を抜粋
```json
"m6a.xlarge": {
    Interface:               4,
    IPv4PerInterface:        15,
    IsTrunkingCompatible:    true,  // この値が true であることが重要
    BranchInterface:         18,    // 最大 18 個の Branch ENI が作成可能
    DefaultNetworkCardIndex: 0,
    NetworkCards: []NetworkCard{
        {
            MaximumNetworkInterfaces: 4,
            NetworkCardIndex:         0,
        },
    },
    Hypervisor:  "nitro",
    IsBareMetal: false,
},
```

## 実装手順

### STEP1: EKS クラスターの作成

まずは Security Groups for Pods をサポートする EKS クラスターを作成します。

1. クラスター設定ファイルを作成します：

```yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: sg-for-pods-cluster
  region: ap-northeast-1
  version: '1.30'
iam:
  withOIDC: true # required

cloudWatch:
  clusterLogging:
    enableTypes:
      - audit
      - api
      - authenticator
      - controllerManager
      - scheduler

managedNodeGroups:
  - name: managed-ng-1
    instanceType: m6a.xlarge  # Nitro ベースのインスタンスを選択
    minSize: 1
    maxSize: 2
    desiredCapacity: 1
    volumeSize: 20
```

2. クラスターを作成します：

```bash
eksctl create cluster -f clusterconfig.yaml
```

クラスターの作成には約 15〜20 分かかります。

### STEP2: Security Groups for Pods の有効化

[参考：Amazon EKS Pod のセキュリティグループ用に Amazon VPC CNI Plugin for Kubernetes を設定する - Amazon EKS](
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/security-groups-pods-deployment.html)


1. まず、現在の VPC CNI プラグインのバージョンを確認します：

```bash
kubectl describe daemonset aws-node --namespace kube-system | grep amazon-k8s-cni: | cut -d : -f 3
```

出力例：
```
v1.19.0-eksbuild.1
```

2. クラスターロールに必要な IAM ポリシーを追加します：

```bash
cluster_role=$(aws eks describe-cluster --name sg-for-pods-cluster --query cluster.roleArn --output text | cut -d / -f 2)
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceController --role-name $cluster_role
```

3. aws-node DaemonSet で ENABLE_POD_ENI 変数を true に設定します：

```bash
kubectl set env daemonset aws-node -n kube-system ENABLE_POD_ENI=true
```

4. 設定が反映されたことを確認します：

```bash
kubectl describe daemonset aws-node -n kube-system | grep ENABLE_POD_ENI
```

出力例：
```
ENABLE_POD_ENI: true
```

5. CNINode カスタムリソースが作成されたことを確認します：

```bash
kubectl get cninode -A
```

出力例：
```
NAME                                               FEATURES
ip-192-168-14-83.ap-northeast-1.compute.internal   [{"name":"SecurityGroupsForPods"}]
```

6. AWS コンソールで、Trunk ENI が作成されたことを確認します：

Description には「aws-k8s-trunk-eni」と表示されます。

### STEP3: セキュリティグループポリシーの作成と適用
[参考：Amazon EKS Pod のセキュリティグループポリシーを使用する - Amazon EKS](
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/sg-pods-example-deployment.html)

1. Pod 用のセキュリティグループを作成します：

AWS コンソールから、クラスターと同じ VPC 内にセキュリティグループを作成します。今回は検証のため、インバウンドおよびアウトバウンドですべてのトラフィック (0.0.0.0/0) を許可する設定にします。

2. Kubernetes 名前空間を作成します：

ここでは my-namespace としています。
```bash
kubectl create namespace my-namespace
```

3. SecurityGroupPolicy リソースを作成します：

```yaml
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: my-security-group-policy
  namespace: my-namespace
spec:
  podSelector:
    matchLabels:
      role: my-role
  securityGroups:
    groupIds:
      - sg-xxxxxxxx  # 作成したセキュリティグループの ID に置き換えてください
```

4. SecurityGroupPolicy をクラスターに適用します：

```bash
kubectl apply -f my-security-group-policy.yaml
```

### STEP4: サンプルアプリケーションのデプロイ

1. サンプルアプリケーションの YAML ファイルを作成します：

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
  namespace: my-namespace
  labels:
    app: my-app
spec:
  replicas: 4  # まずは少ない数の Pod から始める
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        role: my-role  # SecurityGroupPolicy の podSelector と一致させる
    spec:
      terminationGracePeriodSeconds: 120
      containers:
      - name: nginx
        image: public.ecr.aws/nginx/nginx:1.23
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-app
  namespace: my-namespace
  labels:
    app: my-app
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
```

2. アプリケーションをデプロイします：

```bash
kubectl apply -f sample-application.yaml
```

3. Pod の状態を確認します：

```bash
kubectl get pods -n my-namespace
```

出力例：
```
NAME                             READY   STATUS    RESTARTS   AGE
my-deployment-asdfqwer-29l8g   1/1     Running   0          56s
my-deployment-asdfqwer-9jnjn   1/1     Running   0          56s
my-deployment-asdfqwer-frsvs   1/1     Running   0          56s
my-deployment-asdfqwer-lqjdn   1/1     Running   0          56s
```

### STEP5: Pod の詳細情報の確認

1. Pod の詳細情報を確認します：

```bash
kubectl describe pods -n my-namespace my-deployment-asdfqwer-29l8g
```

注目すべき点：

- Annotations セクションに `vpc.amazonaws.com/pod-eni` 情報がある
- Events セクションに `SecurityGroupRequested` イベントがある

```
Annotations:      vpc.amazonaws.com/pod-eni:
                    [{"eniId":"eni-08ebf4f50eedbc88f","ifAddress":"0e:9b:d7:18:01:a3","privateIp":"192.168.31.236","ipv6Addr":"","vlanId":2,"subnetCidr":"192....
```

```
Events:
  Type     Reason                  Age   From                     Message
  ----     ------                  ----  ----                     -------
  Normal   Scheduled               109s  default-scheduler        Successfully assigned my-namespace/my-deployment-asdfqwer-29l8g to ip-192-168-14-83.ap-northeast-1.compute.internal
  Normal   SecurityGroupRequested  109s  vpc-resource-controller  Pod will get the following Security Groups [sg-xxxxxxxx]
  Warning  FailedCreatePodSandBox  109s  kubelet                  Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "d9b9b6ad0d2ebe550dd7a3bdefb20afcf37134a568da9443b90bc663087b095e": plugin type="aws-cni" name="aws-cni" failed (add): add cmd: failed to assign an IP address to container
  Normal   ResourceAllocated       108s  vpc-resource-controller  Allocated [{"eniId":"eni-08ebf4f50eedbc88f","ifAddress":"0e:9b:d7:18:01:a3","privateIp":"192.168.31.236","ipv6Addr":"","vlanId":2,"subnetCidr":"192.168.0.0/19","subnetV6Cidr":""}] to the pod
  Normal   Pulling                 108s  kubelet                  Pulling image "public.ecr.aws/nginx/nginx:1.23"
  Normal   Pulled                  104s  kubelet                  Successfully pulled image "public.ecr.aws/nginx/nginx:1.23" in 3.959s (3.959s including waiting). Image size: 57001878 bytes.
  Normal   Created                 104s  kubelet                  Created container nginx
  Normal   Started                 104s  kubelet                  Started container nginx
```

> **注意**: `FailedCreatePodSandBox` のエラーが表示されていますが、その後 `ResourceAllocated` イベントがあれば問題ありません。これは AWS の公式ドキュメントでも言及されている既知の挙動です。

[参考：Amazon EKS Pod のセキュリティグループポリシーを使用する - Amazon EKS](
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/sg-pods-example-deployment.html)
> kubectl describe pod my-deployment-xxxxxxxxxx-xxxxx -n my-namespace を実行したときに次のようなメッセージが表示されている場合は、無視しても問題ありません
> add cmd: failed to assign an IP address to container

2. AWS コンソールで、Branch ENI が作成されていることを確認します：

各 Pod に対して、「branch」タイプの ENI が作成されているはずです。

### STEP6: セキュリティグループの動作確認

1. Pod に接続して動作確認します：

```bash
kubectl exec -it -n my-namespace my-deployment-asdfqwer-29l8g -- /bin/bash
```

2. Pod 内から Service への接続を確認します：

```bash
curl my-app
```

出力例：
```html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```

3. セキュリティグループの効果を確認するため、アウトバウンドルールを削除します：

AWS コンソールから、先ほど作成したセキュリティグループのアウトバウンドルールをすべて削除します。

4. 再度 Pod 内から Service への接続を試みます：

```bash
curl my-app
```

出力例：
```
curl: (6) Could not resolve host: my-app
```

これは CoreDNS への接続ができなくなり、名前解決ができなくなったためです。セキュリティグループが正しく機能していることを示しています。

## STEP7: Pod 数の上限検証

m6a.xlarge インスタンスでは最大 18 個の Branch ENI を作成できるため、Security Groups for Pods を適用できる Pod 数の上限は 18 個です。この上限を超えた場合の挙動を検証します。

1. サンプルアプリケーションの replicas を 19 に変更します：

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
  namespace: my-namespace
  labels:
    app: my-app
spec:
  replicas: 19  # 上限の 18 を超える 19 に設定
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        role: my-role
    spec:
      terminationGracePeriodSeconds: 120
      containers:
      - name: nginx
        image: public.ecr.aws/nginx/nginx:1.23
        ports:
        - containerPort: 80
```

2. 変更を適用します：

```bash
kubectl apply -f sample-application.yaml
```

3. Pod の状態を確認します：

```bash
kubectl get pods -n my-namespace
```

出力例：
```
NAME                             READY   STATUS    RESTARTS   AGE
my-deployment-asdfqwer-44wgv   1/1     Running   0          14s
my-deployment-asdfqwer-5qzqq   1/1     Running   0          14s
my-deployment-asdfqwer-7whm2   1/1     Running   0          14s
my-deployment-asdfqwer-8zd2r   1/1     Running   0          14s
my-deployment-asdfqwer-bbgcx   1/1     Running   0          14s
my-deployment-asdfqwer-bppbq   1/1     Running   0          14s
my-deployment-asdfqwer-hj2sm   1/1     Running   0          14s
my-deployment-asdfqwer-hktvp   1/1     Running   0          14s
my-deployment-asdfqwer-jsf2l   1/1     Running   0          14s
my-deployment-asdfqwer-mmxb7   1/1     Running   0          14s
my-deployment-asdfqwer-n5tvp   1/1     Running   0          14s
my-deployment-asdfqwer-nlkq9   1/1     Running   0          14s
my-deployment-asdfqwer-nnzhn   1/1     Running   0          14s
my-deployment-asdfqwer-q7wld   1/1     Running   0          14s
my-deployment-asdfqwer-t4z52   1/1     Running   0          14s
my-deployment-asdfqwer-wjxch   1/1     Running   0          14s
my-deployment-asdfqwer-wtb85   1/1     Running   0          14s
my-deployment-asdfqwer-wxmfh   1/1     Running   0          14s
my-deployment-asdfqwer-zhld2   0/1     Pending   0          14s
```

予想通り、19 個目の Pod が Pending 状態になっています。これは m6a.xlarge インスタンスで作成できる Branch ENI の上限が 18 個であるためです。

4. Pending 状態の Pod の詳細を確認します：

```bash
kubectl describe pods my-deployment-asdfqwer-zhld2 -n my-namespace
```

出力例：
```
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  49s   default-scheduler  0/1 nodes are available: 1 Insufficient vpc.amazonaws.com/pod-eni. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.
```

エラーメッセージから、`vpc.amazonaws.com/pod-eni` リソース（Branch ENI）が不足していることが原因で Pod がスケジュールできないことがわかります。

## 検証結果と考察

### Security Groups for Pods の動作確認

1. **正常な動作**
   - Security Groups for Pods を有効化すると、Trunk ENI が各ノードに作成される
   - Pod が作成されると、その Pod 用に Branch ENI が作成される
   - Pod に指定したセキュリティグループが適用される
   - セキュリティグループのルールに従ってトラフィックが制御される

2. **Pod 数の上限**
   - m6a.xlarge インスタンスでは最大 18 個の Branch ENI を作成可能
   - 19 個目の Pod は Pending 状態になり、`Insufficient vpc.amazonaws.com/pod-eni` エラーが発生

### インスタンスタイプによる制限

各インスタンスタイプによって作成できる Branch ENI の数が異なります。一部の例を示します：

| インスタンスタイプ | 最大 Branch ENI 数 |
|-------------------|-------------------|
| m6a.xlarge        | 18                |
| m5.large          | 9                 |
| c5.xlarge         | 18                |
| r5.2xlarge        | 38                |

インスタンスタイプを選択する際は、必要な Pod 数に対して十分な Branch ENI をサポートしているかを確認することが重要です。

### 注意点

1. **既知の警告メッセージ**

   Pod の詳細情報に `FailedCreatePodSandBox` というエラーが表示されることがありますが、その後に `ResourceAllocated` イベントがあれば問題ありません。AWS の公式ドキュメントでも言及されている既知の挙動です。

2. **インスタンスタイプの互換性**

   すべてのインスタンスタイプが Security Groups for Pods をサポートしているわけではありません。特に T2/T3 などの旧世代インスタンスはサポートされていません。

3. **ENI 数の制限**

   Security Groups for Pods を使用すると、各 Pod に Branch ENI が割り当てられるため、インスタンスの ENI 制限に達する可能性があります。大規模なデプロイメントを計画する場合は、この制限を考慮する必要があります。

## 応用例と考察

### マルチノード環境での動作

今回は単一ノードでの検証でしたが、マルチノード環境では各ノードごとに Branch ENI の上限があります。例えば、m6a.xlarge インスタンスを 3 台使用する場合、最大 18 × 3 = 54 個の Pod に Security Groups を適用できます。

### 異なるセキュリティグループの適用

複数の SecurityGroupPolicy を作成し、異なるラベルを持つ Pod に異なるセキュリティグループを適用することも可能です。これにより、マイクロサービスアーキテクチャにおいて、サービスごとに異なるセキュリティポリシーを適用できます。

## まとめ

Amazon EKS の Security Groups for Pods 機能は、Kubernetes の Pod レベルでセキュリティグループを適用できる機能です。この機能を使用することで、以下のメリットがあります：

- Pod ごとに異なるセキュリティグループを適用できる
- AWS のセキュリティグループの細かい制御を Kubernetes の Pod に適用できる
- マイクロサービスアーキテクチャにおいて、サービスごとに異なるセキュリティポリシーを適用できる

一方で、以下の制限や注意点があります：

- インスタンスタイプによって作成できる Branch ENI の数に上限がある
- すべてのインスタンスタイプがサポートされているわけではない
- Pod 数が増えると、ENI の数も増加する

今回の検証では、m6a.xlarge インスタンスで最大 18 個の Pod に Security Groups を適用でき、19 個目の Pod は Pending 状態になることを確認しました。この結果は AWS のドキュメントに記載されている制限と一致しています。

Security Groups for Pods を導入する際は、インスタンスタイプごとの Branch ENI の上限を考慮し、必要な Pod 数に対して適切なインスタンスタイプを選択することが重要です。


この記事が Security Groups for Pods の理解と実装の一助となれば幸いです。実際の環境に導入する際は、アプリケーションの要件に合わせて適切に設計・実装してください。