Translate

четвер, 19 вересня 2024 р.

Kubernetes. Part VIII: EKS Cluster With Terraform, AWS LB Controller

Цього разу подивимось на установку Elastic Kubernetes Service (EKS), версії Kubernetes, що керується cloud-платформою Amazon. Він з'явився у 2018 році і є кращим способом установки Kubernetes в цьому середовищі. Також поглянемо на AWS Load Balancer Controller, що самостійно імплементує Ingress та Service (type: LoadBalancer) абстракції.

Раніше я вже писав про Kops, 3rd-party спосіб інсталяції Kubernetes, що в деякому сенсі нагадує k0s. Це чудовий варіант установки, що підтримує не лише AWS, а і інші cloud-платформи, проте із появою EKS він дещо втратив свою актуальність.

У цій статті опишемо створення мережі, EKS-кластеру, що буде працювати у цій мережі, AWS LB контролера та протестуємо його роботу. Описувати все будемо в Terraform, адже для нього вже створені всі необхідні модулі.

1. CREATING VPC/SUBNETS FOR EKS

Створимо необхідне дерево директорій, де і буде описаний проект:

$ git clone git@github.com:ipeacocks/terraform-aws-example.git
$ mv terraform-aws-example/eks-infra infrastructure
$ rm -rf terraform-aws-example


Створимо virtualenv для Python в який встановимо aws-cli:

$ cd infrastructure
$ python3 -m venv venv
$ source venv/bin/activate

$ pip install awscli
$ aws --version
aws-cli/1.34.19 Python/3.12.3 Linux/6.8.0-44-generic botocore/1.35.19

Додамо key_id та access_key:

$ aws configure
AWS Access Key ID [None]: MYSECRETKEYID
AWS Secret Access Key [None]: MYSERETKEYVALUE
Default region name [None]: us-east-1
Default output format [None]:

Або ж можна просто імпортувати їх як змінні середовища. Деталі про це можна прочитати наприклад тут.

Після цього створимо бакет для стейтів, його ім'я має бути унікальним, тобто вашим власним:

$ aws s3api create-bucket \
      --bucket my-tf-state-2023-06-01 \
      --region us-east-1


За цим посиланням можна почитати про те, як створити lock в dynamodb, але це не принципово цього разу. Якщо відсутній Terraform - встановлюємо його зручним для вас способом. Наразі я використовую версію 1.9.5.

Для створення інфраструктури будемо користуватись готовим модулем terraform-aws-modules/vpc/aws. Проте, як на мене, для довгострокових цілей ліпше за все мережі створювати без модулів і з максимально однозначним іменуванням. Так буде простіше орієнтуватись та розширювати/обслуговувати їх. Мережа - базова річ і щось змінювати у ній потім буде складно і часто з неприємними наслідками. 

Отже параметри модуля для створення VPC виглядатимуть наступним чином:

$ cd vpc
$ cat main.tf
 

data "aws_availability_zones" "available" {}

locals {
  control_plane_subnets = var.control_plane_subnets
  worker_subnets        = var.worker_subnets
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.13.0"

  name = var.vpc_name

  cidr = var.vpc_cidr
  azs  = slice(data.aws_availability_zones.available.names, 0, 3)

  private_subnets = concat(local.control_plane_subnets, local.worker_subnets)
  public_subnets  = var.public_subnets

  enable_nat_gateway     = var.enable_nat_gateway
  single_nat_gateway     = var.single_nat_gateway
  one_nat_gateway_per_az = var.one_nat_gateway_per_az
  enable_dns_hostnames   = var.enable_dns_hostnames

  # tags needed for AWS LB Controller
  # https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.8/deploy/subnet_discovery/
  public_subnet_tags = {
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
  }
}


Теги необхідні для AWS LB controller-а, мова про який піде далі. Конфігураційний файл зі змінними має наступний вигляд:

$ cat variables.tf

 
# Details are here https://github.com/terraform-aws-modules/terraform-aws-vpc#inputs

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "vpc_cidr" {
  description = "VPC CIDR"
  type        = string
  default     = "10.0.0.0/16"
}

variable "control_plane_subnets" {
  description = "Subnets for EKS control plane nodes"
  type        = list(any)
  default     = ["10.0.1.0/27", "10.0.1.32/27", "10.0.1.64/27"]
}

