Автоматизація налаштування Kubernetes кластера за допомогою Terraform та Ansible

Моя стаття відкрита для всіх; читачі без членства можуть натискати на цей посилання щоб прочитати повний текст.

Вступ
Автоматизація налаштування кластерів Kubernetes може значно заощадити час і зменшити кількість помилок у конфігурації, особливо в складних середовищах. Ця стаття розглядає, як оптимізувати цей процес за допомогою Terraform та Ansible — потужних інструментів для спрощення розгортання інфраструктури та керування конфігураціями. Об'єднуючи ці інструменти з kubeadm, можна швидко налаштувати повністю функціонуючі кластери Kubernetes за кілька хвилин, забезпечуючи стабільність і ефективність.

Створення знімку вузла
Першим кроком автоматизації налаштування кластера Kubernetes є створення багаторазового знімку вузла для забезпечення стабільної та надійної інфраструктури. Packer спрощує цей процес, дозволяючи програмно визначати і будувати образи машин. У цьому прикладі Packer інтегрується з Hetzner Cloud для створення знімка, налаштованого спеціально для вузлів Kubernetes.

packer {  
 required_plugins {  
 hcloud = {  
 source = "github.com/hetznercloud/hcloud"  
 version = ">= 1.2.0"  
 }  
 }  
}  

source "hcloud" "base-amd64" {  
 image = "ubuntu-22.04"  
 location = "nbg1"  
 server_type = "cx22"  
 ssh_keys = []  
 user_data = ""  
 ssh_username = "root"  
 snapshot_name = "cluster-node"  
 snapshot_labels = {  
 base = "ubuntu-24.04",  
 version = "v1.0.0",  
 name = "cluster-node"  
 }  
 token = "TOKEN"  
}  

build {  
 sources = [  
 "source.hcloud.base-amd64"  
 ]  

 provisioner "shell" {  
 scripts = [  
 "setup.sh"  
 ]  
 }  
}  

Скрипт setup.sh налаштовує мережу системи, встановлює контейнерний рендер, kubelet, kubeadm і kubectl.

cat < /etc/containerd/config.toml  
sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml  
systemctl restart containerd  
apt-get update  
apt-get install -y apt-transport-https ca-certificates curl gpg  
mkdir -p -m 755 /etc/apt/keyrings  
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg  
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list  
apt-get update  
apt-get install -y kubelet kubeadm kubectl  
apt-mark hold kubelet kubeadm kubectl  
systemctl enable --now kubelet  

Розгортання балансувальника навантаження
Цей код Terraform налаштовує провайдера для Hetzner Cloud і отримує останній знімок "cluster-node" для створення ресурсів. Також він розгортає балансувальник навантаження з ім'ям "load-balancer" із зазначеними типом та розташуванням.

terraform {  
 required_providers {  
 hcloud = {  
 source = "hetznercloud/hcloud"  
 version = "~> 1.45"  
 }  
 }  
}  

provider "hcloud" {  
 token = "TOKEN"  
}  

data "hcloud_image" "snapshot" {  
 with_selector = "name=cluster-node"  
 most_recent = true  
}  

resource "hcloud_load_balancer" "load_balancer" {  
 name = "load-balancer"  
 load_balancer_type = "lb11"  
 location = "nbg1"  
}  

Розгортання головного вузла
Використовуючи заздалегідь підготовлений знімок, ця конфігурація налаштовує головний вузол Kubernetes на Hetzner Cloud із сталими параметрами.

Він інтегрується з балансувальником навантаження для забезпечення розподілу трафіку між вузлами контрольної площини.

resource "hcloud_server" "master" {  
 name = "control-plane-1"  
 image = data.hcloud_image.snapshot.id  
 server_type = "cx22"  
 location = "nbg1"  

 depends_on = [hcloud_load_balancer.load_balancer]  

 public_net {  
 ipv4_enabled = true  
 }  
 user_data = templatefile("master-config.yaml", {  
 load_balancer_ip = "${hcloud_load_balancer.load_balancer.ipv4}"  
 })  
}  

