血と汗となみだを流す

個人の思ったこと、やったことの吐き出し口です。

Karpenter Controller の Pod が起動しない EC2MetadataRequestError

事象

Karpenter Controller Pod を Fargate で起動しようとしたら失敗。以下のエラーが出力された。

$ kubectl logs -n karpenter karpenter-db9c68466-8jzjj
panic: failed to get region from metadata server: EC2MetadataRequestError: failed to get EC2 instance identity document
caused by: RequestError: send request failed
caused by: Get "http://169.254.169.254/latest/dynamic/instance-identity/document": context deadline exceeded (Client.Timeout exceeded while awaiting headers)

goroutine 1 [running]:
github.com/samber/lo.must({0x296ac40, 0xc000942dc0}, {0xc000a1fbf8, 0x1, 0x1})
        github.com/samber/lo@v1.46.0/errors.go:51 +0x1bb
github.com/samber/lo.Must[...](...)
        github.com/samber/lo@v1.46.0/errors.go:65
github.com/aws/karpenter-provider-aws/pkg/operator.NewOperator({0x347c438, 0xc000706f00}, 0xc000653980)
        github.com/aws/karpenter-provider-aws/pkg/operator/operator.go:110 +0x213
main.main()
        github.com/aws/karpenter-provider-aws/cmd/controller/main.go:32 +0x2a

原因(と思われるところ)

アカウントレベルでIMDS(インスタンスメタデータサービス)を有効化していなかった。

対処方法

AWS コンソールの [EC2]-[ダッシュボード]-[アカウントの属性]-[データ保護とセキュリティ]-[IMDS デフォルト]-[管理]からインスタンスメタデータサービスを有効化した。
メタデータのバージョンは「V2のみ(トークンは必須)」

Pod を削除して再起動したところ、無事 Fargate 上で Pod が起動した。

$ kubectl get pod -n karpenter -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP                NODE                                      NOMINATED NODE   READINESS GATES
karpenter-db9c68466-h844g   1/1     Running   0          4m21s   192.168.106.138   fargate-ip-192-168-106-138.ec2.internal   <none>           <none>
karpenter-db9c68466-jr8kt   1/1     Running   0          4m22s   192.168.76.92     fargate-ip-192-168-76-92.ec2.internal     <none>           <none>

補足

インスタンスメタデータサービスの有効化はリージョン単位で実行する必要がある。

Karpenter Controller の Pod が起動しない

現象

  • Karpenter Controller Pod が CrashLoopBackOff のまま起動しない。

エラーログ

$ kubectl logs -n kube-system karpenter-c66f6f595-xqq8x
panic: AWS.SimpleQueueService.NonExistentQueue: The specified queue does not exist or you do not have access to it.

goroutine 1 [running]:
github.com/samber/lo.must({0x2a44ee0, 0xc0007044e0}, {0x0, 0x0, 0x0})
        github.com/samber/lo@v1.46.0/errors.go:53 +0x1df
github.com/samber/lo.Must[...](...)
        github.com/samber/lo@v1.46.0/errors.go:65
github.com/aws/karpenter-provider-aws/pkg/controllers.NewControllers({0x3476bd8, 0xc0005eb590}, {0x348c408, 0xc000220b60}, 0xc000845188, {0x347bf30, 0x4d41c80}, {0x3487fa0, 0xc000b6f440}, {0x3447100, ...}, ...)
        github.com/aws/karpenter-provider-aws/pkg/controllers/controllers.go:73 +0x82f
main.main()
        github.com/aws/karpenter-provider-aws/cmd/controller/main.go:54 +0x5bb

原因

  • helm でインストールする際に serviceAccount の紐づけが出来ていなかった。
