В посте 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-vault, шифруем строку с 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]
Готово.