variable "worker_subnets" {
  description = "Subnets for EKS worker nodes"
  type        = list(any)
  default     = ["10.0.8.0/21", "10.0.16.0/21", "10.0.24.0/21"]
}

variable "public_subnets" {
  description = "Public subnets for LBs, NAT GWs and so on"
  type        = list(any)
  default     = ["10.0.100.0/23", "10.0.102.0/23", "10.0.104.0/23"]
}

variable "credentials" {
  default     = ["~/.aws/credentials"]
  description = "Where your access and secret_key are stored, you create the file when you run the aws config"
  type        = list(any)
}

variable "vpc_name" {
  description = "VPC name"
  type        = string
  default     = "my-vpc"
}

variable "enable_nat_gateway" {
  description = "Should be true if you want to provision NAT Gateways for each of your private networks"
  type        = bool
  default     = true
}

# for prod purposes should be false
variable "single_nat_gateway" {
  description = "Should be true if you want to provision a single shared NAT Gateway across all of your private networks"
  type        = bool
  default     = false
}

variable "one_nat_gateway_per_az" {
  description = "Should be true if you want only one NAT Gateway per availability zone."
  type        = bool
  default     = true
}

variable "enable_dns_hostnames" {
  description = "Should be true to enable DNS hostnames in the Default VPC"
  type        = bool
  default     = true
}

Мережа матиме наступний вигляд:

  • все буде розміщено в регіоні us-east-1, в 3-х зонах доступності
  • для K8s control plane виділені 3 окремі підмережі (змінна control_plane_subnets), Amazon рекомендує їх тримати окремо від підмереж воркерів. /27 підмережа (96 адрес) обрана тому, що вузлів для майстрів зазвичай багато не потрібно. Фізично вузли control plane лежать у власній AWS VPC, але їхні мережеві інтерфейси прокидуються у цю мережу
  • для воркерів також обрано 3 підмережі в різних AZ (змінна worker_subnets). Вони значно більші, адже завдяки Amazon VPC CNI поди будуть використовувати адреси із цього ж діапазону.
  • підмережі public_subnets загалом необхідні для NAT gateway-їв в кожній AZ, балансувальників і хостів (наприклад bastion), для яких необхідний доступ із мережі Інтернет
  • one_nat_gateway_per_az параметр вказує на те, що в кожній AZ перебуватиме NAT gateway для вищої доступності мережі. Це загальна і правильна практика, проте для здешевлення можна зробити один NAT gateway для всіх приватних підмереж.

Детальніше про всі параметри можна почитати за посиланням до коду модуля.

Всі output змінні необхідні для того, щоб їх далі використовувати при створенні самого кластеру:

$ cat outputs.tf


locals {
  control_plane_subnets_length = length(local.control_plane_subnets)
  private_subnets_length       = length(module.vpc.private_subnets)
}

output "region" {
  description = "AWS region"
  value       = var.region
}

output "vpc_id" {
  description = "VPC id"
  value       = module.vpc.vpc_id
}

output "public_subnets" {
  description = "Public subnets"
  value       = module.vpc.public_subnets
}

output "control_plane_subnet_ids" {
  description = "Private subnets for Control plane"
  value       = slice(module.vpc.private_subnets, 0, local.control_plane_subnets_length)
}

output "worker_subnet_ids" {
  description = "Private subnets for Worker nodes"
  value       = slice(module.vpc.private_subnets, local.control_plane_subnets_length, local.private_subnets_length)
}

Ініціюємо додаткові модулі та стартуємо створення VPC:

$ terraform init
$ terraform plan
$ terraform apply


Після підготовки мереж переходимо до наступної секції.


2. CREATING EKS CLUSTER

Аналогічно скористуємось готовим модулем terraform-aws-modules/eks/aws. Будемо інсталювати останню на даний момент версію Kubernetes 1.30, а код, як я вже згадував, знаходиться тут:

$ cd ../eks
$ cat main.tf

data "terraform_remote_state" "vpc" {
  backend = "s3"

  config = {
    bucket = "my-tf-state-2023-06-01"
    key    = "my-vpc.tfstate"
    region = "us-east-1"
  }
}