Конфігурація cloud-init із master-config.yaml налаштовує безпечний вузол контрольної площини Kubernetes і конфігурує доступ через SSH. Вона створює адміністративного користувача kubeadmin, жорстко налаштовує параметри SSH для підвищення безпеки і ініціалізує контрольну площину Kubernetes за допомогою kubeadm, вказуючи CIDR мережі подів та точку доступу балансувальника навантаження.

#cloud-config  
users:  
 - name: kubeadmin  
 groups: users, admin, adm  
 sudo: ALL=(ALL) NOPASSWD:ALL  
 shell: /bin/bash  
 ssh_authorized_keys:  
 - PUBLIC_KEY  
runcmd:  
 - sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)KbdInteractiveAuthentication/s/^.*$/KbdInteractiveAuthentication no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)ChallengeResponseAuthentication/s/^.*$/ChallengeResponseAuthentication no/' /|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config  
 - sed -i '$a AllowUsers kubeadmin' /etc/ssh/sshd_config  
 - sudo kubeadm init --pod-network-cidr=192.168.0.0/16 --control-plane-endpoint=${load_balancer_ip}:6443  
 - service ssh restart  

Розгортання вузлів контрольної площини та робочих вузлів
Тепер потрібно створити ще 2 вузли контрольної площини та 3 робочі вузли. Конфігурація Terraform для цього схожа на конфігурацію для головного вузла, з відмінністю в іншому файлі cloud-init і використанням параметра count для створення кількох екземплярів ресурсу.

resource "hcloud_server" "control_plane" {  
 count = 2  
 name = "control-plane-${count.index + 2}"  
 image = data.hcloud_image.snapshot.id  
 server_type = "cx22"  
 location = "nbg1"  

 depends_on = [hcloud_load_balancer.load_balancer]  

 public_net {  
 ipv4_enabled = true  
 }  
 user_data = file("node-config.yaml")  
}  

resource "hcloud_server" "workers" {  
 count = 3  
 name = "worker-node-${count.index + 1}"  
 image = data.hcloud_image.snapshot.id  
 server_type = "cx22"  
 location = "nbg1"  

 public_net {  
 ipv4_enabled = true  
 }  
 user_data = file("node-config.yaml")  
}  

Вміст node-config.yaml схожий на master-config.yaml, з єдиною відмінністю: kubeadm init не викликається в node-config.yaml.

Конфігурування цілей балансувальника навантаження
Щоб балансувальник навантаження працював, потрібно налаштувати цілі, які вказують вузли контрольної площини для маршрутизації трафіку.

Крім того, потрібно налаштувати сервіс, який вказує протокол зв'язку та порти.

resource "hcloud_load_balancer_target" "load_balancer_target_master" {  
 type = "server"  
 load_balancer_id = hcloud_load_balancer.load_balancer.id  
 server_id = hcloud_server.master.id  

 depends_on = [hcloud_server.master]  
}  

resource "hcloud_load_balancer_target" "load_balancer_target_control_plane" {  
 count = 2  
 type = "server"  
 load_balancer_id = hcloud_load_balancer.load_balancer.id  
 server_id = hcloud_server.control_plane[count.index].id  

 depends_on = [hcloud_server.control_plane]  
}  

resource "hcloud_load_balancer_service" "load_balancer_service" {  
 load_balancer_id = hcloud_load_balancer.load_balancer.id  
 protocol = "tcp"  
 listen_port = 6443  
 destination_port = 6443  
}  

Отримання IP-адрес вузлів
Оскільки IP-адреси налаштованих вузлів будуть використовуватися Ansible, нам потрібно отримати їх за допомогою файлу вихідних даних Terraform (output.tf):

output "master" {  
 value = [hcloud_server.master.ipv4_address]  
}  

output "control_plane" {  
 value = hcloud_server.control_plane[*].ipv4_address  
}  

output "workers" {  
 value = hcloud_server.workers[*].ipv4_address  
}  

З конфігурацією все готово, інфраструктуру можна налаштувати за допомогою terraform apply -auto-approve. Однак на даному етапі цього робити не потрібно, оскільки вона буде використана пізніше в сценарії розгортання на Bash.

Конфігурація інвентарю Ansible
Для того щоб Ansible мав доступ до вузлів, потрібно створити конфігурацію інвентарю Ansible та вказати IP-адреси вузлів разом з приватним ключем SSH та іменем користувача. Для уникнення ручного втручання, цей крок можна автоматизувати за допомогою простого Bash-скрипта.

