Instalar Open Cluster Management en un equipo personal

Open Cluster Management (OCM) es una herramienta modular y extensible para orquestar múltiples clusters de Kubernetes. Este artículo es un tutorial paso a paso de instalación de una versión mínima de Kubernetes y OCM en un equipo personal para poder experimentar con esta herramienta.

Se trata de un proyecto de código abierto, con licencia Apache 2.0 cuyo código fuente podemos encontrar en https://github.com/open-cluster-management-io y que constituye el proyecto upstream de Advanced Cluster Management (RHACM) de Red Hat.

Este tutorial se basa en el artículo de introducción a esta herramienta que figura directamente como guía de inicio rápido en la documentación OCM.

   

Contenido

 

Requisitos de hardware y software

En cuanto a hardware, los requisitos mínimos necesarios con los que se ha conseguido probar son los siguientes:

  • CPU. Procesador con 4 cores, Intel Core i5 de sexta generación o equivalente.
  • RAM. 12 Gb.
  • Disco. Al menos 30 Gb de almacenamiento.

En cuanto a software, la prueba se ha realizado con Ubuntu 20.04 y Fedora 34, pero con ligeros cambios se puede ejecutar en cualquier distribución de Linux. Se utiliza la herramienta multipass para gestionar las dos máquinas virtuales necesarias. Es una herramienta que se puede instalar tanto en Windows 10 como en MacOS, y por lo tanto se podría aplicar en estos sistemas operativos de forma similar casi todo lo expuesto aquí, pero no se ha comprobado realmente el funcionamiento y si los requisitos de hardware mínimos indicados son suficientes en esos casos. Además de multipass es necesario instalar:

  • MicroK8s
  • kubectl
  • clusteradm

   

Conceptos generales

Un cluster de Kubernetes está constituido por varios nodos (equipos físicos o máquinas virtuales) que incluyen el plano de control (pods necesarios para el funcionamiento de la propia infraestructura de Kubernetes) y la capacidad de carga útil para ejecutar aplicaciones. Dependiendo de los recursos y las necesidades nos podemos encontrar con clusters en los que idealmente hay como mínimo cinco nodos:

  • 3 nodos para el plano de control denominados masters y que por lo tanto se utilizan para la gestión de la propia plataforma Kubernetes.
  • 2 nodos denominados workers que ejecutan la carga útil, es decir, las aplicaciones.

Utiliza por tanto un patrón que se denomina master-kubelet o master-agent, ya que en cada worker se ejecuta un pod agente, llamado kubelet, encargado de comunicarse con los nodos master para obtener información de cuál es el estado deseado para las aplicaciones y transmitir a los componentes necesarios las tareas para conseguir ese estado (imagen, número de pods de la aplicación, cuenta de servicio, etc.).

El mínimo de dos nodos workers proporciona tolerancia a fallos en uno de los nodos, en cuyo caso la carga pasaría a ejecutarse en el otro. Este número de nodos es un mínimo, pero lo habitual en un cluster productivo es que haya muchos más nodos workers, incluso decenas de ellos.

Podemos encontrarnos también con clusters constituidos por tres nodos que desempeñan simultáneamente las funciones de master y de worker, aunque generalmente se trata de configuraciones para entornos de laboratorio con pocos recursos. No es lo recomendable para un entorno de producción.

El término cluster implica la existencia de varios elementos que se comportan como uno sólo. Es posible tener una instalación de Kubernetes en un sólo nodo, útil para pruebas de concepto o entornos locales, aunque en este caso se pierden muchas de las bondades de Kubernetes como el reparto de la carga de trabajo. Para este tutorial se crearán dos instalaciones de Kubernetes, cada una con un sólo nodo. Nos tomaremos la libertad de utilizar el término cluster para cada una de ellas, aunque estrictamente hablando es incorrecto. Las operaciones a realizar serían casi idénticas en un cluster de varios nodos y por lo tanto se podrían aplicar a un cluster real. De esta forma nos adaptamos a la terminología que utiliza OCM, que se refiere a la orquestación de clusters.

OCM utiliza una arquitectura maestro-agente (master-agent) entre los clusters, que imita la idea del patrón master-kubelet que existe entre los diferentes nodos de un cluster de Kubernetes, como se ha mencionado anteriormente. Existirán por tanto dos tipos de clusters:

  • Cluster Hub. Se trata del cluster sobre el que se ejecuta el plano de control de OCM. Se trataría de un cluster muy ligero sobre el que se ejecutan un pequeño conjunto de servicios.
  • Cluster Gestionado, Managed cluster o Klusterlet. Serían los clusters que son gestionados por el cluster hub. El servicio klusterlet está continuamente consultando las últimas especificaciones del cluster hub y ajustando de forma consistente el cluster de Kubernetes para que coincida con el estado esperado.

A continuación vamos a describir la instalación de las herramientas y la realización de configuraciones necesarias para conseguir tener un cluster hub y un cluster gestionado en una configuración mínima que pueda ejecutarse en un ordenador personal sin excesivos recursos de hardware.

   

