EKS向けAWS CDK:現実世界のマルチアカウントKubernetesデプロイメントにおける不備

AWS Cloud Development Kit (CDK) は、使い慣れたプログラミング言語を使用してクラウドインフラストラクチャのプロビジョニングを簡素化することを目的としています。そのEKSモジュールはKubernetesクラスターの作成と管理を合理化することを約束していますが、詳しく見ると、特に実用的なマルチアカウントEKSデプロイメントを考慮する場合、重大な欠点が明らかになります。この記事では、これらの制限について詳しく説明し、AWS CDKの現在のEKS実装、特に Cluster.addManifest 関数が、共有マルチアカウントEKS戦略を採用する組織にとって真に役立たないと主張します。

単純さの幻想:Cluster.addManifestとそのアカウント境界

CDKの Cluster.addManifest(id: string, ...manifest: Record<string, any>[]): KubernetesManifest 関数は、KubernetesマニフェストをEKSクラスターにデプロイするための簡単な方法を提供しているように見えます。しかし、EKSクラスターが複数のAWSアカウント間で共有されるように設計されている現実世界のシナリオを考慮すると、この単純さは欺瞞的です。

実際には、中央のEKSクラスターは、別々のAWSアカウントに存在するさまざまなチームやアプリケーションによって共有されることがよくあります。このマルチアカウントアプローチは、セキュリティ、分離、およびコスト管理にとって不可欠です。しかし、Cluster.addManifest は、単一のアカウントとリージョンでのデプロイメントという暗黙の仮定の下で動作します。

この制限の証拠:

ネットワーク基盤の無視:適切な配管のない家

真に実用的なEKSソリューション、特にマルチアカウント設定では、堅牢なネットワーク基盤が不可欠です。これには通常、以下が含まれます。

しかし、aws-quickstart/cdk-eks-blueprintsのようなブループリントを含むAWS CDKのEKS実装は、この重要なネットワークレイヤーを見落としたり単純化したりすることがよくあります。これらのツールはEKSクラスターの作成やVPCプロビジョニングを自動化するかもしれませんが、EKSデプロイメントプロセスの不可欠な部分としてトランジットゲートウェイやVPC共有を設定するための包括的で自動化されたソリューションを提供するという点では、しばしば不十分です。

現実世界のEKSアーキテクチャでは、ネットワークレイヤーは後付けではありません。それは、安全でスケーラブルなマルチアカウントKubernetes環境を構築するための基盤です。CDKがネットワークの複雑さを抽象化しながらクラスター作成の簡素化に重点を置いているため、本番環境グレードの共有EKSデプロイメントには不向きなソリューションが生まれます。

トークン解決の失敗:損なわれたCDKの約束

CDKの強みは、トークンの使用にあります。トークンはデプロイメント中に解決されるプレースホルダーであり、動的な構成とリソース参照を可能にします。しかし、Cluster.addManifest はこれらのトークンを適切に解決できず、その実用性をさらに妨げています。

CDKトークンは、単一のCDKアプリケーションとCloudFormationスタックのスコープ内で解決されるように設計されています。Cluster.addManifest を使用してクラスターにデプロイされたマニフェスト内のリソースからトークンを使用しようとすると、トークン解決がしばしば失敗します。CDKのデフォルトのトークン解決メカニズムは、アカウント境界を越えるようには設計されていません。

この制限により、ユーザーはCDKのエレガントなトークンベースのアプローチを放棄し、VPC ID、サブネットID、セキュリティグループIDなどの具体的な値をコンテキストパラメータまたは環境変数としてCDKアプリケーションに手動で渡すという手段に頼らざるを得なくなります。この手動の値の受け渡しは、エレガントでないだけでなく、エラーの機会を増やし、そもそもCDKを使用する全体的な利点を減少させます。

解決策はネットワーキングから始まる:

前提条件:クラウドにおけるアプリケーション中心のインフラストラクチャの採用 1

この図では:

(元のテキストはここにはない図を参照しています)

AWSアカウントネットワーキングは、NT Enver LEとNT Enver Prodという2つの分離されたenverを実行しています。NT Enver Prodを例にとると:

  1. 複数のアカウント(同じリージョン)にまたがる複数のVPCを接続する1つのトランジットゲートウェイ。
  2. 接続されているすべてのVPCとインターネットを共有するための1つのNAT。
  3. IPの競合を避けるために、接続されているすべてのVPCのサブネット用の1つのIPAMとCIDRプール。
  4. 表示されていないもの:サブネット、ルーティング、SG、DNS、ホストゾーン、組織、管理者委任、証明書など...