master=($(terraform output -json | jq -r '.master.value[]'))  
control_plane=($(terraform output -json | jq -r '.control_plane.value[]'))  
workers=($(terraform output -json | jq -r '.workers.value[]'))  
ini_file="inventory_config.ini"  

echo "[master]" >"$ini_file"  
for ip in $master; do  
 echo "$ip ansible_ssh_private_key_file=key ansible_user=kubeadmin" >>"$ini_file"  
done  

echo "[control_plane]" >>"$ini_file"  
for ip in ${control_plane[@]}; do  
 echo "$ip ansible_ssh_private_key_file=key ansible_user=kubeadmin" >>"$ini_file"  
done  

echo "[workers]" >>"$ini_file"  
for ip in ${workers[@]}; do  
 echo "$ip ansible_ssh_private_key_file=key ansible_user=kubeadmin" >>"$ini_file"  
done  

Цей Bash-скрипт генерує файл інвентарю Ansible (inventoryconfig.ini), отримуючи IP-адреси вузлів контрольної площини та робочих вузлів з вихідних даних Terraform у форматі JSON. Він парсить IP-адреси за допомогою _jq і записує їх в INI-файл, вказуючи приватний ключ SSH (key) та користувача (kubeadmin) для кожного вузла.

Додавання вузлів до кластера
Цей playbook Ansible автоматизує налаштування кластера Kubernetes, завантажуючи сертифікати, отримуючи ключ сертифіката та генеруючи команду kubeadm join на головному вузлі.

Ключ сертифіката та команда приєднання потім використовуються для приєднання вузлів контрольної площини та робочих вузлів до кластера.

- name: Generate kubeadm join command on the master node  
 hosts: master  
 tasks:  
 - name: Initialize config  
 shell:  
 cmd: mkdir -p $HOME/.kube && sudo /bin/cp -rf /etc/kubernetes/admin.conf $HOME/.kube/config && sudo chown $(id -u):$(id -g) $HOME/.kube/config  
 - name: Generate certificates  
 shell:  
 cmd: sudo kubeadm init phase upload-certs --upload-certs > certs  
 - name: Retrieve certificate key  
 shell:  
 cmd: cat certs | awk '/certificate key/ { getline; print; exit}'  
 register: cert_key  
 - name: Print cert key  
 debug:  
 var: cert_key.stdout  
 - name: Generate kubeadm join command  
 command: sudo kubeadm token create --print-join-command  
 register: join_command  

 - name: Save certificate key and join command  
 set_fact:  
 kubeadm_join_cmd: "{{ join_command.stdout }}"  
 kubeadm_cert_key: "{{ cert_key.stdout }}"  
 delegate_to: localhost  

- name: Use the join command to add control plane nodes to the cluster  
 hosts: control_plane  
 serial: 1  
 tasks:  
 - name: Join the control plane node to the cluster  
 command: sudo {{ hostvars[groups['master'][0]]['kubeadm_join_cmd'] }} --control-plane --certificate-key {{ hostvars[groups['master'][0]]['kubeadm_cert_key'] }}  

- name: Use the join command to add worker nodes to the cluster  
 hosts: workers  
 serial: 1  
 tasks:  
 - name: Join the worker node to the cluster  
 command: sudo {{ hostvars[groups['master'][0]]['kubeadm_join_cmd'] }}  

Розгортання та налаштування інфраструктури

Тепер давайте розглянемо файли конфігурацій, які були створені.

main.tf:

terraform {  
 required_providers {  
 hcloud = {  
 source = "hetznercloud/hcloud"  
 version = "~> 1.45"  
 }  
 }  
}  

provider "hcloud" {  
 token = "TOKEN"  
}  

data "hcloud_image" "snapshot" {  
 with_selector = "name=cluster-node"  
 most_recent = true  
}  

resource "hcloud_load_balancer" "load_balancer" {  
 name = "load-balancer"  
 load_balancer_type = "lb11"  
 location = "nbg1"  
}  