Kubectl

kubectl es la herramienta de línea de comandos (CLI) necesaria para gestionar cualquier cluster de Kubernetes. Dependiendo del sistema operativo, se puede obtener de forma sencilla de los gestores de software integrados en el sistema, pero para este ejemplo utilizaremos la descarga directa del binario. Si queremos instalar la versión más reciente:

cd /tmp
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"

Si queremos descargar una versión específica, en este caso la 1.21.8:

cd /tmp
curl -LO "https://storage.googleapis.com/kubernetes-release/release/v1.21.8/bin/linux/amd64/kubectl"

Una vez descargado, le damos permisos de ejecución y lo movemos a una ruta que se encuentre en el PATH:

chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl

Comprobamos que la versión mostrada coincide con la que queremos:

kubectl version --client
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.8", 
GitCommit:"4a3b558c52eb6995b3c5c1db5e54111bd0645a64", GitTreeState:"clean", 
BuildDate:"2021-12-15T14:52:11Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}

Si se desea utiilizar otro método de instalación como los repositorios de paquetes, puede consultarse cómo hacerlo en https://kubernetes.io/es/docs/tasks/tools/install-kubectl/#instalar-mediante-el-gestor-de-paquetes-del-sistema.

   

Instalación de multipass

Para la creación de los clusters vamos a utilizar máquinas virtuales con Ubuntu LTS. Por simplicidad vamos a instalar la herramienta multipass, que nos permite crear y gestionar máquinas virtuales de Ubuntu muy fácilmente. Se instala mediante un paquete snap:

sudo snap install multipass --classic

Nuestro usuario debe pertenecer al grupo que otorga permisos de escritura en el directorio /var/snap/multipass/common/multipass_socket. En el siguiente ejemplo, realizado en un host Ubuntu se trata del grupo sudo:

ls -l /var/snap/multipass/common/multipass_socket
srw-rw---- 1 root sudo 0 ene  10 23:30 /var/snap/multipass/common/multipass_socket

Si hacemos la misma comprobación en Fedora veremos que el grupo que otorga el permiso es wheel:

srw-rw----. 1 root wheel 0 ene 10 23:40 /var/snap/multipass/common/multipass_socket

Comprobamos si nuestro usuario pertenece al grupo sudo o wheel:

groups | grep -E "sudo|wheel"
tty uucp dialout cdrom sudo dip video plugdev kvm syslog lpadmin lxd sambashare libvirt

En Fedora (y cualquier distribución que utilice firewalld), si está activado firewalld, es necesario añadir la interfaz bridge mpqemubr0, que es la que utiliza multipass, a la zona trusted para que puedan arrancar las máquinas virtuales:

firewall-cmd --zone=trusted --change-interface=mpqemubr0
firewall-cmd --runtime-to-permanent

   

Creación de un cluster de un solo nodo para el hub

Creamos una máquina virtual con una CPU, 4 Gb de RAM, llamada ocm-hub, con 10G de disco y basada en la versión LTS de Ubuntu, que al momento de escribir esto es la 20.04 :

multipass launch -m 3Gb -c 1 -n ocm-hub --disk 10G lts

Una vez terminada la operación, comprobamos que aparece en la lista de máquinas virtuales, con su dirección IP asignada y el estado Running:

multipass list
Name                    State             IPv4             Image
ocm-hub                 Running           10.89.130.31     Ubuntu 20.04 LTS

Está en ejecución, así que conectamos por shell con ella:

multipass shell ocm-hub

Y comprobamos el usuario por defecto:

whoami
ubuntu

Por defecto nos conectamos con un usuario llamado ubuntu, con permisos de sudo. Vamos a instalar MicroK8s en la máquina virtual ocm-hub, con una versión concreta de Kubernetes (channel). También asignaremos los permisos necesarios para gestionar MicroK8s, agregando el usuario al grupo microk8s:

sudo snap install microk8s --classic --channel=1.21
sudo usermod -a -G microk8s $USER
sudo chown -f -R $USER ~/.kube

Salimos y volvemos a entrar en la máquina para que se aplique el grupo a nuestro usuario:

exit
multipass shell ocm-hub

Comprobamos el estado del nuevo cluster, que se creará y arrancará automáticamente tras la instalación de MicroK8s:

microk8s status
microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    ha-cluster           # Configure high availability on the current node
  disabled:
    ambassador           # Ambassador API Gateway and Ingress
    cilium               # SDN, fast with full network policy
. . . 

Podemos obtener la url del plano de control:

microk8s kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:16443

Vamos a habilitar el addon de DNS, que nos hará falta más adelante:

microk8s enable dns
Enabling DNS
Applying manifest
serviceaccount/coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created
clusterrole.rbac.authorization.k8s.io/coredns created
clusterrolebinding.rbac.authorization.k8s.io/coredns created
Restarting kubelet
DNS is enabled

