В посте Kubernetes: обновление DNS в Route53 при создании Ingress выполнили ручную установку ExternalDNS, и посмотрели, как он работает – пора добавить автоматизацию его установки на кластера.
В роли Configuration Management Tool у нас используется Ansible, для которого существует модуль community.kubernetes
– используем его.
Вообще, есть много модулей для работы с Helm, например – helm module
или community.general
, но у них есть недостаток – нельзя передать кастомный kubeconfig, тогда как у нас во время провижена кластера генерируется файл, которым потом пользуется kubectl
/helm
.
Собственно, что будем делать:
- создадим IAM-политику для доступа к Route53
- IAM роль, к которой подключим эту политику – она будет использоваться через AussemeRole подом с ExternalDNS
- создадим IAM пользователя с Programmatic Access, который сможет ассюмить эту роль
- обновим Ansible-роль, которая у на занимается установкой всех контроллеров в создаваемые кластера
Содержание
AWS IAM
ExternalDNS policy
Переходим в AWS > IAM > Policies, создаём политику, которая разрешает доступ к Route53:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "route53:ChangeResourceRecordSets" ], "Resource": [ "arn:aws:route53:::hostedzone/*" ] }, { "Effect": "Allow", "Action": [ "route53:ListHostedZones", "route53:ListResourceRecordSets" ], "Resource": [ "*" ] } ] }
Назовём её iam-bttrm-external-dns-route53-policy, сохраняем.
ExternalDNS role
Далее – создаём роль:
В Permissions находим созданную политику:
Сохраняем:
Открываем её Trust relationships, редактируем:
Разрешаем AssumeRole этой роли всем IAM-пользователям нашего AWS-аккаунта – “arn:aws:iam::534***385:root“, где 534***385 – ID аккаунта:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::534***385:root" }, "Action": "sts:AssumeRole" } ] }
AssumeRole Allow policy
Создаём политику, которая будет разрешать выполнение AssumeRole
этой роли:
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::534***385:role/iam-bttrm-eks-external-dns-role" } }
Сохраняем её:
IAM User
И наконец-то создаём пользователя, используя ACCESS и SECRET ключи которого ExternalDNS под будет выполнять API-запросы к Route53:
Подключаем ему политику, которая позволит ему выполнять AssumeRole роли “arn:aws:iam::534***385:role/iam-bttrm-eks-external-dns-role“:
Сохраняем его ключи, и переходим к Ansible.
Ansible
Сначала создадим переменные, потом обновим tasks
нашей Ansible-роли.
Переменные и ansible-vault
Будем использовать три переменные – external_dns_iam_role_arn
, external_dns_iam_user_access_key
и external_dns_iam_user_secret_key
.
Значение для external_dns_iam_user_secret_key
зашифруем используя ansible-vault
, т.к. файлы Ansible хранятся в репозитории, хоть и приватном, откуда потом используются в Jenkins-джобе.
Вызываем ansible-vaul
t, шифруем строку с AWS_ACCESS_SECRET_KEY
:
[simterm]
$ ansible-vault encrypt_string New Vault password: Confirm New Vault password: Reading plaintext input from stdin. (ctrl-d to end input, twice if your content does not already have a newline) gVo***JN1 !vault | $ANSIBLE_VAULT;1.1;AES256 39303830623230306361633733343661393761323135313165393363646461646566306661633464 ... 3636333864656336396631396263313766633930386236633365 Encryption successful
[/simterm]
Редактируем файл с переменными, в нашем случае всё хранится в group_vars/all.yml
, добавляем три новых:
external_dns_iam_role_arn: "arn:aws:iam::534***385:role/iam-bttrm-eks-external-dns-role" external_dns_iam_user_access_key: "AKI***RUN" external_dns_iam_user_secret_key: !vault | $ANSIBLE_VAULT;1.1;AES256 39303830623230306361633733343661393761323135313165393363646461646566306661633464 ... 3636333864656336396631396263313766633930386236633365
Ansible роль
Для установки всяких контроллеров типа ALB Ingress Controller у нас написана отдельная Ansible-роль, сюда же впишем и установку ExternalDNS.
Для этого пишем три task
– установка модуля community.kubernetes
, добавление Helm-репозитория Bitnami, и собственно установка Helm-чарта с ExternalDNS:
- name: "Install Ansible community.kubernetes plugin" command: "ansible-galaxy collection install community.kubernetes" - name: "Add Bitnami chart repo" community.kubernetes.helm_repository: name: "bitnami" repo_url: "https://charts.bitnami.com/bitnami" - name: "Deploy ExternalDNS chart inside {{ eks_env }}-devops-external-dns-ns namespace (and create it)" community.kubernetes.helm: kubeconfig: "{{ kube_config_path }}" name: "external-dns" chart_ref: "bitnami/external-dns" release_namespace: "{{ eks_env }}-devops-external-dns-ns" create_namespace: true values: domainFilters: - example.com aws: credentials: accessKey: "{{ external_dns_iam_user_access_key }}" secretKey: "{{ external_dns_iam_user_secret_key }}" assumeRoleArn: "{{ external_dns_iam_role_arn }}"
kube_config_path
передаётся из другой Ansible-роли, которая после создания кластера с eksctl
генерирует kubeconf, см. AWS Elastic Kubernetes Service: — автоматизация создания кластера, часть 2 — Ansible, eksctl.
eks_env
передаётся из параметров Jenkins-джобы, в котором задаётся имя создаваемого кластера, в данном случае это будет dev-2, используя который формируем значение для переменной release_namespace
– dev-2-devops-external-dns-ns.
В values
передаём значения для чарта – параметры самого ExternalDNS.
Jenkins job
Тут подробно останавливаться не буду – описано в посте Helm: пошаговое создание чарта и деплоймента из Jenkins.
В двух словах – есть stage Apply:
... stage("Apply") { provision.ansibleApply( "${PLAYBOOK}", "${env.TAGS}", "${PASSFILE_ID}") } ...
Который вызывает функцию ansibleApply()
:
def ansibleApply(playbookFile='1', tags='2', passfile_id='3') { withCredentials([file(credentialsId: "${passfile_id}", variable: 'passfile')]) { docker.image('projectname/kubectl-aws:4.4').inside('-v /var/run/docker.sock:/var/run/docker.sock --net=host') { sh """ aws sts get-caller-identity ansible-playbook ${playbookFile} --tags ${tags} --vault-password-file ${passfile} """ } } }
Запускаем:
SUCCESS:
Проверяем под:
[simterm]
$ kubectl -n dev-2-devops-external-dns-ns get pod NAME READY STATUS RESTARTS AGE external-dns-6b9765fd4-fv5lj 1/1 Running 0 46s
[/simterm]
И его логи:
[simterm]
$ kubectl -n dev-2-devops-external-dns-ns logs -f external-dns-6b9765fd4-fv5lj ... time="2020-11-24T08:59:03Z" level=info msg="Instantiating new Kubernetes client" time="2020-11-24T08:59:03Z" level=info msg="Using inCluster-config based on serviceaccount-token" time="2020-11-24T08:59:03Z" level=info msg="Created Kubernetes client https://172.20.0.1:443" time="2020-11-24T08:59:05Z" level=info msg="Assuming role: arn:aws:iam::534**385:role/iam-bttrm-eks-external-dns-role" time="2020-11-24T08:59:10Z" level=info msg="All records are already up to date" time="2020-11-24T09:00:11Z" level=info msg="All records are already up to date"
[/simterm]
msg=”Assuming role: arn:aws:iam::534**385:role/iam-bttrm-eks-external-dns-role” – ага, отлично.
Проверка ExternalDNS
И используем манифест из прошлого поста:
--- apiVersion: v1 kind: Namespace metadata: name: test-namespace --- apiVersion: apps/v1 kind: Deployment metadata: name: test-deployment namespace: test-namespace labels: app: test spec: replicas: 1 selector: matchLabels: app: test template: metadata: labels: app: test spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 dnsConfig: nameservers: - 169.254.20.10 dnsPolicy: None --- apiVersion: v1 kind: Service metadata: name: test-svc namespace: test-namespace spec: type: NodePort selector: app: test ports: - 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 external-dns.alpha.kubernetes.io/hostname: test-dns.example.com spec: rules: - http: paths: - backend: serviceName: test-svc servicePort: 80
Запускаем:
[simterm]
$ kubectl apply -f tests/test-deployment.yaml namespace/test-namespace created deployment.apps/test-deployment created service/test-svc created ingress.extensions/test-ingress created
[/simterm]
Проверяем Ingress:
[simterm]
$ kk -n test-namespace get ingress NAME CLASS HOSTS ADDRESS PORTS AGE test-ingress <none> * ***testnamespace***.us-east-2.elb.amazonaws.com 80 23s
[/simterm]
Логи ExternalDNS:
[simterm]
time="2020-11-24T09:18:24Z" level=info msg="Desired change: CREATE test-dns.examplecom A [Id: /hostedzone/Z07***M6]" time="2020-11-24T09:18:24Z" level=info msg="Desired change: CREATE test-dns.example.com TXT [Id: /hostedzone/Z07***M6]]" time="2020-11-24T09:18:25Z" level=info msg="2 record(s) in zone example.com. [Id: /hostedzone/Z07***M6]] were successfully updated"
[/simterm]
И работу нового домена:
[simterm]
$ dig +short test-dns.example.com 3.23.41.8 3.12.246.228
[/simterm]
Готово.