Proxmox + Kubernetes sobre contenedores LXC (1 nodo master y 1 nodo worker)
En este tutorial utilizaremos .lan como dominio local para los containes lxc
Asumimos que sabes apuntar un dns a una ip de tu LAN en los puertos 443 y 80.
crearemos un master01.lan y un worker01.lan al que llamaremos asi y no hablaremos de IP's, el tutorial funcionaria igual si no tenes resolucion de nombres y siempre que utilice master01.lan suplantes por la ip de la vm que se crea con ese nombre. y lo mismo con el worker01.lan
Preparar el Hypervisor
- Deshabilitar la swap del sistema
sysctl vm.swappiness=0
swapoff -a
- Habilitar ipv4 forwarding en el host.
echo 'net.ipv4.ip_forward=1' >> /etc/sysctl.conf
- reboot para aplicar los cambios
reboot
Crear contenedor que utilizaremos de template para el controlador y los workers. pero todavia no convertirlo en template

en "hostname" escribir template-k3s-22.04 en password setear la password de root deseada







configurar contenedor en proxmox.
- Una vez creado volver al proxmox y editar el archivo /etc/pve/lxc/[ID-DEL-FUTURO-TEMPLATE].conf
Agregar lo siguiente:
lxc.apparmor.profile: unconfined
lxc.cgroup.devices.allow: a
lxc.cap.drop:
lxc.mount.auto: "proc:rw sys:rw"
en el futuro template crear archivo rc.local
- crear archivo
$ sudo vim /etc/rc.local
Llenar con lo siguiente:
#!/bin/sh -e
# Kubeadm 1.15 needs /dev/kmsg to be there, but it's not in lxc, but we can just use /dev/console instead
# see: https://github.com/kubernetes-sigs/kind/issues/662
if [ ! -e /dev/kmsg ]; then
ln -s /dev/console /dev/kmsg
fi
# https://medium.com/@kvaps/run-kubernetes-in-lxc-container-f04aa94b6c9c
mount --make-rshared /
- instalar curl
sudo apt install curl
- agregar usuario y darle permisos de sudo
$ adduser [user]
$ adduser [user] sudo
Crear template.