Salimos de la shell de la máquina virtual ocm-hub con exit y comprobamos de nuevo el listado de máquinas. Podemos ver que aparece una nueva dirección IP asignada a la máquina ocm-hub, correspondiente a una interfaz de red creada por MicroK8s:

multipass list
Name                    State             IPv4             Image
ocm-hub                 Running           10.89.130.31     Ubuntu 20.04 LTS
                                          10.1.96.0

   

Configuración de acceso externo por nombre a través de kubectl para cluster ocm-hub

Abrimos una shell en la máquina ocm-hub:

multipass shell ocm-hub

y editamos el archivo de plantilla de los certificados

vi /var/snap/microk8s/current/certs/csr.conf.template

Agregamos en la sección [ alt_names ] dos entradas DNS nuevas, en este caso DNS.6 y DNS.7 con el nombre que queremos utilizar desde fuera del cluster para conectarnos.

DNS.6 = ocm-hub.local
DNS.7 = ocm-hub

Quedará algo similar a lo siguiente, aunque la cantidad de entradas puede diferir y sobre todo el campo IP.2 será diferente en cada caso y no debemos modificarlo:

[ alt_names ]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster
DNS.5 = kubernetes.default.svc.cluster.local
DNS.6 = ocm-hub.local
DNS.7 = ocm-hub
IP.1 = 127.0.0.1
IP.2 = 10.152.183.1
#MOREIPS

Ejecutamos la regeneración de los certificados:

sudo microk8s refresh-certs
Taking a backup of the current certificates under /var/snap/microk8s/2694/var/log/ca-backup/
Creating new certificates
Can't load /root/.rnd into RNG
140555660350912:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Can't load /root/.rnd into RNG
140458579322304:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Signature ok
subject=C = GB, ST = Canonical, L = Canonical, O = Canonical, OU = Canonical, CN = 127.0.0.1
Getting CA Private Key
Signature ok
subject=CN = front-proxy-client
Getting CA Private Key
1
Creating new kubeconfig file
Stopped.
Started.

The CA certificates have been replaced. Kubernetes will restart the pods of your workloads.
Any worker nodes you may have in your cluster need to be removed and re-joined to become aware of the new CA.

Salimos de la máquina ocm-hub:

exit

En el equipo host vamos a editar el archivo /etc/hosts y añadir una entrada con la dirección IP de la máquina ocm-hub y su nombre. La IP la obtuvimos con el comando multipass list o de forma más directa con:

multipass info ocm-hub | grep IPv4
IPv4:           10.89.130.31

La entrada en este caso será:

10.89.130.31 ocm-hub.local ocm-hub

Probamos el acceso desde el host:

curl -kL https://ocm-hub.local:16443
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}

Preparamos el archivo de configuración para poder utilizar kubectl desde nuestro host. Para ello vamos a copiar el archivo /var/snap/microk8s/current/credentials/client.config de la máquina ocm-hub al directorio $HOME/.kube de nuestro equipo:

KUBECONFIG=$HOME/.kube/ocm-hub.config
multipass exec ocm-hub cat /var/snap/microk8s/current/credentials/client.config > $KUBECONFIG

Editamos el archivo de configuración con vi $KUBECONFIG, cambiando la línea:

    server: https://127.0.0.1:16443

por

    server: https://ocm-hub.local:16443

Probamos a lanzar un comando con kubectl especificando el archivo de configuración que hemos creado para ver los nodos en ejecución. No sería estrictamente necesario especificar el parámetro --kubeconfig puesto que ahora mismo hemos creado la variable la variable KUBECONFIG que determina la ubicación del archivo de configuración para kubectl. Si no existe la variable KUBECONFIG y no utilizamos el parámetro --kubeconfig intentará buscar un archivo de configuración por defecto: $HOME/.kube/config.

kubectl --kubeconfig=$HOME/.kube/ocm-hub.config get nodes
NAME      STATUS   ROLES    AGE   VERSION
ocm-hub   Ready    <none>   30m  v1.21.7-3+7700880a5c71e2

Con esto verificamos que el comando kubectl funciona correctamente y que podemos lanzar comandos contra el cluster ocm-hub desde fuera de la máquina virtual.

Sin utilizar kubectl desde la máquina host podríamos conseguir el mismo resultado lanzando un comando contra la máquina virtual utilizando multipass y la CLI de MicroK8s:

multipass exec ocm-hub microk8s kubectl get nodes
NAME      STATUS   ROLES    AGE   VERSION
ocm-hub   Ready    <none>   30m   v1.21.7-3+7700880a5c71e2

   

Creación de un cluster de un solo nodo que será gestionado desde el cluster hub

Creamos una máquina virtual con una CPU, 4 Gb de RAM, llamada ocm-managed, con 10G total de disco y basada en la versión LTS de Ubuntu, que al momento de escribir esto es la 20.04 :

multipass launch -m 3Gb -c 1 -n ocm-managed --disk 10G lts

Comprobamos que aparece en la lista de máquinas virtuales, la dirección IP asignada y su estado:

multipass list
Name                    State             IPv4             Image
ocm-hub                 Running           10.89.130.31     Ubuntu 20.04 LTS
                                          10.1.96.0