resource "hcloud_server" "master" {  
 name = "control-plane-1"  
 image = data.hcloud_image.snapshot.id  
 server_type = "cx22"  
 location = "nbg1"  

 depends_on = [hcloud_load_balancer.load_balancer]  

 public_net {  
 ipv4_enabled = true  
 }  
 user_data = templatefile("master-config.yaml", {  
 load_balancer_ip = "${hcloud_load_balancer.load_balancer.ipv4}"  
 })  
}  

resource "hcloud_server" "control_plane" {  
 count = 2  
 name = "control-plane-${count.index + 2}"  
 image = data.hcloud_image.snapshot.id  
 server_type = "cx22"  
 location = "nbg1"  

 depends_on = [hcloud_load_balancer.load_balancer]  

 public_net {  
 ipv4_enabled = true  
 }  
 user_data = file("node-config.yaml")  
}  

resource "hcloud_server" "workers" {  
 count = 3  
 name = "worker-node-${count.index + 1}"  
 image = data.hcloud_image.snapshot.id  
 server_type = "cx22"  
 location = "nbg1"  

 public_net {  
 ipv4_enabled = true  
 }  
 user_data = file("node-config.yaml")  
}  

resource "hcloud_load_balancer_target" "load_balancer_target_master" {  
 type = "server"  
 load_balancer_id = hcloud_load_balancer.load_balancer.id  
 server_id = hcloud_server.master.id  

 depends_on = [hcloud_server.master]  
}  

resource "hcloud_load_balancer_target" "load_balancer_target_control_plane" {  
 count = 2  
 type = "server"  
 load_balancer_id = hcloud_load_balancer.load_balancer.id  
 server_id = hcloud_server.control_plane[count.index].id  

 depends_on = [hcloud_server.control_plane]  
}  

resource "hcloud_load_balancer_service" "load_balancer_service" {  
 load_balancer_id = hcloud_load_balancer.load_balancer.id  
 protocol = "tcp"  
 listen_port = 6443  
 destination_port = 6443  
}  

master-config.yaml:

#cloud-config  
users:  
 - name: kubeadmin  
 groups: users, admin, adm  
 sudo: ALL=(ALL) NOPASSWD:ALL  

**node-config.yaml**:

cloud-config