VPCをまたいでリソースをデプロイするためのプロキシとしての中央VPC:

  1. 異なるenverから異なるEKSクラスターにk8sマニフェストをデプロイするためのLambdaを実行します。
  2. 異なるenverから異なるRDSクラスターにDB/スキーマ/ロール/ユーザーをデプロイするためのLambdaを実行します。

VPCをまたいで接続するためのプロキシ/ハブとしての中央VPC:EKS内のポッド、ECS内のタスクが異なるenverから異なるRDSクラスター内の異なるデータベースに接続します:

Enver 1:

k8sマニフェストやデータベース関連リソース(DB、スキーマ、ロールなど)を含むすべてのリソース(緑色でマーク)を論理的に内部で宣言/制御し、トランザクションでデプロイまたはロールバックします。

  1. マニフェストはEKS Enver1のクラスターにデプロイされ、ポッドはIamロール1(SA/oidc経由)を引き受けてDynamoDBにアクセスします。
  2. データベース、スキーマ、ロール/ユーザーはRDS Enver Prodにデプロイされ、内部のECSタスクはiamロール2を引き受けてTGW経由でホストされているDBにアクセスします。

Enver 2:

すべてのリソース(紫色でマーク)も論理的に内部で宣言/制御し、マニフェストがEKS Enver1のクラスターにデプロイされた後、ポッドは次のようになります。

  1. IAMロールAを引き受けてトランジットゲートウェイ経由でDBにアクセスします(Enver 2ではVPCは不要です!)。
  2. IAMロールBを引き受けてファイル用のS3バケットにアクセスします。

プラットフォームがデプロイメントを処理するため、アプリとサービスはビジネスロジック/機能に集中できます:

  1. Enver 1とEnver 2で宣言されたk8sマニフェストは、中央アカウントのVPC-Prod内のLambda関数を介してEKSクラスターに送信されます。
  2. Enver 1とEnver 2で宣言されたDBスキーマ/ロール/ユーザーは、中央アカウントのVPC-Prod内のLambda関数を介してRDSクラスターに送信されます。

Enver中心設計の実用例

https://github.com/ondemandenv/spring-boot-swagger-3-example

このプロジェクトは、クラウドネイティブ開発に対するアプリケーション中心のアプローチを例示しており、すべてのリソース(アプリケーションコード、インフラストラクチャ、依存関係)が単一の垂直な「enver」として定義されています。これは、ユニットとしてデプロイ/ロールバックされる自己完結型の境界付けられたコンテキストです。

1. 垂直リソース所有権

このチュートリアルAPI機能を提供するために必要なすべてのリソースは同じ場所に配置されています。

2. バージョン管理されたユニットとしての環境

各「enver」には以下が含まれます。

3. プラットフォームサービス抽象化

(元のテキストにはここにさらに多くのコンテンツが含まれている可能性があります)

重要な統合ポイント

1. IAMロールバインディング(CDKスタック)

// cdk/lib/cdk-stack.ts
const podSaRole = new Role(this, 'podSaRole', {
    assumedBy: new FederatedPrincipal(
        myEnver.oidcProvider.getSharedValue(this), // From platform enver
        {
            StringEquals: {
                [`${oidcProvider}:aud`]: 'sts.amazonaws.com',
                [`${oidcProvider}:sub`]: `system:serviceaccount:${namespace}:${serviceAccountName}`
            }
        },
        'sts:AssumeRoleWithWebIdentity'
    )
});

2. 環境対応構成

// src/main/java/com/bezkoder/spring/swagger/config/OpenAPIConfig.java
@Value("${aws.s3.bucket-name}")
private String bucketName; // Injected from enver-specific config

@Bean
public S3Client s3Client() {
    return S3Client.builder()
        .credentialsProvider(WebIdentityTokenFileCredentialsProvider.create())
        .build(); // Auto-utilizes IRSA credentials
}

3. インフラストラクチャの一貫性

// cdk/lib/cdk-stack.ts
new cdk8splus.Deployment(chart, 'to-eks', {
    containers: [{
        image: ContainerImage.fromEcrRepository(
            Repository.fromRepositoryName(this, 'repo',
                myEnver.appImgRepoRef.getSharedValue(this)), // Shared ECR
            Fn.select(0, Fn.split(',', // Git SHA-based tagging
                myEnver.appImgLatestRef.getSharedValue(this)))
        ),
        envVariables: {
            bucket_arn: {value: bucket.bucketArn}, // Enver-owned bucket
            region: {value: this.region} // Inherited from platform
        }
    }]
});

主な利点

明らかに、設計と実装全体がプラットフォームに依存します。