ocm-managed             Running           10.89.130.239    Ubuntu 20.04 LTS

Está en ejecución, así que conectamos por shell con ella:

multipass shell ocm-managed

De la misma forma que se ha hecho con la máquina ocm-hub, vamos a instalar MicroK8s y a dar los permisos necesarios:

sudo snap install microk8s --classic --channel=1.21
sudo usermod -a -G microk8s $USER
sudo chown -f -R $USER ~/.kube
exit
multipass shell ocm-managed

Comprobamos el estado del cluster de kubernetes:

microk8s status
microk8s is running
high-availability: no
  datastore master nodes: 127.0.0.1:19001
  datastore standby nodes: none
addons:
  enabled:
    ha-cluster           # Configure high availability on the current node
  disabled:
    ambassador           # Ambassador API Gateway and Ingress
    cilium               # SDN, fast with full network policy
microk8s kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:16443

Vamos a habilitar el addon de DNS, que nos hará falta más adelante:

microk8s enable dns
Enabling DNS
Applying manifest
serviceaccount/coredns created
configmap/coredns created
deployment.apps/coredns created
service/kube-dns created
clusterrole.rbac.authorization.k8s.io/coredns created
clusterrolebinding.rbac.authorization.k8s.io/coredns created
Restarting kubelet
DNS is enabled

Salimos de la shell de la máquina virtual ocm-managed con exit y comprobamos de nuevo el listado de máquinas. Podemos ver que aparece una nueva dirección IP asignada, correspondiente a una interfaz de red creada por MicroK8s:

multipass list
Name                    State             IPv4             Image
ocm-hub                 Running           10.89.130.31     Ubuntu 20.04 LTS
                                          10.1.96.0
ocm-managed             Running           10.89.130.239    Ubuntu 20.04 LTS
                                          10.1.37.0

   

Configuración de acceso externo a través de kubectl para el cluster ocm-managed

Abrimos una shell en la máquina ocm-managed:

multipass shell ocm-managed

y editamos el archivo de plantilla de los certificados:

vi /var/snap/microk8s/current/certs/csr.conf.template

Agregamos en la sección [ alt_names ] dos entradas DNS nuevas, en este caso DNS.6 y DNS.7 con el nombre que queremos utilizar desde fuera del cluster para conectarnos (ocm-managed.local y ocm-managed) ).

DNS.6 = ocm-managed.local
DNS.7 = ocm-managed

Quedará algo similar a lo siguiente, aunque la cantidad de entradas puede diferir y sobre todo el campo IP.2 será diferente en cada caso y no debemos modificarlo:

[ alt_names ]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster
DNS.5 = kubernetes.default.svc.cluster.local
DNS.6 = ocm-managed.local
DNS.7 = ocm-managed
IP.1 = 127.0.0.1
IP.2 = 10.152.183.1
#MOREIPS

Ejecutamos la regeneración de los certificados:

sudo microk8s refresh-certs
Taking a backup of the current certificates under /var/snap/microk8s/2694/var/log/ca-backup/
Creating new certificates
Can't load /root/.rnd into RNG
140603790988736:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Can't load /root/.rnd into RNG
140352521098688:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/root/.rnd
Signature ok
subject=C = GB, ST = Canonical, L = Canonical, O = Canonical, OU = Canonical, CN = 127.0.0.1
Getting CA Private Key
Signature ok
subject=CN = front-proxy-client
Getting CA Private Key
1
Creating new kubeconfig file
Stopped.
Started.

The CA certificates have been replaced. Kubernetes will restart the pods of your workloads.
Any worker nodes you may have in your cluster need to be removed and re-joined to become aware of the new CA.

Salimos de la máquina ocm-managed:

exit

En el equipo host vamos editar el archivo /etc/hosts y añadir una entrada con la dirección IP de la máquina ocm-managed y su nombre. La IP la obtuvimos con el comando multipass list o de forma más directa con:

multipass info ocm-managed | grep IPv4
IPv4:           10.89.130.239

La entrada en este caso será:

10.89.130.239 ocm-managed.local ocm-managed

Probamos el acceso desde el host:

curl -kL https://ocm-managed.local:16443
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}

Preparamos el archivo de configuración para poder utilizar kubectl desde nuestro host:

KUBECONFIG=$HOME/.kube/ocm-managed.config
multipass exec ocm-managed cat /var/snap/microk8s/current/credentials/client.config > $KUBECONFIG

Editamos el archivo de configuración con:

vi $KUBECONFIG

cambiando la línea:

    server: https://127.0.0.1:16443

por

    server: https://ocm-managed.local:16443

Probamos a lanzar un comando con kubectl especificando el archivo de configuración que hemos creado para ver los nodos en ejecución:

kubectl --kubeconfig=$HOME/.kube/ocm-managed.config get nodes

