血と汗となみだを流す

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

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

service "opentelemetry-operator-webhook-service" not found

EKS に AWS Distro for OpenTelemetry をインストールし、OpenTelemetryCollector リソースをデプロイしようとしたら以下のエラーが発生しました。(見やすいように改行してます)

Error from server (InternalError)
: error when creating "otel-collector-xray-prometheus-complete.yaml"
: Internal error occurred
: failed calling webhook "mopentelemetrycollector.kb.io"
: failed to call webhook
: Post "https://opentelemetry-operator-webhook-service.opentelemetry-operator-system.svc:443/mutate-opentelemetry-io-v1alpha1-opentelemetrycollector?timeout=10s"
: service "opentelemetry-operator-webhook-service" not found

mopentelemetrycollector の m って何?と思いながらも調査してたら以下の issue にたどり着きました。

github.com

自分のクラスター内の ValidatingWebhookConfiguration リソースと MutatingWebhookConfiguration を確認してみる。

$ kubectl get ValidatingWebhookConfiguration
NAME                                                      WEBHOOKS   AGE
aws-load-balancer-webhook                                 2          487d
cert-manager-webhook                                      1          479d
eks-aws-auth-configmap-validation-webhook                 1          318d
istio-validator-istio-system                              1          371d
istiod-default-validator                                  1          371d
opentelemetry-operator-validating-webhook-configuration   6          479d
opentelemetry-operator-validation                         4          40m
vpc-resource-validating-webhook                           2          490d
$ kubectl get MutatingWebhookConfiguration
NAME                                                    WEBHOOKS   AGE
0500-amazon-eks-fargate-mutation.amazonaws.com          2          490d
aws-load-balancer-webhook                               2          487d
cert-manager-webhook                                    1          479d
istio-revision-tag-default                              4          371d
istio-sidecar-injector                                  4          371d
opentelemetry-operator-mutating-webhook-configuration   4          479d
opentelemetry-operator-mutation                         3          6m30s
pod-identity-webhook                                    1          490d
vpc-resource-mutating-webhook                           1          490d

たしかにValidatingWebhookConfiguration リソースには

  • opentelemetry-operator-validating-webhook-configuration
  • opentelemetry-operator-validation

MutatingWebhookConfiguration リソースには

  • opentelemetry-operator-mutating-webhook-configuration
  • opentelemetry-operator-mutation

と複数のリソースがありました。

issue にあるように古い方のリソースを削除してみる。

$ kubectl delete ValidatingWebhookConfiguration opentelemetry-operator-validating-webhook-configuration 
$ kubectl delete MutatingWebhookConfiguration opentelemetry-operator-mutating-webhook-configuration

古いリソースを削除したところ、エラーは消えて OpenTelemetryCollector リソースをデプロイできました。

はるか昔、helm で OpenTeletery Operator をインストールしたり削除したりしていましたが、 ValidatingWebhookConfiguration リソースと MutatingWebhookConfiguration がうまく消えずに残ってしまっていたようでした。

Amazon S3 の PUT イベントを EventBridge -> Kinesis Data Firehose -> OpenSearch Service に入れてみる

Amazon S3 にオブジェクトを配置したときに発生する PUT イベントを OpenSearch Service に入れてみます。

Architecture

Amazon EventBridge の[ルール]-[イベントパターン]は以下です。

{
  "source": ["aws.s3"],
  "detail-type": ["Object Access Tier Changed", "Object ACL Updated", "Object Created", "Object Deleted", "Object Restore Completed", "Object Restore Expired", "Object Restore Initiated", "Object Storage Class Changed", "Object Tags Added", "Object Tags Deleted"],
  "detail": {
    "bucket": {
      "name": ["(バケット名)"]
    }
  }
}

ターゲットを Kinesis Data Firehose に指定します。
Kinesis Data Firehose のターゲットは OpenSearch Service を指定します。設定はすべてデフォルトです。
index 名は「kinesis」にしています。