resource "random_string" "suffix" {
  length  = 8
  special = false
}

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "20.24.0"

  cluster_name    = "${var.eks_name_prefix}-${random_string.suffix.result}"
  cluster_version = var.cluster_version
  cluster_addons  = var.cluster_addons

  vpc_id                         = data.terraform_remote_state.vpc.outputs.vpc_id
  control_plane_subnet_ids       = data.terraform_remote_state.vpc.outputs.control_plane_subnet_ids
  subnet_ids                     = data.terraform_remote_state.vpc.outputs.worker_subnet_ids
  cluster_endpoint_public_access = var.cluster_endpoint_public_access

  eks_managed_node_group_defaults      = var.eks_managed_node_group_defaults
  eks_managed_node_groups              = var.eks_managed_node_groups
  node_security_group_additional_rules = var.node_security_group_additional_rules

  kms_key_administrators                   = var.kms_key_administrators
  enable_cluster_creator_admin_permissions = var.enable_cluster_creator_admin_permissions
}

Варто буде попередньо виправити назву бакета (навів жирним), де лежить стейт мережі. Параметри, що я використовую:

$ cat variables.tf

 
# Details https://github.com/terraform-aws-modules/terraform-aws-eks#inputs

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "credentials" {
  default     = ["~/.aws/credentials"]
  description = "Where your access and secret_key are stored, you create the file when you run the aws config"
}

variable "eks_name_prefix" {
  description = "EKS cluster name prefix"
  type        = string
  default     = "my-eks"
}

variable "cluster_version" {
  description = "EKS version"
  type        = string
  default     = "1.30"
}

# can be false if you connect to private network via VPN or something
variable "cluster_endpoint_public_access" {
  description = "Indicates whether or not the Amazon EKS public API server endpoint is enabled"
  type        = bool
  default     = true
}

variable "eks_managed_node_group_defaults" {
  description = "Map of EKS managed node group default configurations"
  default     = { ami_type = "AL2_x86_64" }
}

variable "eks_managed_node_groups" {
  description = "Map of EKS managed node group definitions to create"
  default = {
    one = {
      name = "node-group-1"

      instance_types = ["t3.small"]

      min_size     = 2
      max_size     = 3
      desired_size = 2
    }
  }
}

variable "node_security_group_additional_rules" {
  description = "Node to node traffic access"
  default = {
    ingress_self_any = {
      description = "Node to node All"
      protocol    = "all"
      from_port   = 0
      to_port     = 0
      type        = "ingress"
      self        = true
    }
    egress_self_any = {
      description = "Node to node All"
      protocol    = "all"
      from_port   = 0
      to_port     = 0
      type        = "egress"
      self        = true
    }
  }
}

# maybe better to pin versions here
variable "cluster_addons" {
  description = "Map of cluster addon configurations to enable for the cluster"
  default = {
    coredns = {
      most_recent = true
    }
    kube-proxy = {
      most_recent = true
    }
    vpc-cni = {
      most_recent = true
    }
    eks-pod-identity-agent = {
      most_recent = true
    }
  }
}