No sería estrictamente necesario especificar el parámetro --kubeconfig puesto que ahora mismo hemos creado la variable la variable KUBECONFIG que determina la ubicación del archivo de configuración para kubectl. Si no existe la variable KUBECONFIG y no utilizamos el parámetro --kubeconfig, el comando kubectl intentará buscar un archivo de configuración en $HOME/.kube/config. Nos aparecerá algo como lo siguiente:

NAME          STATUS   ROLES    AGE   VERSION
ocm-managed   Ready    <none>   34m   v1.21.7-3+7700880a5c71e2

Con esto verificamos que el comando kubectl funciona correctamente y que podemos lanzar comandos contra el cluster ocm-managed desde fuera de la máquina virtual.

Sin utilizar kubectl desde la máquina host podríamos conseguir el mismo resultado lanzando un comando contra la máquina virtual utilizando multipass y la CLI de MicroK8s:

multipass exec ocm-managed microk8s kubectl get nodes

   

Instalación de Open Cluster Management

Instalación de clusteradm

Para interactuar con Open Cluster Management (a partir de ahora OCM) existe una herramienta CLI llamada clusteradm.

El primer paso será instalarla. Para ello, descargamos el script de instalación:

curl -L https://raw.githubusercontent.com/open-cluster-management-io/clusteradm/main/install.sh -o /tmp/install-clusteradm.sh
chmod +x /tmp/install-clusteradm.sh

Examinamos el contenido antes de ejecutarlo. Básicamente descarga de github la última versión del binario adecuada para nuestra máquina y la copia en /usr/local/bin, aunque podemos modificar el lugar de destino modificando una variable. Si cambiamos la línea:

: ${INSTALL_DIR:="/usr/local/bin"}

por cualquier otra ruta que se encuentre en el PATH y sobre la que tengamos permisos de escritura, no será necesario ejecutar este script con sudo. Si queremos instalar en la ruta por defecto /usr/local/bin tendremos que utilizar sudo al ejecutar /tmp/install-clusteradm.sh.

Ejecutamos el script:

/tmp/install-clusteradm.sh 

y se realizará la instalación:

Getting the latest clusteradm CLI...
Your system is linux_amd64
Installing clusteradm CLI...

Installing v0.1.0-alpha.7 OCM clusteradm CLI...
Downloading https://github.com/open-cluster-management-io/clusteradm/releases/download/v0.1.0-alpha.7/clusteradm_linux_amd64.tar.gz ...
clusteradm installed into /opt/k8stools successfully.

To get started with clusteradm, please visit https://open-cluster-management.io/getting-started/

Comprobamos que clusteradm funciona y puede conectar con nuestros clusters:

clusteradm  --kubeconfig=$HOME/.kube/ocm-hub.config version
client		version	:0.1.0-alpha.7
server release	version	:v1.21.7-3+7700880a5c71e2
clusteradm  --kubeconfig=$HOME/.kube/ocm-managed.config version
client		version	:0.1.0-alpha.7
server release	version	:v1.21.7-3+7700880a5c71e2

Vamos a definir unos alias para que resulte más fácil ejecutar clusteradm contra nuestros clusters. Serán cahub para conectarnos al cluster hub y camng para conectarnos al cluster gestionado:

alias cahub='clusteradm --kubeconfig=$HOME/.kube/ocm-hub.config'
alias camng='clusteradm --kubeconfig=$HOME/.kube/ocm-managed.config'

y comprobamos que funciona ejecutando el comando version de clusteradm:

cahub version
client		version	:0.1.0-alpha.7
server release	version	:v1.21.7-3+7700880a5c71e2
camng version
client		version	:0.1.0-alpha.7
server release	version	:v1.21.7-3+7700880a5c71e2

y hacemos lo mismo con kubectl. Creamos los alias khub para ejecutar kubectl contra el cluster hub y kmng para ejecutar comandos contra el cluster gestionado:

alias khub='kubectl --kubeconfig=$HOME/.kube/ocm-hub.config'
alias kmng='kubectl --kubeconfig=$HOME/.kube/ocm-managed.config'

y comprobamos que funcionan correctamente:

khub version
kmng version

nos devolverá la versión del cliente y del servidor:

Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.1", GitCommit:"86ec240af8cbd1b60bcc4c03c20da9b98005b92e", GitTreeState:"clean", BuildDate:"2021-12-16T11:41:01Z", GoVersion:"go1.17.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"21+", GitVersion:"v1.21.7-3+7700880a5c71e2", GitCommit:"7700880a5c71e25c44491ef5c7d7fb30527d8337", GitTreeState:"clean", BuildDate:"2021-11-17T22:02:47Z", GoVersion:"go1.16.10", Compiler:"gc", Platform:"linux/amd64"}
WARNING: version difference between client (1.23) and server (1.21) exceeds the supported minor version skew of +/-1
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.1", GitCommit:"86ec240af8cbd1b60bcc4c03c20da9b98005b92e", GitTreeState:"clean", BuildDate:"2021-12-16T11:41:01Z", GoVersion:"go1.17.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"21+", GitVersion:"v1.21.7-3+7700880a5c71e2", GitCommit:"7700880a5c71e25c44491ef5c7d7fb30527d8337", GitTreeState:"clean", BuildDate:"2021-11-17T22:02:47Z", GoVersion:"go1.16.10", Compiler:"gc", Platform:"linux/amd64"}

