AWS: CDK – створення EKS з Python та загальні враження від CDK

Автор |  23/06/2023
 

Terraform то чудово, але поки що вирішили перші кластера AWS EKS створювати за допомогою AWS CDK, бо по-преше – він вже є на проекті, по-друге – самому цікаво спробувати новий інструмент.

Тож сьогодні розглянемо що з цього вийшло, та як створювався кластер і необхідні ресурси.

Про перше знайомство з СDK писав тут – AWS: CDK – знайомство та приклади на Python.

Забігаючи наперед – особисто я, дуже м’яко кажучи, не в захваті від CDK:

  • ніякого тобі KISSKeep It Simple Stupid, ніякого тобі “явне краще неявного”
  • місцями незрозуміла документація з приклами на TypeScript навіть в репозиторії PyPi
  • купа окремих бібліотек та модулів, іноді геморой з їхніми імпортами
  • загальна перенавантаженість коду CDK/Python – Terraform з його HCL або Pulumi з Python виглядає набагато простішим для розуміння загальної картини інфрастуктури, котора цим кодом описана
  • перенавантаженість самого CloudFormation стеку, створенного за допомогою CDK – купа IAM-ролей, якісь Lambda-функції і таке інше – коли воно зламається, то доведеться дуже довго шукати де і що саме “пішло не так”
  • питати Google на тему “AWS CDK Python create something” – майже марна справа, бо результатів або не буде взагалі, але будуть на TypeScript

Хоча пост планувався в стилі “як зробити”, але в результаті вийшло “Як вистрілити собі в ногу, запроваджуючи на проекті AWS CDK”.

AWS CDK vs Terraform

Знову-таки, хоча сам пост не про це, але кілька слів після роботи з CDK та його порівняння з Terraform.

Time To Create: AWS CDK vs Terraform

Перше, що хочеться прям на початку показати – це швидкість роботи AWS CDK vs Terraform:

Тест, звісно, достаточно штучний, але дуже гарно показав різницю в роботі.

Я спецільно не створював NAT Gateways, бо їхнє створення займає більше хвилини просто на запуск самих NAT-інстансів, тоді як на створення VPC/Subnets/etc час не витрачається, тож бачимо саме швидкість роботи CDK/CloudFormation versus Terraform.

Пізніше ще заміряв створення VPC+EKS з CDK та Terraform:

CDK

  • create: 18m54.643s
  • destroy: 26m4.509s

Terraform:

  • create: 12m56.801s
  • destroy: 5m32.329s

AWS CDK workflow

Та й в цілому процес роботи CDK виглядає занадто ускладненим:

  • пишемо код на Python
  • який траснлюється до бекенду CDK на NodeJS
  • генерує CloudFormation Template та ChangeSets
  • CDK для своєї роботи створює пачку Lambda-функцій
  • і тільки потім створюються ресурси

Плюс в самому CloudFormation стеку для EKS створюється ціла купа AIM ролей та Lambda-функцій з неясним та неявним призначенням.

AWS CDK та нові “фічі” AWS

Ще з насправді досить очікуваного – CDK не має всіх нових “плюшок” AWS. Я с цим зіткнувся ще кілька років тому, коли потрібно було у CloudFormation створити cross-region VPC Peering, а CloudFormation це не підтримував, хоча у Terraform це вже було реалізовано.

Аналогічно виявилось і зараз: остання версія CDK (2.84.0) не має підтримки EKS 1.27, реліз якої відбувся майже місць тому, 24-го травня – Amazon EKS now supports Kubernetes version 1.27.  А ось Terraform її вже підтримує – AWS EKS Terraform module.

Але таке. Окей, най буде, як то кажуть.

Давайте пробувати.

Початок роботи: спитаємо ChatGPT

Щоб мати якусь точку для старту – спитав ChatGPT. В цілому, ідею від подав, хоча з застарілими імпортами та деякими атрибутами, які довелось переписувати:

Поїхали.

Python virtualevn

Створюємо Python virtualevn:

[simterm]