# here should be your account ID
variable "kms_key_administrators" {
  description = "A list of IAM ARNs for key administrators"
  default     = ["arn:aws:iam::
78-your-account-id-27:root"]
}

variable "enable_cluster_creator_admin_permissions" {
  description = "Indicates whether or not to add the cluster creator (the identity used by Terraform) as an administrator via access entry"
  default     = true
}


Деякі пояснення до коду:

  • вичитуємо terraform стейт vpc із s3 задля отримання параметрів мережі, в яких буде встановлено EKS кластер
  • вказуємо, що плануємо використовувати eks_managed_node_group. Є ще self_managed_node_groups, де потрібно самостійно менеджити воркера. Їх створення також підтримує цей модуль
  • control plane кластеру буде мати зв'язок із зовнішнім світом завдяки параметру cluster_endpoint_public_access. Це зручно для роботи через kubectl на локальній системі, хоч він і зовсім не обов'язковий, якщо є зв'язок до кластеру через приватну мережу
  • node_security_group_additional_rules дозволяє input/output трафік між воркерами. Це нормально для сервісів, що будуть працювати між собою. Цих правил може бути недостаньо на етапі додавання інших контролерів.

Всі існуючі параметри добре описані в документації до модуля.

Ініціюємо додаткові модулі та стартуємо створення кластеру EKS:

$ terraform init
$ terraform plan
$ terraform apply


Перевіримо на простому додатку чи коректно працює Kubernetes. Але попередньо оновимо kubeconfig:

$ aws eks update-kubeconfig --region us-east-1 --name my-eks-NbP3tleo
Added new context arn:aws:eks:us-east-1:78-your-account-id-27:cluster/my-eks-NbP3tleo to /home/ipeacocks/.kube/config

Нагадаю, що ім'я кластеру було згенеровано із рандомною частиною, тобто воно унікальне. Його можна взяти із terraform output.

$ kubectl get pods -A 

NAMESPACE     NAME                           READY   STATUS    RESTARTS   AGE
kube-system   aws-node-5dt4w                 2/2     Running   0          3h9m
kube-system   aws-node-mntwk                 2/2     Running   0          3h9m
kube-system   coredns-d69f548b6-85stl        1/1     Running   0          3h9m
kube-system   coredns-d69f548b6-bxzkl        1/1     Running   0          3h9m
kube-system   eks-pod-identity-agent-2q4xp   1/1     Running   0          3h9m
kube-system   eks-pod-identity-agent-8thcr   1/1     Running   0          3h9m
kube-system   kube-proxy-fwzss               1/1     Running   0          3h9m
kube-system   kube-proxy-zvbcf               1/1     Running   0          3h9m


Із коробки можна користуватись LoadBalancer-типом сервісу:

$ kubectl apply -f - <<EOF

---
apiVersion: v1
kind: Pod
metadata:
  name: influxdb
  labels:
    name: influxdb
spec:
  containers:
    - name: influxdb
      image: influxdb
      ports:
        - containerPort: 8086
---
kind: Service
apiVersion: v1
metadata:
  name: influxdb
spec:
  type: LoadBalancer
  ports:
    - port: 8086
  selector:
    name: influxdb

EOF

$ kubectl get pods,svc

NAME           READY   STATUS    RESTARTS   AGE
pod/influxdb   1/1     Running   0          2m48s

NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)          AGE
service/influxdb     LoadBalancer   172.20.174.214   a9d00415ab949428b901b9918c87b97d-1421917068.us-east-1.elb.amazonaws.com   8086:30132/TCP   2m47s

$ curl -I a9d00415ab949428b901b9918c87b97d-1421917068.us-east-1.elb.amazonaws.com:8086
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public, max-age=3600
Content-Length: 534
Content-Type: text/html; charset=utf-8
Etag: "5342613538"
Last-Modified: Wed, 26 Apr 2023 13:05:38 GMT
X-Influxdb-Build: OSS
X-Influxdb-Version: v2.7.1
Date: Sat, 03 Jun 2023 23:03:26 GMT
 

Проте у такому разі буде створено Classic LB у публічній підмережі, що дорого на великих об'ємах додатків, адже буде необхідний окремий балансувальник для кожного.


3. INSTALLATION OF AWS LOAD BALANCER CONTROLLER (IRSA)
 

AWS рекомендує використовувати свій контролер для Kubernetes сервісів та Ingress. На відміну від дефолтного підходу із Classic LB для Kubernetes LoadBalancer сервісу, про який я згадав вище, AWS Load Balancer Controller пропонує NLB балансувальник, а для Ingress об'єктів - ALB (цього разу різні target-групи дозволять обслуговувати різні домени).

Як і раніше скористаємось Terraform-ом та офіційним helm-чартом AWS Load Balancer Controller для його установки:

$ cd ../addons/lb-controller
$ cat main.tf

 
data "terraform_remote_state" "eks" {
  backend = "s3"

  config = {
    bucket = "my-tf-state-2023-06-01"
    key    = "my-eks.tfstate"
    region = "us-east-1"
  }
}

module "irsa_role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "5.44.0"

  role_name                              = "eks-lb-controller-${data.terraform_remote_state.eks.outputs.cluster_name}"
  attach_load_balancer_controller_policy = true

  oidc_providers = {
    ex = {
      provider_arn               = data.terraform_remote_state.eks.outputs.oidc_provider_arn
      namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
    }
  }
}

resource "helm_release" "this" {
  name       = "aws-load-balancer-controller"
  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-load-balancer-controller"
  version    = var.helm_package_version
  namespace  = "kube-system"

  set {
    name  = "clusterName"
    value = data.terraform_remote_state.eks.outputs.cluster_name
  }

  set {
    name  = "serviceAccount.create"
    value = "true"
  }

  set {
    name  = "serviceAccount.name"
    value = "aws-load-balancer-controller"
  }

  set {
    name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
    value = module.irsa_role.iam_role_arn
  }
}

Тут можна переглянути офіційні рекомендації по установці контролера, проте ми скористались готовим стороннім модулем . Якщо коротко, то цього разу на стороні AWS IAM ми створюємо:

  • окрему policy, тобто всі дозволи на об'єкти та дії, із котрими має працювати контролер
  • ресурсну роль із цією policy
  • та AssumeRole до ресурсної ролі, котра надає право користування вищезгаданої policy кластеру EKS, через oidc-провайдер. Вона логічно прив'язується до ролі, згаданої вище

У свою чергу в самому EKS має бути доданий ServiceAccount і через анотації описана ця роль (це робить helm-чарт контролера). Service account надає можливість користування роллю всередині Kubernetes подів.

Після всього контролер встановлюється із офіційного helm-чарту. Базові параметри винесені в variables.tf:

$ cat variables.tf

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "credentials" {
  default     = ["~/.aws/credentials"]
  description = "where your access and secret_key are stored, you create the file when you run the aws config"
}

variable "helm_package_version" {
  type        = string
  description = "Version of the helm package."
  default     = "1.8.2"
}


Застосуємо код для установки AWS Load Balancer Controller-а:

$ terraform init
$ terraform plan
$ terraform apply


Не буде зайвим також перевірити роботу подів контролера:

$ kubectl get svc,deploy -A | grep aws-load-balancer
kube-system   service/aws-load-balancer-webhook-service   ClusterIP   172.20.20.174   <none>        443/TCP         46s
kube-system   deployment.apps/aws-load-balancer-controller   2/2     2            2           46s

 

4. INSTALLATION OF AWS LOAD BALANCER CONTROLLER (POD IDENTITY)

У випадку використання воркерів на EC2 вузлах можна скористатись pod-identity замість IRSA. Pod-identity addon було активовано на етапі установки EKS і він має доволі багато обмежень, адже далеко не універсальний: він не може працювати із воркерами на Fargate, OS Windows, із контролерами, що надають функціональність різноманітний дискових томів тощо.

Проте pod-identity дещо простіший в контексті конфігурації, адже не потребує вказання ролі в ServiceAccount додатку і його асоціація відбувається в контексті налаштувань EKS кластеру AWS.

Тому розглянемо установку AWS LB контролера із pod identity. Єдина відмінність у змісті основного main.tf:

$ cat main.tf

data "terraform_remote_state" "eks" {
  backend = "s3"

  config = {
    bucket = "my-tf-state-2023-06-01"
    key    = "my-eks.tfstate"
    region = "us-east-1"
  }
}

module "aws_lb_controller_pod_identity" {
  source  = "terraform-aws-modules/eks-pod-identity/aws"
  version = "v1.4.1"

  name = "aws-lbc"

  attach_aws_lb_controller_policy = true

  # Pod Identity Associations
  association_defaults = {
    namespace       = "kube-system"
    service_account = "aws-load-balancer-controller"
  }

  associations = {
    one = {
      cluster_name = data.terraform_remote_state.eks.outputs.cluster_name
    }
  }
}

resource "helm_release" "this" {
  name       = "aws-load-balancer-controller"
  repository = "https://aws.github.io/eks-charts"
  chart      = "aws-load-balancer-controller"
  version    = var.helm_package_version
  namespace  = "kube-system"

  set {
    name  = "clusterName"
    value = data.terraform_remote_state.eks.outputs.cluster_name
  }

  set {
    name  = "serviceAccount.create"
    value = "true"
  }

  set {
    name  = "serviceAccount.name"
    value = "aws-load-balancer-controller"
  }

}

Деякі пояснення до коду:

  • aws_lb_controller_pod_identity модуль створить окрему роль для pod identity, trusted relationship та відповідну асоціацію в EKS кластері:

 

     Асоціація прив'язується до імені сервіс аккаунта в K8s та неймспейсу:

  • Далі ми встановлюємо сам хелм чарт, де вказуємо ім'я сервіс аккаунту для того, щоб він чітко співпадав із іменем переданим в попередній модуль

Тож тепер все працюватиме і без указання імені ролі в ServiceAccount:

$ kubectl get sa aws-load-balancer-controller -n kube-system -o yaml

apiVersion: v1
automountServiceAccountToken: true
kind: ServiceAccount
metadata:
  annotations:
    meta.helm.sh/release-name: aws-load-balancer-controller
    meta.helm.sh/release-namespace: kube-system
  creationTimestamp: "2024-09-17T23:16:57Z"
  labels:
    app.kubernetes.io/instance: aws-load-balancer-controller
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: aws-load-balancer-controller
    app.kubernetes.io/version: v2.8.2
    helm.sh/chart: aws-load-balancer-controller-1.8.2
  name: aws-load-balancer-controller
  namespace: kube-system
  resourceVersion: "10936"
  uid: 7a521728-e3c7-48b0-a4bd-dc75379eaf4c

Обидва варіанти приведені в репозиторії, тож можна обрати підходящий за власним смаком.

 

5. TESTING OF AWS LB CONTROLLER. INGRESS

Нарешті, протестуємо роботу контролера, для чого встановимо тестовий додаток echoserver:

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: echoserver
---
apiVersion: v1
kind: Service
metadata:
  name: echoserver
  namespace: echoserver
spec:
  ports:
    - port: 8080
      targetPort: 8080
      protocol: TCP
  selector:
    app: echoserver
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echoserver
  namespace: echoserver
spec:
  selector:
    matchLabels:
      app: echoserver
  replicas: 2
  template:
    metadata:
      labels:
        app: echoserver
    spec:
      containers:
      - image: k8s.gcr.io/e2e-test-images/echoserver:2.5
        imagePullPolicy: Always
        name: echoserver
        ports:
        - containerPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: echoserver
  namespace: echoserver
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/group.name: my-group

spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
          - path: /echoserver
            pathType: Exact
            backend:
              service:
                name: echoserver
                port:
                  number: 8080
EOF
namespace/echoserver created
service/echoserver created
deployment.apps/echoserver created
ingress.networking.k8s.io/echoserver created


Для роботи Ingress, у випадку "target-type: ip", потреби в NodePort типу сервіса немає, адже адресація буде пряма на IP-адреси кожного поду в деплойменті.

Перевіримо роботу додатку через kubectl:

$ kubectl get all,ing -n echoserver
NAME                            READY   STATUS    RESTARTS   AGE
pod/echoserver-d46bc6b9-hrnc4   1/1     Running   0          15m
pod/echoserver-d46bc6b9-xjf2f   1/1     Running   0          15m

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/echoserver   ClusterIP   172.20.132.49   <none>        8080/TCP   18m

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/echoserver   2/2     2            2           18m

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/echoserver-d46bc6b9     2         2         2       15m

NAME                                   CLASS   HOSTS   ADDRESS                                                        PORTS   AGE
ingress.networking.k8s.io/echoserver   alb     *       k8s-mygroup-de245f3330-926431661.us-east-1.elb.amazonaws.com   80      18m

Виділену адресу Інгреса перевіряємо за допомогою curl:

$ curl k8s-mygroup-de245f3330-926431661.us-east-1.elb.amazonaws.com/echoserver


Hostname: echoserver-d46bc6b9-hrnc4

Pod Information:
        -no pod information available-

Server values:
        server_version=nginx: 1.14.2 - lua: 10015

Request Information:
        client_address=10.0.102.27
        method=GET
        real path=/echoserver
        query=
        request_version=1.1
        request_scheme=http
        request_uri=http://k8s-mygroup-de245f3330-926431661.us-east-1.elb.amazonaws.com:8080/echoserver