echo Your Karpenter version is: $KARPENTER_VERSION
helm registry logout public.ecr.aws
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter --version "${KARPENTER_VERSION}" --namespace "${KARPENTER_NAMESPACE}" --create-namespace \
  --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=${KARPENTER_IAM_ROLE_ARN} \
  --set settings.clusterName=${CLUSTER_NAME} \
  --set settings.clusterEndpoint=${CLUSTER_ENDPOINT} \
  --set settings.interruptionQueue=${CLUSTER_NAME} \
  --set controller.resources.requests.cpu=1 \
  --set controller.resources.requests.memory=1Gi \
  --set controller.resources.limits.cpu=1 \
  --set controller.resources.limits.memory=1Gi \
  --wait

上記コマンドの環境変数部分 ${KARPENTER_IAM_ROLE_ARN} に値がセットされていなかった。

失敗/成功の比較

  • 起動失敗した Pod
$ kubectl describe pod -n kube-system karpenter-c66f6f595-xqq8x
〜略〜
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-v4f8t (ro)
〜略〜
  • 起動成功した Pod
$ kubectl describe pod -n kube-system karpenter-57b88c6f8f-cnslt
〜略〜
    Mounts:
      /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-fcr7h (ro)
〜略〜

helm インストール時にコマンドミスったせいで /var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token がマウントされていなかった。

Karpenter で起動した node がクラスターに登録されずに落ちる

現象

  • Karpenter で起動したクラスターが node として起動される前に落ちる。
  • AWS コンソールからは EC2 インスタンスは正常に起動しているが、kubectl get node しても表示されない。

エラーメッセージなど

  • NodeClaim 調査
$ kubectl describe nodeclaim default-cfxh5
〜略〜
    Last Transition Time:  2024-09-24T13:33:19Z
    Message:               Node not registered with cluster
    Reason:                NodeNotFound
    Status:                Unknown
    Type:                  Registered
〜略〜
  • kubelet のログ
$ sudo journalctl -u kubelet
〜略〜
Sep 24 11:59:54 ip-192-168-66-178.ec2.internal kubelet[2105]: E0924 11:59:54.270285    2105 kubelet_node_status.go:96] "Unable to register nodewith API server" err="Unauthorized" node="ip-192-168-66-178.ec2.internal"
〜略〜

原因

  • Karpenter で起動した node に付与する IAM ロールを aws-auth ConfigMap に追加するのを忘れてた

追加手順

eksctl create iamidentitymapping \
  --username system:node:{{EC2PrivateDNSName}} \
  --cluster "${CLUSTER_NAME}" \
  --arn "arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}" \
  --group system:bootstrappers \
  --group system:nodes

確認

$ kubectl describe cm -n kube-system aws-authName:         aws-auth
Namespace:    kube-system
Labels:       <none>
Annotations:  <none>

Data
====
mapRoles:
----
- groups:
  - system:bootstrappers
  - system:nodes
  rolearn: arn:aws:iam::[AWS_ACCOUNT_ID]:role/[karpenter controllerが使うIAMロール]
  username: system:node:{{EC2PrivateDNSName}}
- groups:
  - system:bootstrappers
  - system:nodes
  rolearn: arn:aws:iam::020243262758:role/KarpenterNodeRole-[CLUSTER_NAME]
  username: system:node:{{EC2PrivateDNSName}}

mapUsers:
----
[]


BinaryData
====

Events:  <none>

まとめ

kubelet が Unauthorized を出していた時点で認証周りだとは思っていたが、aws-auth にたどり着くまでに時間がかかってしまった。アクセスエントリに早く移行しよう。

docs.aws.amazon.com

Karpenter で作成される Node を削除するときの finalizer 処理

コードを追いながら「おそらくこういう処理だろう」という感じがわかったのでメモを残す。
正しいかどうかわからないので是非コメントいただければと思います。

ざっくりとした流れ

Node が削除されるときの流れは以下ドキュメントに記載があります。

karpenter.sh

1. Add the karpenter.sh/disruption=disrupting:NoSchedule taint to the node to prevent pods from scheduling to it.
2. Begin evicting the pods on the node with the Kubernetes Eviction API to respect PDBs, while ignoring all static pods, pods tolerating the karpenter.sh/disruption=disrupting:NoSchedule taint, and succeeded/failed pods. Wait for the node to be fully drained before proceeding to Step (3).
  - While waiting, if the underlying NodeClaim for the node no longer exists, remove the finalizer to allow the APIServer to delete the node, completing termination.