Utilizar el nombre que deseamos
inicializar template e instalar k3s nodo master.
- crear template con nombre master01
- ssh al nodo
ssh [user]@master01.lan
- usamos el curl auto install de k3s:
curl -sfL https://get.k3s.io | sh -s - server --disable servicelb --disable traefik --write-kubeconfig-mode 644
- seteamos la configuracion de kubectl adentro del nodo master para ver que todo funcione correctamente
$ export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready control-plane,master 5m32s v1.27.3+k3s1
- tomamos nota del token para agregar nodos worker
$ cat /var/lib/rancher/k3s/server/node-token
K173832c8dd5a175bf2123d840ad491850199cbd19309ab3b37827047cdd6319b04::server:faddf0a734d338cd66d4ab19fb4bed73 *<---- TOKEN QUE DEBEMOS GUARDAR*
Iniciar nodo worker
- clonar un template con el nombre worker01
- ssh al nodo
ssh [user]@worker01.lan
- instalamos k3s con la url del nodo master01 y el token que guardamos en el punto 5 del paso anterior
$ curl -sfL https://get.k3s.io | K3S_URL=https://master01.lan:6443 K3S_TOKEN="K173832c8dd5a175bf2123d840ad491850199cbd19309ab3b37827047cdd6319b04::server:faddf0a734d338cd66d4ab19fb4bed73" sh -
- habilitamos el k3s agent
systemctl enable --now k3s-agent
Habilitar nodo para poder agendar pods
en el nodo master01 ejecutar
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready control-plane,master 32m v1.27.3+k3s1
worker01 Ready <none> 21s v1.27.3+k3s1
crear label "worker" para el nodo worker01
$ kubectl label node worker01 node-role.kubernetes.io/worker=worker
node/worker01 labeled
chequear que la etiqueta haya funcionado:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready control-plane,master 37m v1.27.3+k3s1
worker01 Ready worker
Sumar mas workers al cluster.
Para sumar mas nodos workers al cluster repetir los pasos "Iniciar nodo worker" y "Habilitar nodo para poder agendar pods" cambiando el nombre de worker01 a workerNN
instalar kubectl en la terminal que utlizaremos para controlar el cluster de kubernetes
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
$ chmod +x ./kubectl
$ sudo mv ./kubectl /usr/local/bin/kubectl
$ kubectl version --client
Exportar configuracion de kubeconfig para acceder al cluster fuera de los nodos
- en el nodo master01
$ cat /etc/rancher/k3s/k3s.yaml
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tL ...
server: https://127.0.0.1:6443 <---- MODIFICAR CON EL NOMBRE DEL NODO MASTER O LA IP
name: default
contexts:
- context:
cluster: default
user: default
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
user:
client-certificate-data: LS0tLS1 ...
- en la terminar que deseamos utilizar kubectl previamente instalado generar el archivo .kubeconfig
$ touch ~/.kubeconfig
$ vim ~/.kubeconfig
- agregar el contenido del archivo que inspeccionamos en el paso anterior cambiando la url de la clave "server"
- cargar la configuracion de kubectl
export KUBECONFIG=~/.kubeconfig
- probar la configuracion
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
master01 Ready control-plane,master 49m v1.27.3+k3s1
worker01 Ready worker 17m v1.27.3+k3s1
- esta variable podemos agregarla al ~ /.profile para no tener que configurarla en cada login.
$ echo "export KUBECONFIG=~/.kubeconfig" >> ~/.profile
Instalacion metallb system
en este punto tenemos funcionando el cluster de kubernetes. lo que nos queda es instalar el servicio de LoadBalancer para poder recibir trafico sobre una IP de nuestra red de area local e instalar un ingress controller. en nuestro caso sera nginx-ingress, para seguir estandares del mercado, pero bien podria ser traefik ingress o algun otro.
- crear namespace y manifiesto de metallb-system esta es la ultima versión estable al 16/07/2023. podria actualizarse en un futuro.
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.10/config/manifests/metallb-native.yaml
- creamos el archivo de configuracion.
$ vim metallb-config.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.220
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: first-pool-l2
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
En este caso utilizaremos un rango de ip chico del rango de nuestra LAN para recibir todo el trafico en los puertos 443 y 80, luego instalaremos el ingress-nginx y tomara posecion de la IP que utilizamos. podriamos tener varios pods de nginx y varias ip para llegar a un servicio de HA. pero al ser un laboratorio casero, con una ip para el ingress de nginx estamos cubiertos.
- aplicar el archivo
$ kubectl apply -f metallb-config.yaml
ipaddresspool.metallb.io/first-pool created
l2advertisement.metallb.io/first-pool-l2 created
En versiones anteriores de metallb con un configmap bastaba para asignar una ip, ahora es necesario asignar estos dos tipos de CustomResouces
- chequear que los pods de metallb-system funcionen correctamente
$ kubectl get pods -n metallb-system
NAME READY STATUS RESTARTS AGE
controller-6484f698fc-f72ng 1/1 Running 0 33s
speaker-zqs7h 1/1 Running 0 33s
speaker-6m794 1/1 Running 0 33s
Instalar nginx-ingress controller
- aplicar el manifiesto, esta es la ultima version estable al 16/07/2023 del repo de github, en un futuro puede cambiar
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/cloud/deploy.yaml
- chequear pods y servicios
$ kubectl get pods -ningress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-sr9w9 0/1 Completed 0 33m
ingress-nginx-admission-patch-qtzqm 0/1 Completed 1 33m
ingress-nginx-controller-79d66f886c-2zvdk 1/1 Running 0 33m
$ kubectl get svc -ningress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller-admission ClusterIP 10.43.56.199 <none> 443/TCP 36m
ingress-nginx-controller LoadBalancer 10.43.179.158 192.168.1.200 80:31421/TCP,443:31998/TCP 36m
a esta altura deberiamos tener apuntado un DNS externo a nuestro router y el router enviar el trafico de los puertos 80 y 443 a la ip 192.168.1.200.
crear api token en cloudflare para validar dns
- en mi perfil de cloudflare clickear en tokens de api

- hacer click en crear token

- utilizar plantilla de editar zona dns

- agregar permiso zona zona read

- seleccionar zona deseada

- ir al resumen
- guardar token creado
Instalar cert manager y clusterissuer
- instalar cert manager
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml
esto creara un namespace cert-manager
- crear archivo clusterissuers.yaml con el siguiente contenido
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
type: Opaque
stringData:
api-token: [api-token-creado en clouflare]
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: [mail para aviso de letsencrypt]
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- dns01:
cloudflare:
email: [mail de cloudflare]
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [mail para aviso de letsencrypt]
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- dns01:
cloudflare:
email: [mail de cloudflare]
apiTokenSecretRef:
name: cloudflare-api-token-secret
key: api-token
- aplicar el archivo
$ kubectl apply -f clusterissuers.yaml -ncert-manager
Listo. nuestro cluster esta instalado y listo para funcionar con letsencrypt