Request Headers:
        accept=*/*
        host=k8s-mygroup-de245f3330-926431661.us-east-1.elb.amazonaws.com
        user-agent=curl/7.88.1
        x-amzn-trace-id=Root=1-648e50ab-0fd2ef1438b7570b348ce574
        x-forwarded-for=147.10.84.208
        x-forwarded-port=80
        x-forwarded-proto=http

Request Body:
        -no body in request-


Перевіримо адресацію Ingress в AWS-консолі:

У разі проблем можна переглянути роботу контролера наступною командою:

$ kubectl logs -n kube-system --tail -1 -l app.kubernetes.io/name=aws-load-balancer-controller | grep 'echoserver\/echoserver'


Додамо до цього ж ALB ще один сервіс:

$ kubectl apply -f - <<EOF
---
apiVersion: v1
kind: Namespace
metadata:
  name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: game-2048
  name: deployment-2048
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: app-2048
  replicas: 5
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app-2048
    spec:
      containers:
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: app-2048
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  namespace: game-2048
  name: service-2048
spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: NodePort
  selector:
    app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: game-2048
  name: ingress-2048
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: instance
    alb.ingress.kubernetes.io/group.name: my-group

spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: service-2048
              port:
                number: 80
EOF
namespace/game-2048 created
deployment.apps/deployment-2048 created
service/service-2048 created
ingress.networking.k8s.io/ingress-2048 created


$ curl -I k8s-mygroup-de245f3330-926431661.us-east-1.elb.amazonaws.com

HTTP/1.1 200 OK
Date: Sun, 18 Jun 2023 00:52:37 GMT
Content-Type: text/html
Content-Length: 3988
Connection: keep-alive
Server: nginx
Last-Modified: Wed, 06 Oct 2021 17:35:37 GMT
ETag: "615dde69-f94"
Accept-Ranges: byte


Цього разу, зазначивши анотацію "target-type: instance", трафік буде йти на NodePort-и кожного worker-вузла, а не напряму до портів кожного поду:

Це зроблено лише для демонстрації, хоча це єдиний варіант у разі використання інших CNI, відмінних від amazon-vpc-cni.

Загалом обидва додатки будуть додані в один HTTP-listener ALB, завдяки одній групі, що описана в анотаціях "group.name: my-group":

Якщо ж є попередньо створений TLS-сертифікат в ACM, і є бажання шифрувати трафік (а воно має бути), його підключення також можна додати до анотацій:

# TLS
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:account-id:certificate/my-lo-oo-oo-ng-id
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: '443'

У 3 рядку описана автоматична переадресація із 80 на 443 порт.


5. TESTING OF AWS LB CONTROLLER. LOAD BALANCER TYPE SERVICE

Жодних додаткових складностей із цим типом сервісу не має бути. Опишемо та застосуємо наступний додаток Kubernetes:

$ kubectl apply -f - <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nlb-sample-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: public.ecr.aws/nginx/nginx:1.21
          ports:
            - name: tcp
              containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nlb-sample-service
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: external
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing

spec:
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  type: LoadBalancer
  selector:
    app: nginx
EOF
deployment.apps/nlb-sample-app created
service/nlb-sample-service created


Цього разу, завдяки AWS LB контролеру, на допомогу прийде NLB балансувальник. Якщо додатку потрібно буде для роботи декілька портів - то будуть створені додаткові listener-и та target-групи. Проте використати той самий балансер, як у випадку із Ingress, не вийде. Але здається таку логіку можна релізувати із TargetGroupBinding, хоча це дещо псує ідеї Kubernetes як cloud-agnostic системи. Але вже як є.

$ kubectl get svc
NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP                                                                    PORT(S)        AGE
...
nlb-sample-service   LoadBalancer   172.20.234.223   k8s-default-nlbsampl-85a45b3c11-ff31fead319d688c.elb.us-east-1.amazonaws.com   80:30714/TCP   14m

$ curl -I k8s-default-nlbsampl-85a45b3c11-ff31fead319d688c.elb.us-east-1.amazonaws.com
HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Mon, 19 Jun 2023 11:55:28 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
Connection: keep-alive
ETag: "61f01158-267"
Accept-Ranges: bytes

Всі опції на стороні AWS у обох випадках налаштовуються через анотації, із ними можна ознайомитись за посиланням.
А увесь інший Terraform-код, згаданий у цій статті, знаходиться у моєму репозиторію.

Посилання:
https://docs.aws.amazon.com/eks/latest/userguide/aws-load-balancer-controller.html
https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
https://repost.aws/knowledge-center/eks-subnet-auto-discovery-alb
https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/main/docs/examples/echo_server.md
https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.6/deploy/installation/
https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.6/guide/service/nlb/
https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.6/deploy/subnet_discovery
https://kubernetes.io/docs/concepts/security/service-accounts
https://github.com/kubernetes-sigs/aws-load-balancer-controller
https://artifacthub.io/packages/helm/aws/aws-load-balancer-controller

Немає коментарів:

Дописати коментар