Implantação de Cluster Kubernetes de Alta Disponibilidade com Kubespray em Máquinas Virtuais KVM

Preparação do Ambiente e Virtualização KVM

O ambiente de implantação consiste em máquinas virtuais Debian 12 (Bookworm), utilizando containerd como Container Runtime Interface (CRI), Calico como Container Network Interface (CNI) e Kubernetes na versão 1.27.10.

Inicialmente, valide se o processador do host físico suporta virtualização de hardware:

grep -wE '(vmx|svm)' /proc/cpuinfo

Instale os pacotes fundamentais para criação e gerenciamento de máquinas virtuais:

apt update
apt install -y qemu-kvm libvirt-daemon-system libvirt-clients virtinst guestfs-tools

Após a instalação, inicie o serviço libvirtd. Baixe a imagem qcow2 do Debian 12 e configure o disco virtual conforme a necessidade de expansão. Para instanciar as VMs, utilize o virt-install. Como a versão 4.1.0 do virt-install pode não reconhecer nativamente o Debian 12, utilize parâmetros genéricos:

virt-install --name k8s-cp1 --os-type linux --os-variant generic --disk ...

Para redimensionar recursos dinamicamente em VMs em execução, utilize comandos como virsh setvcpus e virsh setmem. Caso a VM não suporte redimensionamento a quente, desligue-a, edite o XML com virsh edit e ligue-a novamente.

Confgiuração de Rede e Acesso Externo

Por padrão, as VMs recém-criadas não possuem acesso à internet. Configure regras de NAT no host físico para permitir a saída de tráfego da sub-rede das VMs:

sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-k8s.conf
iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -o eth0 -j MASQUERADE

apt install iptables-persistent -y
netfilter-persistent save

Automação de Provisionamento das VMs

Instale os pacotes necessários no host para automação das configurações nas VMs:

apt -y install sshpass git python3-pip

Gere chaves SSH e distribua para os nós do cluster:

ssh-keygen -t rsa -N '' -f ~/.ssh/k8s_id -b 2048
for i in {1..3}; do
  sshpass -p'K8sPass' ssh-copy-id -i ~/.ssh/k8s_id.pub k8s-cp$i
  sshpass -p'K8sPass' ssh-copy-id -i ~/.ssh/k8s_id.pub k8s-wk$i
done

Configure os endereços IP estáticos, gateways e DNS nas VMs usando o NetworkManager:

for i in {1..3}; do
  ssh k8s-cp$i "nmcli con modify eth0 ipv4.addresses 192.168.100.1$i/24 ipv4.gateway 192.168.100.1 ipv4.method manual ipv4.dns 8.8.8.8 +ipv4.dns 1.1.1.1"
  ssh k8s-wk$i "nmcli con modify eth0 ipv4.addresses 192.168.100.2$i/24 ipv4.gateway 192.168.100.1 ipv4.method manual ipv4.dns 8.8.8.8 +ipv4.dns 1.1.1.1"
  ssh k8s-cp$i "nmcli con reload && nmcli con up eth0"
  ssh k8s-wk$i "nmcli con reload && nmcli con up eth0"
done

Atualize os repositórios APT e instale utilitários básicos nas VMs:

for i in {1..3}; do
  ssh k8s-cp$i "apt update && apt -y install vim bash-completion network-manager net-tools"
  ssh k8s-wk$i "apt update && apt -y install vim bash-completion network-manager net-tools"
done

Execução do Kubespray

Clone o repositório do Kubespray e prepare o inventário Ansible:

git clone -b release-2.23 https://github.com/kubernetes-sigs/kubespray.git
cd kubespray/
echo "ansible_ssh_pass: K8sPass" >> inventory/sample/group_vars/all/all.yml

Defina a topologia do cluster no arquivo de inventário:

cat > inventory/sample/inventory.ini <<EOF
[all]
k8s-cp1 ansible_host=192.168.100.11
k8s-cp2 ansible_host=192.168.100.12
k8s-cp3 ansible_host=192.168.100.13
k8s-wk1 ansible_host=192.168.100.21
k8s-wk2 ansible_host=192.168.100.22
k8s-wk3 ansible_host=192.168.100.23

[kube_control_plane]
k8s-cp1
k8s-cp2
k8s-cp3

[etcd]
k8s-cp1
k8s-cp2
k8s-cp3

[kube_node]
k8s-cp1
k8s-cp2
k8s-cp3
k8s-wk1
k8s-wk2
k8s-wk3

[k8s_cluster:children]
kube_control_plane
kube_node
EOF

Para evitar conflitos com pacotes Python do sistema operacional, crie um ambiente virtual para executar o playbook do Ansible:

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
ansible-playbook -i inventory/sample/inventory.ini cluster.yml -u root --private-key=~/.ssh/k8s_id -v