users:
- name: kubeadmin
groups: users, admin, adm
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
sshauthorizedkeys:
- PUBLICKEY
runcmd:
- sed -i -e '/^(#|)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd
config
- sed -i -e '/^(#|)PasswordAuthentication/s/^.$/PasswordAuthentication no/' /etc/ssh/sshd_config
- sed -i -e '/^(#|)KbdInteractiveAuthentication/s/^.
$/KbdInteractiveAuthentication no/' /etc/ssh/sshdconfig
- sed -i -e '/^(#|)ChallengeResponseAuthentication/s/^.*$/ChallengeResponseAuthentication no/' /etc/ssh/sshd
config
- sed -i -e '/^(#|)MaxAuthTries/s/^.$/MaxAuthTries 2/' /etc/ssh/sshd_config
- sed -i -e '/^(#|)AllowTcpForwarding/s/^.
$/AllowTcpForwarding no/' /etc/ssh/sshdconfig
- sed -i -e '/^(#|)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd
config
- sed -i -e '/^(#|)AllowAgentForwarding/s/^.$/AllowAgentForwarding(#|)AuthorizedKeysFile/s/^.$/AuthorizedKeysFile .ssh\/authorizedkeys/' /etc/ssh/sshdconfig
- sed -i '$a AllowUsers kubeadmin' /etc/ssh/sshdconfig
- sudo kubeadm init --pod-network-cidr=192.168.0.0/16 --control-plane-endpoint=${load
balancer_ip}:6443
- service ssh restart
```

node-config.yaml:

#cloud-config  
users:  
 - name: kubeadmin  
 groups: users, admin, adm  
 sudo: ALL=(ALL) NOPASSWD:ALL  
 shell: /bin/bash  
 ssh_authorized_keys:  
 - PUBLIC_KEY  
runcmd:  
 - sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)KbdInteractiveAuthentication/s/^.*$/KbdInteractiveAuthentication no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)ChallengeResponseAuthentication/s/^.*$/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config  
 - sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config  
 - sed -i '$a AllowUsers kubeadmin' /etc/ssh/sshd_config  
 - service ssh restart  

output.tf:

output "master" {  
 value = [hcloud_server.master.ipv4_address]  
}  

output "control_plane" {  
 value = hcloud_server.control_plane[*].ipv4_address  
}  

output "workers" {  
 value = hcloud_server.workers[*].ipv4_address  
}  

cluster-node.pkr.hcl:

packer {  
 required_plugins {  
 hcloud = {  
 source = "github.com/hetznercloud/hcloud"  
 version = ">= 1.2.0"  
 }  
 }  
}  

source "hcloud" "base-amd64" {  
 image = "ubuntu-22.04"  
 location = "nbg1"  
 server_type = "cx22"  
 ssh_keys = []  
 user_data = ""  
 ssh_username = "root"  
 snapshot_name = "cluster-node"  
 snapshot_labels = {  
 base = "ubuntu-24.04",  
 version = "v1.0.0",  
 name = "cluster-node"  
 }  
 token = "TOKEN"  
}  

build {  
 sources = [  
 "source.hcloud.base-amd64"  
 ]  

 provisioner "shell" {  
 scripts = [  
 "setup.sh"  
 ]  
 }  
}  

setup.sh:

cat < /etc/containerd/config.toml  
sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml  
systemctl restart containerd  
apt-get update  
apt-get install -y apt-transport-https ca-certificates curl gpg  

## **command.yml**:

  • name: Generate kubeadm join command on the master node
    hosts: master
    tasks:

    • name: Initialize config
      shell:
      cmd: mkdir -p $HOME/.kube && sudo /bin/cp -rf /etc/kubernetes/admin.conf $HOME/.kube/config && sudo chown $(id -u):$(id -g) $HOME/.kube/config
    • name: Generate certificates
      shell:
      cmd: sudo kubeadm init phase upload-certs --upload-certs > certs
    • name: Retrieve certificate key
      shell:
      cmd: cat certs | awk '/certificate key/ { getline; print; exit}'
      register: cert_key
    • name: Print cert key
      debug:
      var: cert_key.stdout
    • name: Generate kubeadm join command
      command: sudo kubeadm token create --print-join-command
      register: join_command
    • name: Save certificate key and join command
      setfact:
      kubeadm
      joincmd: "{{ joincommand.stdout }}"
      kubeadmcertkey: "{{ certkey.stdout }}"
      delegate
      to: localhost
  • name: Use the join command to add control plane nodes to the cluster
    hosts: control_plane
    serial: 1
    tasks:

    • name: Join the control plane node to the cluster
      command: sudo {{ hostvars[groups['master'][0]]['kubeadmjoincmd'] }} --control-plane --certificate-key {{ hostvars[groups['master'][0]]['kubeadmcertkey'] }}
  • name: Use the join command to add worker nodes to the cluster
    hosts: workers
    serial: 1
    tasks:

    • name: Join the worker node to the cluster
      command: sudo {{ hostvars[groups['master'][0]]['kubeadmjoincmd'] }}
      ```

deploy.sh:

packer build .  

shell:

mkdir -p -m 755 /etc/apt/keyrings  
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg  
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list  
apt-get update  
apt-get install -y kubelet kubeadm kubectl  
apt-mark hold kubelet kubeadm kubectl  
systemctl enable --now kubelet  

## Команди для Packer, Terraform та Ansible були додані до Bash-скрипта, щоб зробити _deploy.sh_ єдиним скриптом, який потрібно запускати при створенні Kubernetes кластеру.

Після заміни TOKEN у всіх файлах на актуальний токен Hetzner Cloud, для створення високодоступного Kubernetes кластеру потрібно лише виконати одну команду:

sh deploy.sh
```

Через кілька хвилин кластер стане доступним. Після цього можна підключитись через SSH до головного вузла і використати kubectl для налаштування CNI та розгортання робочих навантажень.

Висновок

У цій статті ми пройшли процес створення високодоступного та масштабованого Kubernetes кластеру, використовуючи Terraform, Packer та Ansible — три інструменти, які спрощують та автоматизують управління інфраструктурою. Цей підхід забезпечує повторюваність та легкість в управлінні, надаючи можливість ефективно виконувати завдання з інфраструктури.

Дякуємо за увагу!

Перекладено з: Automating Kubernetes cluster setup by using Terraform and Ansible

Leave a Reply

Your email address will not be published. Required fields are marked *