S3 にオブジェクトを配置 (Object Created) したときに、OpenSearch Service に格納された PUT イベントデータは以下の形式でした。

{
        "_index" : "kinesis",
        "_id" : "49641493248204684799282029079614073971816170911334137858.0",
        "_score" : 1.0,
        "_source" : {
          "version" : "0",
          "id" : "062098df-c30a-c277-4d09-b16931dad59d",
          "detail-type" : "Object Created",
          "source" : "aws.s3",
          "account" : "999999999999",
          "time" : "2023-06-07T15:00:50Z",
          "region" : "us-east-1",
          "resources" : [
            "arn:aws:s3:::(バケット名)"
          ],
          "detail" : {
            "version" : "0",
            "bucket" : {
              "name" : "(バケット名)"
            },
            "object" : {
              "key" : "logs/yomple6.log",
              "size" : 202,
              "etag" : "3bde6fa2a35bca388769f10379523b26",
              "sequencer" : "0064809BA2121BBCA8"
            },
            "request-id" : "0FQ6PZCKNVCH78XD",
            "requester" : "999999999999",
            "source-ip-address" : "xxx.xxx.xxx.xxx",
            "reason" : "PutObject"
          }
        }
      }

ちなみに、PUT イベントから Lambda 関数をトリガーし出力した内容は以下でした。

{
  "Records": [{
        "eventVersion": "2.1",
        "eventSource": "aws:s3",
        "awsRegion": "us-east-1",
        "eventTime": "2023-06-07T02:15:25.358Z",
        "eventName": "ObjectCreated:Put",
        "userIdentity": {
            "principalId": "AWS:Axxxxxxxxxx"
        },
        "requestParameters": {
            "sourceIPAddress": "xxx.xxx.xxx.xxx"
        },
        "responseElements": {
            "x-amz-request-id": "ZN37J4JFE5YSXBKX",
            "x-amz-id-2": "QU+jgk1+c6TKKu9F3G2XDJL4VLhOgvV+Dg8ay7aMeEjKDN2lHfrR5TNeqDvqFgr/Mbm2AN8gEH4C89nPydRPSo2DR3qU0pZs"
        },
        "s3": {
            "s3SchemaVersion": "1.0",
            "configurationId": "a7c88a0e-e501-4910-b624-9312d2c6e5bf",
            "bucket": {
                "name": "(バケット名)",
                "ownerIdentity": {
                    "principalId": "A2YKLWBP9TAV5B"
                },
                "arn": "arn:aws:s3:::(バケット名)"
            },
            "object": {
                "key": "logs/sample.log",
                "size": 131,
                "eTag": "afa66f2c812defe614f69845dbea43a0",
                "sequencer": "00647FE83D4E98C147"
            }
        }
    }]
}

出力した Lambda (Python) の中身は以下です。

import json
from logging import getLogger, INFO

logger = getLogger(__name__)
logger.setLevel(INFO)

def lambda_handler(event, context):
    print("============ logger.info の出力 ============")
    logger.info(json.dumps(event))

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
        }),
    }

イベントデータの中身がどのようなデータ形式になるかの参考になればと思います。

Aurora MySQL のテーブルを Database Migration Service (DMS) → Kinesis Data Streams → Amazon Data Firehose (旧: Kinesis Data Firehose) → S3 でデータ出力してみた

以下の構成で Amazon Aurora MySQL 上のテーブルのデータを Amazon S3 にエクスポートします。

Architecture

Aurora MySQL 上のテーブルは以下のような形です。

use kajihirodb;
select * from dms_source_table;

id title keyword
1 風の谷のナウシカ ナウシカ
2 となりのトトロ トトロ
3 天空の城ラピュタ パズー
4 ハウルの歩く城 ハウル
5 千と千尋の神隠し 千尋

