最近のEKSを調査してみた


# はじめに

こんにちは。今年ももうすぐ終わりですね。
今年は色々ありましたが、技術的には K8s を学ぶことが多かったです。
実際に本番環境で運用しているわけではないので、来年以降運用などに関われたらなと思っています。

今回は最近の EKS を調査、使ってみた話を書いてみたいと思います。

私自身お家 K8s をゆるくやっているのですが、EKS に関しては去年少し触ったぐらいで、そこまでわかっていなかったですし、最近の EKS の Update も追えていませんでした。
そこで、最近の EKS の機能を調査し、使ってみた感想などを書いていきます。

ちなみに今回利用したサンプルコードは[こちら](https://github.com/u-kai/eks-auto-mode-sample)です。
環境独自の値を多少埋めてもらう必要はありますが、GitHub Actions などでデプロイ可能ですので、よろしければご利用ください!

## EKS Auto Mode について

まずは最近追加されたアツイ機能である EKS Auto Mode についてです。
EKS Auto Mode は 2024 年 12 月 1 日に発表された出来立てほやほやの機能です。

AWS の紹介[^1]では以下の機能が有効になるとされています。

> コンピューティングの自動スケーリングと管理
> アプリケーションの負荷分散管理
> ポッドとサービスのネットワーキングとネットワークポリシー
> クラスター DNS と GPU のサポート
> ブロックストレージボリュームのサポート

簡単にいうと、**K8s の運用に必要な機能は AWS が管理、提供してくれる**ということです笑

もし EKS Auto Mode を使わない場合は、AWS Load Balancer Controller や AWS VPC CNI(もしくは別の CNI 準拠のプラグイン) などなどクラスターの作成とは別に多くの設定が必要です。
また、EKS の設定によっては負荷に合わせたワーカーノードの追加/削除やパッチ当てなども自分で管理する必要があります。

EKS Fargate を利用するとワーカーノードの管理は楽になりますが、Fargate は K8s のコア機能である DaemonSet が使えないことや、Pod につき一つの Fargate インスタンスが割り当てられるため、Pod の収容率をあげてコスト貢献するのが難しいです。

EKS Auto Mode を利用すると上述の通り、K8s の運用に必要な機能がすでに提供されているため、設定がほとんど不要です。
また、EKS Auto Mode は EC2 を利用しているため、Fargate のような制約がなく、その上 EC2 の管理は AWS が行ってくれるため、OS の更新やセキュリティパッチの適用なども自動でやってくれるとのことです

これだけ見ると新規で EKS を始める場合は EKS Auto Mode ほぼ一択なのではないかと個人的には思いました。

作成も簡単で、Terraform の場合以下のようなコードで EKS Auto Mode を作成することができます。

```hcl
resource "aws_eks_cluster" "main" {
  name = var.cluster_name

  access_config {
    authentication_mode = "API"
  }

  // EKS Auto Modeの場合はfalseに設定すること(Terrafomのdefaultがtrueなので)
  bootstrap_self_managed_addons = false
  role_arn                      = aws_iam_role.cluster.arn
  version                       = var.eks_version

  compute_config {
    // このenabledと
    enabled       = true
    node_pools    = ["general-purpose"]
    node_role_arn = aws_iam_role.node.arn
  }

  kubernetes_network_config {
    elastic_load_balancing {
      // このenabledと
      enabled = true
    }
  }

  storage_config {
    block_storage {
      // このenabledがtrueであればEKS Auto Mode
      enabled = true
    }
  }

  vpc_config {
    endpoint_private_access = true
    endpoint_public_access  = true
    subnet_ids              = var.subnet_ids
  }

  depends_on = [
    aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy,
    aws_iam_role_policy_attachment.cluster_AmazonEKSComputePolicy,
    aws_iam_role_policy_attachment.cluster_AmazonEKSBlockStoragePolicy,
    aws_iam_role_policy_attachment.cluster_AmazonEKSLoadBalancingPolicy,
    aws_iam_role_policy_attachment.cluster_AmazonEKSNetworkingPolicy,
  ]

}
```

VPC や Subnet といったネットワークリソースは自分で作成する必要がありますが、それ以外はほぼほぼ AWS が管理してくれるというありがたい機能です。

次は EKS Auto Mode の`コンピューティングの自動スケーリングと管理`で利用されている Karpenter についてまとめています。

## Karpenter について

Karpenter は AWS 主体となって開発している OSS です。こちらは 2021 年ごろに発表[^2]されているため、最近の機能ではないかもですが、紹介させてください。。。

Karpenter の主な責務は前の項目でも述べたとおり、コンピューティングの自動スケーリングと管理です。
同じような機能として ClusterAutoScaler がありますが、ClusterAutoScaler は柔軟なワーカーノードの割り当てが難しいという問題があり、 Karpenter がその問題を解決しているとのことです。[^2]
ちなみに AWS 以外のクラウドプロバイダーでも利用できるとのことです。

Karpenter は NodePool と NodeClass という K8s のリソースを使ってワーカーノードの管理を行います。
NodePool にはネットワーク、ストレージ、タグ付などのインフラ寄りの設定を定義することができ、
NodeClass には インスタンスタイプやアーキテクチャ(amd64,arm64 など),CPU,メモリなどのリソースの設定を定義することができます。

Pod の設定で Affinity や NodeSelector などを使ってノード指定をすると、Karpenter は NodePool,NodeClass に設定された条件を元に**いい具合に**ノードを作成してくれます。
また、Node に Pod が一定時間存在しなくなると、**Node は自動的に削除されコスト効率も良くなる**とのことです。

以下は Private tag がついている Subnet に配置し、for-eks-worker-nodes という名前のセキュリティグループを適用する NodeClass の例です。

```yaml
apiVersion: eks.amazonaws.com/v1
kind: NodeClass
metadata:
  name: schedule-private
spec:
  role: eks-auto-node-example

  subnetSelectorTerms:
    - tags:
        Private: "1"

  securityGroupSelectorTerms:
    - tags:
        Name: "for-eks-worker-nodes"
  ...
```

以下は上記 NodeClass を利用し、独自ラベルである`deployNodeClass`が `default` であることを Pod が要求してきた時に起動する NodePool の例です。

```yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: schedule-private
spec:
  template:
    metadata:
      labels:
        provisioner: private
    spec:
      nodeClassRef:
        group: eks.amazonaws.com
        kind: NodeClass
        name: schedule-private

      requirements:
        - key: "deployNodeClass"
          operator: In
          values: ["default"]
    ...
```

ちなみに Karpenter は helm などを使い自分のクラスターにデプロイする必要がありますが、EKS Auto Mode の場合は**デフォルトで有効化されており、デプロイおよび管理を AWS が行ってくれるようです。**
そのため利用者は NodePool と NodeClass をマニフェストで定義して apply するだけで Karpenter の機能を利用することができます。

helm によるデプロイの場合は、helm に加え IAM Role などの設定が必要になり、ドキュメントを見た感じだいぶ複雑そうでした。
そのため、EKS Auto Mode を利用することで、Karpenter の機能を簡単に利用することができると感じました。

次の項目からは IAM 関連の機能についてまとめています。

## AWS IAM を用いた K8s へのアクセスについて

EKS では AWS の IAM プリンシパル(IAM User や IAM Role) を用いて EKS 上の K8s リソースへアクセスすることができます。
kubectl を使ったアクセスや、EKS のコンソール上から K8s のリソースを閲覧、操作する時に特に利用するのかなと思います。

旧来の EKS では aws-auth という ConfigMap に IAM プリンシパルと K8s の 権限を紐づけることでアクセス権限を付与していました。
aws-auth だと、IAM プリンシパルを追加したい場合都度 aws-auth を更新する必要があったり、aws-auth を誤って削除してしまうとクラスターにアクセスできなくなるなどの問題があったようです。

そこで去年の 12 月頃から AccessEntry と AccessPolicy という機能が EKS で提供されました。

こちらは EKS の API を利用して、IAM プリンシパルを K8s の権限に紐づけることができる機能です。
AccessEntry というリソースを EKS の API を利用して作成し、このリソースに IAM プリンシパルを紐づけます。
加えて AccessPolicy というリソースを作成し、このリソースにも IAM プリンシパルを紐づけます。
AccessPolicy は AWS が既に用意している `arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy` などのポリシーを選択し、付与することができます。

また、AccessEntry には K8s の Group や User を指定することができます。
この Group や User は K8s の RoleBinding を使って権限を紐づけることができるので、AccessPolicy なしでも、RoleBinding を行えば K8s へのアクセス権限を付与することができます。

この方法の良いところは aws-auth で起きうるような事故が起きにくいことです。
aws-auth の情報を消してしまうとクラスターにアクセスできなくなりますが、AccessEntry の場合は誤って削除してしまっても、EKS の API にアクセスできる IAM プリンシパルがあれば、再度 AccessEntry を作成し、アクセスすることができるようになります。

また、AccessEntry は EKS の API を利用して作成するため、EKS クラスターを作成する時と同じ IaC で管理することが容易です。(Terraform や CloudFormation など)
そもため個人的には EKS の関心毎と K8s の関心毎を分離して考えやすくなったのかなと思います。(IAM の設定は K8s ではなく EKS 側の設定に近いと考えています)

以下は `AWS リソースに対しては最小権限だが K8s に関しては Cluster Admin な IAM Role`を作成する例です。

```hcl
// AccessEntryの作成
resource "aws_eks_access_entry" "cluster_admin" {
  cluster_name  = aws_eks_cluster.main.name
  principal_arn = aws_iam_role.cluster_admin.arn
  type          = "STANDARD"
}

// AccessPolicyの作成
// ここではAWSが提供しているAmazonEKSClusterAdminPolicyをAccessEntryに付与
resource "aws_eks_access_policy_association" "cluster_admin" {
  cluster_name  = aws_eks_cluster.main.name
  policy_arn    = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
  principal_arn = aws_iam_role.cluster_admin.arn
  access_scope {
    type = "cluster"
  }
}

// IAM Roleの作成
data "aws_caller_identity" "current" {}
resource "aws_iam_role" "cluster_admin" {
  name = "eks-cluster-admin"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = ["sts:AssumeRole"]
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }
      },
    ]
  })
}
resource "aws_iam_policy" "cluster_admin" {
  name        = "eks-cluster-admin"
  description = "Full access to EKS cluster"
  policy      = data.aws_iam_policy_document.cluster_admin.json
}
// K8sへのアクセスができれば良いのでAWSへの権限は最小限に
data "aws_iam_policy_document" "cluster_admin" {
  statement {
    effect    = "Allow"
    actions   = ["sts:GetCallerIdentity"]
    resources = ["*"]
  }
}
```

現在 aws-auth を利用した設定は非推奨となっており、AccessEntry と AccessPolicy へ移行することが推奨されています。

## K8s から AWS のリソースにアクセス

最後に K8s から AWS のリソースにアクセスする方法についてまとめています。
このケースはよくある話だと思います。例えば、S3 など優秀な AWS サービスを Pod から利用したい場合などです。

K8s から AWS のリソースにアクセスするためにはいくつか方法があります。
前まで一般的だったのが IRSA (IAM Role for ServiceAccount) です。
これは EKS における ServiceAccount が AWS の IAM Role を一時的に引き受ける（AssumeRoleWithWebIdentity）仕組みです。
この方法では、EKS の OIDC プロバイダーを利用して ServiceAccount を認証し、EKS が OIDC トークンを発行します。このトークンを用いて AWS にリクエストを送信することで、ServiceAccount と IAM Role の紐付けを行うものです。

そして去年の 11 月ごろから EKS PodIdentity という方法も提供されています。
こちらは OIDC 認証を必要とせず、Pod に IAM Role を紐づけることができる方法です。

OIDC 認証を利用せずに済むと以下のような IRSA を利用するための設定が不要になります。設定が不要になることで運用面に加え、サービスクォータに引っかかる可能性も減ります。

- EKS の OIDC プロバイダー 有効化
- AssumeRoleWithWebIdentity を可能にするために IAM の Identity provider 設定を EKS クラスターごとに設定
- AssumeRole 可能な IAM Role の信頼関係には OIDC のプロバイダを追加する必要があるため、同じ権限でもクラスター毎に設定が必要

また、Terraform では以下のようなコードで EKS PodIdentity を利用することができ、K8s 側では ServiceAccount の作成と Pod への紐付けだけで済みます。

```hcl
resource "aws_eks_pod_identity_association" "test_pod_identity" {
  cluster_name    = var.cluster_name
  // このnamespaceで
  namespace       = "default"
  // このServiceAccountを利用するPodに
  service_account = "test-pod-identity"
  // このIAM Roleを紐づける
  role_arn        = aws_iam_role.test_pod_identity.arn
}

resource "aws_iam_role" "test_pod_identity" {
  name = "test-pod-identity"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          // 利用主体がpods.eks.amazonaws.comであることが特徴
          Service = "pods.eks.amazonaws.com"
        },
        Action = ["sts:AssumeRole", "sts:TagSession"]
      }
    ]
  })
}
// Podに必要な権限を付与
resource "aws_iam_role_policy_attachment" "test_pod_identity" {
  policy_arn = aws_iam_policy.test_pod_identity.arn
  role       = aws_iam_role.test_pod_identity.name
}
```

ただ、IRSA は様々な環境(EKS 以外でも)で利用可能ですが、EKS PodIdentity は EKS 環境にのみ利用できるため、IRSA にしか対応していない場合は IRSA を利用する必要があります。

ひとまずここで、EKS の最近の機能についての説明は以上にします。
次からは所感などを書いていきます。

## 様々な機能を振り返ってみて

### めちゃ便利になっている！

去年の終わり頃から最近の EKS Auto Mode にかけて、EKS はめちゃめちゃ便利になっているなと感じました。
特に EKS Auto Mode は K8s のややこしい設定の多くを AWS 側が管理してくれていることと、Fargate ではなく AWS マネジドな EC2 を利用可能になったことが大きいと思います。

また、IAM 周りも作成から管理まで簡単にできるようになったのはとても良いと思います。

### それでもやはり難しいところ

EKS は上で述べているように様々な機能があり、やりたいことの多くが提供されていますが、やはり難しい部分もあると感じました。

特に難しいと思ったのが、どこでエラーが発生したかわかりにくいという点です。
これは一部の K8s リソースを AWS のリソースで実現しているためかと個人的には思います。

私がハマったのは、

- Karpenter で Node がなかなか作成されなかった

  - IAM の権限不足でエラーが発生していた
  - spot インスタンスの ServiceLinkedRole がなかった
  - Karpenter に対して AccessEntry が設定されていなかった

- うまく動作する Pod と動作しない Pod があった

  - Node に紐づいている SecurityGroup が通信を許可していなかった

- Ingress,LoadBalancer が動作しているが、アクセスできなかった

  - LoadBalancer が Private Subnet に配置されていた
  - Public Subnet に配置させる方法を学ぶ必要があった

ネットワークに関するエラーは EKS に関係なく生じることかも知れませんが、つまづきがあったため書きました。

Karpenter のエラーに関しては最初は全く解決できなかったです。
なぜなら Karpenter を kubectl describe コマンドで確認しても、**なぜエラーが出ているのかのエラーメッセージが私にとって十分ではなかったから**です。

最終的に CloudTrail のイベントを見ることで、IAM の権限不足でエラーが発生していることがわかりました。
今も何かあれば、K8s の 情報とともに CloudTrail を見ることでエラーを解消しようとしていますが、もっと良い方法もありそうな気もします。

また、ハマったことではないのですが、K8s と AWS の権限が混在しているため、どちらの権限を足せば良いのかはまだ混乱することもあります。
何を操作するかを考えればわかるのですが、簡単ではないと感じています。

ただ、お家 K8s に比べると多くのことが提供されており、K8s のコア機能に集中できると感じています。
本番で利用するのであればやはり自前で K8s を構築するよりも EKS を利用した方が良いと今回の調査を通して感じました。
特に Auto Mode は個人的にほぼ一択だと思います。

## おわりに

EKS Auto Mode が出たことをきっかけに EKS をざっと学んでみましたが、アップデートが多くあり調査できてとてもよかったです。
まだ、オブザーバビリティ周りに関しては調査できていないこともあるので、今度はこの辺りも調査してみたいと思います。
また、Karpenter についてはハマったことも多かったので、コードレベルで挙動を追っていければと思います。
ここまで読んでいただきありがとうございました！

## おまけ

ちなみに今回 Terraform を利用してドキュメント通りに EKS Auto Mode を作成したところ、エラーが起きました。
そこで EKS Auto Mode のドキュメントに対して PR を送り、マージされました!
初の OSS への貢献で、とても嬉しく興奮しました！
今後もこのような OSS への貢献に挑戦していきたいなと思います。

[^1]: https://aws.amazon.com/jp/blogs/aws/streamline-kubernetes-cluster-management-with-new-amazon-eks-auto-mode/?sc_channel=el
[^2]: https://aws.amazon.com/jp/blogs/aws/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/