====== Instalación ====== El proceso instalar un cluster de kubernetes en "**bare metal**", es decir, sobre servidores, sin ningún tipo de plataforma cloud como GCP, Amazon AWS o Azure, es el siguiente: 1. Instalar nodos (al menos un control plane y un worker) 2. Iniciar el cluster (se ejecuta en el primer control plane) 3. (Opcional, solo para alta disponibilidad) Unir control plane adicionales al cluster 4. Unir worker (al menos uno) al cluster ===== Nodo (común para control plane y worker) ===== Estas instrucciones detallan como instalar un nodo de kubernetes, por tanto es COMÚN para todos los nodos, con independencia de su posterior rol dentro del cluster (control plane o worker). 1. Verificar que el nodo cumple todos los siguientes requisitos: https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#before-you-begin 2. Instalar un runtime **IMPORTANTE**: leer la siguientes consideraciones * Kubernetes requiere container runtime que siga especificación CRI (CRI-O, containerd, etc.) * Docker Engine requiere dockershim, que ha sido eliminado, por lo que mejor NO usar Docker Engine como container runtime. Ver [[https://kubernetes.io/docs/setup/production-environment/container-runtimes/|Container runtimes]]. Por curiosidad, el reemplazo (de dockershim) se llama "cri-dockerd" * Para instalar containerd usaremos los paquetes, que los distribuye Docker Inc. * Habrá que instalar luego los plugins CNI, que NO vienen en los paquetes que distribuye Docker Inc. 2.1. Instalar containerd Este ejemplo es para ubuntu sudo apt-get remove docker docker-engine docker.io containerd runc sudo apt-get update sudo apt-get install \ ca-certificates \ curl \ gnupg \ lsb-release sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update **IMPORTANTE**: NO instalar docker engine, solo containerd sudo apt install containerd.io Verificar: systemctl status containerd q 2.2. Redirigir IPv4 y permitir a iptables ver el trafico "bridged" cat < sudo modprobe overlay sudo modprobe br_netfilter # sysctl params required by setup, params persist across reboots cat < # Apply sysctl params without reboot sudo sysctl --system 2.3. Configurar systemd cgroup driver 2.3.1. Copia de seguridad y gener archivo nuevo de trinca sudo cp /etc/containerd/config.toml /etc/containerd/config.toml.bak sudo containerd config default | sudo tee /etc/containerd/config.toml 2.3.2. Reemplazar: sudo sed -i 's/SystemdCgroup \= false/SystemdCgroup \= true/g' /etc/containerd/config.toml Esto es el equivalente a: sudo vim /etc/containerd/config.toml Y dejar esta linea tal que así: # anyadido #SystemdCgroup = false SystemdCgroup = true 2.3.3. Reiniciar el servicio: sudo systemctl restart containerd 3. Instalar plugins CNI En este ejemplo la arquitectura es ARM sudo mkdir -p /opt/cni/bin/ Escoger de: https://github.com/containernetworking/plugins/releases **OJO**: en este ejemplo la arquitectura es ARM sudo wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-arm64-v1.1.1.tgz sudo tar Cxzvf /opt/cni/bin cni-plugins-linux-arm64-v1.1.1.tgz sudo systemctl restart containerd 4. Instalar kubeadm, kubelet and kubectl sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl 5. Configurar driver cgroup 5.1. Configurar container runtime cgroup driver Ya hecho en pasos anteriores, en este caso containerd 5.2. Configurar kubelet cgroup driver https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/configure-cgroup-driver/#configuring-the-kubelet-cgroup-driver Entiendo que NO es necesario porque desde la versión 1.22 configurará por defecto systemd ===== Primer control plane ===== Este paso solo se tiene que hacer **UNA VEZ POR CLUSTER**, y se ejecutará en aquel nodo, ya instalado, que vaya a ser el **PRIMER CONTROL PLANE**. ==== Sin alta disponibilidad ==== 1. Conectarse al servidor ssh k8s2 2. Iniciar el cluster. Ejecutar: sudo kubeadm init \ --control-plane-endpoint "k8s2.local:6443" \ --pod-network-cidr=10.244.0.0/16 \ --upload-certs \ --v=5 Comentarios: * Aunque no sea alta disponibilidad es conveniente usar 'control-plane-endpoint' por si más adelante quiere usarse * Debe ser un nombre que todos los nodos (control plane y workers) resuelvan, o bien la IP privada del control plane * El puerto es el 6443, es en el que escucha el servicio 'kube-apiserver' * El parámetro 'pod-network-cidr' es requerido por flannel, y le pasamos el rango de IPs de los PODS es el '10.244.0.0/16' 3. Anotar de la salida del comando anterior los comandos para unir control planes y workers al cluster: 3.1. Anotar el comando para unir un control plane: sudo kubeadm join \ k8s2.local:6443 \ --token r6mawr.wsgooc91h45a55i8 \ --discovery-token-ca-cert-hash sha256:94e15af3476146deb36c0f9a53f33a9ea3c441470a2f9e3aefdf538ca7fb4443 \ --control-plane \ --certificate-key 7b04bab444d4a5515c514a8b8eeb7a2df6629ee80d46b7f1ed5bd4b1aa3d80ed \ --v=8 Opciones: * Añadir 'sudo' al comando * Añadir '--v' para ver más cosas en la salida del comando cuando se ejecute 3.2. Anoter el comando para unir un worker: sudo kubeadm join \ k8s2.local:6443 \ --token 9sljzd.hm5i967gzi802cah \ --discovery-token-ca-cert-hash sha256:0a3ac17a0a2c093aca67b2cdc8c99e3cc9c374a48fe762385faf705932785de8 \ --v=8 Opciones: * Añadir 'sudo' al comando * Añadir '--v' para ver más cosas en la salida del comando cuando se ejecute 4. Instalar plugin CNI, en este ejemplo "flannel". Ejecutar: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 5. Configurar archivos para poder usar 'kubectl'. Ejecutar: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config sudo mkdir -p /root/.kube sudo cp -i /etc/kubernetes/admin.conf /root/.kube/config 6. Comprobar: kubectl get nodes Salida esperada similar a: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 22h v1.21.1 ==== Con alta disponibilidad ==== En este ejemplo tendremos 3 nodos que actuarán como control plane: ^ DNS ^ IP ^ Puerto ^ Comentario ^ | k8s.local | 192.168.95.69 | 8443 | 'kube-apiserver' cluster. | | k8s2.local | 192.168.95.71 | 6443 | Primer control plane | | k8s3.local | 192.168.95.72 | 6443 | Control plane adicional | | k8s3.local | 192.168.95.73 | 6443 | Control plane adicional | Antes de empezar para cada uno de los tres control plane [[informatica:linux:kubernetes#balanceador_capa_4_kube-apiserver|confifurar balanceador capa 4 kube-apiserver]] 1. Conectarse al servidor ssh k8s2 2. Iniciar el cluster. Ejecutar: sudo kubeadm init \ --control-plane-endpoint "k8s.local:8443" \ --pod-network-cidr=10.244.0.0/16 \ --upload-certs \ --v=5 Comentarios: * El parámetro 'control-plane-endpoint' debe ser un nombre que todos los nodos (control plane y workers) resuelvan, no se recomienda usar una dirección IP. Es el nombre DNS del balanceador capa 4 kube-apiserver * El puerto es el 8443, es en el que escucha balanceador capa 4 kube-apiserver 'kube-apiserver' * El parámetro 'pod-network-cidr' es requerido por flannel, y le pasamos el rango de IPs de los PODS es el '10.244.0.0/16' 3. Anotar de la salida del comando anterior los comandos para unir control planes y workers al cluster: 3.1. Anotar el comando para unir un control plane: sudo kubeadm join \ k8s.local:8443 \ --token r6mawr.wsgooc91h45a55i8 \ --discovery-token-ca-cert-hash sha256:94e15af3476146deb36c0f9a53f33a9ea3c441470a2f9e3aefdf538ca7fb4443 \ --control-plane \ --certificate-key 7b04bab444d4a5515c514a8b8eeb7a2df6629ee80d46b7f1ed5bd4b1aa3d80ed \ --v=8 Opciones: * Añadir 'sudo' al comando * Añadir '--v' para ver más cosas en la salida del comando cuando se ejecute 3.2. Anoter el comando para unir un worker: sudo kubeadm join \ k8s.local:8443 \ --token 9sljzd.hm5i967gzi802cah \ --discovery-token-ca-cert-hash sha256:0a3ac17a0a2c093aca67b2cdc8c99e3cc9c374a48fe762385faf705932785de8 \ --v=8 Opciones: * Añadir 'sudo' al comando * Añadir '--v' para ver más cosas en la salida del comando cuando se ejecute 4. Instalar plugin CNI, en este ejemplo "flannel". Ejecutar: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 5. Configurar archivos para poder usar 'kubectl'. Ejecutar: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config sudo mkdir -p /root/.kube sudo cp -i /etc/kubernetes/admin.conf /root/.kube/config 6. Comprobar: kubectl get nodes Salida esperada similar a: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 22h v1.21.1 ===== Unir nodos al cluster ===== Una vez se ha iniciado el cluster en el primer control plane hay que añadir nodos al cluster: * Control plane. Solo en alta disponibilidad. Si no vamos a configurar alta disponibilidad, un control plane por cluster de kubernetes es suficiente. * Workers. Al menos uno. ==== Control plane (solo alta disponibilidad) ==== Solo debemos añadir control plane adicionales al cluster si vamos a configurar alta disponibilidad. 1. Conectarse al control plane: ssh k8s3 2. Ejecutar el comando obtenido en el paso 3.1. de [[informatica:linux:kubernetes#con_alta_disponibilidad|primer control plane con alta disponibilidad]] con las siguientes modificaciones: * Si hemos configurado alta disponibilidad con [[https://github.com/kubernetes/kubeadm/blob/master/docs/ha-considerations.md#kube-vip|kube-vip]] el directorio '/etc/kubernetes/manifests' no estará vacío, y el comando 'sudo kubeadm join' fallará. Por ese motivo, y solo en este caso, debemos añadir al comando 'sudo kubeadm join' el parámetro '--ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests' * Añadir 'sudo' * (Opcional) Añadir '--v=8' para que la salida del comando muestre más información sudo kubeadm join \ k8s.local:8443 \ --token r6mawr.wsgooc91h45a55i8 \ --discovery-token-ca-cert-hash sha256:94e15af3476146deb36c0f9a53f33a9ea3c441470a2f9e3aefdf538ca7fb4443 \ --control-plane \ --certificate-key 7b04bab444d4a5515c514a8b8eeb7a2df6629ee80d46b7f1ed5bd4b1aa3d80ed \ --ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests \ --v=8 3. Pasos opcionales. Los pasos que se listan a continuación son opcionales, y solo son necesarios si queremos usar el comando 'kubectl' desde el segundo y subsiguientes control plane, lo que es acertado, pues igual nos hace falta si el primer control plane se cae por el motivo que sea. 3.1. Configurar archivos para poder usar 'kubectl'. Ejecutar: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config sudo mkdir -p /root/.kube sudo cp -i /etc/kubernetes/admin.conf /root/.kube/config 3.2. Comprobar: kubectl get nodes Salida esperada similar a: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 22h v1.21.1 k8s3 Ready control-plane,master 22h v1.21.1 4. Repetir los pasos 1 a 3 con el resto de control plane que queramos añadir al cluster. ==== Worker ==== Al menos un nodo debe tener el rol de worker. Las instrucciones son casi idénticas se haya configurado o no alta diponibilidad, pero se muestran de forma separada para hacerlas más legibles. === Sin alta disponibilidad === 1. Conectarse al worker: ssh k8s3 2. Ejecutar el comando obtenido en el paso 3.2. de [[http://wiki.legido.com/doku.php?id=informatica:linux:kubernetes#sin_alta_disponibilidad|primer control plane sin alta disponibilidad]] con las siguientes modificaciones: * Añadir 'sudo' * (Opcional) Añadir '--v=8' para que la salida del comando muestre más información sudo kubeadm join \ k8s2.local:6443 \ --token 9sljzd.hm5i967gzi802cah \ --discovery-token-ca-cert-hash sha256:0a3ac17a0a2c093aca67b2cdc8c99e3cc9c374a48fe762385faf705932785de8 \ --v=8 3. Comprobar 3.1. Conectarse al control plane: ssh k8s2 3.2. Ejecutar: kubectl get nodes Salida esperada similar a: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 2d18h v1.21.1 k8s3 Ready 46h v1.21.1 4. (Opcional) Repetir los pasos 1 a 4 con el resto de workers que se quiera unir al cluster de kubernetes. === Con alta disponibilidad === 1. Conectarse al worker: ssh k8s5 2. Ejecutar el comando obtenido en el paso 3.2. de [[informatica:linux:kubernetes#con_alta_disponibilidad|primer control plane con alta disponibilidad]] con las siguientes modificaciones: * Añadir 'sudo' * (Opcional) Añadir '--v=8' para que la salida del comando muestre más información sudo kubeadm join \ k8s.local:8443 \ --token 9sljzd.hm5i967gzi802cah \ --discovery-token-ca-cert-hash sha256:0a3ac17a0a2c093aca67b2cdc8c99e3cc9c374a48fe762385faf705932785de8 \ --v=8 3. Comprobar 3.1. Conectarse a cualquiera de los control plane: ssh k8s4 3.2. Ejecutar: kubectl get nodes Salida esperada similar a: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 2d18h v1.21.1 k8s3 Ready control-plane,master 2d18h v1.21.1 k8s4 Ready control-plane,master 2d18h v1.21.1 k8s5 Ready 46h v1.21.1 4. (Opcional) Repetir los pasos 1 a 4 con el resto de workers que se quiera unir al cluster de kubernetes. ====== Balanceador capa 4 kube-apiserver ====== https://github.com/kubernetes/kubeadm/blob/master/docs/ha-considerations.md#kube-vip Paso requerido para alta disponibilidad. Vamos a crear un balanceador de carga capa 4 para el servicio [[https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver|kube-apiserver]], en este caso mediante un pod. El objetivo es crear una IP virtual o flotante ('192.168.95.69' en este ejemplo) asociada a un nombre DNS ('k8s.local' en este ejemplo) que irá pasando de un control plane a otro en caso de que el control plane que tenga la IP virtual se caiga. Las peticiones TCP que lleguen a ese nombre ('k8s.local') y ese puerto ('8443' en este ejemplo) serán dirigidas al puerto TCP 6443 del control plane que en ese momento tenga la IP virtual. En ese puerto, TCP 6443, es en el que escucha el servicio 'kube-apiserver'. Todos los nodos (control plane o workers) deben resolver el nombre que elijamos para el balanceador de carga, en este ejemplo 'k8s.local'. Todos los miembros de ese balanceador de carga, los 3 control plane en este ejemplo, deben ser capaces de resolver el nombre DNS de todos y cada uno de sus miembros, ya que usaremos nombres DNS. En este ejemplo tendremos 3 nodos que actuarán como control plane: ^ DNS ^ IP ^ Puerto ^ Comentario ^ | k8s.local | 10.0.0.200 | 8443 | 'kube-apiserver' cluster. | | k8s2.local | 10.0.0.2 | 6443 | Primer control plane | | k8s3.local | 10.0.0.3 | 6443 | Control plane adicional | | k8s3.local | 10.0.0.4 | 6443 | Control plane adicional | ===== Oracle cloud ===== **OJO**: una vez pasa el período de prueba gratix NO se pueden modificar, por lo que es MUY arriesgado confiar en este servicio. Por ejemplo si el puerto del ingress controller cambia del 32386 no se pueden añadir/eliminar backends al backend set Oracle cloud ofrece por la patilla y para siempre un balanceador de capa 4 con IP pública y estática. Resumen de máquinas, nombres, IPs, etc. (las IPs públicas están cambiadas) ^ DNS ^ IP pública ^ IP privada ^ Comentario ^ | k8s.legido.com | 1.2.3.4 | 10.0.0.194 | Balanceador de carga. Escucha (listener) puerto 6443 | | k8s1 | 1.2.3.5 | 10.0.0.2 | Primer control plane | | k8s2 | 1.2.3.6 | 10.0.0.3 | Segundo control plane | | k8s3 | 1.2.3.7 | 10.0.0.4 | Tercer control plane | Creamos: - Un [[http://wiki.legido.com/doku.php?id=informatica:oracle:oci#load_balancer|load balancer]] con su correspondiente [[http://wiki.legido.com/doku.php?id=informatica:oracle:oci#security_list|security list]] - Un [[http://wiki.legido.com/doku.php?id=informatica:oracle:oci#listener-kube-apiserver|listener]] que escuche en el puerto 6443 oara el servicio 'kube-apiserver' ===== Haproxy y keepalived como servicios ===== 1. Keepalived 1.1. Instalar sudo apt install keepalived 1.2. Crear: sudo vim /etc/keepalived/keepalived.conf Con el siguiente contenido: ! /etc/keepalived/keepalived.conf ! Configuration File for keepalived global_defs { router_id LVS_DEVEL } vrrp_script check_apiserver { script "/etc/keepalived/check_apiserver.sh" interval 3 weight -2 fall 10 rise 2 } vrrp_instance VI_1 { state MASTER interface enp0s3 virtual_router_id 51 priority 101 authentication { auth_type PASS auth_pass 42 } virtual_ipaddress { 10.0.0.200 } track_script { check_apiserver } } Explicación: * STATE: MASTER para master, BACKUP para el resto * INTERFACE: enp0s3 * ROUTER_ID: 51 * PRIORITY: 101 para el master, 100 para el resto * AUTH_PASS: 42 * APISERVER_VIP: 10.0.0.200 1.2. Crear: sudo vim /etc/keepalived/check_apiserver.sh Con el siguiente contenido: #!/bin/sh APISERVER_VIP="10.0.0.200" APISERVER_DEST_PORT="8443" errorExit() { echo "*** $*" 1>&2 exit 1 } curl --silent --max-time 2 --insecure https://localhost:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit "Error GET https://localhost:${APISERVER_DEST_PORT}/" if ip addr | grep -q ${APISERVER_VIP}; then curl --silent --max-time 2 --insecure https://${APISERVER_VIP}:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit "Error GET https://${APISERVER_VIP}:${APISERVER_DEST_PORT}/" fi 2. haproxy 2.1. Instalar sudo apt install haproxy 2.2. Crear: sudo vim /etc/haproxy/haproxy.cfg Con el siguiente contenido: # /etc/haproxy/haproxy.cfg #--------------------------------------------------------------------- # Global settings #--------------------------------------------------------------------- global log /dev/log local0 log /dev/log local1 notice daemon #--------------------------------------------------------------------- # common defaults that all the 'listen' and 'backend' sections will # use if not designated in their block #--------------------------------------------------------------------- defaults mode http log global option httplog option dontlognull option http-server-close option forwardfor except 127.0.0.0/8 option redispatch retries 1 timeout http-request 10s timeout queue 20s timeout connect 5s timeout client 20s timeout server 20s timeout http-keep-alive 10s timeout check 10s #--------------------------------------------------------------------- # apiserver frontend which proxys to the control plane nodes #--------------------------------------------------------------------- frontend apiserver bind *:8443 mode tcp option tcplog default_backend apiserver #--------------------------------------------------------------------- # round robin balancing for apiserver #--------------------------------------------------------------------- backend apiserver option httpchk GET /healthz http-check expect status 200 mode tcp option ssl-hello-chk balance roundrobin server k8s1 k8s1.local:6443 check server k8s2 k8s2.local:6443 check server k8s3 k8s3.local:6443 check 3. Arrancar servicios: sudo systemctl enable haproxy --now sudo systemctl enable keepalived --now IMPORTANTE: no va a funcionar hasta que el backend (apiserver) empiece a escuchar en el puerto 6443 ===== Kube-vip ===== Antiguo No funciona en Oracle OCI ^ DNS ^ IP ^ Puerto ^ Comentario ^ | k8s.local | 192.168.95.69 | 8443 | 'kube-apiserver' cluster. | | k8s2.local | 192.168.95.71 | 6443 | Primer control plane | | k8s3.local | 192.168.95.72 | 6443 | Control plane adicional | | k8s3.local | 192.168.95.73 | 6443 | Control plane adicional | 1. Conectarse al control plane. Ejecutar: ssh k8s2 2. Ejecutar: sudo mkdir -p /etc/kube-vip sudo vim /etc/kube-vip/config.yaml Con el siguiente contenido, ajustando: * localPeer. Id lo que queramos, address la IP privada del control plane. * remotePeers. Igual que 'localPeer' pero para el resto de control planes. * vip. La misma para todo el cluster. * startAsLeader. Para el primer control plane 'true', para el resto 'false'. * interface. En este ejemplo se tiene solo una interfaz, por tanto la 'eth0'. * loadBalancers. El cluster escucha en el puerto 8433, cada uno de los control plane escucha en el puerto 6443 localPeer: id: k8s2.local address: 192.168.95.71 port: 10000 remotePeers: - id: k8s3.local address: 192.168.95.72 port: 10000 - id: k8s4.local address: 192.168.95.73 port: 10000 vip: 192.168.95.69 gratuitousARP: true singleNode: false startAsLeader: true interface: eth0 loadBalancers: - name: API Server Load Balancer type: tcp port: 8443 bindToVip: false backends: - port: 6443 address: 192.168.95.71 - port: 6443 address: 192.168.95.72 - port: 6443 address: 192.168.95.73 3. Ejecutar docker run -it --rm plndr/kube-vip:0.1.1 /kube-vip sample manifest \ | sed "s|plndr/kube-vip:'|plndr/kube-vip:0.1.1'|" \ | sudo tee /etc/kubernetes/manifests/kube-vip.yaml Resultado esperado similar a: apiVersion: v1 kind: Pod metadata: creationTimestamp: null name: kube-vip namespace: kube-system spec: containers: - command: - /kube-vip - start - -c - /vip.yaml image: 'plndr/kube-vip:0.1.1' name: kube-vip resources: {} securityContext: capabilities: add: - NET_ADMIN - SYS_TIME volumeMounts: - mountPath: /vip.yaml name: config hostNetwork: true volumes: - hostPath: path: /etc/kube-vip/config.yaml name: config status: {} **IMPORTANTE**: cuando se tengan que unir el segundo y tercer control plane al cluster (con el comando 'sudo kubeadm join') hay que pasar el parámetro: --ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests 4. Repetir los pasos 1 a 4, ajustando los parámetros que se indica en cada paso, hasta que no queden más control plane por añadir al balanceador de carga de capa 4. Cuando se inicie el cluster (primer control plane) o se una el control plane al cluster (segundo y subsiguientes control planes) se iniciará un pod que gestionará la IP virtual ('192.168.95.69' en este ejemplo). ====== Deployment ====== Vamos a desplegar un POD con la imagen [[https://hub.docker.com/r/containous/whoami whoami]]. 1. Conectarse al master ssh master 2. Ejecutar: cat < 3. Comprobar kubectl get deployments Resultado esperado similar a: NAME READY UP-TO-DATE AVAILABLE AGE deployment-whoami 1/1 1 1 40h ====== Service ====== Un [[https://kubernetes.io/docs/concepts/services-networking/service/|Service]] expone un [[https://kubernetes.io/docs/concepts/workloads/controllers/deployment/|Deployment]] de forma que se pueda acceder al 'Deployment' desde fuera del cluster de Kubernetes. Requisitos: * Un [[informatica:linux:kubernetes#deployment|Deployment]] con 'metadata.name' igual a 'deployment-whoami' 1. Conectarse al master ssh master 2. Ejecutar: cat < 3. Comprobar kubectl get services --field-selector metadata.name=service-whoami Resultado esperado similar a: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service-whoami ClusterIP 10.103.221.128 80/TCP 42h ====== Metallb ====== Se trata de un balanceador de carga por software para instalaciones "bare metal", es decir, aquellas que NO se producen en ningún proveedor en la nube, como Amazon AWS, Google Cloud Services o Azure. Es necesario instalarlo para poder instalar un servicio de tipo "LoadBalancer" y que obtenga una "EXTERNAL-IP". Más información [[https://metallb.universe.tf/ aquí]]. En este ejemplo vamos a usar: * Layer 2 * IPs privadas ===== Layer 2 ===== Requisitos: * Tener un rango de IPs **que no se usen**. Es decir, no vale ni las que tienen los nodos del cluster, ni las que usa flannel. En este ejemplo tenemos: * 192.168.95.68 * 192.168.95.69 1. Conectarse al master ssh master 2. Instalar. Ejecutar: kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.6/manifests/namespace.yaml kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.6/manifests/metallb.yaml Solo la primera vez: kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)" 3. Configuración. Ajustar: * Nombre del pool de direcciones. En este ejemplo "default" * IPs. En este ejemplo "192.168.95.68" y "192.168.95.69" * Protocolo. En este ejemplo "layer2" Ejecutar: cat < 4. Probar 4.1. Crear un "Service", no importa que apunte a un "Deployment" valido. Ajustar 'metadata.annotations.metallb.universe.tf/address-pool' para que coincida con el valor del nombre del pool de direcciones, en este ejemplo "default". Ejecutar: cat < 4.2. Comprobar: kubectl get services --field-selector metadata.name=test-service-canberemoved Resultado esperado similar a: NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default test-service-canberemoved LoadBalancer 10.108.71.69 192.168.95.69 80:31652/TCP 3m52s El campo 'EXTERNAL-IP' tiene asignada una IP del pool de direcciones. 4.3. Limpieza kubectl delete services --field-selector metadata.name=test-service-canberemoved ===== Cambiar 'externalTrafficPolicy' ===== [[https://metallb.universe.tf/usage/#layer2|Fuente]] Requisitos: * Tener instalado [[informatica:linux:kubernetes#nginx|nginx controller]] 1. Conectarnos al master ssh k8s2 2. Editar el servicio, en nuestro caso se llama 'ingress-nginx-controller'. Ejecutar: kubectl edit services -n ingress-nginx ingress-nginx-controller 3. Cambiar: externalTrafficPolicy: Cluster Por: externalTrafficPolicy: Local **IMPORTANTE**: NO hace falta hacer un rollout restart de los pods, los cambios los toma inmediatamente. 4. Comprobar. En mi caso accediendo al nombre DNS público: https://example.com El valor de 'X-Forwarded-For' ha cambiado: * Cluster => aparecía la IP de la interfaz de flannel del nodo que estaba ejecutando el pod 'ingress-nginx-controller-7c6b6c6fb8-bvvcf', en este caso '10.244.1.1' * Local => aparece la IP privada del edge router. Todavía tengo que afinar, debería aparecer la IP pública del cliente que hizo la petición HTTPS. ====== Addons ====== [[https://kubernetes.io/docs/concepts/cluster-administration/addons/|Addons]] ===== Cert manager ===== [[https://github.com/jetstack/cert-manager/|Cert-manager]] es un [[https://kubernetes.io/docs/concepts/cluster-administration/addons/|addon]] que sirve para generar certificados SSL Letsencrypt. Para instalarlo: 1. Conectarse al master ssh master 2. Instalar los manifiestos de la [[https://github.com/jetstack/cert-manager/releases/|versión]] marcada como 'Latest release', en este ejemplo la 'v1.8.1': kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.8.1/cert-manager.yaml kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.8.1/cert-manager.crds.yaml 3. Probar 3.1. Ejecutar: watch -n 5 kubectl get pods --namespace cert-manager Hastq que el pod 'webhook' esté corriendo. Salida esperada similar a: NAME READY STATUS RESTARTS AGE cert-manager-7dd5854bb4-zz8wj 1/1 Running 0 71s cert-manager-cainjector-64c949654c-j7s9h 1/1 Running 0 71s cert-manager-webhook-6b57b9b886-9ghg5 1/1 Running 0 71s Suele tardar menos de un minuto. 3.2. Crear: * Namespace * Issuer * Certificate Ejecutar: cat < test-resources.yaml apiVersion: v1 kind: Namespace metadata: name: cert-manager-test --- apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: test-selfsigned namespace: cert-manager-test spec: selfSigned: {} --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: selfsigned-cert namespace: cert-manager-test spec: dnsNames: - example.com secretName: selfsigned-cert-tls issuerRef: name: test-selfsigned EOF 3.3. Cargar el manifiesto creado en el paso anterior. **IMPORTANTE**: esperar un par de minitos desde el paso anterior, o de otra forma podemos obtener un error: x509: certificate signed by unknown authority Yo no fui capaz de resolver ese error. Ejecutar: kubectl apply -f test-resources.yaml 3.4. Ver el certificado. Ejecutar: kubectl describe certificate -n cert-manager-test | grep Message Resultado esperado similar a: Message: Certificate is up to date and has not expired 3.5. Limpieza. Ejecutar: kubectl delete -f test-resources.yaml ====== Issuer ====== Se trata de un [[https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/|custom resource]] creado durante el [[informatica:linux:kubernetes#cert_manager|proceso de instalación]] del [[https://kubernetes.io/docs/concepts/cluster-administration/addons/|Addon]] llamado [[https://github.com/jetstack/cert-manager/|cert-manager]]. Requisitos: * Tener instalado [[informatica:linux:kubernetes#cert_manager|cert-manager]] Para instalarlo: 1. Conectarse al control plane ssh k8s2 2. Ejecutar: Ajustando: * email. Si es del dominio 'example.com' no generará las claves tras llamar a letsencrypt cat < En este ejemplo estamos usando la API de producción de letsencrypt: https://acme-v02.api.letsencrypt.org/directory Pero podemos usar si queremos la de staging: https://acme-staging-v02.api.letsencrypt.org/directory 3. Comprobar. Ejecutar: watch -n 5 kubectl get issuers.cert-manager.io Resultado esperado similar a: NAME READY AGE letsencrypt-production True 43h Tarda menos de un minuto. ====== Edge router ====== Se trata de un servidor completamente por fuera de kubernetes, pero se menciona aquí para completar el ejemplo. Se encarga de escuchar peticiones en los puertos TCP 80 y TCP 443 y dirigirlas al cluster kuberntes. Los nombres DNS de los servicios con certificados SSL válidos, por ejemplo con letsencrypt, deben resolver a la IP pública del edge router. Por ejemplo "example.com" debe resolver a la IP pública del edge router, de forma que la petición finalmente le llegue al cluster de kubernetes a través del siguiente esquema: [Cliente] => example.com => [Edge router] => [Worker] => [Ingress route] => [Service] => [Deployment] Requisitos: * IP pública * IP privada en el mismo rango que los nodos del clúster de kubernetes ("192.168.95.0/24" en este ejemplo) * (Opcional) Docker instalado. Usamos docker para hacer más rápido y sencillo el despliegue de nginx ===== NodePort (usar este) ===== Requisitos: * IPs privadas de los workers del clúster * Puertos en los que escuchan los Nodeport tanto HTTP como HTTPS. Ver paso 8 de [[informatica:linux:kubernetes#nginx_nodeport_daemonset_usar_este|Nginx ingress Nodeport daemonset]] * [[https://docs.docker.com/engine/install/debian|Docker]] ==== Bare metal ==== Estas instrucciones sob para montar completamente por fuera de kubernetes un balanceador de carga capa 7. TODO: refinar, porque cuando jugué con OCI me di cuenta que capa 4 funciona perfectamente, no hace falta liarse con proxy_pass y capa 7 1. Acceder al servidor ssh k8s1 2. Crear el archivo 'default.conf': vim default.conf Con el siguiente contenido: proxy_set_header X-Real-IP $proxy_protocol_addr; proxy_set_header X-Forwarded-For $remote_addr; # To avoid 404. Credits: # https://serverfault.com/a/407983/570054 proxy_set_header Host $host:$server_port; server { listen 80; location / { proxy_pass http://k8s; } error_page 404 /404.html; location = /404.html { root /usr/share/nginx/html/; internal; } } 3. Crear el archivo 'nginx.conf': vim nginx.conf Con el siguiente contenido: user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; upstream k8s { # IP: kubernetes node private IP. Port: nginx ingress controller in NodePort port for HTTP traffic server 192.168.95.74:31104; server 192.168.95.75:31104; server 192.168.95.76:31104; } } stream { server { listen 443; proxy_pass k8s_https; # Requires 'use-proxy-protocol' to 'true' in configmap of nginx ingress controller # https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-proxy-protocol proxy_protocol on; } upstream k8s_https { # IP: kubernetes node private IP. Port: nginx ingress controller in NodePort port for HTTP traffic server 192.168.95.74:30893; server 192.168.95.75:30893; server 192.168.95.76:30893; } log_format basic '$remote_addr - [$time_local] ' '$status ' ''; access_log /var/log/nginx/access.log basic; } Modificar: * IPs y puertos de los workers del clúster de kubernetes. En este ejemplo las IPs son: * 192.168.95.74 * 192.168.95.75 * 192.168.95.76 * Puerto en el que escucha el servicio NodePort para las conexiones HTTP. En este ejemplo '31104' * Puerto en el que escucha el servicio NodePort para las conexiones HTTPS. En este ejemplo '30893' 4. Arrancar el contenedor docker, ajustando la ruta a los archivos creados en el paso anterior: docker run \ --name edge-router \ --v /home/debian/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /home/debian/default.conf:/etc/nginx/conf.d/default.conf \ -p 80:80 \ -p 443:443 \ -d nginx 5. Probar Para esta prueba necesitamos los siguientes requisitos: * Ingress de nginx instalado y configurado [[informatica:linux:kubernetes#nginx_nodeport_con_afinitty_usar_este|así]] * Nombre DNS (en este ejemplo 'example.com') apuntando a la IP pública del servidor * [[informatica:linux:kubernetes#ejemplo_ingress_con_letsencrypt|Ingress]] con letsencrypt y nombre de host 'example.com' 5.1. Pronbar que el contenedor está corriendo: docker logs -f edge-router 5.2. Abrir un navegador y acceder a la URL configurada en el ingress route, en este ejemplo "example.com": https://example.com Resultado esperado similar a: Hostname: deployment-whoami-6b6dc7c84-tbv7d IP: 127.0.0.1 IP: 10.244.2.4 RemoteAddr: 10.244.2.8:42052 GET / HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.5 Upgrade-Insecure-Requests: 1 X-Forwarded-For: 8.8.8.8 X-Forwarded-Host: example.com X-Forwarded-Port: 443 X-Forwarded-Proto: https X-Real-Ip: 8.8.8.8 X-Request-Id: 4d47507a04aa3e8ab3647c5e03983e41 X-Scheme: https Donde '8.8.8.8' es la IP pública desde donde hemos lanzado la petición. ==== Oracle ==== Requisitos: * Un [[http://wiki.legido.com/doku.php?id=informatica:oracle:oci#load_balancer|load balancer]] con su correspondiente [[http://wiki.legido.com/doku.php?id=informatica:oracle:oci#security_list|security list]] Creamos dos listeners, todo ello capa 4: * Uno que escuche en el [[http://wiki.legido.com/doku.php?id=informatica:oracle:oci#listener-ingress-nginx-controller-http|puerto 80]] * Otro que escuche en el [[http://wiki.legido.com/doku.php?id=informatica:oracle:oci#listener-ingress-nginx-controller-https|puerto 443]] ===== LoadBalancer ===== **AVISO**: funciona, pero no devuelve la IP origen. Igual cambiando algo se puede conseguir En nuestro caso se van a redirigir todas las peticiones no a un servicio de tipo 'NodePort' como se menciona [[https://kubernetes.github.io/ingress-nginx/deploy/baremetal/#using-a-self-provisioned-edge|aquí]], sino a la "EXTERNAL-IP" (en nuestro ejemplo una IP privada, la '192.168.95.68') de un servicio de tipo 'LoadBalancer', [[informatica:linux:kubernetes#nginx|este]]. Esquema: [Cliente DMZ] => https://example.com => [Edge router] => http://192.168.95.68 => [ingress-nginx-controller] => [ingress-whoami] => [service-whoami] => [deployment-whoami] Si lo hiciéramos con servicios de tipo [[https://kubernetes.io/docs/concepts/services-networking/service|NodePort]] deberíamos estar continuamente tocando la configuración del edge router para especificar cada puerto, pues cada servicio tomaría un puerto distinto. Nuestro edge router: * Tiene una IP pública. * Tiene una IP privada del rango de IPs del [[informatica:linux:kubernetes#layer_2|pool de IPs]] de metallb. * Tiene instalado docker (porque el servicio nginx va a ser creado mediante un contenedor). Instalación: 1. Conectarnos al edge router ssh edgerouter 2. Crear el siguiente archivo, ajustando la IP a la que le ha sido asignada al [[informatica:linux:kubernetes#nginx|servicio 'ingress-nginx-controller']] como 'EXTERNAL-IP', en este ejemplo '192.168.95.68': vim nginx.conf Con el siguiente contenido: user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; } stream { map $ssl_preread_server_name $targetBackend { ~^(.*)$ 192.168.95.68:443; } server { listen 443; proxy_pass $targetBackend; ssl_preread on; } log_format basic '$remote_addr - [$time_local] ' '$status ' ''; access_log /var/log/nginx/access.log basic; } 3. Crear el siguiente archivo, ajustando la IP a la que le ha sido asignada al [[informatica:linux:kubernetes#nginx|servicio 'ingress-nginx-controller']] como 'EXTERNAL-IP', en este ejemplo '192.168.95.68': vim default.conf Con el siguiente contenido: proxy_set_header X-Real-IP $proxy_protocol_addr; proxy_set_header X-Forwarded-For $remote_addr; server { listen 80; location / { #proxy_pass http://192.168.95.68; proxy_pass http://php; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } error_page 404 /404.html; location = /404.html { root /usr/share/nginx/html/; internal; } } 4. Arrancar el contenedor, ajustando las rutas a los archivos de configuración, ejecutando: docker run \ --name reverse-proxy \ -v /home/debian/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /home/debian/default.conf:/etc/nginx/conf.d/default.conf \ -p 80:80 \ -p 443:443 \ -d nginx docker logs -f reverse-proxy ===== Redundancia ===== Requisitos: * Dos servidores * Cada uno de los cuales tiene una IP pública * Existe una tercera IP pública ("virtual" o "flotante") * Las 3 IPs públicas (las de cada servidor y la flotante) están en la misma subred 1. Instalar los dos servidores [[informatica:linux:kubernetes#nodeport_usar_este|con NodePort]] 2. Instalar y configurar en ambos servidores [[informatica:linux:keepalived|Keepalived]] ====== Ingress ====== Según su propia [[https://kubernetes.io/docs/concepts/services-networking/ingress|definición]]: An API object that manages external access to the services in a cluster, typically HTTP. ===== Nginx ===== [[https://kubernetes.github.io/ingress-nginx|Ingress nginx]] ==== Nginx NodePort daemonset (usar este) ==== Con este procedimiento podremos obtener la IP de origen en los pods, con las otras aproximaciones todavía no lo he conseguido. Características: * Usaremos [[https://kubernetes.io/docs/concepts/services-networking/service/#nodeport|NodePort]], de forma que todos los nodos, incluidos el master, expondrán a la DMZ un puerto para HTTP (el mismo en todos los nodos), y otro para HTTPS. * Usaremos daemonset, de forma que por cada nodo worker, en este ejemplo 3, se creará un pod. * Usaremos "externalTrafficPolicy: Local" para preservar la IP origen. La explicación detallada se puede encontrar [[https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/|aquí]] y [[https://kubernetes.io/docs/tutorials/services/source-ip/|aquí]] 1. Conectarse al master ssh k8s2 2. Obtener el [[https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal-clusters|archivo]]: wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml 3. Forzar que los pods solo envien tráfico a los deployments, según su ingress route, que estén corriendo en el mismo worker node. Añadir 'spec.externalTrafficPolicy: Local'. Editar: vim deploy.yaml Y cambiar: # Source: ingress-nginx/templates/controller-service.yaml apiVersion: v1 kind: Service metadata: annotations: labels: helm.sh/chart: ingress-nginx-3.30.0 app.kubernetes.io/name: ingress-nginx app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/version: 0.46.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: controller name: ingress-nginx-controller namespace: ingress-nginx spec: Por: # Source: ingress-nginx/templates/controller-service.yaml apiVersion: v1 kind: Service metadata: annotations: labels: helm.sh/chart: ingress-nginx-3.30.0 app.kubernetes.io/name: ingress-nginx app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/version: 0.46.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: controller name: ingress-nginx-controller namespace: ingress-nginx spec: # this setting is t make sure the source IP address is preserved. externalTrafficPolicy: Local 4. **AVISO** En la última versión ya es "NodePort", pero dejo esto documentado por si Ascaso Reemplazar para el tipo de balanceo para el servicio vim deploy.yml Y cambiar: apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.2.0 name: ingress-nginx-controller namespace: ingress-nginx spec: externalTrafficPolicy: Local ports: - appProtocol: http name: http port: 80 protocol: TCP targetPort: http - appProtocol: https name: https port: 443 protocol: TCP targetPort: https selector: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx type: LoadBalancer Por: apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.2.0 name: ingress-nginx-controller namespace: ingress-nginx spec: externalTrafficPolicy: Local ports: - appProtocol: http name: http port: 80 protocol: TCP targetPort: http - appProtocol: https name: https port: 443 protocol: TCP targetPort: https selector: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx # anyadido #type: LoadBalancer type: NodePort 5. Instalar. Ejecutar: kubectl apply -f deploy.yaml 6. Probar 6.1. Ejecutar: kubectl get pods \ -n ingress-nginx \ -l app.kubernetes.io/name=ingress-nginx \ -o wide \ --watch Resultado esperado similar a: ingress-nginx-controller-55bc4f5576-78rmm 1/1 Running 0 45h 10.244.5.3 k8s7 ingress-nginx-controller-55bc4f5576-f27v5 1/1 Running 0 45h 10.244.3.3 k8s5 ingress-nginx-controller-55bc4f5576-v7f6h 1/1 Running 0 45h 10.244.4.3 k8s6 ingress-nginx-controller-6f7c7fb6f8-xvzcs 0/1 Pending 0 45h **IMPORTANTE**: debe haber al menos una réplica por cada node worker. 6.2. Verificar que efectivamente cada servicio (ingress controller) corre en un node worker distinto. Ejecutar: kubectl get nodes Resultado esperado similar a: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 2d19h v1.21.1 k8s3 Ready control-plane,master 2d19h v1.21.1 k8s4 Ready control-plane,master 2d18h v1.21.1 k8s5 Ready 46h v1.21.1 k8s6 Ready 46h v1.21.1 k8s7 Ready 45h v1.21.1 Tenemos pues tres workers: * k8s5 * k8s6 * k8s7 Todo correcto, cada worker tiene al menos un pod corriendo. 6.3. Comprobar la versión. Ejecutar: POD_NAMESPACE=ingress-nginx POD_NAME=$(kubectl get pods -n $POD_NAMESPACE -l app.kubernetes.io/name=ingress-nginx --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}') kubectl exec -it $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version Resultado esperado similar a: ------------------------------------------------------------------------------- NGINX Ingress controller Release: v0.46.0 Build: 6348dde672588d5495f70ec77257c230dc8da134 Repository: https://github.com/kubernetes/ingress-nginx nginx version: nginx/1.19.6 ------------------------------------------------------------------------------- 7. Obtener los puertos expuestos. Esto es necesario para instalar y configura el edge router. Ejecutar: kubectl get services -n ingress-nginx --field-selector metadata.name=ingress-nginx-controller Resultado esperado similar a: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller NodePort 10.110.176.170 80:31104/TCP,443:30893/TCP 55m Por lo tanto en este ejemplo: * HTTP => 31104 * HTTPS => 30893 8. **AVISO** Este paso está deprecated. Solo hay que hacerlo si se va a usar un edge router con balanceador capa 7. NO hay que hacerlo, eso lo hice en un primer intento. El balanceador que hay que poner en el edge router tiene que ser capa 4, por tanto NO hay que habilitar proxy protocol. [[informatica:linux:kubernetes#editar_configmaps_para_habilitar_use-proxy-protocol|Editar ConfigMaps para habilitar 'use-proxy-protocol']] ==== Actualizar nginx ingress controller ==== 1. Comprobar si es posible una actualización 1.1. Obtemer la versión actual del nginx ingress controller kubectl describe daemonsets.apps -n ingress-nginx ingress-nginx-controller | grep vers Resultado esperado similar a: app.kubernetes.io/version=1.2.0 Por tanto la versión actual es "1.2.0" 1.2. Determinar si hay una nueva versión: https://github.com/kubernetes/ingress-nginx#changelog La hay, "v1.2.1", compatible con las siguientes versiones de kubectl: 1.3. Determinar la versión actual de kubectl: kubectl version --short Resultado esperado similar a: Client Version: v1.24.2 Kustomize Version: v4.5.4 Server Version: v1.24.2 Tenemos kubectl versión "1.24.x", que está por encima de "1.23", asumo que podemos ir hacia delante. 2. Actualizar la versión de la imagen del daemonset (recordad que cambiamos el "Deployment" por "Daemonset") 2.1. Comprobar que la imagen de docker (tag) existe, y obtener el SHA256, todo esto desde la máquina local docker pull registry.k8s.io/ingress-nginx/controller:v1.2.1 Resultado esperado similar a: v1.2.1: Pulling from ingress-nginx/controller 8663204ce13b: Pull complete 897a18b2d257: Pull complete 3cb02f360cf3: Pull complete 2b63816a7692: Pull complete d61ce16aa3b6: Pull complete 4391833fbf2c: Pull complete 4f4fb700ef54: Pull complete bb397308bcd5: Pull complete 803395581751: Pull complete 153d402a7263: Pull complete c815f058cf7b: Pull complete a872540e4aca: Pull complete 4972574251d0: Pull complete 30197fe775a6: Pull complete b059831ea274: Pull complete Digest: sha256:5516d103a9c2ecc4f026efbd4b40662ce22dc1f824fb129ed121460aaa5c47f8 Status: Downloaded newer image for registry.k8s.io/ingress-nginx/controller:v1.2.1 registry.k8s.io/ingress-nginx/controller:v1.2.1 La imagen existe, y el SHA256 es: sha256:5516d103a9c2ecc4f026efbd4b40662ce22dc1f824fb129ed121460aaa5c47f8 2.2. Limpieza docker image rm registry.k8s.io/ingress-nginx/controller:v1.2.1 3. (k8s server) Actualizar la versión de la imagen del daemonset (paso 1.2.) y el SHA256 (paso 2.1.) 3.1. Actualizar el daemonset: kubectl set image daemonsets/ingress-nginx-controller \ controller=registry.k8s.io/ingress-nginx/controller:v1.2.1@sha256:5516d103a9c2ecc4f026efbd4b40662ce22dc1f824fb129ed121460aaa5c47f8 \ -n ingress-nginx 3.2. Verificar: kubectl get pods -n ingress-nginx -o wide ETA: 3' Resultado esperado similar a: NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES ingress-nginx-admission-create-zxqvn 0/1 Completed 0 139m 10.244.0.75 k8s1 ingress-nginx-admission-patch-c9t4w 0/1 Completed 1 139m 10.244.0.74 k8s1 ingress-nginx-controller-6thbj 1/1 Running 0 2m8s 10.244.0.77 k8s1 ingress-nginx-controller-tv9zd 1/1 Running 0 63s 10.244.1.16 k8s3 ingress-nginx-controller-zgfw5 1/1 Running 0 95s 10.244.2.21 k8s2 ==== Nginx LoadBalancer ==== En este ejemplo asumimos que tenemos un LoadBalancer habilitado, porque hemos instalado [[informatica:linux:kubernetes#layer_2|un balanceador de carga por software]]. 1. Conectarse al master: ssh master 2. Obtener el manifiesto: wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.46.0/deploy/static/provider/baremetal/deploy.yaml 3. Editarlo: vim deploy.yaml Y substituir "type: NodePort": # Source: ingress-nginx/templates/controller-service.yaml apiVersion: v1 kind: Service metadata: annotations: labels: helm.sh/chart: ingress-nginx-3.30.0 app.kubernetes.io/name: ingress-nginx app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/version: 0.46.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: controller name: ingress-nginx-controller namespace: ingress-nginx spec: type: NodePort por "type: LoadBalancer": # Source: ingress-nginx/templates/controller-service.yaml apiVersion: v1 kind: Service metadata: annotations: labels: helm.sh/chart: ingress-nginx-3.30.0 app.kubernetes.io/name: ingress-nginx app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/version: 0.46.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/component: controller name: ingress-nginx-controller namespace: ingress-nginx spec: type: LoadBalancer 4. Instalar el manifiesto: kubectl apply -f deploy.yaml 5. Probar. 5.1. Ejecutar: kubectl get pods -n ingress-nginx \ -l app.kubernetes.io/name=ingress-nginx --watch Hasta que el 'controller' esté corriendo: NAME READY STATUS RESTARTS AGE ingress-nginx-admission-create-jw7f8 0/1 Completed 0 4d18h ingress-nginx-admission-patch-b99q4 0/1 Completed 0 4d18h ingress-nginx-controller-55bc4f5576-77bh8 1/1 Running 2 4d18h 5.2. Ejecutar: kubectl get services --field-selector metadata.name=ingress-nginx-controller -n ingress-nginx El resultado esperado es similar a: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller LoadBalancer 10.109.52.158 192.168.95.68 80:31936/TCP,443:31079/TCP 4d18h Vemos que ha tomado una 'EXTERNAL-IP' del pool de direcciones definida en el pool de direcciones de [[informatica:linux:kubernetes#layer_2|metallb]]. 5.3. Ejecutar: POD_NAMESPACE=ingress-nginx POD_NAME=$(kubectl get pods -n $POD_NAMESPACE -l app.kubernetes.io/name=ingress-nginx --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}') kubectl exec -it $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version Resultado esperado similar a: ------------------------------------------------------------------------------- NGINX Ingress controller Release: v0.46.0 Build: 6348dde672588d5495f70ec77257c230dc8da134 Repository: https://github.com/kubernetes/ingress-nginx nginx version: nginx/1.19.6 ------------------------------------------------------------------------------- Por tanto en este ejemplo las peticiones que lleguen a la IP "192.168.95.68" serán enrutaddas en función de las reglas que se definan en el ingress (ver más adelante). ===== Ejemplo ingress ===== Este ejemplo requiere: * Un [[informatica:linux:kubernetes#service|Service]] cuyo 'metadata.name' sea 'service-whoami' * Tener instalado un [[https://kubernetes.io/docs/concepts/services-networking/ingress/|Ingress]] controller, como [[http://wiki.legido.com/doku.php?id=informatica:linux:kubernetes#nginx|Nginx]] 1. Conectarse al master ssh master 2. Ejecutar: cat < 3. Verificar kubectl get ingress --field-selector metadata.name=ingress-whoami Resultado esperado similar a: NAME CLASS HOSTS ADDRESS PORTS AGE ingress-whoami k8s.kedu.coop 192.168.95.73 80, 443 42h Con esta configuración: * Las peticiones que le lleguen a la IP que tiene le servicio 'ingress-nginx-controller', en este ejemplo la '192.168.95.68' pasarán por las reglas definidas en este 'ingress' * Si se recibe una petición HTTP en el puerto TCP 80 o TCP 443 con un 'header' con clave 'Host' y valor 'example.com' se enrutará al servicio con nombre 'service-whoami' ===== Ejemplo ingress con letsencrypt ===== Este ejemplo requiere: * Un [[informatica:linux:kubernetes#service|Service]] cuyo 'metadata.name' sea 'service-whoami' * Tener instalado un [[https://kubernetes.io/docs/concepts/services-networking/ingress/|Ingress]] controller, como [[http://wiki.legido.com/doku.php?id=informatica:linux:kubernetes#nginx|Nginx]] * Tener instalado el [[https://kubernetes.io/docs/concepts/cluster-administration/addons/|addon]] [[http://wiki.legido.com/doku.php?id=informatica:linux:kubernetes#cert_manager|Cert-manager]] * Tener instalado un [[informatica:linux:kubernetes#issuer|issuer]] llamado 'letsencrypt-production' * [[informatica:linux:kubernetes#edge_router|Edge router]]: * Nginx * Reverse proxy a ingress-nginx-controller 'EXTERNAL-IP' * Que no termine las conexiones SSL, es decr, que redirija en capa 4 lo que le llegue a TCP 443 * Un nombre DNS, en este ejemplo "example.com", que apunte a la IP pública del edge router, en este ejemplo '8.8.8.8' 1. Conectarse al master: ssh master 2. Ejecutar: cat < 3. Comprobar 3.1. Ejecutar: watch -n 5 kubectl get certificate El resultado esperado, tras aproximadamente un minuto, es: NAME READY SECRET AGE quickstart-example-tls True quickstart-example-tls 63s 3.2. Se ha creado un secret llamado 'quickstart-example-tls'. Para ver lo que contiene ejecutar: kubectl describe secrets quickstart-example-tls Salida esperada similar a: Name: quickstart-example-tls Namespace: default Labels: Annotations: cert-manager.io/alt-names: k8s.kedu.coop cert-manager.io/certificate-name: quickstart-example-tls cert-manager.io/common-name: k8s.kedu.coop cert-manager.io/ip-sans: cert-manager.io/issuer-group: cert-manager.io cert-manager.io/issuer-kind: Issuer cert-manager.io/issuer-name: letsencrypt-production cert-manager.io/uri-sans: Type: kubernetes.io/tls Data ==== tls.crt: 5587 bytes tls.key: 1675 bytes 3.3. Abrir un navegador e ir a: https://example.com Debería aparecer el contenido servido por el [[informatica:linux:kubernetes#deployment|deployment]] 'deployment-whoami' con un certificado SSL válido. ===== Ejemplo ingress con modsecurity ===== Requisitos: * Crear un servicio "service-echoserver" cat < Probar: curl http://example.com -k -H "user-agent: fern-scanner" Respuesta esperada: 403 ====== ConfigMaps ====== [[https://kubernetes.io/docs/concepts/configuration/configmap/|ConfigMaps]] ===== Editar ConfigMaps para habilitar 'use-proxy-protocol' ===== **IMPORTANTE**: si habilitamos proxy protocol mediante configmap lo haremos tanto para HTTP como para HTTPS. Eso quiere decir que el "edge router" que esté por delante del cluster de kubernetes debe tener esos protocolos activados. En el ejemplo que hay en esta página NO lo está (en el edge router) para HTTP, pero sigue funcionando porque la aplicación (whoami) redirige el tráfico de HTTP a HTTPS. Pero si se hace con curl (está documentado en esta página) fallará. Para que funcione (el curl), tal y como está documentado, habrá que deshabilitar momentáneamente proxy protocol, hacer la prueba con curl, y volver a habilitar. Ver [[https://github.com/kubernetes/ingress-nginx/issues/7194|7194]] En este ejemplo: * Vamos a editar un ConfigMap * Vamos a ver los cambios que se aplican inmediatamente en el pod 1. Identificar el ConfigMap. En nuestro caso queremos modificar [[https://github.com/kubernetes/ingress-nginx/blob/master/deploy/static/provider/baremetal/deploy.yaml#L26-L39 | este]] 'ConfigMap', por lo que filtramos por el 'Namespace' llamado 'ingress-controller': kubectl get configmaps -n ingress-nginx Resultado esperado similar a: NAME DATA AGE ingress-controller-leader-nginx 0 5d ingress-nginx-controller 0 5d kube-root-ca.crt 1 5d En nuestro caso es [[https://github.com/kubernetes/ingress-nginx/blob/master/deploy/static/provider/baremetal/deploy.yaml#L37 | ingress-nginx-controller ]]. 2. Editamos el 'ConfigMap': kubectl edit configmaps -n ingress-nginx ingress-nginx-controller Vamos a seguir [[https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap | estas instrucciones]], por lo que le vamos a añadir una sección 'data' que incluya [[https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-proxy-protocol|use-proxy-protocol]]: data: use-proxy-protocol: "true" 3. Comprobar 3.1. Buscar el pod que usa ese 'ConfigMap'. En este caso hacemos un poco de atajo: kubectl get pods -n ingress-nginx Resultado esperado similar a: NAME READY STATUS RESTARTS AGE ingress-nginx-admission-create-glxnx 0/1 Completed 0 12h ingress-nginx-admission-patch-njb2p 0/1 Completed 0 12h ingress-nginx-controller-598dd75597-lkpp8 1/1 Running 0 138m ingress-nginx-controller-598dd75597-s9kwg 1/1 Running 0 137m 3.2. Ver los logs kubectl logs -f -n ingress-nginx ingress-nginx-controller-598dd75597-lkpp8 Resultado similar a: I0520 15:47:25.546777 9 event.go:282] Event(v1.ObjectReference{Kind:"ConfigMap", Namespace:"ingress-nginx", Name:"ingress-nginx-controller", UID:"e84d9bc4-bee4-47f0-a687-bac67a95c8d0", APIVersion:"v1", ResourceVersion:"69461", FieldPath:""}): type: 'Normal' reason: 'UPDATE' ConfigMap ingress-nginx/ingress-nginx-controller I0520 15:47:25.549723 9 controller.go:146] "Configuration changes detected, backend reload required" I0520 15:47:25.618572 9 controller.go:163] "Backend successfully reloaded" I0520 15:47:25.623095 9 event.go:282] Event(v1.ObjectReference{Kind:"Pod", Namespace:"ingress-nginx", Name:"ingress-nginx-controller-598dd75597-lkpp8", UID:"eda84159-3520-48be-8220-c4e0491afb07", APIVersion:"v1", ResourceVersion:"60184", FieldPath:""}): type: 'Normal' reason: 'RELOAD' NGINX reload triggered due to a change in configuration 3.3. Comprobar si se ha añadido un setting [[https://nginx.org/en/docs/http/ngx_http_core_module.html?&_ga=2.124121161.2132845062.1621488296-1101867539.1620734415#var_proxy_protocol_addr|proxy_protocol]] por algún lado. Ejecutar: kubectl exec -ti -n ingress-nginx ingress-nginx-controller-598dd75597-lkpp8 -- cat /etc/nginx/nginx.conf | grep proxy_protocol | grep listen Salida esperada similar a: listen 80 proxy_protocol default_server reuseport backlog=511 ; listen 443 proxy_protocol default_server reuseport backlog=511 ssl http2 ; listen 80 proxy_protocol ; listen 443 proxy_protocol ssl http2 ; Por tanto el 'ConfigMap' ha funcionado. ====== Desconectar nodo ====== {{https://kubernetes.io/images/docs/kubectl_drain.svg}} ===== Worker ===== 1. Conectarnos a un control plane ssh k8s2 2. Listar nodos: kubectl get nodes Resultado esperado similar a: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 5d14h v1.21.1 k8s3 Ready control-plane,master 5d14h v1.21.1 k8s4 Ready control-plane,master 5d14h v1.21.1 k8s5 Ready 4d18h v1.21.1 k8s6 Ready 4d17h v1.21.1 k8s7 Ready 4d17h v1.21.1 3. Vaciar de pods el worker node: kubectl drain k8s7 Em este caso nos aparece el siguiente error: node/k8s7 cordoned error: unable to drain node "k8s7", aborting command... There are pending nodes to be drained: k8s7 error: cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-system/kube-flannel-ds-n47nd, kube-system/kube-proxy-cghwq 4. Reintentar el comando anterior añadiendo algunos parámetros: kubectl drain k8s7 --ignore-daemonsets Resultado esperado similar a: node/k8s7 already cordoned WARNING: ignoring DaemonSet-managed Pods: kube-system/kube-flannel-ds-n47nd, kube-system/kube-proxy-cghwq evicting pod ingress-nginx/ingress-nginx-controller-55bc4f5576-78rmm evicting pod cert-manager/cert-manager-7dd5854bb4-td892 evicting pod cert-manager/cert-manager-webhook-6b57b9b886-pwhhq evicting pod ingress-nginx/ingress-nginx-admission-patch-t4dm6 pod/ingress-nginx-admission-patch-t4dm6 evicted pod/cert-manager-7dd5854bb4-td892 evicted pod/cert-manager-webhook-6b57b9b886-pwhhq evicted pod/ingress-nginx-controller-55bc4f5576-78rmm evicted node/k8s7 evicted En este punto: * El worker 'k8s7' no acepta que se programen despliegues de nuevos pods * Todavía está corriendo pods de tipo [[https://kubernetes.io/docs/concepts/workloads/controllers/daemonset|DaemonSet]] 5. (Opcional) Comprobar el estado de los nodos: kubectl get nodes Resultado esperado similar a: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 5d15h v1.21.1 k8s3 Ready control-plane,master 5d14h v1.21.1 k8s4 Ready control-plane,master 5d14h v1.21.1 k8s5 Ready 4d18h v1.21.1 k8s6 Ready 4d18h v1.21.1 k8s7 Ready,SchedulingDisabled 4d17h v1.21.1 6. (Opcional) Listar los pods que todavía están corriendo en el worker 'k8s7': kubectl get pods --all-namespaces -o wide | grep k8s7 Resultado esperado similar a: kube-system kube-flannel-ds-n47nd 1/1 Running 0 4d17h 192.168.95.76 k8s7 kube-system kube-proxy-cghwq 1/1 Running 0 4d17h 192.168.95.76 k8s7 7. Eliminar el nodo kubectl delete node k8s7 Resultado esperado similar a: node "k8s7" deleted 8. Comprobar: kubectl get nodes -w Después de un minuto aproximadamente habrá desaparecido: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 5d15h v1.21.1 k8s3 Ready control-plane,master 5d15h v1.21.1 k8s4 Ready control-plane,master 5d14h v1.21.1 k8s5 Ready 4d18h v1.21.1 k8s6 Ready 4d18h v1.21.1 ====== Definir tiempo pod salta a otro worker ====== Por defecto un pod saltará a otro worker tras **5 minutos** de que el worker esté marcado como "NotReady". **AVISO**: el parámetro [[https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager|pod-eviction-timeout]] parece ser que no funciona. Ver [[https://github.com/kubernetes/kubernetes/issues/74651|#74651]] y [[https://github.com/kubernetes-sigs/kubespray/issues/7112|#7112]] 1. Conectarse al control plane ssh k8s2 2. Realizar una copia de seguridad: sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml ~ 3. Editar: sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml Y añadir '.spec.containers.0.command': # anyadido #- --enable-admission-plugins=NodeRestriction - --enable-admission-plugins=NodeRestriction,DefaultTolerationSeconds - --default-not-ready-toleration-seconds=30 - --default-unreachable-toleration-seconds=30 Ejemplo: apiVersion: v1 kind: Pod metadata: annotations: kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.95.71:6443 creationTimestamp: null labels: component: kube-apiserver tier: control-plane name: kube-apiserver namespace: kube-system spec: containers: - command: - kube-apiserver - --advertise-address=192.168.95.71 # anyadido - --default-not-ready-toleration-seconds=30 - --default-unreachable-toleration-seconds=30 ... 4. Repetir los pasos 1 a 3 para el resto de control plane, en el caso de que haya más por ser un entorno de alta disponibilidad 5. Comprobar 5.1. Comprobar que todos y cada uno de los control plane tienen un pod 'kube-apiserver' corriendo: watch -n 5 "kubectl get pods --all-namespaces | grep kube-api" Resultado esperado similar a: kube-system kube-apiserver-k8s2 1/1 Running 0 43s kube-system kube-apiserver-k8s3 1/1 Running 3 5d20h kube-system kube-apiserver-k8s4 1/1 Running 3 5d20h 5.2. Identificar los pods que corren en un worker: kubectl get pods -o wide Resultado esperado similar a: NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES deployment-whoami-6b6dc7c84-2swm9 1/1 Running 0 101m 10.244.3.37 k8s5 5.3. Detener el worker donde está corriendo el pod 5.3.1. Conectarse al worker ssh k8s5 5.3.2. Apagarlo sudo shutdown -h now 5.3.3. Anotar la hora date 5.4. Monitorizar nodos watch -n 5 kubectl get nodes Resultado esperado similar a **tras aproximadamente 30 segundos**: NAME STATUS ROLES AGE VERSION k8s2 Ready control-plane,master 5d22h v1.21.1 k8s3 Ready control-plane,master 5d22h v1.21.1 k8s4 Ready control-plane,master 5d22h v1.21.1 k8s5 NotReady 5d2h v1.21.1 k8s6 Ready 5d1h v1.21.1 5.5. Monitorizar pods watch -n 5 kubectl get pods -o wide Resultado esperado similar a **tras aproximadamente 60 segundos**: NAME READY STATUS RESTARTS AGE IP N ODE NOMINATED NODE READINESS GATES deployment-whoami-6b6dc7c84-2swm9 1/1 Terminating 0 104m 10.244.3.37 k 8s5 deployment-whoami-6b6dc7c84-mrjjw 1/1 Running 0 19s 10.244.4.31 k 8s6 ====== LOCAL ====== Bajamos código fuente de kubernetes e instalamos: # git clone https://github.com/kubernetes/kubernetes # cd kubernetes # make quick-release Ahora para acabar de instalar hay que decir en que proveedor crea las máquinas virtuales. Lista sacada de https://get.k8s.io/ Google Compute Engine [default] KUBERNETES_PROVIDER=gce Google Container Engine KUBERNETES_PROVIDER=gke Amazon EC2 KUBERNETES_PROVIDER=aws Libvirt (with CoreOS as a guest operating system) KUBERNETES_PROVIDER=libvirt-coreos Microsoft Azure KUBERNETES_PROVIDER=azure-legacy Vagrant (local virtual machines) KUBERNETES_PROVIDER=vagrant VMWare Photon Controller KUBERNETES_PROVIDER=photon-controller Rackspace KUBERNETES_PROVIDER=rackspace OpenStack-Heat KUBERNETES_PROVIDER=openstack-heat En nuestro caso elegimos vagrant que es virtualbox. Instalamos virtualbox: # apt-get install vagrant virtualbox Para que nos cree las dos máquinas virtuales de virtualbox y configure kubernetes ejecutamos este script que se baja los binarios: # export KUBERNETES_PROVIDER=vagrant # wget -q -O - https://get.k8s.io | bash Nos dice que pongamos la ruta en el PATH y volvamos a ejecutarlo: Add '/home/jose/kubernetes/kubernetes/client/bin' to your PATH to use newly-installed binaries. Lo ponemos y ejecutamos: # export PATH=$PATH:/home/jose/kubernetes/kubernetes/client/bin # wget -q -O - https://get.k8s.io | bash Crea dos máquinas con fedora: # VBoxManage list vms "kubernetes_master_1486996921273_58743" {0cae89a8-08f2-4c48-8f02-1ca5df33c5b9} "kubernetes_node-1_1486997158445_96359" {11a27a45-3683-4173-b746-2eb111ad7915} La instalación acabo con: Each machine instance has been created/updated. Now waiting for the Salt provisioning process to complete on each machine. This can take some time based on your network, disk, and cpu speed. It is possible for an error to occur during Salt provision of cluster and this could loop forever. Validating master .................................. Parece que se queda colgado..... Lo lanzo en casa y funciona: Each machine instance has been created/updated. Now waiting for the Salt provisioning process to complete on each machine. This can take some time based on your network, disk, and cpu speed. It is possible for an error to occur during Salt provision of cluster and this could loop forever. Validating master Validating node-1 Waiting for each node to be registered with cloud provider Flag --api-version has been deprecated, flag is no longer respected and will be deleted in the next release Validating we can run kubectl commands. No resources found. Kubernetes cluster is running. The master is running at: https://10.245.1.2 Administer and visualize its resources using Cockpit: https://10.245.1.2:9090 For more information on Cockpit, visit http://cockpit-project.org The user name and password to use is located in /root/.kube/config ... calling validate-cluster Found 1 node(s). NAME STATUS AGE kubernetes-node-1 Ready 38s Validate output: NAME STATUS MESSAGE ERROR scheduler Healthy ok controller-manager Healthy ok etcd-0 Healthy {"health": "true"} etcd-1 Healthy {"health": "true"} Cluster validation succeeded Done, listing cluster services: Kubernetes master is running at https://10.245.1.2 Heapster is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/heapster KubeDNS is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/kube-dns kubernetes-dashboard is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard Grafana is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana InfluxDB is running at https://10.245.1.2/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. Kubernetes binaries at /home/jose/kubernetes/kubernetes/cluster/ You may want to add this directory to your PATH in $HOME/.profile Installation successful! Arrancamos el proxy y ya podemos acceder a la UI del dashboard de Kubernetes: # ./platforms/linux/amd64/kubectl proxy Starting to serve on 127.0.0.1:8001 http://127.0.0.1:8001/ui usuario: vagrant contraseña: vagrant ====== MINIKUBE ====== https://kubernetes.io/docs/tasks/tools/install-minikube/ 1. Instalamos kubectl https://kubernetes.io/docs/tasks/tools/install-kubectl/#install-kubectl-binary-via-curl curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl Lo ponemos en el path: chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl 2. Instalamos minikube https://github.com/kubernetes/minikube/releases Ojo que puede cambiar versión, id a web: curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.20.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ Creamos el cluster. Primero crea la VM en virtualbox y luego la levanta y configura los servicios: # minikube start Starting local Kubernetes cluster... Downloading Minikube ISO 84.07 MB / 84.07 MB [==============================================] 100.00% 0s Kubectl is now configured to use the cluster. # vboxmanage list vms "minikube" {e1e5e6f9-e3cc-437d-81f8-80a038f15ac5} Para conectar a la VM de virtualbox llamada minikube: ssh docker@192.168.99.100 docker/tcuser Miramos que tiene desplegado: # kubectl get pods --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system kube-addon-manager-minikube 0/1 ContainerCreating 0 3m Instalamos el dashboard: # kubectl create -f https://rawgit.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml deployment "kubernetes-dashboard" created service "kubernetes-dashboard" created # kubectl get services --all-namespaces NAMESPACE NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE default kubernetes 10.0.0.1 443/TCP 4m kube-system kubernetes-dashboard 10.0.0.66 80:32220/TCP 21s **Dashboard:** minikube dashboard Te abre http://192.168.99.100:30000 También he hecho un tunel 8080: ssh -L 8080:localhost:8080 docker@192.168.99.100 Url de acceso: http://localhost:8080/ui ====== Autoescalado ====== https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ http://blog.kubernetes.io/2016/07/autoscaling-in-kubernetes.html https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/ https://github.com/kubernetes/heapster/blob/master/docs/influxdb.md Autoescalamos la aplicación: kubectl autoscale deployment apache --min=2 --max=5 --cpu-percent=20 deployment "apache" autoscaled Vemos el autoescalado. Desde la UI se ve en deployments: kubectl get hpa NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE apache Deployment/apache / 1% 2 5 2 6m Con mas detalle: kubectl describe hpa apache Name: apache Namespace: default Labels: Annotations: CreationTimestamp: Fri, 23 Jun 2017 17:51:17 +0200 Reference: Deployment/apache Metrics: ( current / target ) resource cpu on pods (as a percentage of request): / 1% Min replicas: 2 Max replicas: 5 Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 6m 6m 1 horizontal-pod-autoscaler Normal SuccessfulRescale New size: 2; reason: Current number of replicas below Spec.MinReplicas 5m 27s 12 horizontal-pod-autoscaler Warning FailedGetResourceMetric unable to get metrics for resource cpu: failed to get pod resource metrics: the server could not find the requested resource (get services http:heapster:) 5m 27s 12 horizontal-pod-autoscaler Warning FailedComputeMetricsReplicas failed to get cpu utilization: unable to get metrics for resource cpu: failed to get pod resource metrics: the server could not find the requested resource (get services http:heapster:) Daba el error: kubectl describe hpa 35m 18s 72 horizontal-pod-autoscaler Warning FailedComputeMetricsReplicas failed to get cpu utilization: missing request for cpu on container httpd in pod default/httpd-796666570-2h1c6 El problema es que en el deployment hay que decir que quiere monitorizar, en mi caso dentro de resources he añaido las dos líneas de requests: cpu:400m ...... spec: containers: name: httpd image: httpd resources: requests: cpu:400m ...... Después da este error, pero tarda un poco en coger las métricas de heapster: kubectl describe hpa 2m 1m 3 horizontal-pod-autoscaler Warning FailedComputeMetricsReplicas failed to get cpu utilization: unable to get metrics for resource cpu: no metrics returned from heapster ====== LOCAL kubeadm ====== Instalamos docker apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" ====== Local en Centos ====== https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/ Installing kubelet and kubeadm cat < /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64 enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg EOF setenforce 0 yum install -y docker kubelet kubeadm kubernetes-cni systemctl enable docker && systemctl start docker systemctl enable kubelet && systemctl start kubelet ====== Debian ====== apt-get update && apt-get install -y apt-transport-https curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - cat </etc/apt/sources.list.d/kubernetes.list deb http://apt.kubernetes.io/ kubernetes-xenial main EOF apt-get update # Install docker if you don't have it already. apt-get install -y docker-engine apt-get install -y kubelet kubeadm kubernetes-cni Arrancamos kubeadm kubeadm init Your Kubernetes master has initialized successfully! To start using your cluster, you need to run (as a regular user): sudo cp /etc/kubernetes/admin.conf $HOME/ sudo chown $(id -u):$(id -g) $HOME/admin.conf export KUBECONFIG=$HOME/admin.conf You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: http://kubernetes.io/docs/admin/addons/ You can now join any number of machines by running the following on each node as root: kubeadm join --token bad112.b2514df7739b0845 192.168.1.133:6443 Como usuario normal ejecutamos: sudo cp /etc/kubernetes/admin.conf $HOME/ sudo chown $(id -u):$(id -g) $HOME/admin.conf export KUBECONFIG=$HOME/admin.conf Ahora podemos ver los pods: kubectl get pods --all-namespaces Debug errores: sudo journalctl -r -u kubelet Vemos que el pod de dns se queda colgado, hay que configurar la RED Plugin RED https://www.weave.works/docs/net/latest/kube-addon/ kubectl apply -n kube-system -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')" Ahora miramos que todos los pods están activos kubectl get pods --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system etcd-avtp110 1/1 Running 0 26m kube-system kube-apiserver-avtp110 1/1 Running 0 26m kube-system kube-controller-manager-avtp110 1/1 Running 0 26m kube-system kube-dns-692378583-dn3qg 3/3 Running 0 26m kube-system kube-proxy-vdpmv 1/1 Running 0 26m kube-system kube-scheduler-avtp110 1/1 Running 0 26m kube-system weave-net-1s7wd 2/2 Running 0 2m Vemos que el nodo está funcionando kubectl get nodes NAME STATUS AGE VERSION avtp110 Ready 25m v1.6.6 Instalamos dashboard kubectl create -f https://rawgit.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml Para mirar el estado: kubectl describe services kubernetes-dashboard --namespace=kube-system Arrancamos proxy para poder acceder: kubectl proxy Starting to serve on 127.0.0.1:8001 Ya se puede acceder en http://127.0.0.1:8001/ui Al desplegar cualquier cosa me da error: No nodes are available that match all of the following predicates:: PodToleratesNodeTaints (1). Solución: kubectl describe node Name: avtp110 Role: Labels: beta.kubernetes.io/arch=amd64 beta.kubernetes.io/os=linux kubernetes.io/hostname=avtp110 node-role.kubernetes.io/master= Annotations: node.alpha.kubernetes.io/ttl=0 volumes.kubernetes.io/controller-managed-attach-detach=true Taints: node-role.kubernetes.io/master:NoSchedule CreationTimestamp: Sat, 24 Jun 2017 00:33:04 +0200 Phase: Conditions: Type Status LastHeartbeatTime LastTransitionTime Reason Message ---- ------ ----------------- ------------------ ------ ------- OutOfDisk False Sat, 24 Jun 2017 01:00:18 +0200 Sat, 24 Jun 2017 00:33:04 +0200 KubeletHasSufficientDisk kubelet has sufficient disk space available MemoryPressure False Sat, 24 Jun 2017 01:00:18 +0200 Sat, 24 Jun 2017 00:33:04 +0200 KubeletHasSufficientMemory kubelet has sufficient memory available DiskPressure False Sat, 24 Jun 2017 01:00:18 +0200 Sat, 24 Jun 2017 00:33:04 +0200 KubeletHasNoDiskPressure kubelet has no disk pressure Ready True Sat, 24 Jun 2017 01:00:18 +0200 Sat, 24 Jun 2017 00:34:54 +0200 KubeletReady kubelet is posting ready status Addresses: 192.168.1.133,192.168.1.133,avtp110 Capacity: cpu: 4 memory: 20445912Ki pods: 110 Allocatable: cpu: 4 memory: 20343512Ki pods: 110 System Info: Machine ID: 72d18515f62d4c7186558889b32712f9 System UUID: 4C4C4544-0035-3010-8042-B8C04F504332 Boot ID: c529b79b-7f1c-4e1c-95f6-6187bdc57f6a Kernel Version: 4.9.0-3-amd64 OS Image: Debian GNU/Linux 9 (stretch) Operating System: linux Architecture: amd64 Container Runtime Version: docker://1.11.2 Kubelet Version: v1.6.6 Kube-Proxy Version: v1.6.6 ExternalID: avtp110 Non-terminated Pods: (8 in total) Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits --------- ---- ------------ ---------- --------------- ------------- kube-system etcd-avtp110 0 (0%) 0 (0%) 0 (0%) 0 (0%) kube-system kube-apiserver-avtp110 250m (6%) 0 (0%) 0 (0%) 0 (0%) kube-system kube-controller-manager-avtp110 200m (5%) 0 (0%) 0 (0%) 0 (0%) kube-system kube-dns-692378583-f0sk0 260m (6%) 0 (0%) 110Mi (0%) 170Mi (0%) kube-system kube-proxy-1j5hd 0 (0%) 0 (0%) 0 (0%) 0 (0%) kube-system kube-scheduler-avtp110 100m (2%) 0 (0%) 0 (0%) 0 (0%) kube-system kubernetes-dashboard-2039414953-s1k99 0 (0%) 0 (0%) 0 (0%) 0 (0%) kube-system weave-net-sw6k7 20m (0%) 0 (0%) 0 (0%) 0 (0%) Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) CPU Requests CPU Limits Memory Requests Memory Limits ------------ ---------- --------------- ------------- 830m (20%) 0 (0%) 110Mi (0%) 170Mi (0%) Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 27m 27m 1 kubelet, avtp110 Normal Starting Starting kubelet. 27m 27m 1 kubelet, avtp110 Warning ImageGCFailed unable to find data for container / 27m 27m 28 kubelet, avtp110 Normal NodeHasSufficientDisk Node avtp110 status is now: NodeHasSufficientDisk 27m 27m 28 kubelet, avtp110 Normal NodeHasSufficientMemory Node avtp110 status is now: NodeHasSufficientMemory 27m 27m 28 kubelet, avtp110 Normal NodeHasNoDiskPressure Node avtp110 status is now: NodeHasNoDiskPressure 27m 27m 1 kube-proxy, avtp110 Normal Starting Starting kube-proxy. 25m 25m 1 kubelet, avtp110 Normal NodeReady Node avtp110 status is now: NodeReady Nos fijamos en la línea: Taints: node-role.kubernetes.io/master:NoSchedule Lanzamos el comando: kubectl taint nodes avtp110 node-role.kubernetes.io/master:NoSchedule- node "avtp110" tainted Para monitorización: kubectl create -f https://raw.githubusercontent.com/luxas/kubeadm-workshop/master/demos/monitoring/heapster.yaml ====== Balanceador de carga ====== La gracia de Kubernetes es que no hay que poner ips, se configura por nombres. Por ejemplo, deplegamos un apache y lo llamamos httpd. Si levantamos varios pods serán accesibles a sus ips por el puerto 80 Podemos crear un balanceador con el siguiente fichero apuntado a la aplicación httpd: { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "ejemplo-balanceador" }, "spec": { "ports": [{ "port": 8080, "targetPort": 80 }], "selector": { "app": "httpd" }, "type": "LoadBalancer" } } Lo desplegamos en kubernetes: kubectl create -f balancecador.yaml Vemos su configuración: kubectl describe services ejemplo-balanceador Name: ejemplo-balanceador Namespace: default Labels: Annotations: Selector: app=httpd Type: LoadBalancer IP: 10.96.97.216 Port: 8080/TCP NodePort: 32249/TCP Endpoints: 10.32.0.22:80,10.32.0.23:80 Session Affinity: None Events: Si accedemos a http://10.96.97.216:8080 nos balancea entre los dos PODS que tenemos desplegados de apache ====== CLOUD ====== Sitios para instalar en cloud:\\ https://kubernetes.io/docs/setup/pick-right-solution/#hosted-solutions ====== Seguridad ====== https://kubernetes.io/docs/admin/authentication/ ====== Balanceo de un nodo físico ====== https://kubernetes.io/docs/tasks/administer-cluster/cluster-management/ Si queremos parar un nodo por mantenimiento: kubectl --server=192.168.2.1:8001 drain kubernetes2 node "kubernetes2" cordoned error: DaemonSet-managed pods (use --ignore-daemonsets to ignore): kube-proxy-648gj, weave-net-3z537 Parece que no funciona. Lo lanzamos con el comando: kubectl --ignore-daemonsets --server=192.168.2.1:8001 drain kubernetes2 node "kubernetes2" already cordoned WARNING: Ignoring DaemonSet-managed pods: kube-proxy-648gj, weave-net-3z537 pod "servidorweb-3127718498-tpts3" evicted pod "servidorweb-3127718498-3trvp" evicted pod "heapster-57121549-zx3dt" evicted pod "kubernetes-dashboard-2039414953-hsvkn" evicted pod "apache-4284376775-7z96m" evicted pod "kube-dns-692378583-x9kzv" evicted node "kubernetes2" drained Para volver a dejarlo funcionando: kubectl --server=192.168.2.1:8001 uncordon kubernetes2 ====== Flecos ====== **TODO**: revisar si lo que hay aquí anotado se conserva o se suprime Mesos fleet tutum grafana Monitorización: http://www.sysdig.org/ System metrics: Datadog \\ Container logs: Papertrail ====== Ejemplo ====== En este ejemplo: * Configuramos kubernetes en alta disponibilidad * Exponemos un servicio a través de un certificado SSL válido * El pod que recibe la petición muestra la IP pública del cliente Máquinas: ^ DNS ^ IP privada ^ IP pública ^ Comentario ^ | k8s.local | 192.168.95.69 | - | Cluster 'kube-apiserver' | | k8s1.local | 192.168.95.71 | 8.8.8.8 | Edge router. La IP pública es figurada | | k8s2.local | 192.168.95.71 | - | Primer control plane | | k8s3.local | 192.168.95.72 | - | Control plane adicional | | k8s3.local | 192.168.95.73 | - | Control plane adicional | | k8s4.local | 192.168.95.74 | - | Worker | | k8s5.local | 192.168.95.75 | - | Worker | | k8s6.local | 192.168.95.76 | - | Worker | 1. Crear el cluster 1.1. [[https://docs.docker.com/engine/install/debian|Instalar docker]] en todos los nodos, los tres control plane (k8s2.local, k8s3.local y k8s4.local) y los tres workers (k8s5.local, k8s6.local y k8s7.local). 1.2. [[informatica:linux:kubernetes#nodo_comun_para_control_plane_y_worker|Instalar paquetes de kubernetes]] en todos los nodos, los tres control plane (k8s2.local, k8s3.local y k8s4.local) y los tres workers (k8s5.local, k8s6.local y k8s7.local). 1.3. Crear un [[informatica:linux:kubernetes#balanceador_capa_4_kube-apiserver|balanceador de kube-apiserver]] y unir los tres control plane al mismo. 1.4. [[informatica:linux:kubernetes#con_alta_disponibilidad|Iniciar el cluster]] en el primer control plane 1.5. [[informatica:linux:kubernetes#control_plane_solo_alta_disponibilidad|Unir al cluster]] el resto de control plane (k8s3.local y k8s4.local). 1.6. [[informatica:linux:kubernetes#worker|Unir al cluster]] los tres workers. En este momento ya tenemos el cluster de kubernetes montado. 2. Exponer un servicio con certificado SSL válido y que muestre la IP pública del cliente 2.1. Instalar [[informatica:linux:kubernetes#nginx_nodeport_con_afinitty_usar_este|Nginx ingress con nodeport, afinitty y proxy protocol]]. Como dice el paso 8, anotar los puertos HTTP y HTTPS que exponen cada uno de los nodos. 2.2. Instalar [[informatica:linux:kubernetes#cert_manager|Cert-manager addon]] 2.3. Crear [[informatica:linux:kubernetes#issuer|issuer]] para letsencrypt en producción 2.4. Crear un [[informatica:linux:kubernetes#deployment|deployment]] que despliegue el contenedor "whoami" 2.5. Exponer el anterior deployment a través de un [[informatica:linux:kubernetes#service|service]]. 2.6. Crear un [[http://wiki.legido.com/doku.php?id=informatica:linux:kubernetes#ejemplo_ingress_con_letsencrypt|ingress con letsencrypt]] para que un nombre DNS público, "example.com" llegue al servicio definido en el paso anterior. En este punto si: * Se deshabilita momentáneamente proxy protoco (revirtiendo [[informatica:linux:kubernetes#editar_configmaps_para_habilitar_use-proxy-protocol|este paso]] * Se obtiene el worker donde está corriendo el pod 'whoami' * Se obtiene la IP privada de ese worker * Se obtiene el puerto en el que escucha el servicio HTTP Nodeport (ver paso 2.1. de estas instrucciones) * Se le pasa la cabecera "Host" con el mombre adecuado (en este ejemplo "example.com"9 Se podría llegar al pod. Ejemplo: _HOST=192.168.95.75 _PORT=32079 curl -L -s -i -H "Host: example.com" http://$_HOST:$_PORT 3. Configurar edge router En este ejemplo vamos a instalar dos servidores, que van a compartir una IP flotante para tener redundancia. 3.1. Seguir [[informatica:linux:kubernetes#redundancia|estas instrucciones]]. 3.2. Crear un nombre DNS, "example.com", que apunte a la IP pública flotante, por ejemplo '8.8.8.8' 4. Probar https://example.com Resultado esperado: * Debería mostrar un certificado SSL válido * Debería mostrar la IP pública del equipo que realizó la petición ====== Bash completion ====== https://kubernetes.io/es/docs/tasks/tools/included/optional-kubectl-configs-bash-linux/ source /usr/share/bash-completion/bash_completion echo 'source <(kubectl completion bash)' >>~/.bashrc sudo su kubectl completion bash >/etc/bash_completion.d/kubectl exit exit ====== Desplegar pods en control plane ====== No es lo recomendable, pero si tenemos un cluster con 3 nodos (stacked etc), los 3 control plane, y sin workers, podemos desplegar pods en los control plane si les quitamos los taints que precisamente marcan que NO se puedan desplegar pods allí. Para cada uno de los nodos del cluster, en este caso "k8s1": kubectl taint nodes k8s1 node-role.kubernetes.io/control-plane:NoSchedule- kubectl taint nodes k8s1 node-role.kubernetes.io/master:NoSchedule- ====== Volúmenes ====== TODO ===== Persistentes ===== ==== kadalu (usar este) ==== [[https://kadalu.io/docs/k8s-storage/devel/quick-start|Fuente]] 1. Instalar cliente glusterfs. **TODO**: igual solo con el cliente mejor, pero así funciona sudo apt install -y glusterfs-server 2. Instalar kadalu curl -fsSL https://github.com/kadalu/kadalu/releases/latest/download/install.sh | sudo bash -x Comprobar: kubectl kadalu version Salida esperada similar a: kubectl-kadalu plugin: 0.8.15 Listar pods: kubectl get pods -n kadalu Salida esperada similar a: NAME READY STATUS RESTARTS AGE kadalu-csi-nodeplugin-h8nm8 3/3 Running 0 74s kadalu-csi-nodeplugin-kfp5c 3/3 Running 0 74s kadalu-csi-nodeplugin-sfgn9 3/3 Running 0 74s kadalu-csi-provisioner-0 5/5 Running 0 63s operator-57b47b555f-h6gdm 1/1 Running 0 75s 3. (En cada uno de los nodos de kubernetes) Crear directorio donde se almacenará el cluster de glusterfs sudo mkdir -p /opt/kadalu/brick1/gv1 4. Crear el volumen del cluster de glusterfs kubectl kadalu storage-add \ storage-pool1 \ --verbose \ --type=Disperse \ --data 2 \ --redundancy 1 \ --path k8s1:/opt/kadalu/brick1/gv1 \ --path k8s2:/opt/kadalu/brick1/gv1 \ --path k8s3:/opt/kadalu/brick1/gv1 Salida esperada similar a: The following nodes are available: k8s1, k8s2, k8s3 Storage Yaml file for your reference: apiVersion: "kadalu-operator.storage/v1alpha1" kind: "KadaluStorage" metadata: name: "storage-pool1" spec: type: "Disperse" storage: - node: "k8s1" path: "/opt/kadalu/brick1/gv1" - node: "k8s2" path: "/opt/kadalu/brick1/gv1" - node: "k8s3" path: "/opt/kadalu/brick1/gv1" disperse: data: 2 redundancy: 1 Is this correct?(Yes/No): Teclear "Yes" y pulsar "Enter" Salida esperada similar a: Storage add request sent successfully kadalustorage.kadalu-operator.storage/storage-pool1 created Listar pods: kubectl get pods -n kadalu -o wide Salida esperada similar a: NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kadalu-csi-nodeplugin-h8nm8 3/3 Running 0 15m 10.244.2.13 k8s2 kadalu-csi-nodeplugin-kfp5c 3/3 Running 0 15m 10.244.0.26 k8s1 kadalu-csi-nodeplugin-sfgn9 3/3 Running 0 15m 10.244.1.6 k8s3 kadalu-csi-provisioner-0 5/5 Running 0 15m 10.244.0.25 k8s1 operator-57b47b555f-h6gdm 1/1 Running 0 15m 10.244.0.24 k8s1 server-storage-pool1-0-0 1/1 Running 0 83s 10.244.0.27 k8s1 server-storage-pool1-1-0 1/1 Running 0 82s 10.244.2.14 k8s2 server-storage-pool1-2-0 1/1 Running 0 82s 10.244.1.7 k8s3 5. Repetir para cada pod: 5.1. Crear PVC (Persistent Volume Claim): **IMPORTANTE**: la documentación dice que "storageClassName:" debe ser "kadalu.disperse", pero no, tiene que ser el nombre del volumen de glusterfs creado anteriormente. cat < Un nuevo volumen persistente se ha creado: kubectl get persistentvolume Salida esperada similar a: NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-5df9ed7d-e77d-4910-b73b-a6145efb9c96 1Gi RWX Delete Bound default/persistent-volume-claim-1 kadalu.storage-pool1 52s Una nueva reclamación de volumen persistente se ha creado: kubectl get persistentvolumeclaims Salida esperada similar a: NAME STATUS VOLUME CAPACITY ACCE SS MODES STORAGECLASS AGE persistent-volume-claim-1 Bound pvc-5df9ed7d-e77d-4910-b73b-a6145efb9c96 1Gi RWX kadalu.storage-pool1 22s 5.2. Crear el pod referenciando la reclamación de volumen persistente: cat < ==== Usando glusterfs externo ==== Requisitos: * Un [[informatica:linux:kubernetes#cluster_externo_glusterfs|Cluster glusterfs]] funcionando. En este ejemplo los 3 nodos serán a la vez: * Control Plane * Workers * Nodos del cluster glusterfs [[https://github.com/kubernetes/examples/tree/master/volumes/glusterfs|Fuente]] 1. Crear endpoints * "ip" es la IP de cada nodo. En nuestro caso es el mismo que los nodos de kubernetes * "port" un número arbitrario, "1" es suficiente cat < Comprobar: kubectl get endpoints Resultado esperado similar a: NAME ENDPOINTS AGE glusterfs-cluster 10.0.0.2:1,10.0.0.3:1,10.0.0.4:1 22s 2. Crear servicio cat < Comprobar: kubectl get services Resultado esperado similar a: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE glusterfs-cluster ClusterIP 10.105.75.133 1/TCP 51s 3. Crear pod * Campo "path" debe ser el nombre de un volumen de glusterfs **previamente** creado cat < Comprobar (puede tardar 1 minuto): kubectl get pods Resultado esperado similar a: NAME READY STATUS RESTARTS AGE glusterfs 1/1 Running 0 13s **Resumen**: * El pod "glusterfs" tiene montado localmente un directorio, "/usr/share/nginx/html" * En este directorio puede escribir * Lo que escriba se escribirá en el volumen "gv1" del cluster glusterfs, que a su vez se escribirá en el brick "/opt/brick1/gv1" de cada uno de los nodos del cluster de glusterfs * Se pueden montar más pods, o este mismo si se tiene que recrear (porque se arrancla con "replica 1" o similar), que escriban en ese volumen de glusterfs **TODO**: * Nótese que la provisión de volúmenes de glusterfs NO es dinámica. Primero se crea manualmente el volumen de glusterfs y luego se referencia, manualmente, en el pod de kubernetes * Parece ser que para que se pueda provisionar dinámicamente parece ser que se requiere Heketi, que es un proyecto que está de capa caída === Cluster externo Glusterfs === Vamos a instalar un cluster de Glusterfs "externo", o como servicio, en lugar de montarlo como daemonset, que parece que requiere de un servicio (heketi) que no parece tener mucho futuro. 1. (Cada nodo) Editar: sudo vim /etc/hosts Y añadir: 10.0.0.2 k8s1 10.0.0.3 k8s2 10.0.0.4 k8s3 2. (Cada nodo) Instalar: sudo apt install -y glusterfs-server sudo systemctl enable glusterd --now sudo service glusterd status q 3. (Desde un nodo, en este caso k8s1) Ejecutar: sudo gluster peer probe k8s2 sudo gluster peer probe k8s3 4. (Desde otro nodo, en este caso k8s3) Ejecutar: sudo gluster peer probe k8s1 5. (Desde cualquier nodo) Ejecutar: sudo gluster peer status 6. Crear brick. Estas instrucciones se pueden ejecutar desde cualquier nodo del cluster 6.1. Ejecutar: sudo mkdir -p /opt/brick1/gv1 **WARNING**: vamos a usar "force" porque usaremos la misma partición de sistema Vamos a usar el modo disperse con redundancia 1, que significa que podemos perder un máximo de un nodo y todo seguirá funcionando. sudo gluster volume create gv1 disperse 3 redundancy 1 k8s{1..3}:/opt/brick1/gv1 force sudo gluster volume start gv1 sudo gluster volume info