Продовжуємо тему розгортання кластеру AWS Elastic Kubernetes Service за допомогою Terraform.
У першій частині підготували AWS VPC – див. Terraform: створення EKS, частина 1 – VPC, Subnets та Endpoints.
В цій частині розгорнемо сам кластер і налаштуємо AIM для нього, а в наступній – встановимо Karpenter та решту контроллерів.
Зміст
Планування
В цілому, список TODO наразі виглядає так:
- створити default NodeGroup з Taints
CrticalAddonsOnly=true(див. Kubernetes: Pods та WorkerNodes – контроль розміщення подів на нодах) - створити StorageClass з
ReclaimPolicy=Retain– для PVC, диски котрих треба зберігати при видаленні Deployment/StatefulSet - створити IAM “masters_access_role” з політикою
eks:DescribeClusterдля використанняaws eks update-kubeconfig, щоб потім додавати юзерів - до
aws-authConfigMap додати цю masters_access_role та мого IAM User як адмінів – поки не будемо ускладнювати з RBAC, бо “все тільки починається” (с) - створити OIDC Provider для кластеру
- додати subscription filter до EKS Cloudwatch Log Group, щоб збирати логи в Grafana Loki (див. Loki: збір логів з CloudWatch Logs з використанням Lambda Promtail)
- у самому кластері:
- встановити Karpenter
- встановити EKS EBS CSI Addon
- встановити ExternalDNS контролер
- встановити AWS Load Balancer Controller
- додати SecretStore CSI Driver та ASCP
- встановити Metrics Server
- і додати Vertical Pod Autoscaler та Horizontal Pod Autoscaler
Для кластеру також використаємо модуль, знову від @Anton Babenko – Terraform EKS module. Проте й інші модулі, наприклад – terraform-aws-eks від Cookpad – я ним теж трохи користвувався, працював добре, але порівнювати не візьмусь.
Як і для модуля VPC, у Terraform EKS module теж маємо приклад кластеру та пов’язаних ресурсів – examples/complete/main.tf.
Terraform Kubernetes provider
Для роботи модулю з aws-auth ConfigMap потрібно буде додати ще один провайдер – kubernetes.
У файлі providers.tf додаємо його:
...
provider "kubernetes" {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["--profile", "tf-admin", "eks", "get-token", "--cluster-name", module.eks.cluster_name]
}
}
Тут зверніть увагу, що в args передається AWS-профайл, бо сам кластер створюється Terraform від імені IAM Role:
...
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::492***148:role/tf-admin"
}
...
І AWS CLI Profile tf-admin як раз теж виконує IAM Role Assume:
... [profile work] region = us-east-1 output = json [profile tf-admin] role_arn = arn:aws:iam::492***148:role/tf-admin source_profile = work ...
Error: The configmap “aws-auth” does not exist
Досить часта помилка, принаймні я неодноразово з нею стикався – коли під час виконання terraform apply в кінці отримуємо цю помилку, а сама aws-auth в кластері не створена.
Це призводить по-перше до того, що до кластеру не підключаються дефолтні WokrerNodes, по-друге – ми не можемо отримати доступ до кластеру з kubectl, бо хоча aws eks update-kubeconfig створює новий контекст в локальному ~/.kube/config, сам kubectl повертає помилку авторизації в кластері.
Продебажити це допомогло включення дебаг-логу Terraform через змінну TF_LOG=INFO, де була сама помилка аутентифиікації провайдеру:
...
[DEBUG] provider.terraform-provider-kubernetes_v2.23.0_x5: "kind": "Status",
[DEBUG] provider.terraform-provider-kubernetes_v2.23.0_x5: "apiVersion": "v1",
[DEBUG] provider.terraform-provider-kubernetes_v2.23.0_x5: "metadata": {},
[DEBUG] provider.terraform-provider-kubernetes_v2.23.0_x5: "status": "Failure",
[DEBUG] provider.terraform-provider-kubernetes_v2.23.0_x5: "message": "Unauthorized",
[DEBUG] provider.terraform-provider-kubernetes_v2.23.0_x5: "reason": "Unauthorized",
[DEBUG] provider.terraform-provider-kubernetes_v2.23.0_x5: "code": 401
[DEBUG] provider.terraform-provider-kubernetes_v2.23.0_x5: }
...
Помилка виникала саме через те, що в args провайдеру не було задано правильний локальний профайл.
Є інший варіант аутентифікації – через token, див. цей коментар в GitHub Issues.
Але з ним були проблеми при створенні кластеру з нуля, бо Терраформ не міг виконанти data "aws_eks_cluster_auth". Треба ще якось спробувати, бо в цілому ідея з токеном мені подобається більше, ніж через AWS CLI. З іншого боку – у нас ще будуть провайдери kubectl та helm, і не факт, що їх можна аутентифікувати через токен (хоча, скоріш за все можно, але треба покопатись).
Terraform Kubernetes module
Окей, з провайдером розібрались – давайте додавати сам модуль.
Спочатку запустимо сам кластер с однією NodeGroup, а потім вже будемо додавати всякі контроллери.
Типи EKS NodeGroups
AWS має два типи NodeGroups – Self-Managed, та Amazon Managed, див. Amazon EKS nodes.
Головна, як на мене, перевага Amazon Managed це те, що ви не маєте перейматись оновленнями – все, що стосується операційної системи і компонентів самого Kubernetes, бере на себе Амазон:
Хоча якщо робити Self-managed Nodes використовуючи AMI від самого AWS з Amazon Linux – то там все вже буде налаштовано, і навіть для апдейтів достатньо ребутнути чи перестворити ЕС2 – тоді він запуститься з AMI з останніми патчами.
Окремо варто загадти Fargate – див. AWS: Fargate – можливості, порівняння з Lambda/EC2 та використання з AWS EKS, але я не бачу в них якогось великого сенсу, тим більш на них не зможемо створювати DaemonSets з, наприклад, Promtail для логів.
Також, Managed NodeGroups не потребують окремих налаштувать у aws-auth ConfigMap – EKS сам додасть необхідні записи.
Anayway, щоб полегшити собі життя – будемо використовувати Amazon Managed Nodes. На цих нодах будуть жити тільки контроллери – “Critical Addons”, а ноди для ворклоадів будуть менеджитись Karpenter-ом.
Terraform EKS variables
Спершу нам потрібні будуть змінні.
Взагалі добре пройтись по всім inputs, і подивитись що можна налаштувати під себе.
Для мінімального конфігу нам знадобляться:
cluster_endpoint_public_access– boolcluster_enabled_log_types– listeks_managed_node_groups:min_size,max_sizeтаdesired_size– number-
instance_types– list capacity_type– stringmax_unavailable_percentage– number
aws_auth_roles– mapaws_auth_users– map
Поділимо змінні на три групи – одна для самого EKS, друга – з параметрами для NodeGroups, і третя – для IAM Users.
Описуємо першу змінну – с параметрами для самого EKS:
...
variable "eks_params" {
description = "EKS cluster itslef parameters"
type = object({
cluster_endpoint_public_access = bool
cluster_enabled_log_types = list(string)
})
}
Та terraform.tfvars зі значеннями – поки включимо всі логи, потім залишимо тільки реально потрібні:
...
eks_params = {
cluster_endpoint_public_access = true
cluster_enabled_log_types = ["audit", "api", "authenticator", "controllerManager", "scheduler"]
}
Далі, параметри для NodeGroups. Створимо об’єкт типу map, в якому зможемо додавати конфігурції для декількох груп, які будемо тримати в елементах з типом object, бо параметри будуть різних типів:
...
variable "eks_managed_node_group_params" {
description = "EKS Managed NodeGroups setting, one item in the map() per each dedicated NodeGroup"
type = map(object({
min_size = number
max_size = number
desired_size = number
instance_types = list(string)
capacity_type = string
taints = set(map(string))
max_unavailable_percentage = number
}))
}
Приклад додавання Taints є тут>>>, тож описуємо їх та інші параметри у tfvars:
...
eks_managed_node_group_params = {
default_group = {
min_size = 2
max_size = 6
desired_size = 2
instance_types = ["t3.medium"]
capacity_type = "ON_DEMAND"
taints = [
{
key = "CriticalAddonsOnly"
value = "true"
effect = "NO_SCHEDULE"
},
{
key = "CriticalAddonsOnly"
value = "true"
effect = "NO_EXECUTE"
}
]
max_unavailable_percentage = 50
}
}
І третя группа – список IAM юзерів, котрі будуть додані до aws-auth ConfgiMap для доступу до кластеру. Тут використовуємо тип set з ще одним object, бо для юзера потрібно буде передавати list зі список RBAC-груп:
...
variable "eks_aws_auth_users" {
description = "IAM Users to be added to the aws-auth ConfigMap, one item in the set() per each IAM User"
type = set(object({
userarn = string
username = string
groups = list(string)
}))
}
Значення в tfvars:
...
eks_aws_auth_users = [
{
userarn = "arn:aws:iam::492***148:user/arseny"
username = "arseny"
groups = ["system:masters"]
}
]
Як і з NodeGroups, тут ми зможемо задати кілька юзерів, і всі вони потім будуть передані до aws_auth_users модулю EKS.
Створення кластеру
Створюємо файл eks.tf, додаємо модуль:
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.0"
cluster_name = "${local.env_name}-cluster"
cluster_version = var.eks_version
cluster_endpoint_public_access = var.eks_params.cluster_endpoint_public_access
cluster_enabled_log_types = var.eks_params.cluster_enabled_log_types
cluster_addons = {
coredns = {
most_recent = true
}
kube-proxy = {
most_recent = true
}
vpc-cni = {
most_recent = true
}
}
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
control_plane_subnet_ids = module.vpc.intra_subnets
manage_aws_auth_configmap = true
eks_managed_node_groups = {
default = {
min_size = var.eks_managed_node_group_params.default_group.min_size
max_size = var.eks_managed_node_group_params.default_group.max_size
desired_size = var.eks_managed_node_group_params.default_group.desired_size
instance_types = var.eks_managed_node_group_params.default_group.instance_types
capacity_type = var.eks_managed_node_group_params.default_group.capacity_type
taints = var.eks_managed_node_group_params.default_group.taints
update_config = {
max_unavailable_percentage = var.eks_managed_node_group_params.default_group.max_unavailable_percentage
}
}
}
cluster_identity_providers = {
sts = {
client_id = "sts.amazonaws.com"
}
}
aws_auth_users = var.eks_aws_auth_users
#aws_auth_roles = TODO
}
Якщо для Addons треба додати параметри – можна зробити з configuration_values, див. приклад тут>>>.
Додамо трохи outputs:
...
output "eks_cloudwatch_log_group_arn" {
value = module.eks.cloudwatch_log_group_arn
}
output "eks_cluster_arn" {
value = module.eks.cluster_arn
}
output "eks_cluster_endpoint" {
value = module.eks.cluster_endpoint
}
output "eks_cluster_iam_role_arn" {
value = module.eks.cluster_iam_role_arn
}
output "eks_cluster_oidc_issuer_url" {
value = module.eks.cluster_oidc_issuer_url
}
output "eks_oidc_provider" {
value = module.eks.oidc_provider
}
output "eks_oidc_provider_arn" {
value = module.eks.oidc_provider_arn
}
Перевіряємо з terraform plan, деплоїмо, та перевіряємо сам кластер:
Створюємо ~/.kube/config:
$ aws --profile work --region us-east-1 eks update-kubeconfig --name atlas-eks-dev-1-27-cluster --alias atlas-eks-dev-1-27-work-profile Updated context atlas-eks-dev-1-27-work-profile in /home/setevoy/.kube/config
Та перевіряємо доступ з can-i:
$ kubectl auth can-i get pod yes
Додаткова IAM Role
Окремо створимо IAM Role з політикою eks:DescribeCluster, і підключимо її до кластеру в групу system:masters – використовуючи цю роль, інші юзери зможуть проходити авторизацію в кластері.
В роль нам потрібно буде передати AWS Account ID, щоб в Principal обмежити можливість виконання AssumeRole тільки юзерами цього акаунту.
Щоб не виносити це окремою змінною в variables.tf – в eks.tf додамо ресурс data "aws_caller_identity":
...
data "aws_caller_identity" "current" {}
І далі описуємо саму роль з assume_role_policy – кому буде дозволено assume цієї ролі, та inline_policy з дозволом на виконання eks:DescribeCluster:
...
resource "aws_iam_role" "eks_masters_access_role" {
name = "${local.env_name}-masters-access-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
AWS: "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
}
]
})
inline_policy {
name = "${local.env_name}-masters-access-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = ["eks:DescribeCluster*"]
Effect = "Allow"
Resource = "*"
},
]
})
}
tags = {
Name = "${local.env_name}-access-role"
}
}
Повертаємось до module "eks" і в aws_auth_roles додаємо маппінг цієї ролі:
...
aws_auth_users = var.eks_aws_auth_users
aws_auth_roles = [
{
rolearn = aws_iam_role.eks_masters_access_role.arn
username = aws_iam_role.eks_masters_access_role.arn
groups = ["system:masters"]
}
]
...
Додамо output:
...
output "eks_masters_access_role" {
value = aws_iam_role.eks_masters_access_role.arn
}
Деплоїмо зміни:
$ terraform apply ... Outputs: ... eks_masters_access_role = "arn:aws:iam::492***148:role/atlas-eks-dev-1-27-masters-access-role" ...
Перевіряємо саму aws-auth ConfigMap:
$ kk -n kube-system get cm aws-auth -o yaml
apiVersion: v1
data:
...
mapRoles: |
- "groups":
- "system:bootstrappers"
- "system:nodes"
"rolearn": "arn:aws:iam::492***148:role/default-eks-node-group-20230907145056376500000001"
"username": "system:node:{{EC2PrivateDNSName}}"
- "groups":
- "system:masters"
"rolearn": "arn:aws:iam::492***148:role/atlas-eks-dev-1-27-masters-access-role"
"username": "arn:aws:iam::492***148:role/atlas-eks-dev-1-27-masters-access-role"
mapUsers: |
- "groups":
- "system:masters"
"userarn": "arn:aws:iam::492***148:user/arseny"
"username": "arseny"
...
Додаємо новий профайл до ~/.aws/confing:
... [profile work] region = us-east-1 output = json [profile eks-1-27-masters-role] role_arn = arn:aws:iam::492***148:role/atlas-eks-dev-1-27-masters-access-role source_profile = work
Додаємо новий контекст для kubectl:
$ aws --profile eks-1-27-masters-role --region us-east-1 eks update-kubeconfig --name atlas-eks-dev-1-27-cluster --alias eks-1-27-masters-role Updated context eks-1-27-masters-role in /home/setevoy/.kube/config
І перевіряємо доступ:
$ kubectl auth can-i get pod yes $ kubectl get pod -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system aws-node-99gg6 2/2 Running 0 41h kube-system aws-node-bllg2 2/2 Running 0 41h ...
В наступній частині вже встановимо решту – Karpenter та різні Controllers.
Помилка Get “http://localhost/api/v1/namespaces/kube-system/configmaps/aws-auth”: dial tcp: lookup localhost on 10.0.0.1:53: no such host
Під час тестів перестворював кластер, щоб впевнитись, що весь код, описаний тут, працює.
І при видаленні кластеру Terraform видавав помилку:
... Plan: 0 to add, 0 to change, 34 to destroy. ... ╷ │ Error: Get "http://localhost/api/v1/namespaces/kube-system/configmaps/aws-auth": dial tcp: lookup localhost on 10.0.0.1:53: no such host │ ...
Рішення – видалити aws-auth зі стейт-файлу:
$ terraform state rm module.eks.kubernetes_config_map_v1_data.aws_auth[0]
Ясна річ, що робити це треба тільки для тестового кластеру, а не Production.