Vamos a comprobar los namespaces existentes en nuestro cluster hub:

khub get namespaces
NAME              STATUS   AGE
kube-system       Active   40m
kube-public       Active   40m
kube-node-lease   Active   40m
default           Active   40m

Instalación del Control Plane de OCM en el cluster hub

Una vez instalado clusteradm y creados los alias, instalamos un Cluster Manager de OCM en el cluster de Kubernetes que utilizaremos como hub:

cahub init
The multicluster hub control plane has been initialized successfully!

You can now register cluster(s) to the hub control plane. Log onto those cluster(s) and run the following command:

    clusteradm join \
    --hub-token eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQ.eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQ \
    --hub-apiserver https://ocm-hub.local:16443 \
    --cluster-name <cluster_name>

Replace <cluster_name> with a cluster name of your choice. For example, cluster1.

Con este comando, clusteradm instala en el cluster hub el operador registration-operator que es el responsable de instalar y actualizar de forma consistente varios componentes del entorno de OCM.

Al final de la ejecución nos muestra un comando con un token que debemos guardar, ya que nos permitirá registrar los clusters gestionados desde el hub.

Comprobamos los pods que están en ejecución en el cluster hub tras el comando init:

khub get pods -A
NAMESPACE                     NAME                                                      READY   STATUS    RESTARTS   AGE
kube-system                   calico-kube-controllers-f7868dd95-tm7nq                   1/1     Running   3          2d
kube-system                   calico-node-8kfnv                                         1/1     Running   3          2d
open-cluster-management       cluster-manager-6bc5bd8856-6mpm6                          1/1     Running   0          5m3s
open-cluster-management-hub   cluster-manager-registration-controller-59f487d68-dtrjt   1/1     Running   0          4m46s
open-cluster-management-hub   cluster-manager-registration-webhook-c96d56db-9bxcc       1/1     Running   0          4m46s
open-cluster-management-hub   cluster-manager-work-webhook-547d7cc954-ctxbs             1/1     Running   0          4m46s
open-cluster-management-hub   cluster-manager-placement-controller-66485c45fd-2kdkt     1/1     Running   0          4m46s

Tras la ejecución en el cluster hub del comando init vamos a comprobar los namespaces que tenemos disponibles:

khub get namespaces
NAME                          STATUS   AGE
kube-system                   Active   40m
kube-public                   Active   40m
kube-node-lease               Active   40m
default                       Active   40m
open-cluster-management       Active   5m
open-cluster-management-hub   Active   5m

podemos ver que nos ha añadido dos nuevos namespaces: open-cluster-management y open-cluster-management-hub.

En el namespace open-cluster-management se encuentra el pod del registration operator:

khub -n open-cluster-management get pods
NAME                               READY   STATUS    RESTARTS   AGE
cluster-manager-6bc5bd8856-6mpm6   1/1     Running   0          24m

En el namespace open-cluster-management-hub se ejecutan los pods del control plane de OCM:

khub -n open-cluster-management-hub get pods
NAME                                                      READY   STATUS    RESTARTS   AGE
cluster-manager-registration-controller-59f487d68-dtrjt   1/1     Running   0          25m
cluster-manager-registration-webhook-c96d56db-9bxcc       1/1     Running   0          25m
cluster-manager-work-webhook-547d7cc954-ctxbs             1/1     Running   0          25m
cluster-manager-placement-controller-66485c45fd-2kdkt     1/1     Running   0          25m

Se ha creado un Custom Resource de tipo clustermanager llamado cluster-manager:

khub get clustermanager
NAME              AGE
cluster-manager   27m

Para examinar la información de la instalación podemos examinar el contenido de ese objeto:

khub get clustermanager cluster-manager -o yaml

Desplegar un agente klusterlet en el cluster gestionado

Una vez que se han instalado todos los componentes del control plane en el cluster hub y que están en ejecución, podemos pasar a registrar un cluster en OCM para que sea gestionado por él.

Para ello, primero tenemos que asegurarnos de que la API del cluster hub es accesible desde el cluster que queremos que sea gestionado.

Obtenemos la dirección IP del cluster hub:

multipass info ocm-hub | grep IPv4 | tr -s ' ' | cut -d ' ' -f 2
10.89.130.31

Podemos lanzar una petición con curl sobre una dirección htttps en el puerto 16443 correspondiente a esa dirección IP. Si llegamos correctamente nos devolverá un mensaje JSON de autenticación incorrecta (código 401):

multipass exec ocm-managed -- \
curl -kL https://$(multipass info ocm-hub | grep IPv4 | tr -s ' ' | cut -d ' ' -f 2):16443
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {
    
  },
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}