$ python -m venv .venv
$ ls -l .venv/
total 16
drwxr-xr-x 2 setevoy setevoy 4096 Jun 20 11:18 bin
drwxr-xr-x 3 setevoy setevoy 4096 Jun 20 11:18 include
drwxr-xr-x 3 setevoy setevoy 4096 Jun 20 11:18 lib
lrwxrwxrwx 1 setevoy setevoy    3 Jun 20 11:18 lib64 -> lib
-rw-r--r-- 1 setevoy setevoy  176 Jun 20 11:18 pyvenv.cfg

[/simterm]

Активуємо його:

[simterm]

$ . .venv/bin/activate
(.venv)

[/simterm]

Тепер можемо створювати новий application.

AWS CDK Init

Створюємо шаблон нашого стеку з Python:

[simterm]

$ cdk init app --language python
...
✅ All done!

[/simterm]

Отримуємо таку структуру файлів та каталогів:

[simterm]

$ tree .
.
├── README.md
├── app.py
├── atlas_eks
│   ├── __init__.py
│   └── atlas_eks_stack.py
├── cdk.json
├── requirements-dev.txt
├── requirements.txt
├── source.bat
└── tests
    ├── __init__.py
    └── unit
        ├── __init__.py
        └── test_atlas_eks_stack.py

4 directories, 11 files

[/simterm]

Встановлюємо залежності:

[simterm]

$ pip install -r requirements.txt
Collecting aws-cdk-lib==2.83.1
  Using cached aws_cdk_lib-2.83.1-py3-none-any.whl (41.5 MB)
...

[/simterm]

Перевіряємо, що все працює:

[simterm]

$ cdk list
AtlasEksStack

[/simterm]

Зміст app.py зараз маємо такий:

Та у atlas_eks/atlas_eks_stack.py маємо шаблон для створення стеку:

from aws_cdk import (
    # Duration,
    Stack,
    # aws_sqs as sqs,
)
from constructs import Construct

class AtlasEksStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here

        # example resource
        # queue = sqs.Queue(
        #     self, "AtlasEksQueue",
        #     visibility_timeout=Duration.seconds(300),
        # )

Додамо змінні оточення до app.py – аккаунт та регіон, та оновлюємо виклик AtlasEksStack():

...
AWS_ACCOUNT = os.environ["AWS_ACCOUNT"]
AWS_REGION = os.environ["AWS_REGION"]

app = cdk.App()
AtlasEksStack(app, "AtlasEksStack",
        env=cdk.Environment(account=AWS_ACCOUNT, region=AWS_REGION),
    )
...

Задаємо змінні в консолі:

[simterm]

$ export AWS_ACCOUNT=492***148 
$ export AWS_REGION=us-east-1

[/simterm]

Перевіряємо ще раз з cdk list.

Створення EKS кластеру

Повертаємось до ChatGPT, що він там далі рекомендує:

Нам тут цікаві тільки імпорти (з якими він не вгадав), та сам ресурс cluster = eks.Cluster(), якому він пропонує версію 1.21, бо сам ChatGPT, як ми знаємо, має базу до 2021 року.

CDK: AttributeError: type object ‘KubernetesVersion’ has no attribute ‘V1_27’

Щодо AWS CDK та версії EKS, писав про це на початку – виглядала помилка так:

AttributeError: type object ‘KubernetesVersion’ has no attribute ‘V1_27’

Окей – давайте поки 1.26, там подивимось, як з цим жити.

Обновлюємо файл atlas_eks_stack.py, використовуємо eks.KubernetesVersion.V1_26:

from aws_cdk import (
    aws_eks as eks,
    Stack
)
from constructs import Construct

class AtlasEksStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        cluster = eks.Cluster(
            self, 'EKS-Cluster',
            cluster_name='my-eks-cluster',
            version=eks.KubernetesVersion.V1_26,
        )

Перевіряємо з cdk synth:

[simterm]

