Creación de un Clúster de Kubernetes usando Vagrant y Ansible en 3 minutos

¿Qué es Kubernetes?

Kubernetes (K8S) es la plataforma líder para el despliegue y gestión de contenedores (Docker, containerd y CRI-O), permite desplegar aplicaciones y servicios de forma declarativa e independiente del IaaS o SaaS.

El uso de Kubernetes como plataforma para desplegar aplicaciones y servicios permite:

  • Despliegue declarativo de aplicaciones, servicios, micro servicios y trabajos
  • Disponer de un repositorio de configuración para contenedores y servicios
  • Abstraerse del proveedor de Cloud y sistema operativo permitiendo portabilidad y evitando vendor lock-in
  • Unificar y automatizar el sistema de despliegue permitiendo la integración y el despliegue continuo.
  • Monitorización del estado de salud de las aplicaciones, sus componentes y servicios
  • Dotar a las aplicaciones de alta disponibilidad
  • Implementar redundancia
  • Realizar balanceo de carga
  • Conseguir un aislamiento eficaz de aplicaciones
  • Disponer de múltiples opciones de conectividad y enrutado avanzado de aplicaciones y micro-servicios (vea Instalar Istio)

¿Por qué necesita un clúster de Kubernetes para desarrollo local?

Para que los desarrolladores realicen pruebas realistas, utilicen el mismo procedimiento de despliegue en todos los entornos y dispongan de todos los micro servicios y dependencias en un entorno portable.

El desarrollo moderno de aplicaciones divide el trabajo en multitud de especialistas que requieren a su vez tener acceso al resto de la infraestructura, por ejemplo un desarrollador de interfaz de usuario necesita acceso al API o back-end y estos a su vez a algún tipo de base de datos o índice con datos de prueba. El entorno portable de Kubernetes le permite disponer de todas estas piezas en su propio equipo, que podrá utilizar incluso estando desconectado de Internet.

En este tutorial se crea un clúster de Kubernetes realista para desarrollo local y compuesto por un servidor maestro (API, Scheduler, Controller) y n nodos (Pods, kubelet, proxy, and Docker) ejecutando el proyecto Calico para la implementación del modelo de red de Kubernetes.

Existen otras distribuciones de Kubernetes que permiten disponer de un entorno Kubernetes en local, sin embargo suelen estar compuestas por un único nodo (ej. Minikube o Docker Desktop Kubernetes) y no reflejan la realidad de un entorno complejo y distribuido.

Así mismo es posible que cada desarrollador tenga su propio clúster de Kubernetes en un proveedor Cloud como AWS, Azure o Google, sin embargo es una opción que puede resultar muy costosa a largo plazo y por supuesto no es portable y requiere conexión permanente a Internet.

¿Cómo crear un clúster portable de Kubernetes?

Se utiliza Vagrant para crear las máquinas virtuales sobre VirtualBox que actúa como hipervisor y se utilizan playbooks de Ansible para configurar el clúster de Kubernetes.

El objetivo es la creación de un clúster para desarrollo local y aprendizaje lo más similar posible a uno de producción.

Este tutorial ha sido posible gracias a la ayuda obtenida en otros blogs (https://kubernetes.io/blog and https://docs.projectcalico.org/). Se incluyen enlaces a las páginas relevantes en el código fuente.

Tras instalar el clúster local de Kubernetes, se sugiere instalar Istio para dotar a Kubernetes de balanceo de carga para el tráfico interno y externo, control de fallos, re-intentos, enrutado, aplicación de límites y monitorización del tráfico entre servicios, securización de tráfico entre micro-servicios. Vea el tutorial Instalación de Istio en Kubernetes con Ansible y Vangrant para más información.

Actualizaciones:

  • 22 Dec 2019: Añadir información acerca del uso de un registro privado de Docker (sugerencia de by Brian Quandt).
  • 4 Nov 2019: Instalación y publicación del Dashboard de Kubernetes con la ayuda de Alex Alongi. Se añade la sección de prerrequisitos.
  • 26 Sep 2019: Actualización de Calico y seguridad de red a la versión 3.9
  • 6 June 2019: Solucionar problema «kubectl was not able to recover logs» añadiendo “Configuración de node-ip … en kubelet”.
  • 10 Jul 2020:
    • Actualización a últimas versiones:
    • Cambio selección de hosts de Ansible de grupos a patrón de nombres (hosts: k8s-m-* and hosts: k8s-n-*)
  • 3 Ago 2020:
    • Permitir diferente memoria y CPU en master y nodos
  • 29 Sep 2020:
    • Pruebas con Kubernetes 1.19
  • 16 agosto 2021: Actualización de los playbooks de ansible para usar containerd en lugar de Docker (Kubernetes ha deprecado el uso de Docker como runtime tras la versión 1.20). Probado con Kubernetes 1.22

Mejores Prácticas: Kubernetes con Ansible

Este playbook de Ansible aplicar las mejores practicas de IT Wonder Lab y puede ser utilizado para configurar un nuevo clúster de Kubernetes en un proveedor Cloud o en un hipervisor diferente al no tener ninguna dependencia con VirtualBox y Vagrant

Creación de un clúster de Kubernetes con Vagrant y Ansible

Pulse el botón play para ver la ejecución de Vagrant creando el clúster de desarrollo con Ansible.

Prerrequisitos

Hardware:

Un equipo Linux (Este tutorial usa Ubuntu 20.10) con al menos 8 GB de RAM y 15 GB.

lsb_release -a
Distributor ID: Ubuntu
Description: Ubuntu 20.14 LTS
Release: 20.04
Codename: groovy

Software:

  • Vagrant 2.2.16
    wget https://releases.hashicorp.com/vagrant/2.2.16/vagrant_2.2.16_x86_64.deb
    sudo apt install ./vagrant_2.2.16_x86_64.debb
  • VirtualBox 6.1.22 or above
    sudo apt install virtualbox
  • Ansible 2.9.9
    sudo apt-add-repository --yes --update ppa:ansible/ansible
    sudo apt install ansible

Estructura de ficheros

El código utilizado para crear un Clúster de Kubernetes con Vagrant y Ansible se compone de:

  • .vagrant/: directorio oculto usando por Vagrant para gestionar los recursos. Incluye el inventario generado vagrant_ansible_inventory que usará Ansible para identificar la pertenencia de cada máquina virtual al grupo master o worker/nodo e instalar los roles correctos.
  • add_packages: playbook de Ansible para instalar/eliminar paquetes software en Ubuntu usando APT.
  • roles/
    • common/: instala los paquetes necesarios para Kubernetes (delegando en add_packages) y realiza la configuración común para el maestro y los nodos.
    • k8s/master/: playbook de Ansible para configurar el maestro de Kubernetes, utiliza el playbook common para los componentes comunes.
    • k8s/node/: playbook de Ansible para configurar los nodos de Kubernetes, utiliza el playbook common para los componentes comunes.
    • ditwl-k8s-01-join-command: este fichero es generado por Kubernetes tras instalar el maestro y contiene el comando necesario para unir los nodos al clúster junto con el token que lo permite.
    • k8s.yml: Playbook de Ansible que hace uso de los roles de Ansible para Kubernetes. Será el ejecutado por aprovisionador de Vagrant
  • Vagrantfile: contiene la definición de las máquinas virtuales (CPU, memoria, red), la configuración del aprovisionador (Ansible y propiedades).

Redes en Kubernetes sobre VirtualBox

La mayor dificultad para utilizar Kubernetes en máquinas virtuales de VirtualBox es la configuración del entorno de red.

La red del clúster de Kubernetes y las máquinas Virtualbox estará compuesta por al menos 3 redes que se muestran en el siguiente diagrama de red:

LAN, NAT, HOST Only and Tunnel Kubernetes networks
Kubernetes Network Overview

Red externa de Kubernetes en VirtualBox

La red VirtualBox HOST ONLY es la red utilizada para acceder al maestro y a los nodos de Kubernetes desde fuera de VirtualBox. La consideraremos como el equivalente a una red pública dentro de nuestro entorno desarrollo.

En el diagrama se muestra en color verde con conexiones a cada máquina virtual y al router de VirtualBox en su interfaz virtual de vboxnet:

  • K8S-M-1 at eth1: 192.168.50.11
  • K8S-N-1 at eth1: 192.168.50.12
  • K8S-N-2 at eth1: 192.168.50.13
  • vboxnet0 virtual iface: 192.168.50.1

VirtualBox crea las rutas necesarias y la interfaz vboxnet0:

$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         _gateway        0.0.0.0         UG    100    0        0 enx106530cde22a
link-local      0.0.0.0         255.255.0.0     U     1000   0        0 enx106530cde22a
192.168.50.0    0.0.0.0         255.255.255.0   U     0      0        0 vboxnet0
192.168.100.0   0.0.0.0         255.255.255.0   U     100    0        0 enx106530cde22a

Las aplicaciones publicadas mediante un NodePort de Kubernetes estarán disponibles en las IPs asignadas a las máquinas virtuales de Kubernetes dentro de la red VirtualBox HOST ONLY

Por ejemplo, para acceder a una aplicación publicada en el NodePort 30000 se utilizarán las siguientes URLs desde fuera del clúster Kubernetes:

Vea el tutorial cómo publicar una aplicación fuera del clúster de Kubernetes.

También es posible utilizar estas IPs para acceder a los nodos y el maestro de Kubernetes mediante SSH:

$ ssh [email protected]
[email protected]'s password: vagrant
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-29-generic x86_64)
...
Last login: Mon Apr 22 16:45:17 2019 from 10.0.2.2

Red NAT de VirtualBox

La interfaz de red NAT, con la misma IP (10.0.2.15) para todas las máquinas virtuales del clúster de Kubernetes, es asignada a la primera interfaz de red de cada máquina de VirtualBox. Se utiliza para acceder desde las máquinas del clúster de Kubernetes al exterior (LAN e Internet).

En el diagrama se muestra en color amarillo con conexiones a cada máquina del clúster y a un router NAT que conecta con la LAN e Internet.

Esta conexión es la utilizada por Ansible (ejecutándose en cada nodo) durante la configuración del clúster para descargar los paquetes necesarios desde Internet. Al tratarse de una interfaz NAT no se permiten, por defecto, conexiones entrantes.

Red de PODs de Kubernetes

Las conexiones internas entre los PODs de Kubernetes utilizan una red de túneles con IPs en el rango 192.168.112.0/20 (configurado en el playbook de Ansible). En el diagrama se muestra en color naranja conectando cada máquina de Kubernetes utilizando interfaces túnel.

Kubernetes asigna IPs de la red POD a cada POD creado. Las IPs de los POD no son accesibles desde fuera del clúster de Kubernetes y son efímeras (cambiarán cuando un POD sea creado o destruido).

Red del Clúster de Kubernetes (Cluster-IP)

La red del clúster de Kubernetes es una red que utiliza un rango privado de IPs y que está destinada a ser usada dentro del clúster de Kubernetes, dando a cada servicio de Kubernetes una IP dedicada. En el diagrama se muestra en color morado.

El siguiente listado muestra servicios ejecutándose en Kubernetes y su CLUSTER-IP asignada:

$ kubectl get all
NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-d7b95894f-2hpjk   1/1     Running   0          5m47s
pod/nginx-deployment-d7b95894f-49lrh   1/1     Running   0          5m47s
pod/nginx-deployment-d7b95894f-wl497   1/1     Running   0          5m47s
NAME                       TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/kubernetes         ClusterIP      10.96.0.1      <none>        443/TCP          137m
service/nginx-service-np   NodePort       10.103.75.9    <none>        8082:30000/TCP   5m47s
NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   3/3     3            3           5m47s
NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-d7b95894f   3         3         3       5m47s

No es posible acceder desde fuera del clúster a las IPs del rango CLUSTER-IP, por lo que se hace necesario crear un NodePort (en el caso de clúster sobre Cloud, se usa un Load Balancer nativo del proveedor del IaaS) para publicar aplicaciones fuera del clúster. Un NodePort utiliza las IPs externas de Kubernetes (Red externa de Kubernetes en VirtualBox)

Vea “Publish an Application outside Kubernetes Cluster” para una explicación sobre cómo publicar una aplicación de Kubernetes.

Vagrantfile

El fichero Vagrantfile define la configuración hardware de las máquinas virtuales que serán creadas por Vagrant y ejecutadas en VirtualBox. Además se define el playbook de Ansible que se ejecutará para aprovisionarlas.

IMAGE_NAME = "bento/ubuntu-20.04"
K8S_NAME = "ditwl-k8s-01"
MASTERS_NUM = 1
MASTERS_CPU = 2 
MASTERS_MEM = 2048

NODES_NUM = 2
NODES_CPU = 2
NODES_MEM = 2048

IP_BASE = "192.168.50."

VAGRANT_DISABLE_VBOXSYMLINKCREATE=1

Vagrant.configure("2") do |config|
    config.ssh.insert_key = false

    (1..MASTERS_NUM).each do |i|      
        config.vm.define "k8s-m-#{i}" do |master|
            master.vm.box = IMAGE_NAME
            master.vm.network "private_network", ip: "#{IP_BASE}#{i + 10}"
            master.vm.hostname = "k8s-m-#{i}"
            master.vm.provider "virtualbox" do |v|
                v.memory = MASTERS_MEM
                v.cpus = MASTERS_CPU
            end            
            master.vm.provision "ansible" do |ansible|
                ansible.playbook = "roles/k8s.yml"
                #Redefine defaults
                ansible.extra_vars = {
                    k8s_cluster_name:       K8S_NAME,                    
                    k8s_master_admin_user:  "vagrant",
                    k8s_master_admin_group: "vagrant",
                    k8s_master_apiserver_advertise_address: "#{IP_BASE}#{i + 10}",
                    k8s_master_node_name: "k8s-m-#{i}",
                    k8s_node_public_ip: "#{IP_BASE}#{i + 10}"
                }                
            end
        end
    end

    (1..NODES_NUM).each do |j|
        config.vm.define "k8s-n-#{j}" do |node|
            node.vm.box = IMAGE_NAME
            node.vm.network "private_network", ip: "#{IP_BASE}#{j + 10 + MASTERS_NUM}"
            node.vm.hostname = "k8s-n-#{j}"
            node.vm.provider "virtualbox" do |v|
                v.memory = NODES_MEM
                v.cpus = NODES_CPU
            end             
            node.vm.provision "ansible" do |ansible|
                ansible.playbook = "roles/k8s.yml"                   
                #Redefine defaults
                ansible.extra_vars = {
                    k8s_cluster_name:     K8S_NAME,
                    k8s_node_admin_user:  "vagrant",
                    k8s_node_admin_group: "vagrant",
                    k8s_node_public_ip: "#{IP_BASE}#{j + 10 + MASTERS_NUM}"
                }
            end
        end
    end
end

Características de los nodos de Kubernetes:

NodoCaracterísticasValor
Maestro K8SCPU2 cores
Memoria RAM2 GB
Worker 1…NCPU2 cores
Memoria RAM2 GB

Las líneas 1 a 11 definen la propiedades del clúster de Kubernetes:

  • IMAGE_NAME: es la box (máquina virtual) que descargará Vagrant y usará para crear las máquinas del clúster. Usaremos una máquina identificada como “bento/ubuntu-18.04”, que corresponde a una instalación mínima de Ubuntu 18.04 empaquetada por el proyecto Bento.
  • K8S_NAME: nombre que tendrá el clúster y que será usado para identificar el comando de unión de nodos al master. En nuestro ejemplo es “ditwl-k8s-01”, acrónimo de Demo IT Wonder Lab Kubernetes (k8s) y 01, el número de clúster.
  • MASTERS_NUM: número de nodos maestros, se utiliza para crear una clúster con alta disponibilidad en los nodos maestros. No está implementado en el código de Ansible por lo que actualmente el número máximo de maestros es 1.
  • MASTERS_MEM / NODES_MEM: cantidad de memoria en mega bytes para cada maestro/nodo de Kubernetes.
  • MASTERS_CPU / NODOS_CPU: número de CPUs disponibles para cada maestro/nodo.
  • NODES_NUM = Número de nodos trabajadores de Kubernetes. Son los nodos que ejecutan los PODs mediante contenedores, crearemos 2 nodos.
  • IP_BASE = Primeros octetos de la dirección IP que usaremos para definir la red Red externa de Kubernetes en VirtualBox, es decir las interfaces externas de las máquinas de VirtualBox, en el ejemplo se asignarán:
    • 192.168.50.1 for the vboxnet0
    • 192.168.50.11 for k8s-m-1 (Kubernetes master node 1)
    • 192.168.50.12 for k8s-n-1 (Kubernetes worker node 1)
    • 192.168.50.13 for k8s-n-2 (Kubernetes worker node 2)

Las líneas 12 a 15 configuran al memoria y CPU de las maquinas, el maestro se crea entre las líneas 17 y 40, usando un bucle para crear MASTERS_NUM máquinas con las siguientes características:

El nombre de las máquinas se compone usando la expresión “k8s-m-#{i}” que identifica la máquina como miembro del clúster de Kubernetes (k8s), en el rol de maestro (m) y en la posición #{i}. El valor de i aumenta en cada iteración del bucle.

El aprovisionador de Ansible se ejecutará con el playbook “roles/k8s.yml”

Se definen variables extra de Ansible para modificar los valores asignados por defecto en los playbooks de Ansible.

  • k8s_cluster_name: K8S_NAME
  • k8s_master_admin_user: “vagrant”
  • k8s_master_admin_group: “vagrant”
  • k8s_master_apiserver_advertise_address: “#{IP_BASE}#{i + 10}”
  • k8s_master_node_name: “k8s-m-#{i}”

Los nodos trabajadores de Kubernetes se crean entre las líneas 42 y 64 usando un código similar al de los maestros.

El nombre de los nodos se crear usando la expresión “k8s-n-#{j}” que identifica la máquina como perteneciente al clúster de k8s, como nodo «n» y con un número (usando la variable de bucle j)

Ansible

Playbook k8s.yml

El playbook ejecutado por el provisionador Ansible de Vagrant, Selecciona los host utilizando un patrón con comodín (k8s-m-* and k8s-n-*) que permite diferenciar entre maestro y nodos.

- hosts: k8s-m-*
  become: yes
  roles:
    - { role: k8s/master}   
- hosts: k8s-n-*
  become: yes
  roles:
    - { role: k8s/node}   

k8s/master

El playbook de Ansible k8s/master crea el nodo maestros de Kubernetes usando la siguiente configuración por defecto que puede ser redefinida en el fichero Vagrantfile (ver Vagrantfile Ansible extra vars).

k8s_master_admin_user:  "ubuntu"
k8s_master_admin_group: "ubuntu"
k8s_master_node_name: "k8s-m"
k8s_cluster_name:     "k8s-cluster"
k8s_master_apiserver_advertise_address: "192.168.101.100"
k8s_master_pod_network_cidr: "192.168.112.0/20"

Al tratarse de un clúster muy pequeño, se ha modificado el rango para la red de PODs, cambiándolo de 192.168.0.0/16 a 192.168.112.0/20 (192.168.112.0 – 192.168.127.255).

Se puede modificar tanto en la configuración por defecto del rol de Ansible o en al fichero Vagrantfile como variable extra.

El rol requiere la instalación de algunos paquetes adicionales que son comunes a los nodos maestros y trabajadores de Kubernetes. Existe un rol específico para cada tarea, y se utiliza la carpeta meta para listar dependencias del rol y las variables de esas dependencias.

Detalle de las dependencias del rol para el maestro de Kubernetes en Ansible. Se indica que depende de la ejecución previa del rol k8s/common y se indica el valor de las variables del role:

dependencies:
  - { role: k8s/common,
      k8s_common_admin_user: "{{k8s_master_admin_user}}",
      k8s_common_admin_group: "{{k8s_master_admin_group}}"
    }

Una vez resueltas las dependencias se ejecuta el playbook que instala el master:

#https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/
#https://kubernetes.io/docs/reference/config-api/kubeadm-config.v1beta3/
- name: Configuring the kubelet cgroup driver
  template:
     src: kubeadm-config.yaml
     dest: /home/{{ k8s_master_admin_user }}/kubeadm-config.yaml 

#https://docs.projectcalico.org/v3.6/getting-started/kubernetes/
#kubeadm init --config /home/vagrant/kubeadm-config.yaml
# --cri-socket /run/containerd/containerd.sock
#--apiserver-advertise-address=192.168.50.11 --apiserver-cert-extra-sans=192.168.50.11 --node-name=k8s-m-1 --pod-network-cidr=192.168.112.0/20 
- name: Configure kubectl
  command: kubeadm init --config /home/{{ k8s_master_admin_user }}/kubeadm-config.yaml
  # --apiserver-advertise-address="{{ k8s_master_apiserver_advertise_address }}" --apiserver-cert-extra-sans="{{ k8s_master_apiserver_advertise_address }}" --node-name="{{ k8s_master_node_name }}" --pod-network-cidr="{{ k8s_master_pod_network_cidr }}"
  args: 
    creates: /etc/kubernetes/manifests/kube-apiserver.yaml

- name: Create .kube dir for {{ k8s_master_admin_user }} user
  file:
      path: "/home/{{ k8s_master_admin_user }}/.kube"
      state: directory

- name: Copy kube config to {{ k8s_master_admin_user }} home .kube dir 
  copy:
    src: /etc/kubernetes/admin.conf
    dest:  /home/{{ k8s_master_admin_user }}/.kube/config
    remote_src: yes
    owner: "{{ k8s_master_admin_user }}"
    group: "{{ k8s_master_admin_group }}"
    mode: 0660

#Rewrite calico replacing defaults
#https://docs.projectcalico.org/getting-started/kubernetes/self-managed-onprem/onpremises
- name: Rewrite calico.yaml
  template:
     src: calico/3.15/calico.yaml
     dest: /home/{{ k8s_master_admin_user }}/calico.yaml 
    
- name: Install Calico (using Kubernetes API datastore)
  become: false
  command: kubectl apply -f /home/{{ k8s_master_admin_user }}/calico.yaml 
  
# Step 2.6 from https://kubernetes.io/blog/2019/03/15/kubernetes-setup-using-ansible-and-vagrant/
- name: Generate join command
  command: kubeadm token create --print-join-command
  register: join_command

- name: Copy join command for {{ k8s_cluster_name }} cluster to local file
  become: false
  local_action: copy content="{{ join_command.stdout_lines[0] }}" dest="./{{ k8s_cluster_name }}-join-command"

k8s/node

Cada nodo trabajador debe ser añadido como miembro del clúster, para lo cual se utiliza el comando y el token generado al instalar el maestro.

El comando de unión es kubeadm join y los parámetros son la IP y puerto del API (api-server-endpoint) ubicado en el maestro, el token y un hash para validar la clave pública del CA (autoridad certificadora) del clúster.

kubeadm join 192.168.50.11:6443 --token lmnbkq.80h4j8ez0vfktytw --discovery-token-ca-cert-hash sha256:54bbeb6b1a519700ae1f2e53c6f420vd8d4fe2d47ab4dbd7ce1a7f62c457f68a1

El playbook para instalar los nodos es muy breve, tiene una dependencia con el playbook k8s/common para fijar el usuario y grupo administrado.

dependencies:
  - { role: k8s/common,
      k8s_common_admin_user: "{{k8s_node_admin_user}}",
      k8s_common_admin_group: "{{k8s_node_admin_group}}"
    }

Tareas específicas para instalar los nodos de Kubernetes con Ansible:

- name: Copy the join command to {{ k8s_cluster_name }} cluster
  copy: 
    src: "./{{ k8s_cluster_name }}-join-command" 
    dest: /home/{{ k8s_node_admin_user }}/{{ k8s_cluster_name }}-join-command
    owner: "{{ k8s_node_admin_user }}"
    group: "{{ k8s_node_admin_group }}"
    mode: 0760  
- name: Join the node to cluster {{ k8s_cluster_name }}
  command: sh /home/{{ k8s_node_admin_user }}/{{ k8s_cluster_name }}-join-command

k8s/common

El rol k8s/common se utiliza tanto por el maestro como por los nodos trabajadores.

Este rol a su vez hace uso de la carpeta «meta» de Ansible para indicar las dependencias. Se define una dependencia con el rol add_packages que debe instalar los paquetes indicados en la variable k8s_common_add_packages_names junto a sus repositorios y claves públicas.

dependencies:
  - { role: add_packages,
    linux_add_packages_repositories: "{{ k8s_common_add_packages_repositories }}",
    linux_add_packages_keys: "{{ k8s_common_add_packages_keys }}",
    linux_add_packages_names: "{{ k8s_common_add_packages_names }}",
    linux_remove_packages_names: "{{ k8s_common_remove_packages_names }}"
    }

Definición de variables:

k8s_common_add_packages_keys:
- key: https://download.docker.com/linux/ubuntu/gpg
- key: https://packages.cloud.google.com/apt/doc/apt-key.gpg

k8s_common_add_packages_repositories:
- repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ansible_distribution_release}} stable"
- repo: "deb https://apt.kubernetes.io/ kubernetes-xenial main" #k8s not available for Bionic (Ubuntu 18.04)

k8s_common_add_packages_names:
- name: apt-transport-https
- name: curl
- name: containerd.io
- name: kubeadm 
- name: kubelet 
- name: kubectl

k8s_common_remove_packages_names:
- name: 

k8s_common_modprobe:
  - name: overlay
  - name: br_netfilter

k8s_common_sysctl:
  - name: net.bridge.bridge-nf-call-iptables
    value: 1
  - name: net.ipv4.ip_forward
    value: 1
  - name: net.bridge.bridge-nf-call-ip6tables
    value: 1

k8s_common_admin_user:  "ubuntu"
k8s_common_admin_group: "ubuntu"

El playbook de Ansible k8s/common:

- name: Remove current swaps from fstab
  lineinfile:
    dest: /etc/fstab
    regexp: '^/[\S]+\s+none\s+swap '
    state: absent

- name: Disable swap
  command: swapoff -a
  when: ansible_swaptotal_mb > 0

- name: Configure containerd.conf modules
  template:
     src: etc/modules-load.d/containerd.conf
     dest: /etc/modules-load.d/containerd.conf 

- name: Load containerd kernel modules
  modprobe:
    name: "{{ item.name }}"
    state: present
  loop: "{{ k8s_common_modprobe }}"

- name: Configure kubernetes-cri sys params
  sysctl:
    name: "{{ item.name }}"
    value: "{{ item.value }}"
    state: present
    reload: yes
  loop: "{{ k8s_common_sysctl }}"

# https://github.com/containerd/containerd/issues/4581
# File etc/containerd/config.toml needs to be deleted before kubeadm init
- name: Configure containerd.conf
  template:
    src: etc/containerd/config.toml
    dest: /etc/containerd/config.toml
  notify: restart containerd

- name: Configure node-ip {{ k8s_node_public_ip }} at kubelet
  lineinfile:
    path: '/etc/systemd/system/kubelet.service.d/10-kubeadm.conf'
    line: 'Environment="KUBELET_EXTRA_ARGS=--node-ip={{ k8s_node_public_ip }}"'
    regexp: 'KUBELET_EXTRA_ARGS='
    insertafter: '\[Service\]'
    state: present
  notify: restart kubelet

- name: restart containerd
  service: 
    name: containerd 
    state: restarted 
    daemon_reload: yes

- name: Delete configuration for containerd.conf (see https://github.com/containerd/containerd/issues/4581)
  file:
    state: absent
    path:  /etc/containerd/config.toml

roles/add_packages

El rol add_packages está especializado en la instalación y eliminación de paquetes software.

Pasos:

  • Añadir claves públicas de los repositorios,
  • Añadir repositorios a las fuentes
  • Actualizar la caché de paquetes (si se han añadido repositorios),
  • Eliminar paquetes
  • Instalar paquetes
---
- name: Add new repositories keys
  apt_key:
    url='{{item.key}}'
  with_items: "{{ linux_add_packages_keys | default([])}}"
  when: linux_add_packages_keys is defined and not (linux_add_packages_keys is none or linux_add_packages_keys | trim == '')
  register: aptnewkeys
- name: Add new repositories to sources
  apt_repository:
    repo='{{item.repo}}'
  with_items: "{{ linux_add_packages_repositories | default([])}}"
  when: linux_add_packages_repositories is defined and not (linux_add_packages_repositories is none or linux_add_packages_repositories | trim == '')
- name: Force update cache if new keys added
  set_fact:
        linux_add_packages_cache_valid_time: 0
  when: aptnewkeys.changed
- name: Remove packages
  apt:
    name={{ item.name }}
    state=absent
  with_items: "{{ linux_remove_packages_names | default([])}}"
  when: linux_remove_packages_names is defined and not (linux_remove_packages_names is none or linux_remove_packages_names | trim == '')
- name: Install packages
  apt:
    name={{ item.name }}
    state=present
    update_cache=yes
    cache_valid_time={{linux_add_packages_cache_valid_time}}
  with_items: "{{ linux_add_packages_names | default([])}}"
  when: linux_add_packages_names is defined and not (linux_add_packages_names is none or linux_add_packages_names | trim == '')

Creación del Clúster de Kubernetes

Para ejecutar el Clúster de Kubernetes debe descargar el código fuente desde el repositorio de IT Wonder Lab, cumplir con los prerequisitorios y ejecutar vagrant up:

#vagrant up

Siguientes pasos:

Creación de un Clúster de Kubernetes usando Vagrant y Ansible en 3 minutos

8 comentarios

  1. Excelente trabajo muy detallado y me sirve de mucho en mi proceso de aprendizaje de K8s, donde podre hacer muchas pruebas, muchas gracias por la dedicación.

  2. Tu estructura de archivos no coincide con la configuracion, por ejemplo en el Vagranfile haces referencia a una carpeta «roles» la cual no esta mencionada en la estructura de archivos.
    Podrias proveer un github con todo el contenido
    Gracias

  3. Hola, Excelente tutorial, basante detallado, tengo un problema con la versión de Docker, al hacer el Vagrant Up, cuando Ansible está instalando los playbooks k8s/master falla el proceso, y dentro del log de fallo manifiesta lo siguiente:

    this Docker version is not on the list of validated versions: 20.10.1. Latest validated version: 19.03

    Intuyo que tiene que ver directamente con el repositorio de Docker que se está usando, poniendo la versión 20.10.1 en las máquinas aprovisionadas, pero para Kubernetes esta versión no es válida, he estado buscando la manera de cambiar el url al repositorio correspondiente a la última versión validada la (19.03) pero no me ha sido posible.

    Muchas gracias

    Adjunto todo el log por si es de ayuda

    fatal: [k8s-m-1]: FAILED! => {«changed»: true, «cmd»: [«kubeadm», «init», «–apiserver-advertise-address=192.168.50.11», «–apiserver-cert-extra-sans=192.168.50.11», «–node-name=k8s-m-1», «–pod-network-cidr=192.168.112.0/20»], «delta»: «0:28:18.621996», «end»: «2020-12-29 20:32:03.440619», «msg»: «non-zero return code», «rc»: 1, «start»: «2020-12-29 20:03:44.818623», «stderr»: «\t[WARNING IsDockerSystemdCheck]: detected \»cgroupfs\» as the Docker cgroup driver. The recommended driver is \»systemd\». Please follow the guide at https://kubernetes.io/docs/setup/cri/\n\t[WARNING SystemVerification]: this Docker version is not on the list of validated versions: 20.10.1. Latest validated version: 19.03\nerror execution phase wait-control-plane: couldn’t initialize a Kubernetes cluster\nTo see the stack trace of this error execute with –v=5 or higher», «stderr_lines»: [«\t[WARNING IsDockerSystemdCheck]: detected \»cgroupfs\» as the Docker cgroup driver. The recommended driver is \»systemd\». Please follow the guide at https://kubernetes.io/docs/setup/cri/«, «\t[WARNING SystemVerification]: this Docker version is not on the list of validated versions: 20.10.1. Latest validated version: 19.03», «error execution phase wait-control-plane: couldn’t initialize a Kubernetes cluster», «To see the stack trace of this error execute with –v=5 or higher»], «stdout»: «[init] Using Kubernetes version: v1.20.1\n[preflight] Running pre-flight checks\n[preflight] Pulling images required for setting up a Kubernetes cluster\n[preflight] This might take a minute or two, depending on the speed of your internet connection\n[preflight] You can also perform this action in beforehand using ‘kubeadm config images pull’\n[certs] Using certificateDir folder \»/etc/kubernetes/pki\»\n[certs] Generating \»ca\» certificate and key\n[certs] Generating \»apiserver\» certificate and key\n[certs] apiserver serving cert is signed for DNS names [k8s-m-1 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.50.11]\n[certs] Generating \»apiserver-kubelet-client\» certificate and key\n[certs] Generating \»front-proxy-ca\» certificate and key\n[certs] Generating \»front-proxy-client\» certificate and key\n[certs] Generating \»etcd/ca\» certificate and key\n[certs] Generating \»etcd/server\» certificate and key\n[certs] etcd/server serving cert is signed for DNS names [k8s-m-1 localhost] and IPs [192.168.50.11 127.0.0.1 ::1]\n[certs] Generating \»etcd/peer\» certificate and key\n[certs] etcd/peer serving cert is signed for DNS names [k8s-m-1 localhost] and IPs [192.168.50.11 127.0.0.1 ::1]\n[certs] Generating \»etcd/healthcheck-client\» certificate and key\n[certs] Generating \»apiserver-etcd-client\» certificate and key\n[certs] Generating \»sa\» key and public key\n[kubeconfig] Using kubeconfig folder \»/etc/kubernetes\»\n[kubeconfig] Writing \»admin.conf\» kubeconfig file\n[kubeconfig] Writing \»kubelet.conf\» kubeconfig file\n[kubeconfig] Writing \»controller-manager.conf\» kubeconfig file\n[kubeconfig] Writing \»scheduler.conf\» kubeconfig file\n[kubelet-start] Writing kubelet environment file with flags to file \»/var/lib/kubelet/kubeadm-flags.env\»\n[kubelet-start] Writing kubelet configuration to file \»/var/lib/kubelet/config.yaml\»\n[kubelet-start] Starting the kubelet\n[control-plane] Using manifest folder \»/etc/kubernetes/manifests\»\n[control-plane] Creating static Pod manifest for \»kube-apiserver\»\n[control-plane] Creating static Pod manifest for \»kube-controller-manager\»\n[control-plane] Creating static Pod manifest for \»kube-scheduler\»\n[etcd] Creating static Pod manifest for local etcd in \»/etc/kubernetes/manifests\»\n[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory \»/etc/kubernetes/manifests\». This can take up to 4m0s\n[kubelet-check] Initial timeout of 40s passed.\n\n\tUnfortunately, an error has occurred:\n\t\ttimed out waiting for the condition\n\n\tThis error is likely caused by:\n\t\t- The kubelet is not running\n\t\t- The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)\n\n\tIf you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:\n\t\t- ‘systemctl status kubelet’\n\t\t- ‘journalctl -xeu kubelet’\n\n\tAdditionally, a control plane component may have crashed or exited when started by the container runtime.\n\tTo troubleshoot, list all containers using your preferred container runtimes CLI.\n\n\tHere is one example how you may list all Kubernetes containers running in docker:\n\t\t- ‘docker ps -a | grep kube | grep -v pause’\n\t\tOnce you have found the failing container, you can inspect its logs with:\n\t\t- ‘docker logs CONTAINERID'», «stdout_lines»: [«[init] Using Kubernetes version: v1.20.1», «[preflight] Running pre-flight checks», «[preflight] Pulling images required for setting up a Kubernetes cluster», «[preflight] This might take a minute or two, depending on the speed of your internet connection», «[preflight] You can also perform this action in beforehand using ‘kubeadm config images pull'», «[certs] Using certificateDir folder \»/etc/kubernetes/pki\»», «[certs] Generating \»ca\» certificate and key», «[certs] Generating \»apiserver\» certificate and key», «[certs] apiserver serving cert is signed for DNS names [k8s-m-1 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.50.11]», «[certs] Generating \»apiserver-kubelet-client\» certificate and key», «[certs] Generating \»front-proxy-ca\» certificate and key», «[certs] Generating \»front-proxy-client\» certificate and key», «[certs] Generating \»etcd/ca\» certificate and key», «[certs] Generating \»etcd/server\» certificate and key», «[certs] etcd/server serving cert is signed for DNS names [k8s-m-1 localhost] and IPs [192.168.50.11 127.0.0.1 ::1]», «[certs] Generating \»etcd/peer\» certificate and key», «[certs] etcd/peer serving cert is signed for DNS names [k8s-m-1 localhost] and IPs [192.168.50.11 127.0.0.1 ::1]», «[certs] Generating \»etcd/healthcheck-client\» certificate and key», «[certs] Generating \»apiserver-etcd-client\» certificate and key», «[certs] Generating \»sa\» key and public key», «[kubeconfig] Using kubeconfig folder \»/etc/kubernetes\»», «[kubeconfig] Writing \»admin.conf\» kubeconfig file», «[kubeconfig] Writing \»kubelet.conf\» kubeconfig file», «[kubeconfig] Writing \»controller-manager.conf\» kubeconfig file», «[kubeconfig] Writing \»scheduler.conf\» kubeconfig file», «[kubelet-start] Writing kubelet environment file with flags to file \»/var/lib/kubelet/kubeadm-flags.env\»», «[kubelet-start] Writing kubelet configuration to file \»/var/lib/kubelet/config.yaml\»», «[kubelet-start] Starting the kubelet», «[control-plane] Using manifest folder \»/etc/kubernetes/manifests\»», «[control-plane] Creating static Pod manifest for \»kube-apiserver\»», «[control-plane] Creating static Pod manifest for \»kube-controller-manager\»», «[control-plane] Creating static Pod manifest for \»kube-scheduler\»», «[etcd] Creating static Pod manifest for local etcd in \»/etc/kubernetes/manifests\»», «[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory \»/etc/kubernetes/manifests\». This can take up to 4m0s», «[kubelet-check] Initial timeout of 40s passed.», «», «\tUnfortunately, an error has occurred:», «\t\ttimed out waiting for the condition», «», «\tThis error is likely caused by:», «\t\t- The kubelet is not running», «\t\t- The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)», «», «\tIf you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:», «\t\t- ‘systemctl status kubelet'», «\t\t- ‘journalctl -xeu kubelet'», «», «\tAdditionally, a control plane component may have crashed or exited when started by the container runtime.», «\tTo troubleshoot, list all containers using your preferred container runtimes CLI.», «», «\tHere is one example how you may list all Kubernetes containers running in docker:», «\t\t- ‘docker ps -a | grep kube | grep -v pause'», «\t\tOnce you have found the failing container, you can inspect its logs with:», «\t\t- ‘docker logs CONTAINERID'»]}

  4. Hola, Excelente tutorial, basante detallado, tengo un problema con la versión de Docker, al hacer el Vagrant Up, cuando Ansible está instalando los playbooks k8s/master falla el proceso, y dentro del log de fallo manifiesta lo siguiente:

    this Docker version is not on the list of validated versions: 20.10.1. Latest validated version: 19.03

    Intuyo que tiene que ver directamente con el repositorio de Docker que se está usando, poniendo la versión 20.10.1 en las máquinas aprovisionadas, pero para Kubernetes esta versión no es válida, he estado buscando la manera de cambiar el url al repositorio correspondiente a la última versión validada la (19.03) pero no me ha sido posible, me gustaría saber si alguno de ustedes ha solucionado este problema o tal vez el problema esté en otra cosa.

    Muchas gracias

    1. Hola Carlo

      Gracias por indicar que te gusta el tutorial. No he conseguido reproducir el error que indicas, aunque he encontrado en Internet el mismo error. Lo investigaré.

      Para especificar la versión de un paquete en Ansible se debe añadir la versión al nombre del paquete. En este tutorial se haría así:

      ansible-vbox-vagrant-kubernetes/roles/k8s/common/defaults/main.yml

      k8s_common_add_packages_names:
      - name: apt-transport-https
      - name: curl
      - name: docker-ce=5:19.03.14~3-0~ubuntu-focal
      - name: docker-ce-cli=5:19.03.14~3-0~ubuntu-focal
      - name: containerd.io
      - name: kubeadm
      - name: kubelet
      - name: kubectl

      Sin embargo esto trae otros problemas por las dependencias y fallará salvo que se permita a APT (en Ansible) hacer un downgrade del paquete.

      En las próximas versiones de Kubernetes se dejará de usar docker como wrapper de containerd.

      He subido la última versión del código de este tutorial al repositorio de GITHUB, por favor prueba con esa.

      Un saludo
      Javier

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

ansible-aws-ec2-terraform-tags - ansible-aws-ec2-terraform-tags-ec2.png
Tutoriales de Amazon Web Services (AWS)

Integración de Ansible y Terraform

Integración de Ansible y Terraform para configuración de infraestructura en AWS mediante tags e inventario dinámico.