ここから DMS を利用してテーブルデータを取得し、Kinesis Data Streams に流します。
Kinesis Data Streams から Kinesis Data Firehose に送り、S3 に出力します。
Kinesis 関連の設定はすべてデフォルト値です。

以下、S3 に出力した結果です。

S3 Console

Kinesis Data Firehose で指定した S3 バケット上に yyyy / mm / dd で区切られ、その中にファイルが出力されています。
ファイルの中身は以下でした。

  • KDS-S3-KdgnY-1-2024-02-20-09-42-55-76b4ea85-b321-497a-b3b1-0e3d79b8d705
{
    "metadata": {
        "timestamp":    "2024-02-20T09:42:55.692040Z",
        "record-type":  "control",
        "operation":    "create-table",
        "partition-key-type":   "task-id",
        "schema-name":  "",
        "table-name":   "awsdms_apply_exceptions"
    }
}
  • KDS-S3-KdgnY-1-2024-02-20-09-42-56-7f7b4097-2208-4d6f-93f9-c8e5b8fc1b4a
{
    "metadata": {
        "timestamp":    "2024-02-20T09:42:56.340335Z",
        "record-type":  "control",
        "operation":    "drop-table",
        "partition-key-type":   "task-id",
        "schema-name":  "kajihirodb",
        "table-name":   "dms_source_table"
    }
}{
    "metadata": {
        "timestamp":    "2024-02-20T09:42:56.348950Z",
        "record-type":  "control",
        "operation":    "create-table",
        "partition-key-type":   "task-id",
        "schema-name":  "kajihirodb",
        "table-name":   "dms_source_table"
    }
}{
    "data": {
        "id":   1,
        "title":    "風の谷のナウシカ",
        "keyword":  "ナウシカ"
    },
    "metadata": {
        "timestamp":    "2024-02-20T09:42:56.360235Z",
        "record-type":  "data",
        "operation":    "load",
        "partition-key-type":   "primary-key",
        "schema-name":  "kajihirodb",
        "table-name":   "dms_source_table"
    }
}{
    "data": {
        "id":   2,
        "title":    "となりのトトロ",
        "keyword":  "トトロ"
    },
    "metadata": {
        "timestamp":    "2024-02-20T09:42:56.368645Z",
        "record-type":  "data",
        "operation":    "load",
        "partition-key-type":   "primary-key",
        "schema-name":  "kajihirodb",
        "table-name":   "dms_source_table"
    }
}{
    "data": {
        "id":   3,
        "title":    "天空の城ラピュタ",
        "keyword":  "パズー"
    },
    "metadata": {
        "timestamp":    "2024-02-20T09:42:56.376638Z",
        "record-type":  "data",
        "operation":    "load",
        "partition-key-type":   "primary-key",
        "schema-name":  "kajihirodb",
        "table-name":   "dms_source_table"
    }
}{
    "data": {
        "id":   5,
        "title":    "千と千尋の神隠し",
        "keyword":  "千尋"
    },
    "metadata": {
        "timestamp":    "2024-02-20T09:42:56.394266Z",
        "record-type":  "data",
        "operation":    "load",
        "partition-key-type":   "primary-key",
        "schema-name":  "kajihirodb",
        "table-name":   "dms_source_table"
    }
}
  • KDS-S3-KdgnY-1-2024-02-20-09-42-56-eb681b7d-0113-4e51-a1b7-b7670fdef71c
{
    "data": {
        "id":   4,
        "title":    "ハウルの歩く城",
        "keyword":  "ハウル"
    },
    "metadata": {
        "timestamp":    "2024-02-20T09:42:56.385582Z",
        "record-type":  "data",
        "operation":    "load",
        "partition-key-type":   "primary-key",
        "schema-name":  "kajihirodb",
        "table-name":   "dms_source_table"
    }
}

実際に S3 に出力した場合にどのようなデータ形式になるかの参考になればと思います。

プライバシーポリシー