$ cdk synth
[Warning at /AtlasEksStack/EKS-Cluster] You created a cluster with Kubernetes Version 1.26 without specifying the kubectlLayer property. This may cause failures as the kubectl version provided with aws-cdk-lib is 1.20, which is only guaranteed to be compatible with Kubernetes versions 1.19-1.21. Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v26.
Resources:
  EKSClusterDefaultVpc01B29049:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: AtlasEksStack/EKS-Cluster/DefaultVpc
    Metadata:
      aws:cdk:path: AtlasEksStack/EKS-Cluster/DefaultVpc/Resource
...

[/simterm]

CDK сам тсворить VPC та subnets і все інше для мережі, та IAM ролі. Це, в принципі, зручно, хоча там є свої питання.

Ми далі будемо створювати власну VPC.

Warning: You created a cluster with Kubernetes Version 1.26 without specifying the kubectlLayer property

На початку cdk synth каже щось про kubectlLayer:

[Warning at /AtlasEksStack/EKS-Cluster] You created a cluster with Kubernetes Version 1.26 without specifying the kubectlLayer property. This may cause failures as the kubectl version provided with aws-cdk-lib is 1.20, which is only guaranteed to be compatible with Kubernetes versions 1.19-1.21. Please provide a kubectlLayer from @aws-cdk/lambda-layer-kubectl-v26.

З імені можно припустити, що CDK створить Lambda-функцію, в якій буде викликати kubectl для виконання якихось задач в самоу Kubernetes.

В документації KubectlLayer сказано, що “An AWS Lambda layer that includes kubectl and helm.

Дуже дякую – все відразу стало зрозуміло. Де воно використовується, для чого?

Ну, ок… Давайте спробуємо позбутися цього варнінгу.

Знову спитаємо ChatGP:

Пробуємо встановити aws-lambda-layer-kubectl-v26:

[simterm]

$ pip install aws-cdk.aws-lambda-layer-kubectl-v26
ERROR: Could not find a version that satisfies the requirement aws-cdk.aws-lambda-layer-kubectl-v26 (from versions: none)
ERROR: No matching distribution found for aws-cdk.aws-lambda-layer-kubectl-v26

[/simterm]

Da f*****ck!

Ну, добре… Пам’ятаємо, що ChatGP “старенький” – може, ліба якось інакше називається?

PyPI no longer supports ‘pip search’

Пробуємо pip search – спочатку перевіримо, що search в PiP взагалі є, бо давно ним не користувався:

[simterm]

$ pip search --help

Usage:   
  pip search [options] <query>

Description:
  Search for PyPI packages whose name or summary contains <query>.

