Мы пользуемся AWS ALB Ingress Controller для создания AWS Application LoadBalancer (ALB) при создании Ingress в Kubernetes.
Последняя доступная версия – v2.2.1, тогда как у нас версия 1.14.
См. changelog версии 2.0 тут>>> и документацию по обновлению тут>>>.
Из интересных обновлений:
- изменение имени: вместо AWS ALB Ingress Controller он теперь называется AWS LoadBalancer Controller
- поддержка Network Load Balancers
- IngressGroup: возможность использовать один ALB для нескольких Ingress
- TargetGroupBinding: новый Kubernetes Custom Resource (CR), позволяющий открывать доступ подам через существующие TargetGroups
Кроме того, хочется переделать его деплой: сейчас он устанавливается из голого манифест-файла, а для новой версии используем установку из Helm-шаблона AWS Load Balancer Controller.
Что сделаем:
- создадим тестовый кластер с AWS ALB Ingress Controller v1
- задеплоим туда Ingress, проверим, что AWS LoadBalancer (ALB) создан
- удалим старый Контроллер AWS ALB Ingress Controller v1
- задеплоим новый, AWS Load Balancer Controller (он же AWS ALB Ingress Controller v2)
- посмотрим различные варианты подключения IAM-политик и/или роли
- попробуем удалить Ingress и ALB, что бы убедиться в том, что контроллер версии 2 может работать с ресурсами, созданными версией 1
- столкнёмся с проблемами при удалении Ingress и AWS SecurityGroups, и посмотрим причины и варианты решений
Содержание
Тестовое приложение
Описываем Deployment, Service, Ingress:
--- apiVersion: v1 kind: Namespace metadata: name: test-namespace --- apiVersion: apps/v1 kind: Deployment metadata: name: test-deployment namespace: test-namespace labels: app: test version: v1 spec: replicas: 1 selector: matchLabels: app: test template: metadata: labels: app: test version: v1 spec: containers: - name: web image: nginxdemos/hello ports: - containerPort: 80 resources: requests: cpu: 100m memory: 100Mi readinessProbe: httpGet: path: / port: 80 --- apiVersion: v1 kind: Service metadata: name: test-svc namespace: test-namespace spec: type: NodePort selector: app: test ports: - name: http protocol: TCP port: 80 targetPort: 80 --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress namespace: test-namespace annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]' alb.ingress.kubernetes.io/inbound-cidrs: 0.0.0.0/0 spec: rules: - http: paths: - backend: serviceName: test-svc servicePort: 80
Деплоим, проверяем:
[simterm]
$ kubectl -n test-namespace get ingress NAME CLASS HOSTS ADDRESS PORTS AGE test-ingress <none> * 1c00ce34-testnamespace-tes-5874-459061000.eu-west-3.elb.amazonaws.com 80 12s $ curl -I 1c00ce31c00ce34-testnamespace-tes-5874-459061000.eu-west-3.elb.amazonaws.com HTTP/1.1 200 OK
[/simterm]
Всё работает.
Удаление AWS ALB Ingress Controller (v1)
Проверяем текущую версию:
[simterm]
$ kubectl -n kube-system describe deployment alb-ingress-controller | grep Image Image: docker.io/amazon/aws-alb-ingress-controller:v1.1.4
[/simterm]
Удаляем Deployment, ClusterRole, ClusterRoleBinding, ServiceAccount:
[simterm]
$ kubectl -n kube-system delete deploy alb-ingress-controller deployment.apps "alb-ingress-controller" deleted $ kubectl -n kube-system delete serviceaccount alb-ingress-controller serviceaccount "alb-ingress-controller" deleted $ kubectl delete clusterrole alb-ingress-controller clusterrole.rbac.authorization.k8s.io "alb-ingress-controller" deleted $ kubectl delete clusterrolebinding alb-ingress-controller clusterrolebinding.rbac.authorization.k8s.io "alb-ingress-controller" deleted
[/simterm]
Установка AWS Load Balancer Controller (v2)
Сама установка достаточно тривиальна, а вот с IAM (внезапно 🙂 ) пришлось повозиться.
Правда, не столько с самим AWS IAM – тут всё просто, сколько с тем, как именно выполнять аутентификацию и авторизацию контроллера.
IAM и ServiceAccount
Что у нас есть: есть одна новая IAM-политика, которую надо подключить к контроллеру для его работы с AWS API, и есть одна дополнительная, которая используется для работы с ресурсами, созданными контроллером первой версии.
Создать политики и/или роли можно вручную, можно описать в CloudFormation, как у нас это уже сделано для Cluster Autoscaler, см. AWS Elastic Kubernetes Service: автоматизация создания кластера, часть 1 — CloudFormation.
А для использования политик есть несколько вариантов:
- использовать ServiceAccount, в
annotations
которого будет задана IAM-роль, которую сможет Assume наш контроллер (см. AWS: IAM AssumeRole — описание, примеры). - подключить IAM-роль или политики напрямую к WorkerNodes.
И для первого, и для второго варианта тоже есть варианты:
- при использовании ServiceAccount:
- создать ServiceAccount, используя values Helm-чарт AWS Load Balancer Controller, и в его
serviceAccount.annotations
указать AIM-роль - можно создать ServiceAccount используя
eskctl
и его файлы параметров для кластера, см. IAM Roles for Service Accounts - можно создать IAM ServiceAccount, используя прямой вызов
eksctl create iamserviceaccount
, передав аргументами политики через--attach-policy-arn
- создать ServiceAccount, используя values Helm-чарт AWS Load Balancer Controller, и в его
- при использовании IAM для WorkerNodes:
- можно подключить IAM-роль с политиками или просто две политики (+дефолтные, см. документацию) через файл параметров для WorkerNode, см. Attaching policies by ARN
- можно и создать, и подключить политики через
iam.withAddonPolicies.albIngress
самогоeksctl
Учитывая факт, что у нас кластера уже есть, и обновлять их параметры лишний раз не хочется – то поступим самым костыльным простым образом:
- сами политики создадим вручную через вызов
aws iam create-policy
- используем Kubernetes ServiceAccount, который создадим и настроим прямым вызовом
eksctl create iamserviceaccount
- в values чарта укажем
serviceAccount.create
== false и зададимserviceAccount.name
, в котором передадим имя аккаунта, с которым этот ServiceAccount будет создан в п.1
Создание IAM-политик
Загружаем основную политику:
[simterm]
$ curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json
[/simterm]
Создаём её в AWS IAM с именем AWSLoadBalancerControllerIV2AMPolicy, сохраняем её ARN:
[simterm]
$ aws iam create-policy --policy-name AWSLoadBalancerControllerIV2AMPolicy --policy-document file://iam-policy.json ... Arn": "arn:aws:iam::534***385:policy/AWSLoadBalancerControllerIV2AMPolicy", ...
[/simterm]
Загружаем дополнительную политику:
[simterm]
$ wget https:/https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.0/install/iam_policy_v1_to_v2_additional.json
[/simterm]
Создаём её с именем AWSLoadBalancerControllerIV2AdditionalAMPolicy, и тоже записываем ARN:
[simterm]
$ aws iam create-policy --policy-name AWSLoadBalancerControllerIV2AdditionalAMPolicy --policy-document file://iam_policy_v1_to_v2_additional.json
[/simterm]
Ansible role
Далее, обновим Absible-роль, в которой выполняется установка всяких контроллеров.
Сейчас ALB Contoller устанавливается напрямую из манифеста, скачанного больше года тому:
... - name: "Creating ALB Ingress ServiceAccount" command: "kubectl --kubeconfig={{ kube_config_path }} apply -f roles/controllers/templates/alb-ingress-rbac-role.yml.j2" - name: "Generate ALB Ingress Controller deployment" template: src: "alb-ingress-controller.yml.j2" dest: "/tmp/alb-ingress-controller.yml" - name: "Install ALB Ingress Controller" command: "kubectl --kubeconfig={{ kube_config_path }} apply -f /tmp/alb-ingress-controller.yml" ...
Вместо этого – используем прямые вызовы AWS CLI и eksctl
, а затем – установим AWS Load Balancer Controller из его Helm-чарта.
OIDC, IAM и ServiceAccount
Удаляем или комментируем предыдущую установку, и добавляем подключение OIDC к кластеру:
... - name: "Create IAM OIDC provider" command: "eksctl --region={{ region }} --cluster={{ eks_cluster_name }} utils associate-iam-oidc-provider --approve" ...
Добавляем создание IAM ServiceAccount через прямой вызов eksctl
, которому передаём ARN-ы политик, которые создали ранее:
... - name: "Running eksctl create iamserviceaccount for the AWS ALB Controller" command: "eksctl --region={{ region }} create iamserviceaccount --cluster={{ eks_cluster_name }} --namespace=kube-system --name=aws-load-balancer-controller --attach-policy-arn=arn:aws:iam::534***385:policy/AWSLoadBalancerControllerIV2AMPolicy --attach-policy-arn=arn:aws:iam::534***385:policy/AWSLoadBalancerControllerIV2AdditionalAMPolicy --approve" ...
При использовании ClusterConfig
для eksctl
– он выглядит так:
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: "{{ eks_cluster_name }}" region: "{{ region }}" version: "{{ k8s_version }}" iam: withOIDC: true serviceAccounts: - metadata: name: aws-load-balancer-controller namespace: kube-system attachPolicyARNs: - "arn:aws:iam::534***385:policy/AWSLoadBalancerControllerIV2AMPolicy" - "arn:aws:iam::534***385:policy/AWSLoadBalancerControllerIV2AdditionalAMPolicy" ...
Будем использовать его при создании будущих новых кластеров.
AWS LoadBalancer Controller Helm-чарт
Используя модуль community.kubernetes.helm
описываем установку контроллера из чарта:
... - name: "Install Ansible community.kubernetes plugin" command: "ansible-galaxy collection install community.kubernetes" - name: "Add AWS EKS chart repo" community.kubernetes.helm_repository: name: "eks" repo_url: "https://aws.github.io/eks-charts" - name: "Deploy AWS ALB Ingress Controller chart" community.kubernetes.helm: kubeconfig: "{{ kube_config_path }}" name: "aws-load-balancer-controller" chart_ref: "eks/aws-load-balancer-controller" release_namespace: "kube-system" create_namespace: false values: clusterName: "{{ eks_cluster_name }}" enableWafv2: true enableShield: true logLevel: "debug" replicaCount: 1 vpcId: "{{ vpc_id }}" region: "{{ region }}" serviceAccount: create: false name: "aws-load-balancer-controller" ...
Указываем на то, что создавать ServiceAccount не надо, а надо использовать уже готовый.
Деплоим:
Проверяем CloudFormation стек, созданный при вызове eksctl create iamserviceaccount
:
И политики, подключенные к этой роли:
Проверяем версию AWS LoadBalancer Controller:
[simterm]
$ kubectl -n kube-system describe deployment aws-load-balancer-controller | grep Image Image: 602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon/aws-load-balancer-controller:v2.2.1
[/simterm]
Проверяем, используется ли созданный ServiceAccount:
[simterm]
$ kubectlkubectl -n kube-system get deploy aws-load-balancer-controller -o yaml | grep -w serviceAccount f:serviceAccount: {} serviceAccount: aws-load-balancer-controller
[/simterm]
И аннотации этого ServiceAccount:
[simterm]
$ kubectl -n kube-system get sa aws-load-balancer-controller -o jsonpath='{.metadata.annotations}' map[eks.amazonaws.com/role-arn:arn:aws:iam::534***385:role/eksctl-bttrm-eks-test-1-18-addon-iamservicea-Role1-NM3BLAGO21RR]
[/simterm]
Вот и наша роль.
Ошибка “failed to delete securityGroup: timed out waiting for the condition” и обновление SecurityGroup
Теперь пробуем удалить Ingress и AWS Application Loadbalancer, созданный в самом начале с использованием AWSALBIngressController версии 1:
[simterm]
$ kubectl delete -f ~/Work/devops-kubernetes/tests/test-deployment.yaml namespace "test-namespace" deleted deployment.apps "test-deployment" deleted service "test-svc" deleted ingress.extensions "test-ingress" deleted
[/simterm]
И тут kubectl
зависает.
Проверяем логи контроллера:
[simterm]
$ kubectl -n kube-system logs -f deployment/aws-load-balancer-controller ... {"level":"error","ts":1625487068.8594606,"logger":"controller","msg":"Reconciler error","controller":"ingress","name":"test-ingress","namespace":"test-namespace","error":"failed to delete securityGroup: timed out waiting for the condition"} {"level":"info","ts":1625487069.7219825,"logger":"controllers.ingress","msg":"deleting securityGroup","securityGroupID":"sg-0b70595044826c9ac"} ...
[/simterm]
Сейчас контроллер не может удалить SecrityGroup, которая была создана старым контроллером.
Case #1 Ручное добавление Description в SecurityGroup Rules
Одна из возможных причин описана в документации по миграции, и она вполне ожидаема:
SecurityGroup sg-0b70595044826c9ac – это группа, созданная контроллером при создании ALB.
Проверяем SecurityGroup нашей WorkerNode eksctl-bttrm-eks-test-1-18-2-nodegroup-common-eu-west-3a-v2021-02-SG-NGRWCUG2UJDT – находим Allow правило из группы sg-0b70595044826c9ac:
Добавляем Description со значением “elbv2.k8s.aws/targetGroupBinding=shared“:
Повторяем для всех WorkerNodes, и через 5 минут старые Ingress и AWS LoadBalancer удалены.
Case #2 Ошибки в Kubernetes Service
Второй случай заставил задуматься на пару дней и даже открыть Issue на Github.
Казалось бы – всё верно: Description в правилах есть, IAM настроен, нигде никаких ошибок нет.
Но при этом при удалении Ingress происходит странное:
- удаляются все ресурсы, кроме Kubernetes Namespace и SecurityGroup, которая была создана для ALB
- в SecurityGroup-ах, которые подключены к WorkerNodes не удаляется Rule – нет вызова
RevokeSecurityGroupIngress
, а CloudTrail сообщает об ошибкеClient.DependencyViolation
Пока копал – приходилось вычищать их вручную.
Причина оказалась очень (ожидаемо) простой: на Dev-кластере оставался один “заброшенный” Ingress и LoadBalancer, у которого был криво настроен Service (ошибка “service type must be either ‘nodeport’ or ‘loadbalancer'”).
Из-за этого Controller и не выполнял очистку SecurityGroup во всех других Ingress/ALB, хотя разработчики вроде меняют логику его работы, что бы избежать таких зависимостей.