Para registrar el cluster gestionado utilizaremos el comando que nos generó clusteradm init y lo lanzaremos desde nuestro equipo. Salimos de la máquina del cluster gestionado y lanzamos el comando con clusteradmin (concretamente con el alias camng) contra nuestro cluster gestionado. El valor del parámetro --cluster-name no tiene por qué coincidir con el nombre de la máquina virtual, puede ser cualquier nombre que queramos asignarle.

Salimos de la máquina virtual:

exit

y desde el host lanzamos el comando join contra el cluster gestionado, utilizando el alias de clusteradm camng y la dirección IP de la MV ocm-hub en el parámetro --hub-apiserver, ya que desde nuestro cluster gestionado no resolverá el nombre ocm-hub u ocm-hub.local:

camng join \
--hub-token eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQ.eyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQeyJhbGciOiJSUzI1NiIsImtpZCI6Ik5Va2FPSXVCYTM2WDExVHBqaEFkdm92Slp0SWwxcTlkM29DQnVIczlnNTQifQ \
--hub-apiserver https://10.89.130.31:16443 \
--cluster-name managed1
Waiting for the management components to become ready...
Please log onto the hub cluster and run the following command:

    clusteradm accept --clusters managed1

Los CSR, Certificate Signing Request o Solicitudes de Firma de Certificados, son uno de los primeros pasos para la obtención de un certificado TLS. Cuando se realiza la operación join correctamente, el cluster gestionado genera CSRs en el cluster hub, que deben ser aceptadas. Por ello revisamos si hay recursos CSR en el cluster hub:

khub get csr -w
NAME             AGE    SIGNERNAME                            REQUESTOR                                                         CONDITION
managed1-d479q   5m   kubernetes.io/kube-apiserver-client   system:serviceaccount:open-cluster-management:cluster-bootstrap   Pending

Aceptamos la petición de unión del cluster gestionado al cluster hub:

cahub accept --clusters managed1
CSR managed1-d479q approved
set hubAcceptsClient to true for managed cluster managed1

Verificamos que el objeto managedcluster se ha creado correctamente:

khub get managedcluster
NAME       HUB ACCEPTED   MANAGED CLUSTER URLS         JOINED   AVAILABLE   AGE
managed1   true           https://10.248.21.90:16443   True     True        10m

Comprobamos la creación de namespaces en el cluster gestionado:

kmng get namespace
NAME                                  STATUS   AGE
kube-system                           Active   1h
kube-public                           Active   1h
kube-node-lease                       Active   1h
default                               Active   1h
open-cluster-management               Active   10m
open-cluster-management-agent-addon   Active   10m
open-cluster-management-agent         Active   10m

Verficamos la instalación de los agentes de OCM en el cluster gestionado con:

kmng -n open-cluster-management-agent get pod
NAME                                             READY   STATUS    RESTARTS   AGE
klusterlet-registration-agent-598fd79988-jxx7n   1/1     Running   0          10m
klusterlet-work-agent-7d47f4b5c5-dnkqw           1/1     Running   0          10m

La información de instalación debe figurar en un custom resource llamado klusterlet que sólo debe estar instalado en el cluster gestionado:

kmng get klusterlet klusterlet -o yaml

En el cluster hub se habrá creado un namespace con el nombre que hamos dado al managedcluster:

khub get namespace
NAME                          STATUS   AGE
kube-system                   Active   2h
kube-public                   Active   2h
kube-node-lease               Active   2h
default                       Active   2h
open-cluster-management       Active   15m
open-cluster-management-hub   Active   15m
managed1                      Active   10m

Vemos que se ha creado lo que se denomina cluster namespace, un namespace con el mismo nombre que se ha dado al cluster gestionado durante el registro. Este namespace lo crea el registration controller del hub de forma automática cuando finaliza el registro y se utiliza para almacenar custom resources y configuraciones que pertenezcan al cluster gestionado.

Ya disponemos de dos clusters de Kubernetes, uno que sirve como hub para gestionar otros clusters y un cluster gestionado. Lo recomendado es que el cluster hub se utilice sólamente para gestionar otros clusters, pero es posible instalar el agente en el propio cluster hub y que sea gestionado para cargas de trabajo y otros fines como cualquier otro cluster gestionado, a través de OCM.

   

Despliegue de una aplicación en el cluster gestionado

Vamos a hacer un despliegue de una aplicación sencilla en el cluster gestionado para comprobar que funciona correctamente OCM.

Vamos a manejar dos conceptos:

  • ManifestWork: Un custom resource que se encuentra en el cluster hub y que agrupa una lista de recursos de Kubernetes que se desean aplicar en el cluster gestionado.
  • AppliedManifestWork: Un custom resource del cluster gestionado que se utiliza para persistir la lista de recursos definidos en un ManifestWork.

Primero debemos verificar tres puntos:

  1. Que la Custom Resource Definition (CRD) ManifestWork está instalada en el cluster hub:
khub get crd manifestworks.work.open-cluster-management.io
manifestworks.work.open-cluster-management.io   2022-01-14T23:30:58Z
  1. Que la CRD AppliedManifestWork está instalada en el cluster gestionado:
kmng get crd appliedmanifestworks.work.open-cluster-management.io
NAME                                                   CREATED AT
appliedmanifestworks.work.open-cluster-management.io   2022-01-14T23:31:12Z
  1. Que el agente está ejecutándose correctamente en el cluster gestionado:
kmng -n open-cluster-management-agent get pod
NAME                                             READY   STATUS    RESTARTS   AGE
klusterlet-registration-agent-69c5568fcc-98fj9   1/1     Running   1          1h
klusterlet-work-agent-54dfddf7b-78llj            1/1     Running   11         1h

Una vez comprobados los puntos anteriores, vamos a crear en el clusternamespace (en nuestro caso managed1) un ManifestWork. Este ManifestWork crea los siguientes objetos en el cluster:

  • Un namespace llamado frontend.
  • Una service account llamada nginx-sa en el namespace frontend.
  • Un deployment en el namespace frontend llamado nginx-deployment que desplegará 3 pods de nginx a la escucha en el puerto 8080.
  • Un servicio de tipo NodePort que apuntará a los pods del deployment anterior, que permitirá acceder a los nginx desde fuera del cluster.
khub apply -f - <<EOF
apiVersion: work.open-cluster-management.io/v1
kind: ManifestWork
metadata:
  namespace: managed1
  name: nginx-simple
spec:
  workload:
    manifests:
      - apiVersion: v1
        kind: Namespace
        metadata:
          name: frontend
      - apiVersion: v1
        kind: ServiceAccount
        metadata:
          namespace: frontend
          name: nginx-sa
      - apiVersion: apps/v1
        kind: Deployment
        metadata:
          namespace: frontend
          name: nginx-deployment
          labels:
            app: nginx
        spec:
          replicas: 3
          selector:
            matchLabels:
              app: nginx
          template:
            metadata:
              labels:
                app: nginx
            spec:
              serviceAccountName: nginx-sa
              containers:
                - name: nginx
                  image: nginx:1.21.5
                  ports:
                    - containerPort: 8080
      - apiVersion: v1
        kind: Service
        metadata:
          namespace: frontend
          name: nginx
          labels:
            app: nginx
        spec:
          type: NodePort
          ports:
          - nodePort: 30001
            port: 8080
            protocol: TCP
            targetPort: 8080
          selector:
            app: nginx
EOF
manifestwork.work.open-cluster-management.io/nginx-simple created

Comprobamos el resultado con:

khub get manifestwork nginx-simple -n managed1 -o yaml

En el apartado .status.resourceStatus del comando anterior aparecerá información de cómo se ha aplicado el recurso:

  resourceStatus:
    manifests:
    - conditions:
      - lastTransitionTime: "2022-01-11T08:31:07Z"
        message: Apply manifest complete
        reason: AppliedManifestComplete
        status: "True"
        type: Applied
      - lastTransitionTime: "2022-01-11T08:31:07Z"
        message: Resource is available
        reason: ResourceAvailable
        status: "True"
        type: Available

Comprobamos si en el cluster gestionado se han desplegado los pods de ngnix en el namespace frontend:

kmng get pods -n frontend
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-844674d9b-m2vgn   1/1     Running   0          5m
nginx-deployment-844674d9b-7z6ph   1/1     Running   0          5m
nginx-deployment-844674d9b-z9jz6   1/1     Running   0          5m

En la especificación del servicio hemos indicado el puerto 30001 en la línea:

          - nodePort: 30001

Comprobamos el servicio creado en el namespace frontend:

kmng get svc -n frontend
NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
nginx   NodePort   10.152.183.92   <none>        8080:30001/TCP   5m

Vemos que efectivamente el puerto expuesto al exterior del cluster es el 30001. Vamos a hacer una petición con curl:

curl http://ocm-managed:30001/
$ curl http://ocm-managed:30001/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

También podemos introducir la url http://ocm-managed:30001/ en nuestro navegador:

Con esto hemos hecho una breve prueba de que OCM está funcionando. Cualquier cambio que hagamos sobre el ManifestWork se aplicará en el cluster managed en unos segundos.

   

Parada de clusters y máquinas virtuales

Una vez terminada la prueba, el orden recomendado para parar el entorno que hemos creado sería detener los clusters y luego las máquinas virtuales, aunque microk8s se ejecuta como un servicio de las máquinas y por lo tanto se detendría en caso de parada ordenada de la máquina.

Para detener el cluster gestionado:

multipass exec ocm-managed microk8s stop

Parada del cluster hub:

multipass exec ocm-hub microk8s stop

Y por último parada de las máquinas virtuales:

multipass stop ocm-managed
multipass stop ocm-hub

   

Arranque de máquinas virtuales y clusters

Si en otro momento queremos arrancar de nuevo los clusters, arrancaríamos las máquinas virtuales y luego los clusters:

multipass start ocm-hub
multipass start ocm-managed
multipass exec ocm-hub microk8s start
multipass exec ocm-managed microk8s start

   

Referencias