Search Options:
  -i, --index <url>           Base URL of Python Package Index (default https://pypi.org/pypi)
...

[/simterm]

Окей – шукаємо:

[simterm]

$ pip search aws-lambda-layer-kubectl
ERROR: XMLRPC request failed [code: -32500]
RuntimeError: PyPI no longer supports 'pip search' (or XML-RPC search). Please use https://pypi.org/search (via a browser) instead. See https://warehouse.pypa.io/api-reference/xml-rpc.html#deprecated-methods for more information.

[/simterm]

WHAAAAT?!?

Тобто, просто з консолі з PiP знайти пакет неможливо? Це як так? Трохи “розрив шаблону”.

Окей – поки лишимо, як є, хоча далі з цим знову зустрінемось, і таки доведеться фіксити.

Змінні в CDK Stack

Що тепер хочеться, це додати змінну для Environment – Dev/Stage/Prod, і потім використовати її в іменах ресурсів та тегах.

Додамо до app.py змінну $EKS_STAGE, а до створення AtlasEksStack() – передаємо її другим агрументом, щоб використати як ім’я стеку, і додаємо параметр stage, що потім використовувати всередені класу:

...
EKS_STAGE = os.environ["EKS_ENV"]


app = cdk.App()
AtlasEksStack(app, "f'eks-{EKS_STAGE}-1-26'",
        env=cdk.Environment(account=AWS_ACCOUNT, region=AWS_REGION),
        stage=EKS_STAGE
    )
...

Далі у файлі atlas_eks_stack.py описуємо параметр stage: str, і використовуємо його при створенні eks.Cluster() у параметрі cluster_name:

...
    def __init__(self, scope: Construct, construct_id: str, stage: str, **kwargs)
    ...
        cluster = eks.Cluster(
            self, 'EKS-Cluster',
            cluster_name=f'eks-{stage}-1-26-cluster',
            version=eks.KubernetesVersion.V1_26,
        )

Задаємо змінну оточення в терміналі:

[simterm]

$ export EKS_ENV=dev

[/simterm]

З cdk list перевіримо, що ім’я стеку змінилось і має $EKS_ENV:

[simterm]

$ cdk list
eks-dev-1-26

[/simterm]

Та з sdk synth перевіримо, що ім’я кластеру теж змінилось:

[simterm]

$ cdk synth
...
    Type: Custom::AWSCDK-EKS-Cluster
    Properties:
      ServiceToken:
        Fn::GetAtt:
          - awscdkawseksClusterResourceProviderNestedStackawscdkawseksClusterResourceProviderNestedStackResource9827C454
          - Outputs.AtlasEksStackawscdkawseksClusterResourceProviderframeworkonEvent588F9666Arn
      Config:
        name: eks-dev-1-26-cluster
...

[/simterm]

Добре – кластер є, тепер створимо для нього VPC.

Створення VPC та Subnets

Кастомну VPC хочеться, бо по-дефолту CDK створить по Subnet-у у кожній AvailabilityZone, тобто три мережі, плюс до кожної буде свій NAT Gateway. Але по-перше – мені більше подобається самому контролювати розбивку мережі, по-друге – кожен NAT Gateway коштує грошей, а нам поки що fault-tolerance аж на три AvailabilityZone не потрібен, краще зекономити трохи грошей.

Документація по CDK VPC – aws_cdk.aws_ec2.Vpc.

Типи Subnet тут – SubnetType.

Тут як на мене ще один не найкращий нюанс CDK: так, це зручно, що він має багато викорорівневих ресурсів, коли тобі достатньо просто вказати subnet_type=ec2.SubnetType.PUBLIC, а CDK сам створить все необхідне, але особисто мені декларативний підхід Terraform та його HCL виглядає привабливішим, бо навіть якщо використовувати модуль VPC, а не описувати все вручну – набагато простіше зайти в код того модулю і подивитись, що він має “під капотом”, ніж копатись у бібліотеці CDK. Але це чисто особисте “Я так бачу“.

Крім того, в документації не сказано, що PRIVATE_WITH_NAT вже deprecated, побачив це тільки коли перевіряв створення ресурсів:

[simterm]

$ cdk synth
[WARNING] aws-cdk-lib.aws_ec2.VpcProps#cidr is deprecated.
  Use ipAddresses instead
  This API will be removed in the next major release.
[WARNING] aws-cdk-lib.aws_ec2.SubnetType#PRIVATE_WITH_NAT is deprecated.
  use `PRIVATE_WITH_EGRESS`
  This API will be removed in the next major release.
[WARNING] aws-cdk-lib.aws_ec2.SubnetType#PRIVATE_WITH_NAT is deprecated.
  use `PRIVATE_WITH_EGRESS`
  This API will be removed in the next major release.
...

[/simterm]

Окей.

Додаємо availability_zones, в яких хочемо створювати subnets, і описуємо subnet_configuration.

В subnet_configuration описуємо subnet group – одну Public, та одну Private – CDK створить subnet кожного типу в кожній Availability Zone.

На майбутнє – відразу створимо S3 Endpoint, бо в кластері планується Grafana Loki, яка буде ходити в S3 бакети.

До ресурсу eks.Cluster() додаємо параметр vpc.

Весь файл тепер виглядає так:

from aws_cdk import (
    aws_eks as eks,
    aws_ec2 as ec2,
    Stack
)
from constructs import Construct

class AtlasEksStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, stage: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        availability_zones = ['us-east-1a', 'us-east-1b']

        # Create a new VPC
        vpc = ec2.Vpc(self, 'Vpc',
            ip_addresses=ec2.IpAddresses.cidr("10.0.0.0/16"),
            vpc_name=f'eks-{stage}-1-26-vpc',
            enable_dns_hostnames=True,
            enable_dns_support=True,
            availability_zones=availability_zones,
            subnet_configuration=[
                ec2.SubnetConfiguration(
                    name=f'eks-{stage}-1-26-subnet-public',
                    subnet_type=ec2.SubnetType.PUBLIC,
                    cidr_mask=24
                ),
                ec2.SubnetConfiguration(
                    name=f'eks-{stage}-1-26-subnet-private',
                    subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS,
                    cidr_mask=24
                )
            ]
        )

        # Add an S3 VPC endpoint
        vpc.add_gateway_endpoint('S3Endpoint',
                                 service=ec2.GatewayVpcEndpointAwsService.S3)

        cluster = eks.Cluster(
            self, 'EKS-Cluster',
            cluster_name=f'eks-{stage}-1-26-cluster',
            version=eks.KubernetesVersion.V1_26,
            vpc=vpc
        )

Деплоємо, перевіряємо:

[simterm]

$ cdk deploy eks-dev-1-26
...
eks-dev-1-26: deploying... [1/1]
eks-dev-1-26: creating CloudFormation changeset...
...
✨  Total time: 1243.08s

[/simterm]

1243.08s секунд – 20 хвилин. Окей…

Додавання Stack Tags

Що ще хочеться – це додати власні теги до всіх ресурсів, які буде створювати CDK в цьому стеку.

В app.py використовуємо cdk.Tags, якому передаємо об’єкт AtlasEksStack():

...
app = cdk.App()

eks_stack = AtlasEksStack(app, f'eks-{EKS_STAGE}-1-26',
        env=cdk.Environment(account=AWS_ACCOUNT, region=AWS_REGION),
        stage=EKS_STAGE
    )
cdk.Tags.of(eks_stack).add("environment", EKS_STAGE)
cdk.Tags.of(eks_stack).add("component", "EKS")

app.synth()

Деплоїмо (Total time: 182.67s просто на додавання тегів на ресурси), та перевіряємо теги:

Все є.

Створення NodeGroup

Взагалі скоріш за все будемо використовувати Karpenter замість “класичного” Cluster Autoscaler, бо про Karpenter чув багато гарних відгуків і хочеться його спробувати у ділі, і тоді ноди треба буде переробити, але поки що створимо звичайну Managed NodeGroup за допомогою add_nodegroup_capacity().

До файлу atlas_eks_stack.py додаємо cluster.add_nodegroup_capacity() з Amazon Linux AMI :

...
        # Create the EC2 node group
        nodegroup = cluster.add_nodegroup_capacity(
            'Nodegroup',
            instance_types=[ec2.InstanceType('t3.medium')],
            desired_size=1,
            min_size=1,
            max_size=3,
            ami_type=eks.NodegroupAmiType.AL2_X86_64
        )

Необхідні IAM-ролі CDK має створити сам – подивимось.

У ресурсі eks.Cluster() вказуємо default_capacity=0, щоб СDK не створював власну дефолтну групу:

...
        cluster = eks.Cluster(
            self, 'EKS-Cluster',
            cluster_name=f'eks-{stage}-1-26-cluster',
            version=eks.KubernetesVersion.V1_26,
            vpc=vpc,
            default_capacity=0
        )
...

Error: b’configmap/aws-auth configured\nerror: error retrieving RESTMappings to prune: invalid resource batch/v1beta1, Kind=CronJob, Namespaced=true: no matches for kind “CronJob” in version “batch/v1beta1″\n’

Зараз стек вже задеплоєно, запускаємо cdk deploy, щоб оновити – і…

[simterm]

eks-dev-1-26: creating CloudFormation changeset...
1:26:35 PM | UPDATE_FAILED        | Custom::AWSCDK-EKS-KubernetesResource | EKSClusterAwsAuthmanifest5D430CCD
Received response status [FAILED] from custom resource. Message returned: Error: b'configmap/aws-auth configured\nerror: error retrieving RESTMappings to prune: invalid resource batch/v1beta1, Kind=CronJob, Namespaced=true: no matches for kind "CronJob" in version "bat
ch/v1beta1"\n'

[/simterm]

Шта? Якого біса?

aws-auth ConfigMap, Kind=CronJob? Звідки це?

Тобто, мабуть, CDK намагається оновити aws-auth ConfigMap, щоб додати NodeGroup AIM роль, але… Але – що?

Судячи з Гуглу, проблема як раз пов’язана з kubectlLayer, про яку писав вище – aws-eks: cdk should validate cluster version and kubectl layer version.

При чьому проявляється це тільки під час оновлення стеку. Якщо створювати його заново – то все працює. Але тут варто згадати про швидкість роботи CDK/CloudFormation, бо видалення і створення займає хвилин 30-40.

KubectlV26Layer

Ну, все ж довелося фіксити цю проблему.

Добре… Шукаємо просто в браузері – aws-cdk.lambda-layer-kubectl-v26. Є така ліба. Але навіть у PyPi репозиторії приклади на TypeScript – щиро дякую:

Це взагалі проблема при роботі з AWS CDK на Python – дуже багато прикладів все одно на TS.

Окей, ладно – лібу знайшли, вона називається aws-cdk.lambda-layer-kubectl-v26, встановлюємо:

[simterm]

$ pip install aws-cdk.lambda-layer-kubectl-v26

[/simterm]

Додаємо до requirements.txt:

[simterm]

$ pip freeze | grep -i lambda-layer-kubectl >> requirements.txt

[/simterm]

Додаємо у файл atlas_eks_stack.py:

...
from aws_cdk.lambda_layer_kubectl_v26 import KubectlV26Layer
...

        # to fix warning "You created a cluster with Kubernetes Version 1.26 without specifying the kubectlLayer property" 
        kubectl_layer = KubectlV26Layer(self, 'KubectlV26Layer')
        ...
        cluster = eks.Cluster(
            self, 'EKS-Cluster',
            cluster_name=f'eks-{stage}-1-26-cluster',
            version=eks.KubernetesVersion.V1_26,
            vpc=vpc,
            default_capacity=0,
            kubectl_layer=kubectl_layer
        )
...

Повторюємо деплой для апдейту вже існуючого стеку, і…

CloudFormation UPDATE_ROLLBACK_FAILED

І маємо іншу помилку, бо після “Error: b’configmap/aws-auth configured\nerror” стек лишився у статусі UPDATE_ROLLBACK_FAILED:

[simterm]

...
eks-dev-1-26: deploying... [1/1]
eks-dev-1-26: creating CloudFormation changeset...

 ❌  eks-dev-1-26 failed: Error [ValidationError]: Stack:arn:aws:cloudformation:us-east-1:492***148:stack/eks-dev-1-26/9c7daa50-10f4-11ee-b64a-0a9b7e76090b is in UPDATE_ROLLBACK_FAILED state and can not be updated.
...

[/simterm]

Тут варіант або просто видалити стек і створити заново (вбити ще хвилин 30-40 свого часу), або погуглилити та знайти How can I get my CloudFormation stack to update if it’s stuck in the UPDATE_ROLLBACK_FAILED state.

Спробуємо ContinueUpdateRollback:

Але нє – все-одно стек поломаний:

Тож видаляємо, і йдемо на Фейсбук дивитись котиків, поки воно буде перестворюватись.

Cannot replace cluster “since it has an explicit physical name

На цьому місці ще ловив “Cannot replace cluster “eks-dev-1-26-cluster” since it has an explicit physical name.“, виглядало це так:

[simterm]

...
2:30:45 PM | UPDATE_FAILED        | Custom::AWSCDK-EKS-Cluster            | EKSCluster676AE7D7
Received response status [FAILED] from custom resource. Message returned: Cannot replace cluster "eks-dev-1-26-cluster" since it has an explicit physical name. Either rename the cluster or remove the "name" configuration
...

[/simterm]

Але на цей раз не зарепродьюсилось, хоча треба мати на увазі, бо точно вилізе ще колись.

Добре, отже тепер вже маємо VPC, EKS Cluster та NodeGroup – час подумати про IAM.

IAM Role та aws-auth ConfigMap

Що треба зробити наступним – це створити IAM-роль, яку можна буде assume для отримання доступу до кластеру.

Поки що без всяких RBAC та юзер-груп – просто роль, щоб потім виконати aws eks update-kubeconfig.

Використовуємо aws_cdk.aws_iam.Role() і  aws_cdk.aws_eks.AwsAuth():

from aws_cdk import (
    ...
    aws_iam as iam,
    ...
)
...
        # Create an IAM Role to be assumed by admins
        masters_role = iam.Role(
            self,
            'EksMastersRole',
            assumed_by=iam.AccountRootPrincipal()
        )

        # Attach an IAM Policy to that Role so users can access the Cluster
        masters_role_policy = iam.PolicyStatement(
            actions=['eks:DescribeCluster'],
            resources=['*'],  # Adjust the resource ARN if needed
        )
        masters_role.add_to_policy(masters_role_policy)

        cluster.aws_auth.add_masters_role(masters_role)

        # Add the user to the cluster's admins
        admin_user = iam.User.from_user_arn(self, "AdminUser", user_arn="arn:aws:iam::492***148:user/arseny")
        cluster.aws_auth.add_user_mapping(admin_user, groups=["system:masters"])

masters_role – роль, яку можна буде assume будь-ким з AWS-аккаунту, а admin_user – мій IAM юзер для “прямого” доступу до кластеру.

CfnOutput

Outputs CloudFormation-стеку. Наскільки пам’ятаю, може використовуватись для cross-stack передачі values, але нам більше треба для отримання ARN-у masters_role:

from aws_cdk import (
    ...
    Stack, CfnOutput
)
...
        # Output the EKS cluster name
        CfnOutput(
            self,
            'ClusterNameOutput',
            value=cluster.cluster_name,
        )

        # Output the EKS master role ARN
        CfnOutput(
            self,
            'ClusterMasterRoleOutput',
            value=masters_role.role_arn
        )

Деплоїмо:

[simterm]

...
Outputs:
eks-dev-1-26.ClusterMasterRoleOutput = arn:aws:iam::492***148:role/eks-dev-1-26-EksMastersRoleD1AE213C-1ANPWK8HZM1W5
eks-dev-1-26.ClusterNameOutput = eks-dev-1-26-cluster

[/simterm]

Налаштування kubectl з AWS CLI

Ну й врешті-решт, після всіх страждань – спробуємо отримати доступ до кластеру.

Спочатку через master_role – оновлюємо ~/.aws/config:

[profile work]
region = us-east-1
output = json

[profile work-eks]
role_arn = arn:aws:iam::492***148:role/eks-dev-1-26-EksMastersRoleD1AE213C-1ANPWK8HZM1W5
source_profile = work

У [profile work-eks] виконуємо IAM Role Assume – використовуємо нашу master_role, використовуючи ACCESS/SECRET ключи профайлу [work].

Створюємо kube-config:

[simterm]

$ aws --profile work-eks eks update-kubeconfig --region us-east-1 --name eks-dev-1-26-cluster --alias eks-dev-1-26
Added new context eks-dev-1-26 to /home/setevoy/.kube/config

[/simterm]

І перевіряємо доступ:

[simterm]

$ kubectl get node
NAME                        STATUS   ROLES    AGE   VERSION
ip-10-0-2-60.ec2.internal   Ready    <none>   19h   v1.26.4-eks-0a21954

[/simterm]

Аналогічно, якщо використовувати персональний AIM-аккаунт, тобто user_arn="arn:aws:iam::492***148:user/arseny":

[simterm]

$ aws --profile work eks update-kubeconfig --region us-east-1 --name eks-dev-1-26-cluster --alias eks-dev-1-26-personal

[/simterm]

Вона працює” (с)

По результату можу сказати одне – особисто я не готовий брати відповідальність за роботу такого стеку у production.

Можливо, якщо з CDK попрацювати ще, і знати основні його підводні камені, та згадати всі “особливості” CloudFormation – то з ними можна жити. Але поки що – ні, взагалі не хочеться.