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
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:
2. Instalar un runtime
IMPORTANTE: leer la siguientes consideraciones
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 <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF
sudo modprobe overlay sudo modprobe br_netfilter
# sysctl params required by setup, params persist across reboots
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF
# 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
Entiendo que NO es necesario porque desde la versión 1.22 configurará por defecto systemd
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.
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:
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:
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:
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
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 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:
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:
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:
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
Una vez se ha iniciado el cluster en el primer control plane hay que añadir nodos al cluster:
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 primer control plane con alta disponibilidad con las siguientes modificaciones:
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.
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.
1. Conectarse al worker:
ssh k8s3
2. Ejecutar el comando obtenido en el paso 3.2. de primer control plane sin alta disponibilidad con las siguientes modificaciones:
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 <none> 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.
1. Conectarse al worker:
ssh k8s5
2. Ejecutar el comando obtenido en el paso 3.2. de primer control plane con alta disponibilidad con las siguientes modificaciones:
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 <none> 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.
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 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 |
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:
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:
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
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: 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).
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 <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: deployment-whoami labels: app: deployment-whoami spec: replicas: 1 selector: matchLabels: app: deployment-whoami template: metadata: labels: app: deployment-whoami spec: containers: - name: whoami image: containous/whoami ports: - containerPort: 80 EOF
3. Comprobar
kubectl get deployments
Resultado esperado similar a:
NAME READY UP-TO-DATE AVAILABLE AGE deployment-whoami 1/1 1 1 40h
Un Service expone un Deployment de forma que se pueda acceder al 'Deployment' desde fuera del cluster de Kubernetes.
Requisitos:
1. Conectarse al master
ssh master
2. Ejecutar:
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: service-whoami spec: selector: app: deployment-whoami ports: - protocol: TCP port: 80 EOF
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 <none> 80/TCP 42h
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:
Requisitos:
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:
Ejecutar:
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.95.68-192.168.95.69 EOF
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 <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: test-service-canberemoved annotations: metallb.universe.tf/address-pool: default spec: ports: - port: 80 targetPort: 80 selector: app: nonexistantdeployment type: LoadBalancer EOF
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
Requisitos:
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:
El valor de 'X-Forwarded-For' ha cambiado:
Cert-manager es un addon que sirve para generar certificados SSL Letsencrypt.
Para instalarlo:
1. Conectarse al master
ssh master
2. Instalar los manifiestos de la 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:
Ejecutar:
cat <<EOF > 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
Se trata de un custom resource creado durante el proceso de instalación del Addon llamado cert-manager.
Requisitos:
Para instalarlo:
1. Conectarse al control plane
ssh k8s2
2. Ejecutar:
Ajustando:
cat <<EOF | kubectl apply -f - apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-production namespace: default spec: acme: # Staging #server: https://acme-staging-v02.api.letsencrypt.org/directory # Production server: https://acme-v02.api.letsencrypt.org/directory # Email address used for ACME registration email: letsencrypt@example.com # Name of a secret used to store the ACME account private key privateKeySecretRef: name: letsencrypt-production # Enable the HTTP-01 challenge provider solvers: # An empty 'selector' means that this solver matches all domains - selector: {} http01: ingress: class: nginx EOF
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.
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:
Requisitos:
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:
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:
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.
Requisitos:
Creamos dos listeners, todo ello capa 4:
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 aquí, sino a la “EXTERNAL-IP” (en nuestro ejemplo una IP privada, la '192.168.95.68') de un servicio de tipo 'LoadBalancer', 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 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:
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 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 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
Requisitos:
1. Instalar los dos servidores con NodePort
2. Instalar y configurar en ambos servidores Keepalived
Según su propia definición:
An API object that manages external access to the services in a cluster, typically HTTP.
Con este procedimiento podremos obtener la IP de origen en los pods, con las otras aproximaciones todavía no lo he conseguido.
Características:
1. Conectarse al master
ssh k8s2
2. Obtener el 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 <none> <none> ingress-nginx-controller-55bc4f5576-f27v5 1/1 Running 0 45h 10.244.3.3 k8s5 <none> <none> ingress-nginx-controller-55bc4f5576-v7f6h 1/1 Running 0 45h 10.244.4.3 k8s6 <none> <none> ingress-nginx-controller-6f7c7fb6f8-xvzcs 0/1 Pending 0 45h <none> <none> <none> <none>
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 <none> 46h v1.21.1 k8s6 Ready <none> 46h v1.21.1 k8s7 Ready <none> 45h v1.21.1
Tenemos pues tres workers:
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 <none> 80:31104/TCP,443:30893/TCP 55m
Por lo tanto en este ejemplo:
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.
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 <none> <none> ingress-nginx-admission-patch-c9t4w 0/1 Completed 1 139m 10.244.0.74 k8s1 <none> <none> ingress-nginx-controller-6thbj 1/1 Running 0 2m8s 10.244.0.77 k8s1 <none> <none> ingress-nginx-controller-tv9zd 1/1 Running 0 63s 10.244.1.16 k8s3 <none> <none> ingress-nginx-controller-zgfw5 1/1 Running 0 95s 10.244.2.21 k8s2 <none> <none>
En este ejemplo asumimos que tenemos un LoadBalancer habilitado, porque hemos instalado 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 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).
Este ejemplo requiere:
1. Conectarse al master
ssh master
2. Ejecutar:
cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx name: ingress-whoami spec: rules: - host: example.com http: paths: - backend: service: name: service-whoami port: number: 80 path: / pathType: Prefix EOF
3. Verificar
kubectl get ingress --field-selector metadata.name=ingress-whoami
Resultado esperado similar a:
NAME CLASS HOSTS ADDRESS PORTS AGE ingress-whoami <none> k8s.kedu.coop 192.168.95.73 80, 443 42h
Con esta configuración:
Este ejemplo requiere:
1. Conectarse al master:
ssh master
2. Ejecutar:
cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: # Should match 'Issuer.metadata.name' cert-manager.io/issuer: letsencrypt-production kubernetes.io/ingress.class: nginx name: ingress-whoami spec: rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: service-whoami port: number: 80 tls: - hosts: - example.com secretName: quickstart-example-tls EOF
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: <none> 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:
Debería aparecer el contenido servido por el deployment 'deployment-whoami' con un certificado SSL válido.
Requisitos:
cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: # https://awkwardferny.medium.com/enabling-modsecurity-in-the-kubernetes-ingress-nginx-controller-111f9c877998 nginx.ingress.kubernetes.io/enable-modsecurity: "true" nginx.ingress.kubernetes.io/modsecurity-snippet: | SecRuleEngine On SecRequestBodyAccess On SecAuditEngine RelevantOnly SecAuditLogParts ABIJDEFHZ SecAuditLog /var/log/modsec_audit.log SecRule REQUEST_HEADERS:User-Agent \"fern-scanner\" \"log,deny,id:107,status:403,msg:\'Fern Scanner Identified\'\" name: ingress-echoserver spec: rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: service-echoserver port: number: 80 EOF
Probar:
curl http://example.com -k -H "user-agent: fern-scanner"
Respuesta esperada: 403
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 7194
En este ejemplo:
1. Identificar el ConfigMap. En nuestro caso queremos modificar 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 ingress-nginx-controller .
2. Editamos el 'ConfigMap':
kubectl edit configmaps -n ingress-nginx ingress-nginx-controller
Vamos a seguir estas instrucciones, por lo que le vamos a añadir una sección 'data' que incluya 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 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.
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 <none> 4d18h v1.21.1 k8s6 Ready <none> 4d17h v1.21.1 k8s7 Ready <none> 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:
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 <none> 4d18h v1.21.1 k8s6 Ready <none> 4d18h v1.21.1 k8s7 Ready,SchedulingDisabled <none> 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 <none> <none> kube-system kube-proxy-cghwq 1/1 Running 0 4d17h 192.168.95.76 k8s7 <none> <none>
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 <none> 4d18h v1.21.1 k8s6 Ready <none> 4d18h v1.21.1
Por defecto un pod saltará a otro worker tras 5 minutos de que el worker esté marcado como “NotReady”.
AVISO: el parámetro pod-eviction-timeout parece ser que no funciona. Ver #74651 y #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 <none> <none>
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 <none> 5d2h v1.21.1 k8s6 Ready <none> 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 <none> <none> deployment-whoami-6b6dc7c84-mrjjw 1/1 Running 0 19s 10.244.4.31 k 8s6 <none> <none>
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
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 <none> 443/TCP 4m kube-system kubernetes-dashboard 10.0.0.66 <nodes> 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
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 <unknown> / 1% 2 5 2 6m
Con mas detalle:
kubectl describe hpa apache
Name: apache Namespace: default Labels: <none> Annotations: <none> CreationTimestamp: Fri, 23 Jun 2017 17:51:17 +0200 Reference: Deployment/apache Metrics: ( current / target ) resource cpu on pods (as a percentage of request): <unknown> / 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
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"
https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/
Installing kubelet and kubeadm
cat <<EOF > /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
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 <<EOF >/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
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: <none> Annotations: <none> Selector: app=httpd Type: LoadBalancer IP: 10.96.97.216 Port: <unset> 8080/TCP NodePort: <unset> 32249/TCP Endpoints: 10.32.0.22:80,10.32.0.23:80 Session Affinity: None Events: <none>
Si accedemos a http://10.96.97.216:8080 nos balancea entre los dos PODS que tenemos desplegados de apache
Sitios para instalar en cloud:
https://kubernetes.io/docs/setup/pick-right-solution/#hosted-solutions
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
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
En este ejemplo:
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. 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. 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 balanceador de kube-apiserver y unir los tres control plane al mismo.
1.4. Iniciar el cluster en el primer control plane
1.5. Unir al cluster el resto de control plane (k8s3.local y k8s4.local).
1.6. 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 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 Cert-manager addon
2.3. Crear issuer para letsencrypt en producción
2.4. Crear un deployment que despliegue el contenedor “whoami”
2.5. Exponer el anterior deployment a través de un service.
2.6. Crear un 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 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 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
Resultado esperado:
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
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-
TODO
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 <none> <none> kadalu-csi-nodeplugin-kfp5c 3/3 Running 0 15m 10.244.0.26 k8s1 <none> <none> kadalu-csi-nodeplugin-sfgn9 3/3 Running 0 15m 10.244.1.6 k8s3 <none> <none> kadalu-csi-provisioner-0 5/5 Running 0 15m 10.244.0.25 k8s1 <none> <none> operator-57b47b555f-h6gdm 1/1 Running 0 15m 10.244.0.24 k8s1 <none> <none> server-storage-pool1-0-0 1/1 Running 0 83s 10.244.0.27 k8s1 <none> <none> server-storage-pool1-1-0 1/1 Running 0 82s 10.244.2.14 k8s2 <none> <none> server-storage-pool1-2-0 1/1 Running 0 82s 10.244.1.7 k8s3 <none> <none>
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 <<EOF | kubectl apply -f - kind: PersistentVolumeClaim apiVersion: v1 metadata: name: persistent-volume-claim-1 spec: storageClassName: kadalu.storage-pool1 accessModes: - ReadWriteMany resources: requests: storage: 1Gi EOF
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 <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: nginx-test spec: containers: - name: nginx-test image: nginx volumeMounts: - mountPath: "/usr/share/nginx/html" name: nginx-test-volume volumes: - name: nginx-test-volume persistentVolumeClaim: claimName: persistent-volume-claim-1 EOF
Requisitos:
1. Crear endpoints
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Endpoints metadata: name: glusterfs-cluster subsets: - addresses: - ip: 10.0.0.2 ports: - port: 1 - addresses: - ip: 10.0.0.3 ports: - port: 1 - addresses: - ip: 10.0.0.4 ports: - port: 1 EOF
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 <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: glusterfs-cluster spec: ports: - port: 1 EOF
Comprobar:
kubectl get services
Resultado esperado similar a:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE glusterfs-cluster ClusterIP 10.105.75.133 <none> 1/TCP 51s
3. Crear pod
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: glusterfs spec: containers: - name: glusterfs image: nginx volumeMounts: - mountPath: "/usr/share/nginx/html" name: glusterfsvol volumes: - name: glusterfsvol glusterfs: endpoints: glusterfs-cluster path: gv1 readOnly: false EOF
Comprobar (puede tardar 1 minuto):
kubectl get pods
Resultado esperado similar a:
NAME READY STATUS RESTARTS AGE glusterfs 1/1 Running 0 13s
Resumen:
TODO:
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