EKSマネージドノードグループの起動に任意の処理を追加する

# まえがき

たとえばプライベート証明書を使ったコンテナリポジトリを使っている場合、ノードにプライベート証明書をインストールする必要があります。EKS最適化AMIを使ったマネージドノードグループでこれをするには起動テンプレートをカスタマイズすればいいです。Amazon Linux 2023のEKS最適化AMIを使ったマネージドノードの場合の例を記載します。

**参考**

- [Use private certificates to enable a container repository in Amazon EKS](https://aws.amazon.com/jp/blogs/containers/use-private-certificates-to-enable-a-container-repository-in-amazon-eks/)
- [起動テンプレートを使用してマネージドノードをカスタマイズする](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/launch-templates.html#launch-template-user-data)

# Terraformのサンプル

結論としては以下のようなコードでマネージドノードグループと起動テンプレートを作成すればいいです。Terraform内の`extra_scripts`に追加処理のシェルスクリプトを記述します。例だとS3バケットに配置したプラベート証明書をインストールするシェルスクリプトを書いています。

ちなみに、以下コードはVPCを[AWS VPC Terraform module](https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest)、EKSを[AWS EKS Terraform module](https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest)で作成して変数を参照しています。ノードグループのIAMロールにはS3へのアクセス権も付与してあります。

``` sample.tf
resource "aws_eks_node_group" "extra" {
  cluster_name    = module.eks.cluster_name
  node_group_name = "extra"
  node_role_arn   = module.eks.eks_managed_node_groups.default.iam_role_arn
  subnet_ids      = module.vpc.private_subnets

  scaling_config {
    desired_size = 1
    max_size     = 1
    min_size     = 1
  }

  launch_template {
    id      = aws_launch_template.extra.id
    version = "$Latest"
  }
}

# AL2023 EKS最適化AMIの最新IDを取得
data "aws_ssm_parameter" "eks_ami_image_id" {
  name = "/aws/service/eks/optimized-ami/${module.eks.cluster_version}/amazon-linux-2023/x86_64/standard/recommended/image_id"
}

# 変数にuserdataの内容を格納
locals {
  user_data = base64encode(templatefile("./al2023_user_data.tpl", {
    apiServerEndpoint    = module.eks.cluster_endpoint
    certificateAuthority = module.eks.cluster_certificate_authority_data
    cidr                 = module.eks.cluster_service_cidr
    name                 = module.eks.cluster_name
    extra_scripts        = "aws s3 --region リージョン cp s3://バケット名/証明書 /etc/pki/ca-trust/source/anchors/ && update-ca-trust extract"
  }))
}

resource "aws_launch_template" "extra" {
  name                   = "extra"
  image_id               = nonsensitive(data.aws_ssm_parameter.eks_ami_image_id.value)
  user_data              = local.user_data
  vpc_security_group_ids = [module.eks.node_security_group_id]

  monitoring {
    enabled = true
  }

  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"
    http_put_response_hop_limit = 2
    instance_metadata_tags      = "disabled"
  }
}
```

``` al2023_user_data.tpl
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    apiServerEndpoint: ${apiServerEndpoint}
    certificateAuthority: ${certificateAuthority}
    cidr: ${cidr}
    name: ${name}
--//
Content-Type: text/x-shellscript

#!/bin/bash
${extra_scripts}

--//--
```

# 解説

## EKSへの参加

2024/10現在、EKS最適化AMIのAmazonLinuxは`AL2`と`AL2023`の2つがあり、EKSへの参加方法も異なります。`AL2`は**bootstrap.sh**というシェルスクリプト使っています。このスクリプトを実行してEKSへの参加を行いますが、その際AWSの`DescribeCluster`APIを使用していました。これだと大規模な環境ではAPIの大量実行が発生する恐れもあったそうです。そこで`AL2023`では**nodeadm**を使うように変更されました。今まで`DescribeCluster`で取得していた情報をYAMLに記述することでAPIの大量実行を防ぐようになりました。**nodeadm**はServiceとして登録されておりOS起動時に自動実行され、起動時の設定はuserdataから読み込まれるようです。

**参考**

- [Amazon Linux 2 から Amazon Linux 2023 にアップグレードする](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/al2023.html)
- [awslabs/amazon-eks-ami: Packer configuration for building a custom EKS AMI (github.com)](https://github.com/awslabs/amazon-eks-ami)

## cloud-init(userdata)

Amazon Linuxのインスタンス初期設定はcloud-initを使用しており、ユーザーデータに書かれた処理が実行されます。ユーザーデータは以下のようなMIMEマルチパートドキュメントの形式で書かれています。`--//`で囲まれた部分が1つの塊で、Content-Typeでデータの型(type/subtype;parameter=value)を指定し、型に沿った内容(Content)を記述します。

```
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: type/subtype;parameter=value

Content

--//
Content-Type: type/subtype;parameter=value

Content
--//--
```

`AL2`のEKS最適化AMIの場合、**bootstrap.sh**の実行が記述されています。`AL2023`の場合は**nodeadm**の設定が記述されています。**nodeadm**の設定は`Content-Type:application/node.eks.aws`の型を使用します。この型はnodeadm設定用であるため、任意のコマンドを実行したい時は`Conntent-Type:text/x-shellscript`を追加すればいいです。たとえば以下のユーザーデータならnodeadmの設定をしつつファイル「hogehoge」を作成します。

```
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="//"

--//
Content-Type: application/node.eks.aws

---
apiVersion: node.eks.aws/v1alpha1
kind: NodeConfig
spec:
  cluster:
    apiServerEndpoint: APIサーバーのエンドポイント
    certificateAuthority: EKSクラスタの証明書
    cidr: サービスCIDR
    name: クラスタ名

--//
Content-Type: text/x-shellscript

#!/bin/bash
touch hogehoge

--//--
```

**参考**

- [起動テンプレートを使用してマネージドノードをカスタマイズする#Amazon EC2 ユーザーデータ](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/launch-templates.html#launch-template-user-data)

## ノードグループと起動テンプレートの設定

Terraformだと[aws_eks_node_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_node_group)と[aws_launch_template](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template)で似たような設定項目があります。例えばセキュリティグループは両方にパラメータが用意されています。起動テンプレートをカスタマイズする場合にどちらで設定すべきかはAWSのドキュメントに記載があります。セキュリティグループは起動テンプレートで設定します。

**参考**

- [起動テンプレートを使用してマネージドノードをカスタマイズする#起動テンプレート設定の基本](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/launch-templates.html#launch-template-user-data)

## 起動テンプレートのメタデータオプション

カスタムでないデフォルトのマネージドノードグループの場合、起動テンプレートはIMDSv2が必須になっており`HttpPutResponseHopLimit`も2に設定されています。カスタムした起動テンプレートを使う場合、明示的にこれらの設定をします。特に`HttpPutResponseHopLimit`を2にしないとPodがノードのIAMロールを継承できません。IRSAでPodにIAMを付与するか`HttpPutResponseHopLimit`を2に設定して回避します。サンプルのTerraformでは`HttpPutResponseHopLimit`を2に設定しています。

**参考**

- [Amazon Linux 2 から Amazon Linux 2023 にアップグレードする](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/al2023.html)