Моя стаття відкрита для всіх; читачі без членства можуть натискати на цей посилання щоб прочитати повний текст.
Вступ
Автоматизація налаштування кластерів 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/sshdconfig
- 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/sshdconfig
- 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/sshdconfig
- 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=${loadbalancer_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:
kubeadmjoincmd: "{{ joincommand.stdout }}"
kubeadmcertkey: "{{ certkey.stdout }}"
delegateto: localhost
- name: Initialize config
-
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: Join the control plane node to the cluster
-
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'] }}
```
- name: Join the worker node to the cluster
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