Gerenciar um cluster Kubernetes em bare metal pode parecer complicado, mas com as ferramentas certas, o processo se torna mais simples e eficiente. Este artigo explica como criar uma configuração com três nós de plano de controle (master) e quatro máquinas de trabalho, tudo dinamicamente em servidores bare metal. A ideia é simular um cenário onde um data center fornece as máquinas físicas, utilizando Talos para inicialização via rede com PXE e a Sidero Cluster API para conectar tudo. Vamos descobrir como essa abordagem pode facilitar a vida dos administradores de sistemas.
Preparando o Ambiente com DHCP e Sidero
Para começar, vamos configurar um servidor DHCP. No entanto, não será necessário um servidor de inicialização next-server e TFTP, pois o controller-manager da Sidero já implementa isso, incluindo um DHCP proxy. A versão 0.6 da Sidero já vem com esse DHCP proxy, que facilita a atribuição de IPs às máquinas, integrando automaticamente as instruções de boot PXE. A configuração do dnsmasq ficaria assim:
services:
dnsmasq:
image: quay.io/poseidon/dnsmasq:v0.5.0-32-g4327d60-amd64
container_name: dnsmasq
cap_add:
- NET_ADMIN
network_mode: host
command: >
-d -q -p0
--dhcp-range=10.1.1.3,10.1.1.30
--dhcp-option=option:router,10.1.1.1
--log-queries
--log-dhcp
Para rodar a Sidero, é preciso primeiro um Management cluster do Kubernetes. Ele precisa ser a versão 1.26 ou superior, com capacidade de expor serviços TCP e UDP para as máquinas do workload cluster e acesso via talosctl kubeconfig
.
Criaremos um cluster de um nó com allowSchedulingOnControlPlanes: true
, o que permite rodar o workload nos nós do plano de controle. Veja como o nó aparece:
k get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
talos-74m-2r5 Ready control-plane 11m v1.31.2 10.1.1.18 <none> Talos (v1.8.3) 6.6.60-talos containerd://2.0.0
Com a Sidero inclusa como provedora de infraestrutura padrão no clusterctl, instalar tanto a Sidero quanto os componentes da Cluster API (CAPI) se resume a usar a ferramenta clusterctl.
Primeiro, dizemos para a Sidero usar hostNetwork: true
para que ela vincule suas portas diretamente ao host, em vez de estar disponível só de dentro do cluster. Há várias formas de expor os serviços, mas essa é a mais simples para o management cluster de um nó. Ao escalar o management cluster, será preciso usar outro método, como um load balancer externo ou algo como MetalLB.
export SIDERO_CONTROLLER_MANAGER_HOST_NETWORK=true
export SIDERO_CONTROLLER_MANAGER_DEPLOYMENT_STRATEGY=Recreate
export SIDERO_CONTROLLER_MANAGER_API_ENDPOINT=10.1.1.18
export SIDERO_CONTROLLER_MANAGER_SIDEROLINK_ENDPOINT=10.1.1.18
clusterctl init -b talos -c talos -i sidero
k get po
NAMESPACE NAME READY STATUS RESTARTS AGE
cabpt-system cabpt-controller-manager-6b8b989d68-lwxbw 1/1 Running 0 39h
cacppt-system cacppt-controller-manager-858fccc654-xzfds 1/1 Running 0 39h
capi-system capi-controller-manager-564745d4b-hbh7x 1/1 Running 0 39h
cert-manager cert-manager-5c887c889d-dflnl 1/1 Running 0 39h
cert-manager cert-manager-cainjector-58f6855565-5wf5z 1/1 Running 0 39h
cert-manager cert-manager-webhook-6647d6545d-k7qhf 1/1 Running 0 39h
sidero-system caps-controller-manager-67f75b9cb-9z2fq 1/1 Running 0 39h
sidero-system sidero-controller-manager-97cb45f57-v7cv2 4/4 Running 0 39h
curl -I http://10.1.1.18:8081/tftp/snp.efi
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 1020416
Content-Type: application/octet-stream
Gerenciando o Ambiente e Classes de Servidores
Os Environments são um recurso personalizado fornecido pelo Metal Controller Manager. Um environment é uma descrição codificada do que o servidor PXE deve retornar quando um servidor físico tenta fazer o boot via PXE.
Os environments podem ser fornecidos a um determinado servidor no nível do Server ou do ServerClass. A hierarquia de respeito é:
.spec.environmentRef provided at Server level
.spec.environmentRef provided at ServerClass level
"default" Environment created automatically and modified by an administrator
kubectl edit environment default
apiVersion: metal.sidero.dev/v1alpha2
kind: Environment
metadata:
creationTimestamp: "2024-11-23T13:16:12Z"
generation: 1
name: default
resourceVersion: "6527"
uid: 9e069ed5-886c-4b3c-9875-fe8e7f453dda
spec:
initrd:
url: https://github.com/siderolabs/talos/releases/download/v1.8.3/initramfs-amd64.xz
kernel:
args:
- console=tty0
- console=ttyS0
- consoleblank=0
- earlyprintk=ttyS0
- ima_appraise=fix
- ima_hash=sha512
- ima_template=ima-ng
- init_on_alloc=1
- initrd=initramfs.xz
- nvme_core.io_timeout=4294967295
- printk.devkmsg=on
- pti=on
- slab_nomerge=
- talos.platform=metal
url: https://github.com/siderolabs/talos/releases/download/v1.8.3/vmlinuz-amd64
Servers são o recurso básico de bare metal no Metal Controller Manager. Eles são criados fazendo o boot PXE dos servidores e permitindo que enviem uma requisição de registro ao management plane.
Server classes são uma forma de agrupar recursos de servidor distintos. Os qualificadores e as chaves de seleção permitem que o administrador especifique os critérios para agrupar esses servidores.
Criaremos duas ServerClasses para masters:
apiVersion: metal.sidero.dev/v1alpha1
kind: ServerClass
metadata:
name: masters
spec:
qualifiers:
hardware:
- system:
manufacturer: QEMU
memory:
totalSize: "12 GB"
configPatches:
- op: add
path: /machine/network/interfaces
value:
- deviceSelector:
busPath: "0*"
dhcp: true
vip:
ip: "10.1.1.50"
- op: add
path: /machine/network/nameservers
value:
- 1.1.1.1
- 1.0.0.1
- op: replace
path: /machine/install
value:
disk: none
diskSelector:
size: '< 100GB'
- op: replace
path: /cluster/network/cni
value:
name: none
# name: "custom"
# urls:
# - "https://raw.githubusercontent.com/kubebn/talos-proxmox-kaas/main/manifests/talos/cilium.yaml"
- op: replace
path: /cluster/proxy
value:
disabled: true
- op: replace
path: /machine/kubelet/extraArgs
value:
rotate-server-certificates: true
- op: replace
path: /cluster/inlineManifests
value:
- name: cilium
contents: |-
apiVersion: v1
kind: Namespace
metadata:
name: cilium
labels:
pod-security.kubernetes.io/enforce: "privileged"
- op: replace
path: /cluster/extraManifests
value:
- https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
- https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml
E para os workers:
apiVersion: metal.sidero.dev/v1alpha1
kind: ServerClass
metadata:
name: workers
spec:
qualifiers:
hardware:
- system:
manufacturer: QEMU
memory:
totalSize: "19 GB"
configPatches:
- op: add
path: /machine/network/interfaces
value:
- deviceSelector:
busPath: "0*"
dhcp: true
- op: add
path: /machine/network/nameservers
value:
- 1.1.1.1
- 1.0.0.1
- op: replace
path: /machine/install
value:
disk: none
diskSelector:
size: '< 100GB'
- op: replace
path: /cluster/proxy
value:
disabled: true
- op: replace
path: /machine/kubelet/extraArgs
value:
rotate-server-certificates: true
Vamos inicializar as máquinas:
# 3 nodes with 12GB memory
for id in {105..107}; do
qm create $id --name vm$id --memory 12288 --cores 3 --net0 virtio,bridge=vmbr0 --ostype l26 --scsihw virtio-scsi-pci --sata0 lvm1:32 --cpu host && qm start $id
done
# 4 nodes with 19GB memory
for id in {108..111}; do
qm create $id --name vm$id --memory 20288 --cores 3 --net0 virtio,bridge=vmbr0 --ostype l26 --scsihw virtio-scsi-pci --sata0 lvm1:32 --cpu host && qm start $id
done
Na ServerClass, usamos a diferença entre as alocações de memória para os nós:
hardware:
- system:
manufacturer: QEMU
memory:
totalSize: "12 GB" # masters
---
totalSize: "19 GB" # workers
kubectl get serverclasses
NAME AVAILABLE IN USE AGE
any [] [] 18m
masters [] [] 9s
workers [] [] 9s
kubectl get servers
NAME HOSTNAME ACCEPTED CORDONED ALLOCATED CLEAN POWER AGE
13f56641-ff59-467c-94df-55a2861146d9 (none) true true on 96s
26f39da5-c622-42e0-b160-ff0eb58eb56b (none) true true on 75s
4e56c769-8a35-4a68-b90b-0e1dca530fb0 (none) true true on 107s
5211912d-8f32-4ea4-8738-aaff57386391 (none) true true on 96s
a0b83613-faa9-468a-9289-1aa270117d54 (none) true true on 104s
a6c8afca-15b9-4254-82b4-91bbcd76dba0 (none) true true on 96s
ec39bf0e-632d-4dca-9ae0-0b3509368de6 (none) true true on 108s
f426397a-76ff-4ea6-815a-2be97265f5e6 (none) true true on 107s
Podemos descrever o servidor para ver outros detalhes:
kubectl get server 10ea52da-e1fc-4b83-81ef-b9cd40d1d25e -o yaml
apiVersion: metal.sidero.dev/v1alpha2
kind: Server
metadata:
creationTimestamp: "2024-11-25T04:26:35Z"
finalizers:
- storage.finalizers.server.k8s.io
generation: 1
name: 10ea52da-e1fc-4b83-81ef-b9cd40d1d25e
resourceVersion: "562154"
uid: 4c08c327-8aba-4af0-8204-5985b2a76e95
spec:
accepted: false
hardware:
compute:
processorCount: 1
processors:
- coreCount: 3
manufacturer: QEMU
productName: pc-i440fx-9.0
speed: 2000
threadCount: 3
totalCoreCount: 3
totalThreadCount: 3
memory:
moduleCount: 1
modules:
- manufacturer: QEMU
size: 12288
type: ROM
totalSize: 12 GB
network:
interfaceCount: 3
interfaces:
- flags: broadcast|multicast
index: 2
mac: 36:d0:4f:23:f7:03
mtu: 1500
name: bond0
- flags: broadcast
index: 3
mac: d6:9b:8b:99:6f:d0
mtu: 1500
name: dummy0
- addresses:
- 10.1.1.6/24
flags: up|broadcast|multicast
index: 8
mac: bc:24:11:a1:77:25
mtu: 1500
name: eth0
storage:
deviceCount: 1
devices:
- deviceName: /dev/sda
productName: QEMU HARDDISK
size: 34359738368
type: HDD
wwid: t10.ATA QEMU HARDDISK QM00005
totalSize: 32 GB
system:
manufacturer: QEMU
productName: