Press "Enter" to skip to content

Como instalar Kubernetes en Linux con Ansible

El presente proyecto parte de la necesidad que tuve hace unos meses de instalar un clúster Kubernetes de manera on-premise (bare metal) para un trabajo particular. En mi opinión, el despliegue de Kubernetes desde cero es complejo, toma tiempo y requiere de mucha habilidad y conocimiento técnico de parte del administrador.

Es por ello que una vez logré realizarlo con éxito, decidí recopilar todas la tareas y configuraciones necesarias en un proyecto Ansible, de manera que pudiera reutilizar el procedimiento cuando lo necesitase, además de quedar documentado para el futuro.

En el presente artículo trato de explicar el procedimiento de instalación de un clúster Kubernetes en Linux usando Ansible, el despliegue de aplicaciones simples, así como también el uso de comandos para verficar el correcto funcionamiento de nodos, deployments, servicios, ingress, etc. en Kubernetes.

🔰 Requerimientos Iniciales

Para la ejecución del proyecto Ansible necesitaremos como mínimo 3 hosts Linux (Ansible, Master y Worker), los cuales tendrán las siguientes funciones y características:

ServidorFunciónSoftwareHardware
AnsibleControlador Ansible desde el cual se realizará el despliegue.Centos 7/8
Rocky Linux 8
Debe tener instalado Ansible y GIT
2 CPU
2 GB RAM
MasterRealizará la función de Kubernetes MasterCentos 7/8
Rocky Linux 8
Instalación Limpia
2 CPU
4 GB RAM
WorkerRealizará la función de Kubernetes Worker.
Podemos desplegar 1 o más.
Centos 7/8
Rocky Linux 8
Instalación Limpia
2 CPU
4 GB RAM
NFS
(Opcional)
Ejecutará la función de Servidor NFS
Podemos reutilizar el host Master para esta función
Centos 7/8
Rocky Linux 8
Instalación Limpia
2 CPU
2 GB RAM
  • Para todos los servidores podemos usar CentOS 7/8 o Rocky Linux 8.
  • El servidor Ansible funcionará como controlador y debe tener Ansible y el cliente de GIT instalados:
[adminUsername@localhost ~]$ sudo yum install epel-release
[adminUsername@localhost ~]$ sudo yum install ansible
[adminUsername@localhost ~]$ sudo yum install git
[adminUsername@localhost ~]$
[adminUsername@localhost ~]$ git --version
git version 2.27.0
[adminUsername@localhost ~]$ ansible --version
ansible 2.9.25
  • Para los servidores Master, Worker y NFS, el sistema operativo debe estar recién instalado, actualizado y sin paquetes ni software adicional.
  • Para el presente proyecto se recomienda desplegar un sólo servidor Master y NFS. Podemos desplegar tantos servidores Worker como deseemos y los recursos nos lo permitan.

🌉 Infraestructura de Despliegue

Todas los servidores se encontrarán conectados al mismo segmento de red (en este caso 10.0.1.0/24) según la imagen:

Instalar Kubernetes con Ansible
Diagrama de la Infraestructura Kubernetes

Ya con los servidores preparados, procederemos a explicar el procedimiento de despliegue:

En el host Ansible descargaremos el proyecto Ansible con git, personalizaremos algunos archivos, configuraremos el acceso a los hosts Kubernetes (master, workers y nfs) y lanzaremos el despliegue sobre dichos servidores.

🧐 Estructura del Proyecto Ansible

El proyecto Ansible que utilizaremos se encuentra disponible en el siguiente repositorio GitHub: https://github.com/jruizcampos/kubernetes-deploy

Procedemos a clonar el repositorio:

[adminUsername@localhost ~]$ git clone https://github.com/jruizcampos/kubernetes-deploy.git
Cloning into 'kubernetes-deploy'...
remote: Enumerating objects: 730, done.
remote: Counting objects: 100% (730/730), done.
remote: Compressing objects: 100% (432/432), done.
remote: Total 730 (delta 341), reused 513 (delta 140), pack-reused 0
Receiving objects: 100% (730/730), 158.54 KiB | 418.00 KiB/s, done.
Resolving deltas: 100% (341/341), done.

Ingresamos al proyecto, carpeta ansible y revisamos su contenido:

[adminUsername@localhost ~]$ cd kubernetes-deploy/ansible/
[adminUsername@localhost ansible]$ ls -lh
total 40K
-rw-rw-r--. 1 john john 171 Nov  8 05:51 01-configuraciones-comunes.yml
-rw-rw-r--. 1 john john 150 Nov  8 05:51 02-configurando-nfs.yml
-rw-rw-r--. 1 john john 204 Nov  8 05:51 03-configuraciones-master-worker.yml
-rw-rw-r--. 1 john john 163 Nov  8 05:51 04-configurando-master.yml
-rw-rw-r--. 1 john john 166 Nov  8 05:51 05-configurando-workers.yml
-rw-rw-r--. 1 john john 155 Nov  8 05:51 06-tests-cluster.yml
-rwxrwxr-x. 1 john john 228 Nov  8 05:51 deploy.sh
-rw-rw-r--. 1 john john 173 Nov  8 05:51 despliegue-aplicacion.yml
-rw-rw-r--. 1 john john 755 Nov  8 05:51 despliegue-kubernetes.yml
drwxrwxr-x. 2 john john 141 Nov  8 05:51 group_vars
-rw-rw-r--. 1 john john 157 Nov  8 05:51 hosts
drwxrwxr-x. 9 john john 106 Nov  8 05:51 roles

De la lista, los 2 playbooks principales son despliegue-kubernetes.yml y despliegue-aplicacion.yml. Estos 2 playbooks llaman o usan todos los demás archivos del proyecto según el siguiente esquema:

Proyecto Ansible Kubernetes
Estructura del Proyecto Ansible

El playbook despliegue-kubernetes.yml se encarga del despliegue del clúster Kubernetes en sí, y el playbook despliegue-aplicacion.yml instala de manera opcional un par de aplicaciones de ejemplo sobre el clúster ya instalado.

El script bash deploy.sh es sólo una manera cómoda de llamar a ambos playbooks de manera secuencial.

🎚️ Personalizando el Proyecto

Antes de realizar el despliegue, debemos editar un par de archivos con valores acordes a nuestra infraestructura en particular.

📄 Archivo ansible/hosts:

[all:vars]
# Usuario linux para conectarse a los hosts
ansible_user=adminUsername

# Definimos el host master y su ip
[master]
master1 ansible_host=10.0.1.21

# Definimos los hosts worker y sus ips
[workers]
worker1 ansible_host=10.0.1.51
# worker2 ansible_host=10.0.1.52
# .
# .
# .
# worker[n] ansible_host=10.0.1.n

# Definimos el host NFS y su ip
[nfs]
nfs1 ansible_host=10.0.1.21

Este archivo debemos modificarlo de acuerdo a lo siguiente:

ansible_user=adminUsername

En esta sección colocaremos en ansible_user el Usuario Linux que Ansible utilizará para conectarse a los hosts [master], [workers] y [nfs] (en este caso adminUsername). Debemos colocar el que nosotros hayamos configurado en dichos sistemas.

[master]
master1 ansible_host=10.0.1.21

En esta sección colocaremos en ansible_host la dirección ip del host master1 (en este caso 10.0.1.21).

[workers]
worker1 ansible_host=10.0.1.51
worker2 ansible_host=10.0.1.52
...

En esta sección colocaremos en ansible_host la dirección ip de los hosts worker1, worker2, etc. En este caso 10.0.1.51, 10.0.1.52, etc.

[nfs]
nfs1 ansible_host=10.0.1.21

En esta sección colocaremos en ansible_host la dirección ip del host nfs. En este caso 10.0.1.21 (estamos reutilizando el master para la función nfs).

📄 Archivo ansible/group_vars/master.yaml

# Red de Docker configurada en el master. La obtenemos a través de los facts
docker_network: "{{ ansible_docker0.ipv4.network }}/16"

# Aquí definimos la red por defecto para los pods. Podemos cambiarla si así lo deseamos.
pod_network: "10.255.0.0/16"

# Aquí definimos que tipo de SDN queremos desplegar Calico o Flannel
sdn: flannel
# sdn: calico

En este archivo modificaremos parámetros relacionados a la instalación de Kubernetes de acuerdo a lo siguiente:

pod_network: "10.255.0.0/16"

En esta sección colocaremos en pod_network el segmento de red de los pods en formato CIDR. Este valor por lo general no se cambia y se usa el que está por defecto: 10.255.0.0/16

sdn: calico

En esta sección colocaremos en sdn el tipo de SDN a utilizar en la instalación de Kubernetes: calico o flannel (en minúsculas).

La elección de Calico o Flannel depende de la infraestructura subyacente sobre la cual estamos instalando Kubernetes:

  1. Si estamos realizando una instalación on-premise (bare-metal) se recomienda usar Calico puesto que es más completo y personalizable (aunque Flannel también funcionará).
  2. Si estamos realizando una instalación sobre máquinas virtuales desplegadas en un proveedor de cloud como Azure, debemos usar Flannel puesto que los proveedores de cloud por defecto bloquean a nivel de red ciertos puertos y protocolos necesarios para el correcto funcionamiento de Calico.

🔐 Configurando los permisos de Ansible

🔑 Configurando el acceso directo por SSH a los hosts

Antes de proceder con el despliegue, debemos configurar el acceso por SSH desde el host Ansible hacia los hosts master y workers sin necesidad de ingresar usuario y contraseña.

Para ello, en nuestro host Ansible debemos contar con las llaves (keys) SSH privada y pública de nuestro usuario Linux. En caso no se encuentren creadas, podemos hacerlo con el comando ssh-keygen:

[adminUsername@localhost ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/adminUsername/.ssh/id_rsa):
Created directory '/home/adminUsername/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/adminUsername/.ssh/id_rsa.
Your public key has been saved in /home/adminUsername/.ssh/id_rsa.pub.

Ahora procederemos a copiar nuestra llave pública a los hosts master y worker:

[localhost ~]$ ssh-copy-id -i ~/.ssh/id_rsa.pub adminUsername@10.0.1.21
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/adminUsername/.ssh/id_rsa.pub"
The authenticity of host '10.0.1.21 (10.0.1.21)' can't be established.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
adminUsername@10.0.1.21's password:

Number of key(s) added: 1

[localhost ~]$ ssh-copy-id -i ~/.ssh/id_rsa.pub adminUsername@10.0.1.51
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/adminUsername/.ssh/id_rsa.pub"
The authenticity of host '10.0.1.51 (10.0.1.51)' can't be established.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
adminUsername@10.0.1.51's password:

Number of key(s) added: 1

De esta manera, ya podemos iniciar sesión en dichos servidores sin necesidad de usar usuario y clave. Podemos verificarlo con los comandos a continuación:

[adminUsername@localhost ~]$ ssh adminUsername@10.0.1.21
[adminUsername@localhost ~]$
[adminUsername@localhost ~]$ ssh adminUsername@10.0.1.51
[adminUsername@localhost ~]$

⭐ Configurando visudo en los hosts Kubernetes

Adicionalmente, debemos habilitar en los hosts master, workers y nfs que el usuario Linux pueda escalar privilegios mediante sudo pero sin solicitar la contraseña.

Para ello, en cada servidor ejecutamos el comando visudo como root:

[adminUsername@localhost ~]$ sudo visudo

Agregamos la siguiente línea al final del archivo y guardamos:

## Same thing without a password
adminUsername    ALL=(ALL)       NOPASSWD: ALL

Ahora si probamos escalar privilegios como superusuario (root), veremos que ya no se nos pide la contraseña:

[adminUsername@localhost ~]$ sudo su
[root@localhost adminUsername]#

🏗️ Despliegue del Clúster Kubernetes

Ya estamos listos para realizar el despliegue de nuestro clúster Kubernetes.

⚙️ Instalando Kubernetes

Ingresamos a la carpeta kubernetes-deploy/ansible y ejecutamos lo siguiente:

[localhost ansible]$ ansible-playbook -i hosts despliegue-kubernetes.yml

Mientras se ejecuta el playbook, se mostrarán una a una las tareas que se van realizando sobre los hosts. A continuación un resumen:

PLAY [Realizando configuraciones comunes a todos los nodos] ***********************

TASK [Gathering Facts] ************************************************************
ok: [master1]
ok: [nfs1]
ok: [worker1]

TASK [common : Configuramos el hostname] ******************************************
skipping: [nfs1]
changed: [worker1]
changed: [master1]

TASK [common : Actualizando los paquetes del Sistema] *****************************
changed: [worker1]
changed: [master1]
changed: [nfs1]

TASK [master_workers : Instalamos Docker] *****************************************
changed: [worker1]
changed: [master1]

TASK [master_workers : Instalamos Kubernetes] *************************************
changed: [master1]
changed: [worker1]

TASK [master : debug] *************************************************************
ok: [master1] => {
    "msg": "Se ejecutara: kubeadm init --apiserver-advertise-address 10.0.1.21 --pod-network-cidr 10.255.0.0/16"
}

TASK [master : Mostrando la salida de la ejecución del kubeadm init] **************
ok: [master1] => {
    "msg": [
        "[init] Using Kubernetes version: v1.22.3",
        "[preflight] Running pre-flight checks",
        "[preflight] Pulling images required for setting up a Kubernetes cluster",
        "Your Kubernetes control-plane has initialized successfully!",
        "You should now deploy a pod network to the cluster.",
        "Run \"kubectl apply -f [podnetwork].yaml\" with one of the options listed at:",
        "  https://kubernetes.io/docs/concepts/cluster-administration/addons/",
        "",
        "Then you can join any number of worker nodes by running the following on each as root:",
        "",
        "kubeadm join 10.0.1.21:6443 --token xxxxxx.xxxxxxxxxxxxxxxx \\",
        "\t--discovery-token-ca-cert-hash sha256:fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff "
    ]
}

TASK [master : Extrayendo el hash CRT CA del Master] ******************************
changed: [master1]

TASK [master : Extrayendo el token del Master en formato JSON] ********************
changed: [master1]

TASK [master : Instalamos el operador de Tigera] **********************************
changed: [master1]

TASK [master : Instalamos la SDN Calico] ******************************************
changed: [master1]

TASK [master : Mostramos el resultado de la instalación de la SDN Calico] *********
ok: [master1] => {
    "msg": [
        "installation.operator.tigera.io/default created"
    ]
}

TASK [master : Desplegando el Nginx Ingress Controller] ***************************
changed: [master1]

TASK [master : Mostramos el resultado de la instalación Nginx Ingress Controller] * 
ok: [master1] => {
    "msg": [
        "namespace/ingress-nginx created",
        "serviceaccount/ingress-nginx created",
        "configmap/ingress-nginx-controller created",
        "clusterrole.rbac.authorization.k8s.io/ingress-nginx created",
        "clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created",
        "role.rbac.authorization.k8s.io/ingress-nginx created",
        "rolebinding.rbac.authorization.k8s.io/ingress-nginx created",
        "service/ingress-nginx-controller-admission created",
        "service/ingress-nginx-controller created",
        "deployment.apps/ingress-nginx-controller created",
        "ingressclass.networking.k8s.io/nginx created",
        "serviceaccount/ingress-nginx-admission created",
        "role.rbac.authorization.k8s.io/ingress-nginx-admission created",
        "job.batch/ingress-nginx-admission-create created",
        "job.batch/ingress-nginx-admission-patch created"
    ]
}

PLAY [Realizando configuraciones en los Workers] **********************************

TASK [workers : debug] ************************************************************
ok: [worker1] => {
    "msg": "La IP del master es: 10.0.1.21"
}

TASK [workers : Habilitamos los puertos para unirse al clúster 10250] *************
changed: [worker1]

TASK [workers : Habilitamos los puertos para unirse al clúster 30000 32767] *******
changed: [worker1]

TASK [workers : Unimos el Worker al clúster Kubernetes] ***************************
changed: [worker1]

TASK [workers : Resultado de la unión del Worker al clúster] **********************
ok: [worker1] => {
    "msg": [
        "[preflight] Running pre-flight checks",
        "[preflight] Reading configuration from the cluster...",
        "[kubelet-start] Writing kubelet configuration to file \"/var/lib/kubelet/config.yaml\"",
        "[kubelet-start] Writing kubelet environment file with flags to file \"/var/lib/kubelet/kubeadm-flags.env\"",
        "[kubelet-start] Starting the kubelet",
        "[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...",
        "This node has joined the cluster:",
        "* Certificate signing request was sent to apiserver and a response was received.",
        "* The Kubelet was informed of the new secure connection details.",
        "Run 'kubectl get nodes' on the control-plane to see this node join the cluster."
    ]
}

TASK [tests : Mostrando información del Cluster recién desplegado] ****************
ok: [master1] => {
    "msg": "\"--> SE CULMINÓ EL DESPLIEGUE DEL CLÚSTER DE KUBERNETES\"\n\"['\\x1b[0;32mKubernetes control plane\\x1b[0m is running at \\x1b[0;33mhttps://10.0.1.21:6443\\x1b[0m', '\\x1b[0;32mCoreDNS\\x1b[0m is running at \\x1b[0;33mhttps://10.0.1.21:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy\\x1b[0m', '', \"To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.\"]\"\n"
}

TASK [tests : Ejecutando kubectl get svc] *****************************************
changed: [master1]

TASK [tests : Mostramos el resultado de kubectl get svc] **************************
ok: [master1] => {
"msg": [
"NAMESPACE    NAME    TYPE    CLUSTER-IP    EXTERNAL-IP    PORT(S)    AGE",
"default    kubernetes    ClusterIP 10.96.0.1    <none>    443/TCP    45s",
"ingress-nginx  ingress-nginx-controller  NodePort  10.97.92.38  <none>  80:30614/TCP,443:31329/TCP  32s",
"ingress-nginx  ingress-nginx-controller-admission  ClusterIP  10.101.84.254  <none>  443/TCP  32s",
"kube-system  kube-dns  ClusterIP  10.96.0.10  <none>  53/UDP,53/TCP,9153/TCP  42s"
    ]
}

Al finalizar la ejecución del playbook, podemos verificar si todas las tareas terminaron correctamente o si algunas fallaron:

PLAY RECAP ************************************************************************
master1: ok=71 changed=50 unreachable=0  failed=0  skipped=2  rescued=0  ignored=0
nfs1   : ok=2  changed=2  unreachable=0  failed=0  skipped=1  rescued=0  ignored=0
worker1: ok=39 changed=28 unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

💡 Instalando las Aplicaciones de ejemplo

Con el clúster Kubernetes ya instalado, podemos proceder a instalar las aplicaciones de ejemplo. Para ello ejecutamos:

[localhost ansible]$ ansible-playbook -i hosts despliegue-aplicacion.yml

Al igual que con el playbook anterior, se mostrarán una a una las tareas que se van realizando sobre el host Kubernetes master:

PLAY [Realizando el Despliegue de la aplicación sobre Kubernetes] *****************

TASK [Gathering Facts] ************************************************************
ok: [master1]

TASK [app : Copiando los archivos de las Aplicaciones] ****************************
changed: [master1] => (item=mongodb-secret.yaml)
changed: [master1] => (item=mongodb-cm.yaml)
changed: [master1] => (item=mongodb.yaml)
changed: [master1] => (item=mongo-express.yaml)
changed: [master1] => (item=ajedrez-app.yaml)
changed: [master1] => (item=myingress.yaml)

TASK [app : Desplegando las Aplicaciones] *****************************************
changed: [master1] => (item=mongodb-secret.yaml)
changed: [master1] => (item=mongodb-cm.yaml)
changed: [master1] => (item=mongodb.yaml)
changed: [master1] => (item=mongo-express.yaml)
changed: [master1] => (item=ajedrez-app.yaml)
changed: [master1] => (item=myingress.yaml)

TASK [app : Mostramos el resultado del despliegue de las Aplicaciones] ************
ok: [master1] => (item={'cmd': 'kubectl apply -f /root/mongodb-secret.yaml', 'stdout': 'secret/mongodb-secret created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:33.595560', 'end': '2021-11-10 16:21:33.895110', 'delta': '0:00:00.299550', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/mongodb-secret.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['secret/mongodb-secret created'], 'stderr_lines': [], 'failed': False, 'item': 'mongodb-secret.yaml', 'ansible_loop_var': 'item'}) => {
    "msg": [
        "secret/mongodb-secret created"
    ]
}

ok: [master1] => (item={'cmd': 'kubectl apply -f /root/mongodb-cm.yaml', 'stdout': 'configmap/mongodb-configmap created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:34.264286', 'end': '2021-11-10 16:21:34.618406', 'delta': '0:00:00.354120', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/mongodb-cm.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['configmap/mongodb-configmap created'], 'stderr_lines': [], 'failed': False, 'item': 'mongodb-cm.yaml', 'ansible_loop_var': 'item'}) => {
    "msg": [
        "configmap/mongodb-configmap created"
    ]
}

ok: [master1] => (item={'cmd': 'kubectl apply -f /root/mongodb.yaml', 'stdout': 'deployment.apps/mongodb-deployment created\nservice/mongodb-service created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:34.943632', 'end': '2021-11-10 16:21:35.427144', 'delta': '0:00:00.483512', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/mongodb.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['deployment.apps/mongodb-deployment created', 'service/mongodb-service created'], 'stderr_lines': [], 'failed': False, 'item': 'mongodb.yaml', 'ansible_loop_var': 'item'}) => {
    "msg": [
        "deployment.apps/mongodb-deployment created",
        "service/mongodb-service created"
    ]
}

ok: [master1] => (item={'cmd': 'kubectl apply -f /root/mongo-express.yaml', 'stdout': 'deployment.apps/mongo-express created\nservice/mongo-ex-service created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:37.110115', 'end': '2021-11-10 16:21:40.956915', 'delta': '0:00:03.846800', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/mongo-express.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['deployment.apps/mongo-express created', 'service/mongo-ex-service created'], 'stderr_lines': [], 'failed': False, 'item': 'mongo-express.yaml', 'ansible_loop_var': 'item'}) => {
    "msg": [
        "deployment.apps/mongo-express created",
        "service/mongo-ex-service created"
    ]
}

ok: [master1] => (item={'cmd': 'kubectl apply -f /root/ajedrez-app.yaml', 'stdout': 'deployment.apps/ajedrez-app created\nservice/ajedrez-app-service created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:42.890756', 'end': '2021-11-10 16:21:44.295909', 'delta': '0:00:01.405153', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/ajedrez-app.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['deployment.apps/ajedrez-app created', 'service/ajedrez-app-service created'], 'stderr_lines': [], 'failed': False, 'item': 'ajedrez-app.yaml', 'ansible_loop_var': 'item'}) => {
    "msg": [
        "deployment.apps/ajedrez-app created",
        "service/ajedrez-app-service created"
    ]
}

ok: [master1] => (item={'cmd': 'kubectl apply -f /root/myingress.yaml', 'stdout': 'ingress.networking.k8s.io/myingress created', 'stderr': '', 'rc': 0, 'start': '2021-11-10 16:21:46.202769', 'end': '2021-11-10 16:21:49.322063', 'delta': '0:00:03.119294', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'kubectl apply -f /root/myingress.yaml', '_uses_shell': True, 'warn': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['ingress.networking.k8s.io/myingress created'], 'stderr_lines': [], 'failed': False, 'item': 'myingress.yaml', 'ansible_loop_var': 'item'}) => {
    "msg": [
        "ingress.networking.k8s.io/myingress created"
    ]
}

Terminada la ejecución del playbook, podemos verificar si todas las tareas terminaron satisfactoriamente o si alguna falló:

PLAY RECAP ***********************************************************************
master1: ok=6  changed=3  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

👌 Pruebas de estado y acceso a las Aplicaciones

En esta sección verificaremos el estado del clúster Kubernetes que acabamos de instalar y el acceso a las aplicaciones de prueba.

✅ Verificando el estado del Clúster Kubernetes

Nos conectamos al nodo master y como usuario root ejecutamos el comando kubectl cluster-info. La salida nos muestra información del clúster Kubernetes recién instalado como la URL del Control Plane y del CoreDNS:

[root@master1 ~]# kubectl cluster-info
Kubernetes control plane is running at https://10.0.1.21:6443
CoreDNS is running at https://10.0.1.21:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

Ahora ejecutaremos kubectl get nodes -o wide. Este comando nos mostrará la lista de nodos que forman parte del clúster, su estado y dirección IP:

[root@master1 ~]# kubectl get nodes -o wide
NAME                     STATUS  ROLES                 AGE  VERSION  INTERNAL-IP
master1.jruizcampos.com  Ready   control-plane,master  57m  v1.22.3  10.0.1.21
worker1.jruizcampos.com  Ready   <none>                57m  v1.22.3  10.0.1.51

Ejecutamos kubectl get deployments -o wide y kubectl get pods -o wide. Con estos comandos verificamos si las aplicaciones de prueba que desplegamos se llegaron a inicializar de manera correcta:

[root@master1 ~]# kubectl get deployments -o wide
NAME                READY  UP-TO-DATE  AVAILABLE  AGE  CONTAINERS     IMAGES
ajedrez-app         1/1    1           1          34m  ajedrez-app    chess
mongo-express       1/1    1           1          34m  mongo-express  mongo-express
mongodb-deployment  1/1    1           1          34m  mongodb        mongo
[root@master1 ~]# kubectl get pods -o wide
NAME                                READY  STATUS   RESTARTS  AGE  IP
ajedrez-app-8459955f76-699sm        1/1    Running  0         26m  10.255.78.76
mongo-express-78fcf796b8-dgbhl      1/1    Running  0         26m  10.255.78.75
mongodb-deployment-8f6675bc5-fc9gn  1/1    Running  0         26m  10.255.78.74

En los resultados mostrados se verifica que todos los despliegues de aplicación se realizaron correctamente y están listos (READY 1/1).

🖥️ Acceso a las aplicaciones directamente en los Nodos

Las aplicaciones de prueba desplegadas son 2: Un juego de ajedrez web basado en PHP y una base de datos mongodb con su gestor web mongo-express.

El acceso a estas aplicaciones es a través de servicios configurados en Kubernetes como tipo NodePort. Para ver el estado de los servicios ejcutamos kubectl get svc:

[root@master1 ~]# kubectl get svc
NAME                TYPE        CLUSTER-IP      EXTERNAL-IP  PORT(S)         AGE
ajedrez-app-service NodePort    10.96.76.52     <none>       8082:31236/TCP  57m
kubernetes          ClusterIP   10.96.0.1       <none>       443/TCP         62m
mongo-ex-service    NodePort    10.106.141.185  <none>       8081:32622/TCP  57m
mongodb-service     ClusterIP   10.103.213.238  <none>       27017/TCP       57m

Al ser de tipo NodePort, para acceder a un servicio en particular debemos ingresar a la dirección ip de cualquier nodo del clúster y en el puerto devuelto por el comando anterior (resaltado en naranja).

Por ejemplo para acceder a la aplicación de ajedrez en el nodo master: http://10.0.1.21:31236

Juego de ajedrez en el nodo Master de Kubernetes
Juego de ajedrez en el nodo Master

Accediendo al ajedrez en el nodo worker: http://10.0.1.51:31236

Juego de ajedrez en el nodo Worker de Kubernetes
Juego de ajedrez en el nodo Worker

Ahora accederemos a la segunda aplicación, el gestor web mongo-express a través del nodo master http://10.0.1.21:32622 :

Mongo Express en el nodo Master de Kubernetes
Mongo Express en el nodo Master

🚦 Acceso a las aplicaciones usando un Ingress Controller

La otra forma de publicar una aplicación Kubernetes es usando un Ingress Controller. En este caso, al momento de realizar el despliegue de Kubernetes se instaló el ingress controller de Nginx. Podemos verificarlo ejecutando kubectl get svc -n ingress-nginx :

[root@master1 ~]# kubectl get svc -n ingress-nginx
NAME                      TYPE     CLUSTER-IP    EXTERNAL-IP  PORT(S)
ingress-nginx-controller  NodePort 10.107.49.177 <none>  80:30575/TCP,443:32507/TCP
ingress-nginx-controller-admission  ClusterIP 10.104.4.66 <none>  443/TCP

En el resultado mostrado podemos ver que el ingress controller escucha en el puerto 30575 para http y 32507 para https. Estos valores nos servirán más adelante.

Como ya contamos con un servicio de ingress controller instalado en el clúster, nos queda desplegar la configuración de ingress específica para la publicación de nuestros servicios.

Cuando realizamos el despliegue de las aplicaciones con el archivo despliegue-aplicacion.yml, se desplegó también la configuración de ingress especificada en myingress.yaml. Podemos verificarlo ejecutando kubectl get ingress:

[root@master1 ~]# kubectl get ingress
NAME        CLASS    HOSTS             ADDRESS     PORTS   AGE
myingress   <none>   jruizcampos.com   10.0.1.51   80      3h35m

Podemos revisar más a detalle esta configuración con el comando kubectl describe ingress myingress:

[root@master1 ~]# kubectl describe ingress myingress
Name:             myingress
Namespace:        default
Address:          10.0.1.51
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host             Path  Backends
  ----             ----  --------
  jruizcampos.com
                   /mongodb   mongo-ex-service:8081 (10.255.1.6:8081)
                   /ajedrez   ajedrez-app-service:8082 (10.255.1.7:80)
Annotations:       kubernetes.io/ingress.class: nginx
                   nginx.ingress.kubernetes.io/add-base-url: true
                   nginx.ingress.kubernetes.io/rewrite-target: /
Events:
  Type    Reason  Age                  From                      Message
  ----    ------  ----                 ----                      -------
  Normal  Sync    42s (x3 over 3h35m)  nginx-ingress-controller  Scheduled for sync

La configuración nos indica que podemos acceder a la aplicación ajedrez usando el dominio jruizcampos.com y la ruta /ajedrez.

Adicionalmente debemos agregar a la URL de acceso el número de puerto en el que escucha el Ingress Controller, que como vimos al inicio de la sección es 30575 (http) por lo que nuestra URL quedaría así: http://jruizcampos.com:30575/ajedrez

Accediendo al juego de Ajedrez a través del ingress controller de Nginx
Accediendo al juego de Ajedrez a través de un Ingress Controller

Para que el acceso por dominio funcione, debemos tener previamente configurado en nuestro DNS que el dominio (en este caso jruizcampos.com) apunte a la dirección ip del nodo master (en este caso 10.0.1.21).

En mi caso y al ser una instalación de prueba, he realizado la configuración DNS agregando la siguiente línea al final del archivo hosts de mi equipo:

10.0.1.21 jruizcampos.com

Bueno, este artículo ha quedado más extenso de lo previsto. Espero les haya sido de utilidad. Nos vemos pronto!

0 0 votos
Calificación del artículo
Suscribirse
Notificar de
guest
0 Comentarios
Inline Feedbacks
View all comments
error: Contenido protegido!
0
Déjanos tus comentarios!x
()
x