Abrir publicación

Kubernetes más simple: K3s

K3s ( https://k3s.io/ ) es una solución Kubernetes creada por Rancher Labs ( https://rancher.com/ ) que nos promete fácil instalación, pocos requisitos y un uso de memoría mínimo.

Para el planteamiento de un entorno Demo/Desarrollo esto se convierte en una gran mejora sobre lo que hemos hablado anteriormente en https://www.disasterproject.com/index.php/2019/07/12/entorno-minimo-para-demos-de-kubernetes/, donde la creación del entorno es compleja y requiere de muchos recursos aunque sea Ansible quien realice el trabajo difícil.

Pasamos a comprobar si es cierto lo que se nos presenta y si podemos incluir las herramientas metallb (https://metallb.universe.tf/) que nos permitirá emular la potencia de los balanceadores de los entornos Cloud y K8dash (https://github.com/herbrandson/k8dash) que nos permitirá un seguimiento de estado de la infraestructura .

La configuración de las máquinas virtuales la realizamos de la misma manera que para Kubernetes, con la instalación de dependencias:

#Debian
sudo apt-get install -y ebtables ethtool socat libseccomp2 conntrack ipvsadm
#Centos
sudo yum install -y ebtables ethtool socat libseccomp conntrack-tools ipvsadm

Descargamos la última versión de k3s desde https://github.com/rancher/k3s/releases/latest/download/k3s y la ponemos en /usr/bin con permisos de ejecución en todos los nodos.

K3s incluye tres servicios «extras» que nos van a cambiar el planteamiento inicial que usamos para Kubernetes, el primero es Flannel ( https://github.com/coreos/flannel) que está integrado en K3s y nos va a realizar toda la capa de gestión de red interna a Kubernetes, aunque no es tan completo en características como Weave (por ejemplo soporte multicast) cumple con ser compatible con Metallb. Una comparación muy completa de proveedores de red de Kubernetes se puede ver en https://rancher.com/blog/2019/2019-03-21-comparing-kubernetes-cni-providers-flannel-calico-canal-and-weave/ .

El segundo servicio es Traefik ( https://traefik.io/ ) que realiza funciones de entrada desde fuera del entorno Kubernetes, es un potente proxy inverso/balanceador con múltiples características que realizará las funciones de red de capa 7 por detrás de Metallb que realizará las funciones de balanceador de capa de red 3.

El último servicio «extra» de K3s es servicelb, que permite dispone de balaceador de carga de las aplicaciones, el problema de este servicio es que trabaja en capa 3 y sobre los mismos puntos que metallb, por lo que no podemos instalarlo.

En el primer nodo (que será el master) a instalar ejecutamos /usr/bin/k3s server --no-deploy servicelb --bind-address IP_MÁQUINA, si queremos que se realize la ejecución cada vez que se inicie la máquina configuramos un servicio creando el archivo /etc/systemd/system/k3smaster.service

[Unit]
Description=k3s

[Service]
ExecStart=/usr/bin/k3s server --no-deploy servicelb --bind-address 192.168.8.10
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

Y ejecutamos

sudo systemctl enable k3smaster
sudo systemctl start k3smaster

Para que se lance K3s e instale el nodo master, al final de la instalación (unos 20 segundos), guardaremos el contenido del fichero /var/lib/rancher/k3s/server/node-token ya que es el código de activación para los nodos (token), es importante ver que está en base64 y para usarlo hay que decodificarlo.

Antes de preparar los nodos procedemos a instalar Metallb y el panel K8dash:

$ sudo mkdir ~/.kube
$ sudo cp -i /etc/rancher/k3s/k3s.yaml $HOME/.kube/config
$ sudo k3s kubectl apply -f "https://raw.githubusercontent.com/danderson/metallb/master/manifests/metallb.yaml"
$ sudo k3s kubectl apply -f "https://raw.githubusercontent.com/herbrandson/k8dash/master/kubernetes-k8dash.yaml"

Debemos observar que para reducir al máximo el espacio, kubectl está incluido dentro del propio ejecutable k3s, lo que lo hace ideal para entornos con muy poco espacio de almacenamiento.

Para activar Metallb le creamos una configuración que tendrá la forma:

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      protocol: layer2
      addresses:
      - 192.168.8.240/28

Que al aplicar con k3s kubectl apply -f pool.yml configurará Metallb para que en el caso de que existan servicios con loadBalancer utilicen una de las IPs definidas en el rango especificado, en nuestro ejemplo corresponde a la red definida en inventory.k3s.yml y el rango x.x.x.240 a x.x.x.254.

apiVersion: v1
kind: Service
metadata:
name: k8dashlb
namespace: kube-system
spec:
ports:

  • name: http
    port: 80
    protocol: TCP
    targetPort: 4654
    selector:
    k8s-app: k8dash
    type: LoadBalancer

configurará Metallb para que en el caso de que existan servicios con loadBalancer utilicen una de las IPs definidas en el rango especificado, en nuestro ejemplo corresponde a la red definida en inventory.k3s.yml y el rango x.x.x.240 a x.x.x.254.

Para probar dicho cargamos un servicio para acceder a K8dash:

apiVersion: v1
kind: Service
metadata:
  name: k8dashlb
  namespace: kube-system
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 4654
  selector:
    k8s-app: k8dash
  type: LoadBalancer

Aplicamos k3s kubectl apply -f service.yml y vamos a ver que IPs tienen los servicios activos:

$sudo k3s kubectl get services -A
NAMESPACE     NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
default       kubernetes   ClusterIP      10.43.0.1      <none>          443/TCP                      32m
kube-system   k8dash       ClusterIP      10.43.183.3    <none>          80/TCP                       31m
kube-system   k8dashlb     LoadBalancer   10.43.86.34    192.168.8.240   80:32324/TCP                 31m
kube-system   kube-dns     ClusterIP      10.43.0.10     <none>          53/UDP,53/TCP,9153/TCP       32m
kube-system   traefik      LoadBalancer   10.43.91.178   192.168.8.241   80:30668/TCP,443:31859/TCP   30m

Vemos por un lado que los servicios marcados como loadBalancer han cogido correctamente las IPs, tanto el que acabamos de configurar, como el servicio Traefik desplegado por K3s.

En el resto de nodos ejecutamos /usr/bin/k3s agent --server https://IP_MASTER:6443 --token TOKEN_DECODIFICADO_BASE64, si queremos que la ejecución sea cada vez que se inicie la máquina configuramos un servicio creando el archivo /etc/systemd/system/k3s.service

[Unit]
Description=k3s

[Service]
ExecStart=/usr/bin/k3s agent --server https://IP_MASTER:6443 --token TOKEN_DECODIFICADO_BASE64
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

Y ejecutamos para configurar el servicio

sudo systemctl enable k3s
sudo systemctl start k3s
Si accedemos a la IP de servicio para K8dash obtenida anteriormente y con el token que no da el comando k3s kubectl get secret `k3s kubectl get secret|grep ^k8dash|awk '{print $1}'` -o jsonpath="{.data.token}"|base64 -d (nota: el Ansible al finalizar de ejecutar nos devolverá la IP y el token para acceder) podemos comprobar el funcionamiento de la plataforma:

Por último una pequeña comparación entre kubernetes y k3s en uso de memoria, ambos recién instalados:

  • Kubernetes master: 574 MB
  • Kubernetes nodo: 262 MB
  • K3s master: 260 MB
  • K3s node: 137 MB
Abrir publicación

Crear un entorno mínimo para demos con Kubernetes

Cada día más los entornos de contenedores no cumplen los requisitos de los clientes para la realización de demostraciones en clientes, ya que los mismos plantean una migración/utilización Cloud o entornos Kubernetes/Openshift en la propia infraestructura del cliente.

Kubernetes no es un entorno amigable para llevarlo en un portatil de media capacidad (8GB a 16 GB de RAM) y menos con una demo que requiera ciertos recursos.

Este caso se basa en el despliegue basado en Kubeadm (https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/) para el despliegue de Kubernetes, usando containerd (https://containerd.io/) como gestor del ciclo de vida de los contenedores y para poder disponer de una gestión de red mínima usaremos metallb (https://metallb.universe.tf/) que nos permitirá emular la potencia de los balanceadores de los entornos Cloud y Weave (https://www.weave.works/blog/weave-net-kubernetes-integration/) que nos permite la gestión de red de los contenedores y se integra sin problemas con metallb.

Por último y aprovechando la infraestructura desplegamos el gestor en tiempo real de los recursos K8dash (https://github.com/herbrandson/k8dash) que nos permitirá un seguimiento de estado de la infraestructura y de las aplicaciones que desplegamos en la misma.

Aunque los roles de Ansible que hemos utilizado con anterioridad (ver https://github.com/aescanero/disasterproject) nos permiten desplegar el entorno con facilidad y limpieza vamos a examinar el mismo para entender como los cambios que usaremos en siguientes capítulos (usar k3s y microk8s) repercuten de manera importante en la disponibilidad y rendimiento del entorno de demo/desarrollo.

El primer paso en la instalación son las dependencias que tiene Kubernetes y una muy buena referencia sobre las mismas es la documentación que Kelsey Hightower pone a disposición de aquellos que necesitan conocer a fondo Kubernetes (https://github.com/kelseyhightower/kubernetes-the-hard-way), especialmente de todos aquellos que están interesados en certificaciones Kubernetes como CKA (https://www.cncf.io/certification/cka/).

Empezamos por una serie de paquetes de red

#Debian
sudo apt-get install -y ebtables ethtool socat libseccomp2 conntrack ipvsadm
#Centos
sudo yum install -y ebtables ethtool socat libseccomp conntrack-tools ipvsadm

Instalamos el gestor de vida de los contenedores (Containerd que incluye CRI y CNI) y aprovechamos el paquetes que viene con los interfaces de red de Kubernetes (CNI o Container Network Interface)

sudo sh -c "curl -LSs https://storage.googleapis.com/cri-containerd-release/cri-containerd-cni-1.2.7.linux-amd64.tar.gz |tar --no-overwrite-dir -C / -xz"

El paquete incluye el servicio para systemd por lo que basta con iniciar el servicio:

sudo systemctl enable containerd
sudo systemctl start containerd

Ahora descargamos los ejecutables de kubernetes, en el caso de la primera máquina a configurar será el master y tenemos que descargar los binarios kubeadm (el instalador de kubernetes), kubelet (el agente que se conectará con containerd en cada máquina, para saber cual es la versión estable de kubernetes ejecutamos:

VERSION=`curl -sSL https://dl.k8s.io/release/stable.txt`

Y descargamos los binarios (en el master todos y en los nodos solo es necesario kubelet)

sudo curl -Ol https://storage.googleapis.com/kubernetes-release/release/$VERSION/bin/linux/amd64/{"kubectl","kubelet","kubeadm"} -o /usr/bin/"#1"
sudo chmod u+x /usr/bin/{"kubectl","kubelet","kubeadm"}

Configuramos un servicio para kubelet en cada máquina creando el archivo /etc/systemd/system/kubelet.service que dependerá de si la máquina tiene la función de master o nodo. Para el master tenemos:

[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=http://kubernetes.io/docs/

[Service]
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
Environment="KUBELET_EXTRA_ARGS='--network-plugin=cni --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --node-ip={{ vm_ip }}'"
Environment="KUBELET_KUBECONFIG_ARGS='--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf'"
Environment="KUBELET_CONFIG_ARGS='--config=/var/lib/kubelet/config.yaml'"
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

Para el resto de nodos:

[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=http://kubernetes.io/docs/

[Service]
Environment="KUBELET_EXTRA_ARGS='--network-plugin=cni --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --node-ip={{ vm_ip }}'"
Environment="KUBELET_KUBECONFIG_ARGS='--bootstrap-kubeconfig=/var/lib/kubelet/bootstrap-kubeconfig --kubeconfig=/var/lib/kubelet/kubeconfig'"
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_EXTRA_ARGS
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

Una vez cargada la configuración procedemos a utilizar kubeadm en el primer node (master), indicando la ip que se va a publicar, la red interna de los pods (un /16, aunque no olvidemos que es un entorno de demo/desarrollo por lo que no debe ser un problema). No vamos a utilizar kubeadm en el resto de nodos, así que no necesitamos recoger información de la ejecución de este comando.

$ sudo kubeadm init --apiserver-advertise-address {{ vm_ip }} --pod-network-cidr=10.244.0.0/16 --cri-socket /run/containerd/containerd.sock

Antes de preparar los nodos procedemos a cargar dos elementos, el primero «metallb» nos permitirá tener servicios «balanceables por carga: loadbalancer» accesibles en el mismo rango que las máquinas virtuales y el segundo «weave» es un gestor de red que permitirá la comunicación entre pods que se ejecutan entre máquinas diferentes dentro de la red que hemos definido anteriormente. Ambos servicios se cargan con los siguientes comandos ejecutados en master:

$ sudo mkdir ~/.kube
$ sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config
$ sudo kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(sudo kubectl version | base64 | tr -d '\n')"
$ sudo kubectl apply -f "https://raw.githubusercontent.com/danderson/metallb/master/manifests/metallb.yaml"

Para añadir los nodos hemos de crear un usuario, como no disponemos de integración con ldap o directorio activo, usaremos las cuentas de sistema o sa, generamos la cuenta initnode para añadir los nodos con el siguiente yaml:

apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: null
  name: initnode
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  creationTimestamp: null
  name: initauth
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:node-bootstrapper
subjects:
- kind: ServiceAccount
  name: initnode
  namespace: kube-system

Una vez generada la cuenta de servicio ejecutamos kubectl get serviceaccount -n kube-system initnode -o=jsonpath="{.secrets[0].name}"
que nos dará el nombre del token y kubectl get secrets "NOMBRE_TOKEN" -n kube-system -o=jsonpath="{.data.token}"|base64 -d nos dará el valor del mismo que hemos de guardar.

Además de crear el usuario hay que crear una configuración de inicio para los nodos, para ello ejecutamos los siguientes comandos:

$ sudo kubectl config set-cluster kubernetes --kubeconfig=~/bootstrap-kubeconfig --certificate-authority=/etc/kubernetes/pki/ca.crt --embed-certs --server
$ sudo kubectl config set-credentials initnode --kubeconfig=~/bootstrap-kubeconfig --token=VALOR_TOKEN
$ sudo kubectl config set-context initnode@kubernetes --cluster=kubernetes --user=initnode --kubeconfig=~/bootstrap-kubeconfig
$ sudo kubectl config use-context initnode@kubernetes --kubeconfig=~/bootstrap-kubeconfig

El archivo generado hemos de copiarlo en todos los nodos en /var/lib/kubelet/bootstrap-kubeconfig y procedemos a iniciar el servicio kubelet en cada nodo que se encargará de contactar con el master y pedir acceso.

sudo systemctl enable kubelet
sudo systemctl start kubelet

Para autorizar el o los nodos en el master hemos de ir al mismo y consultar por las peticiones con el comando kubectl get certificatesigningrequests.certificates.k8s.io
, y autorizarlas con el comando kubectl certificate approve

Elegir entre Docker o Podman para entornos de pruebas y desarrollo

Muchas veces nos encontramos que tenemos muy pocos recursos y necesitamos un entorno para realizar una completa demostración de producto a un cliente.

En esos casos vamos a necesitar simular un entorno de la manera más sencilla y con menos recursos posibles y para ello usaremos contenedores, pero ¿cual es la mejor solución para esos pequeños entornos?

Docker es el entorno de contedores estandar en el mercado, es el más extendido y conforma un conjunto de herramientas extraordinariamente potentes como son un cliente desde linea de comandos, un API server, un gestor del ciclo de vida de los contenedores (containerd), un lanzador de contenedores (runc).

Podman es un entorno de contenedores que no utiliza un servicio y por lo tanto no dispone de un API server, las peticiones se hacen únicamente desde la linea de comandos, lo que tiene ventajas y desventajas que explicaremos al final.

Instalar docker es realmente fácil, ya que docker suministra un script que realiza el proceso de preparar y configurar los requisitos y los repositorios necesario y por último instala y configura docker dejando el servicio listo para usar.

Instalar podman es fácil en un entorno Centos (yum install -y podman para Centos 7 y yum install -y container-tools para Centos 8) pero necesita cierto trabajo en un entorno Debian:

# sudo apt update && sudo apt install -y software-properties-common dirmngr
# sudo apt-key adv --keyserver ha.pool.sks-keyservers.net --recv-keys 0x018BA5AD9DF57A4448F0E6CF8BECF1637AD8C79D
# sudo sh -c "echo 'deb http://ppa.launchpad.net/projectatomic/ppa/ubuntu bionic main' > /etc/apt/sources.list.d/container.list"
# sudo apt update && sudo apt install -y podman skopeo buildah uidmap debootstrap

En nuestro caso hemos utilizado los roles Ansible desarrollados en https://github.com/aescanero/disasterproject, para desplegar dos máquinas virtuales, una con podman y la otra con docker.

En el caso de usar una distribución basada en Debian instalamos Ansible:

$ sudo sh -c 'echo "deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main" >>/etc/apt/sources.list'
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367
$ sudo apt-get update && sudo apt-get install -y ansible 

Procedemos a descargar el entorno y configurar Ansible:

$ git clone https://github.com/aescanero/disasterproject
$ cd disasterproject/ansible
$ chmod 600 files/insecure_private_key

Editamos el archivo inventory.yml y le ponemos el siguiente formato:

all:
  children:
    vms:
      hosts:
        MACHINE_NAME1:
          memory: MEMORY_IN_MB
          vcpus: vCPUS_FOR_VM
          vm_ip: "IP_VM_MACHINA_NAME1"
          linux_flavor: "debian|centos"
          container_engine: "docker|podman"
        MACHINE_NAME2:
          memory: MEMORY_IN_MB
          vcpus: vCPUS_FOR_VM
          vm_ip: "IP_VM_MACHINA_NAME2"
          linux_flavor: "debian|centos"
          container_engine: "docker|podman"
      vars:
        network_name: NETWORK_NAME
        network: "VM_NETWORK"

Existen algunas variables globales que cuelgan de «vars:», que son:

  • network_name: Nombre descriptivo de la red de libvirt que crearemos y que además será el nombre del interface que se configurará en el anfitrión KVM y que servirá de puerta de enlace de las máquinas virtuales
  • network: los tres primeros campos de la dirección IPv4 para confirmar una red con máscara 255.255.255.0, las máquinas virtuales deberán tener una IP de dicho rango (menos 0,1 y 255)

El formato de cada equipo cuelga se define por los siguientes atributos:

  • nombre: Nombre descriptivo de la máquina virtual a desplegar, será además el hostname del equipo
  • memory: Memoria de la máquina virtual en MB
  • vcpus: Número de CPUs virtuales en la máquina virtual
  • vm_ip: IP de la máquina virtual, debe pertenecer al rango definido en la variable general «network»
  • linux_flavor: Es la distribución de la máquina virtual, se permiten dos opciones: debian y centos
  • container_engine: (Opcional) Es el motor de contenedores que se puede desplegar en la máquina virtual, se permiten dos opciones: docker y podman

Desplegamos el entorno con:

$ ansible-playbook -i inventory.yml create.yml --ask-become-pass

Como resultado obtenemos dos máquinas virtuales a las cuales podemos acceder vía ssh con ssh -i files/insecure_private_key vagrant@IP_MÁQUINA_VIRTUAL, en cada una de ellas procedemos a lanzar un contenedor, docker run -td nginx y podman run -dt nginx. Una vez desplegados los contenedor nginx, analizamos que servicios se ven ejecutados en cada caso.

  • Docker, arranca 32 procesos para lanzar nginx:
containerd-+-containerd-shim-+-nginx---nginx
                     |                 `-10*[{containerd-shim}]
                     `-8*[{containerd}]
dockerd---9*[{dockerd}]
3194 root      20   0  546332  92220  39252 S  0.0  9.5   0:07.23 dockerd
 2314 root      20   0  463200  41696  25768 S  0.0  4.3   0:00.57 containerd
 3621 root      20   0   32648   5244   4564 S  0.0  0.5   0:00.08 nginx
 3603 root      20   0   11720   4612   3856 S  0.0  0.5   0:00.03 containerd-shim
 3500 root      20   0   20472   2692   1656 S  0.0  0.3   0:00.00 dhclient
 3479 root      20   0   20352   2676   1636 S  0.0  0.3   0:00.00 dhclient
 3656 systemd+  20   0   33100   2508   1468 S  0.0  0.3   0:00.00 nginx
  • Podman, arranca un proceso para lanzar nginx:
conmon-+-nginx---nginx
                 `-{gmain}
 3471 root      20   0   32648   5180   4500 S  0.0  0.5   0:00.06 nginx
 3482 systemd+  20   0   33100   2420   1380 S  0.0  0.2   0:00.00 nginx
 3461 root      20   0   85800   1924   1768 S  0.0  0.2   0:00.00 conmon

Entre las dos máquinas virtuales hay solo 100 MB de diferencia entre ambas:

1,5G docker.qcow2
1,4G podman.qcow2

Como conclusión tenemos los siguientes puntos:

DockerPodman
Gestión de ciclo de vida, por ejemplo reinicio de contenedores que fallan automáticamente, iniciar contenedores automáticamente cuando el equipo se reinicia, ejecuta chequeos sobre los contenedores, iniciar contenedores en un orden determinado, etc.Es compatible con Docker a nivel de CLI, imagen y carga desde registros.
Depende de systemd para gestionar el ciclo de vida de los contenedores.
Requiere más recursos, pero en un entorno de demo/desarrollo con pocos contenedores no debería de requerir importantes recursos.Dispone de más recursos disponibles para los contenedores, lo que es ideal para un entorno de demos en máquinas virtuales.
Ocupa un espacio menor y requiere de menos dependencias.

Podman es un proyecto respaldado por RedHat que está tomando fuerza y muchos servicios de desarrollo se plantean su adopción (por ejemplo el proyecto syslog-ng: https://www.syslog-ng.com/community/b/blog/posts/replacing-docker-with-podman-in-the-syslog-ng-build-container

Más información en como adaptar Podman en Replacing Docker with Podman.

Volver arriba