3. Terminate the NodeClaim in the Cloud Provider.
4. Remove the finalizer from the node to allow the APIServer to delete the node, completing termination.

翻訳

1. Pod がスケジューリングされないようにするため、ノードに karpenter.sh/disruption=disrupting:NoSchedule の taint を追加します。
2. Kubernetes Eviction API を使用してノード上の Pod の退避を開始し、PDB を尊重します。この際、すべての静的 Pod、karpenter.sh/disruption=disrupting:NoSchedule の taint を許容する Pod、および成功/失敗した Pod は無視します。ステップ(3)に進む前に、ノードが完全に drain されるのを待ちます。
3. 待機中に、ノードの基となる NodeClaim が存在しなくなった場合、API Server がノードを削除できるように finalizer を削除し、終了を完了します。
4. クラウドプロバイダーで NodeClaim を終了します。
5. AP IServer がノードを削除できるようにノードから finalizer を削除し、終了を完了します。

API Server 側

ユーザーまたはコントローラーがオブジェクトの削除をリクエストすると、API Server がそのリクエストを受け取ってオブジェクトの削除処理を始めます。

github.com

API Server は、Delete 処理内の BeforeDelete 処理で、オブジェクトのメタデータdeletionTimestamp フィールドを追加します。

github.com

Karpenter 側

Node リソースの Reconcile で deletionTimestamp フィールドが追加されたことを検知し、finalize 処理が始まります。

github.com

NodeClaim 側の finalize 処理はこちら

github.com

finalize 処理の中で、冒頭記載した処理を実行していきます。
リソースが削除可能になったら finalizer が削除され、API Server がリソースを削除します。

ちなみに、finalizer で指定される "karpenter.sh/termination" はここに定義され、

github.com

指定の finalizer が設定されているかの判定で使われている。

github.com

Karpenter で Pod Identity で割り当てた IAM ロールが使われずに Worker Node の IAM ロールが使われてしまった件

現象

Karpenter をインストールする際に Pod Identity を使うように設定したが、何故か Karpenter Controller が Worker Node の IAM ロールを使って、EC2NodeClass で Failed to resolve AMIs エラーが発生していた。

原因

  • terraform-aws-modules を使って Karpenter の設定をしていたが、namespace が未指定だったので Service Account などは namespace kube-system に作成されていた。
  • helm_release モジュールを使って Karpenter のインストールをしたが、そのときは namespace karpenter を指定していた。
  • Pod Identity に紐づく Service Account が見つからないため、Worker Node の IAM ロールが使われていた。

経緯

使った terraform module はこれです。

registry.terraform.io

terraform-aws-modules で Pod Identity を使うように指定した (が、namespace が未指定のままだった)。

module "karpenter" {
  source = "terraform-aws-modules/eks/aws//modules/karpenter"

  cluster_name = module.eks.cluster_name
 
  enable_pod_identity             = true
  create_pod_identity_association = true
  node_iam_role_additional_policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }
}

Karpenter のインストールを helm_release モジュールで実施してみる。

registry.terraform.io

resource "helm_release" "karpenter" {
  namespace        = "karpenter"
  create_namespace = true

  name                = "karpenter"
  repository          = "oci://public.ecr.aws/karpenter"
  repository_username = data.aws_ecrpublic_authorization_token.token.user_name
  repository_password = data.aws_ecrpublic_authorization_token.token.password
  chart               = "karpenter"
  version             = var.karpenter_version

  values = [
    <<-EOT
    serviceAccount:
      name: ${module.karpenter.service_account}
    settings:
      clusterName: ${module.eks.cluster_name}
      clusterEndpoint: ${module.eks.cluster_endpoint}
    EOT
  ]

  # https://github.com/hashicorp/terraform-provider-helm/issues/593 
  depends_on = [
    module.eks
  ]
}

Karpenter Controller で発生したエラー。

{
    "level": "ERROR",
    "time": "2024-07-15T14:51:23.650Z",
    "logger": "controller",
    "message": "Reconciler error",
    "commit": "490ef94",
    "controller": "nodeclass.status",
    "controllerGroup": "karpenter.k8s.aws",
    "controllerKind": "EC2NodeClass",
    "EC2NodeClass": {
        "name": "default"
    },
    "namespace": "",
    "name": "default",
    "reconcileID": "3d9e21ea-5b21-441f-886b-9aa94562b1c8",
    "error": "creating instance profile, getting instance profile \"karpenter_xxxxxxxxxx\", AccessDenied: User: arn:aws:sts::999999999999:assumed-role/default-eks-node-group-xxxxxxxxxxx/i-9999999999is not authorized to perform: iam:GetInstanceProfile on resource: instance profile karpenter_xxxxxxxxxx because no identity-based policy allows the iam:GetInstanceProfile action\n\tstatus code: 403, request id: 825b11e9-54b3-4631-88ff-885fb83357b8"
}

EC2NodeClass も見てみるとエラーになっている。

$ kubectl describe EC2NodeClass default
〜略〜
Message: Failed to resolve AMIs

ただし、EC2NodeClass には Karpenter Controller を動かすのに必要なロールが 付与されていた。

Role: Karpenter-multi-arch-inference-xxxxxxxxxx

AMI 系のエラーを調査すべく CloudTrail を見ると、ec2:DescribeImages で失敗しているログがでているが、ユーザーは何故か Worker Node の IAM ロールだった。

You are not authorized to perform this operation. 
User: arn:aws:sts::999999999999:assumed-role/default-eks-node-group-xxxxxxxxxxx/i-002aefdbea1442380 is not authorized to perform: ec2:DescribeImages because no identity-based policy allows the ec2:DescribeImages action"

Karpenter module の namespace を "karpenter" に合わせたところエラーは解消した。

module "karpenter" {
  source = "terraform-aws-modules/eks/aws//modules/karpenter"

  cluster_name = module.eks.cluster_name
  namespace = "karpenter"

  enable_pod_identity             = true
  create_pod_identity_association = true
  node_iam_role_additional_policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }
}

関連ドキュメント

おそらくこれか?Service Account が見つからず、Worker Node の IAM ロールが使われた。しかし Worker Node の IAM には必要な権限が付与されていなかった。

Pod がサービスアカウントに関連付けられた IAM ロールの AWS 認証情報を使用する場合、AWS CLI またはその Pod のコンテナ内の他の SDK は、そのロールによって提供される認証情報を使用します。
Amazon EKS ノード IAM ロール に提供された認証情報へのアクセスを制限しない場合、Pod は引き続きこれらの認証情報にアクセスできます。
詳細については、「ワーカーノードに割り当てられたインスタンスプロファイルへのアクセスを制限する」を参照してください。

docs.aws.amazon.com

Error: Kubernetes cluster unreachable: the server has asked for the client to provide credentials

terraform で EKS を作成し、helm_release で karpenter や metric_server をインストールしようとした時にタイトルのエラーが発生した。

Error: Kubernetes cluster unreachable: the server has asked for the client to provide credentials

バージョンは以下の通り

terraform {
  required_version = ">= 1.9.2"
  required_providers {
    aws        = "~> 5.58.0"
    kubernetes = "~> 2.31.0"
    helm       = "~> 2.14.0"
    kubectl = {
      source  = "gavinbunney/kubectl"
      version = ">= 1.14.0"
    }
  }
}

古い terraform のコードで書かれていたのを修正している時に発生した。 原因としては権限不足で、以下を追加したところエラーは解消した。

enable_cluster_creator_admin_permissions = true

パラメータの意味は以下の通り

Indicates whether or not to add the cluster creator (the identity used by Terraform) as an administrator via access entry

アクセスエントリを介してクラスター作成者(Terraformが使用するID)を管理者として追加するかどうかを示します

デフォルト値は false。これを true に設定したところ上手く行った。

[参考]

registry.terraform.io

プライバシーポリシー