Operações Básicas com Pods

Após a conclusão da implantação, verifique o estado do cluster:

kubectl get nodes
kubectl get pods -A

Principais comandos para manipulação de Pods:

  • kubectl describe pod <nome> -n <namespace>: Detalha eventos e condições do Pod.
  • kubectl run <nome> --image=<imagem>: Cria um Pod de teste.
  • kubectl delete pod <nome>: Remove o Pod.
  • kubectl logs <nome>: Exibe os logs do contêiner.
  • kubectl exec -it <nome> -- /bin/sh: Acessa o terminal do contêiner.

No ecossistema de contêineres, ferramentas como crictl, nerdctl e ctr permitem a interação direta com o containerd. Um Pod pode conter contêineres do tipo Pause (Infra), Init, aplicação principal, Sidecar e Ephemeral.

A política de obtenção de imagens (imagePullPolicy) suporta três estados: Always, IfNotPresent (padrão para tags fixas) e Never. Já a política de reinício (restartPolicy) define o comportamento diante de falhas, sendo Always o padrão, além de OnFailure e Never.

Configuração de Alta Disponibilidade (HA)

O Kubespray configura o endpoint do kube-apiserevr localmente em 127.0.0.1. Para garantir alta disponibilidade real no plano de controle, implemente um balanceador de carga com Keepalived e HAProxy. Crie os seguintes templates Ansible em um dirretório dedicado:

templates/keepalived.conf.j2

global_defs {
    router_id K8S_HA_PROXY
}
vrrp_instance VI_1 {
    state MASTER
    interface {{ vrrp_interface }}
    virtual_router_id 42
    priority {{ keepalived_priority }}
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass HAPass123
    }
    virtual_ipaddress {
        {{ virtual_ip }}/32 dev {{ vrrp_interface }}
    }
}

templates/haproxy.cfg.j2

global
    log stdout format raw local0
    maxconn 4096

defaults
    log global
    mode tcp
    option tcplog
    retries 3
    timeout connect 5s
    timeout client 30m
    timeout server 30m

frontend k8s_api_frontend
    bind *:{{ haproxy_frontend_port }}
    default_backend k8s_api_backend

backend k8s_api_backend
    balance roundrobin
    {% for host in groups['kube_control_plane'] %}
    server {{ host }} {{ hostvars[host].ansible_host }}:{{ haproxy_backend_port }} check
    {% endfor %}

deploy-ha.yml

---
- name: Provisionar Keepalived e HAProxy no Plano de Controle
  hosts: kube_control_plane
  vars:
    virtual_ip: "192.168.100.50"
    keepalived_priority: 100
    vrrp_interface: "eth0"
    haproxy_frontend_port: 16443
    haproxy_backend_port: 6443
  tasks:
    - name: Instalar pacotes HA
      apt:
        update_cache: yes
        name: ['keepalived', 'haproxy']
        state: present

    - name: Configurar Keepalived
      template:
        src: templates/keepalived.conf.j2
        dest: /etc/keepalived/keepalived.conf
      notify: Reiniciar Keepalived

    - name: Configurar HAProxy
      template:
        src: templates/haproxy.cfg.j2
        dest: /etc/haproxy/haproxy.cfg
      notify: Reiniciar HAProxy

    - name: Habilitar servicos
      systemd:
        name: "{{ item }}"
        state: started
        enabled: yes
      loop: ['keepalived', 'haproxy']
  handlers:
    - name: Reiniciar Keepalived
      systemd: name=keepalived state=restarted
    - name: Reiniciar HAProxy
      systemd: name=haproxy state=restarted

Execute o playbook:

ansible-playbook -i inventory/sample/inventory.ini deploy-ha.yml -v

Atualize o arquivo ~/.kube/config nos nós de controle para apontar para o IP Virtual (VIP) na porta do HAProxy, e reexecute o playbook do Kubespray para sincronizar os certificados:

for i in {1..3}; do
  ssh k8s-cp$i "sed -i 's|server:.*|server: https://192.168.100.50:16443|' ~/.kube/config"
done
ansible-playbook -i inventory/sample/inventory.ini cluster.yml -u root --private-key=~/.ssh/k8s_id -v

Para validar a alta disponibilidade, desligue o nó que detém o VIP. O Keepalived migrará o IP para outro nó de controle e o acesso ao kube-apiserver permanecerá ininterrupto, verificável através de kubectl get --raw /healthz. O uso de máscara /32 para o VIP é a prática recomendada, pois cria uma rota de host específica e otimiza o tráfego.

Tags: kubernetes Kubespray kvm Alta Disponibilidade Keepalived

Publicado em 7-2 20:24