Completa Kubernetes desde cero en Raspberry Pi





Más recientemente, una empresa conocida anunció que está transfiriendo su línea de computadoras portátiles a la arquitectura ARM. Al escuchar esta noticia, recordé: mientras volvía a mirar los precios de EC2 en AWS, noté Gravitons con un precio muy sabroso. El problema, por supuesto, fue que es ARM. Ni siquiera se me ocurrió entonces que ARM era bastante serio ...



Para mí, esta arquitectura siempre ha sido la gran cantidad de dispositivos móviles y otras cosas de IoT. Los servidores "reales" en ARM son de alguna manera inusuales, de alguna manera incluso salvajes ... Sin embargo, un nuevo pensamiento se me quedó en la cabeza, así que un fin de semana decidí comprobar qué podría lanzarse hoy en ARM. Y para esto decidí comenzar con uno cercano y querido: un clúster de Kubernetes. Y no sólo un "cluster" condicional, sino todo "a la manera adulta" para que sea lo más parecido posible a lo que estoy acostumbrado a ver en producción.



Según mi idea, el clúster debería ser accesible desde Internet, alguna aplicación web debería ejecutarse en él y debería haber al menos supervisión. Para implementar esta idea, necesitará un par (o más) Raspberry Pi modelo 3B + o superior. AWS también podría convertirse en una plataforma para experimentos, pero fueron las "frambuesas" las que me resultaron interesantes (que aún permanecían inactivas). Entonces, implementaremos un clúster de Kubernetes con Ingress, Prometheus y Grafana en ellos.



Preparación de "frambuesas"



Instalación del SO y SSH



No me molesté mucho con la elección del sistema operativo para la instalación: acabo de tomar la última Raspberry Pi OS Lite del sitio web oficial . La documentación de instalación también está disponible allí , todas las acciones de las cuales deben realizarse en todos los nodos del futuro clúster. A continuación, debe realizar las siguientes manipulaciones (también en todos los nodos).



Después de conectar el monitor y el teclado, primero debe configurar la red y SSH:



  1. Para que el clúster funcione, el maestro debe tener una dirección IP estática y los nodos trabajadores deben tener una dirección IP estática. Preferí direcciones estáticas en todas partes para facilitar la configuración.
  2. Se puede configurar una dirección estática en el sistema operativo ( /etc/dhcpcd.confhay un ejemplo adecuado en el archivo ) o fijando el arrendamiento en el servidor DHCP del enrutador usado (en mi caso, doméstico).
  3. ssh-server solo se incluye en raspi-config ( opciones de interfaz -> ssh ).


Después de eso, ya puede iniciar sesión a través de SSH (de forma predeterminada, el inicio de sesión es piy la contraseña es la raspberryque cambió) y continuar con la configuración.



Otros ajustes



  1. Establezcamos el nombre de host. En mi ejemplo, pi-controly se utilizará pi-worker.
  2. Comprobemos que el sistema de archivos se expande a todo el disco ( df -h /). Se puede ampliar si es necesario utilizando raspi-config.
  3. Cambie la contraseña de usuario predeterminada en raspi-config.
  4. Desactive el archivo de intercambio (este es el requisito de Kubernetes; si está interesado en los detalles sobre este tema, consulte el número 53533 ):



    dphys-swapfile swapoff
    systemctl disable dphys-swapfile
  5. Actualicemos los paquetes a las últimas versiones:



    apt-get update && apt-get dist-upgrade -y
  6. Instale Docker y paquetes adicionales:



    apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistent


    Durante la instalación, iptables-persistentdeberá guardar la configuración de iptables para ipv4 y /etc/iptables/rules.v4agregar las reglas a la cadena en el archivo FORWARD, así:



    # Generated by xtables-save v1.8.2 on Sun Jul 19 00:27:43 2020
    *filter
    :INPUT ACCEPT [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [0:0]
    -A FORWARD -s 10.1.0.0/16  -j ACCEPT
    -A FORWARD -d 10.1.0.0/16  -j ACCEPT
    COMMIT
  7. Solo queda reiniciar.


Ahora está listo para instalar su clúster de Kubernetes.



Instalación de Kubernetes



En esta etapa, pospuse deliberadamente todos mis desarrollos corporativos y los nuestros sobre la automatización de la instalación y configuración del clúster K8s. En su lugar, usaremos la documentación oficial de kubernetes.io (ligeramente aumentada con comentarios y abreviaturas).



Agrega el repositorio de Kubernetes:



curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
sudo apt-get update


Más adelante en la documentación se propone instalar CRI (interfaz de tiempo de ejecución del contenedor). Dado que Docker ya está instalado, continuemos e instalemos los componentes principales:



sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni


En el paso de instalar los componentes principales, agregué inmediatamente kubernetes-cnilo que se requiere para que el clúster funcione. Y aquí hay un punto importante: kubernetes-cnipor alguna razón, el paquete no crea un directorio predeterminado para la configuración de la interfaz CNI, así que tuve que crearlo manualmente:



mkdir -p /etc/cni/net.d


Para que funcione el backend de la red, que se analizará a continuación, debe instalar el complemento para CNI. Elegí el complemento portmap, que me resulta familiar y claro (consulte la documentación para obtener una lista completa ):



curl -sL https://github.com/containernetworking/plugins/releases/download/v0.7.5/cni-plugins-arm-v0.7.5.tgz | tar zxvf - -C /opt/cni/bin/ ./portmap


Configurar Kubernetes



Nodo del plano de control



Configurar el clúster en sí es bastante sencillo. Y para acelerar este proceso y verificar que las imágenes de Kubernetes estén disponibles, primero puede ejecutar:



kubeadm config images pull


Ahora llevamos a cabo la instalación en sí: inicializamos el plano de control del clúster:



kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certs


Tenga en cuenta que las subredes para servicios y pods no deben superponerse entre sí o con redes existentes.



Al final, se nos mostrará un mensaje indicando que todo está bien, y al mismo tiempo te dirán cómo adjuntar nodos de trabajo al plano de control:



Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
 mkdir -p $HOME/.kube
 sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
 sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
 https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of the control-plane node running the following command on each as root:
 kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
   --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050 \
   --contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
   --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050


Sigamos las recomendaciones para agregar una configuración para el usuario. Y al mismo tiempo, recomiendo agregar la finalización automática para kubectl de inmediato:



 kubectl completion bash > ~/.kube/completion.bash.inc
 printf "
 # Kubectl shell completion
 source '$HOME/.kube/completion.bash.inc'
 " >> $HOME/.bash_profile
 source $HOME/.bash_profile


En esta etapa, ya puede ver el primer nodo del clúster (aunque aún no está listo):



root@pi-control:~# kubectl get no
NAME         STATUS     ROLES    AGE   VERSION
pi-control   NotReady   master   29s   v1.18.6


Configuración de la red



Además, como se dijo en el mensaje después de la instalación, deberá instalar la red en el clúster. La documentación ofrece una opción de Calico, Cilium, contiv-vpp, Kube-router y Weave Net ... Aquí me desvié de las instrucciones oficiales y elegí una opción más familiar y comprensible para mí: franela en modo host-gw (para obtener más información sobre los backends disponibles, consulte la documentación proyecto ).



Instalarlo en un clúster es bastante simple. Primero, descargue los manifiestos:



wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml


Luego cambie el tipo de vxlana en la configuración host-gw:



sed -i 's/vxlan/host-gw/' kube-flannel.yml


... y la subred del pod, desde el valor predeterminado hasta el especificado durante la inicialización del clúster:



sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.yml


Después de eso, creamos recursos:



kubectl create -f kube-flannel.yml


¡Hecho! Después de un tiempo, el primer nodo K8s pasará al estado Ready:



NAME         STATUS   ROLES    AGE   VERSION
pi-control   Ready    master   2m    v1.18.6


Agregar un nodo trabajador



Ahora puede agregar un trabajador. Para hacer esto, en él, después de instalar Kubernetes según el escenario descrito anteriormente, solo necesita ejecutar el comando recibido anteriormente:



kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
    --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050


Sobre esto podemos asumir que el clúster está listo:



root@pi-control:~# kubectl get no
NAME         STATUS   ROLES    AGE    VERSION
pi-control   Ready    master   28m    v1.18.6
pi-worker    Ready    <none>   2m8s   v1.18.6


Solo tenía dos Raspberry Pi a mano, así que no quería dar uno de ellos solo bajo el plano de control. Así que eliminé la corrupción instalada automáticamente del nodo pi-control ejecutando:



root@pi-control:~# kubectl edit node pi-control


... y quitando las líneas:



 - effect: NoSchedule
   key: node-role.kubernetes.io/master


Llenar el clúster con el mínimo requerido



Primero que nada, necesitamos a Helm . Por supuesto, puede hacer todo sin él, pero Helm le permite personalizar algunos componentes a su discreción, literalmente, sin editar archivos. Y de hecho es solo un archivo binario que "no pide pan".



Entonces, vaya a helm.sh en la sección de documentación / instalación y ejecute el comando desde allí:



curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash


Después de eso, agregue el repositorio de gráficos:



helm repo add stable https://kubernetes-charts.storage.googleapis.com/


Ahora instalemos los componentes de la infraestructura de acuerdo con la idea:



  • Controlador de entrada;
  • Prometeo;
  • Grafana;
  • cert-manager.


Controlador de ingreso



El primer componente, el controlador Ingress , es fácil de instalar y está listo para usar de inmediato. Para hacer esto, simplemente vaya a la sección completa en el sitio y ejecute el comando de instalación desde allí:



kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yaml


Sin embargo, en este momento, la "frambuesa" comenzó a tensarse y a ejecutarse en IOPS de disco. El hecho es que junto con el controlador Ingress, se instalan una gran cantidad de recursos, se realizan muchas solicitudes de API y, en consecuencia, se escriben muchos datos en etcd. En general, una tarjeta de memoria de clase 10 no es muy productiva o una tarjeta SD básicamente no es suficiente para tal carga. Sin embargo, después de 5 minutos todo empezó.



Se creó un espacio de nombres y apareció un controlador y todo lo que necesita:



root@pi-control:~# kubectl -n ingress-nginx get pod
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-2hwdx        0/1     Completed   0          31s
ingress-nginx-admission-patch-cp55c         0/1     Completed   0          31s
ingress-nginx-controller-7fd7d8df56-68qp5   1/1     Running     0          48s


Prometeo



Los siguientes dos componentes son bastante fáciles de instalar a través de Helm desde el repositorio de gráficos.



Busque Prometheus , cree un espacio de nombres e instálelo en él:



helm search repo stable | grep prometheus
kubectl create ns monitoring
helm install prometheus --namespace monitoring stable/prometheus --set server.ingress.enabled=True --set server.ingress.hosts={"prometheus.home.pi"}


Por defecto, Prometheus pide 2 discos: para los datos del propio Prometheus y para los datos del AlertManager. Dado que no se ha creado ninguna clase de almacenamiento en el clúster, los discos no se ordenarán y los pods no se iniciarán. Para las instalaciones de Kubernetes bare metal, generalmente usamos Ceph rbd, pero en el caso de Raspberry Pi, esto es excesivo.



Así que creemos un almacenamiento local simple en la ruta de host. Los manifiestos de PV (volumen persistente) para prometheus-server y prometheus-alertmanager se combinan en un archivo prometheus-pv.yamlen el repositorio de Git con ejemplos para el artículo . El directorio para PV debe crearse previamente en el disco del nodo al que queremos vincular Prometheus: en el ejemplo nodeAffinity, se especifica el nombre de host pi-workery los directorios /data/localstorage/prometheus-servery se crean en él /data/localstorage/prometheus-alertmanager.



Descarga (clona) el manifiesto y agrégalo a Kubernetes:



kubectl create -f prometheus-pv.yaml


En esta etapa, me encontré por primera vez con el problema de la arquitectura ARM. Kube-state-metrics, que se establece de forma predeterminada en el gráfico de Prometheus, se negó a comenzar. Lanzaba un error:



root@pi-control:~# kubectl -n monitoring logs prometheus-kube-state-metrics-c65b87574-l66d8
standard_init_linux.go:207: exec user process caused "exec format error"


El hecho es que para kube-state-metrics, se usa la imagen del proyecto CoreOS, que no está compilada para ARM:



kubectl -n monitoring get deployments.apps prometheus-kube-state-metrics -o=jsonpath={.spec.template.spec.containers[].image}
quay.io/coreos/kube-state-metrics:v1.9.7


Tuve que buscar en Google un poco y encontrar, por ejemplo, esta imagen . Para aprovecharlo, actualice la versión, especificando qué imagen usar para kube-state-metrics:



helm upgrade prometheus --namespace monitoring stable/prometheus --set server.ingress.enabled=True --set server.ingress.hosts={"prometheus.home.pi"} --set kube-state-metrics.image.repository=carlosedp/kube-state-metrics --set kube-state-metrics.image.tag=v1.9.6


Comprobamos que todo ha comenzado:



root@pi-control:~# kubectl -n monitoring get po
NAME                                             READY   STATUS              RESTARTS   AGE
prometheus-alertmanager-df65d99d4-6d27g          2/2     Running             0          5m56s
prometheus-kube-state-metrics-5dc5fd89c6-ztmqr   1/1     Running             0          5m56s
prometheus-node-exporter-49zll                   1/1     Running             0          5m51s
prometheus-node-exporter-vwl44                   1/1     Running             0          4m20s
prometheus-pushgateway-c547cfc87-k28qx           1/1     Running             0          5m56s
prometheus-server-85666fd794-z9qnc               2/2     Running             0          4m52s


Grafana y cert-manager



Para gráficos y paneles, instale Grafana :



helm install grafana --namespace monitoring stable/grafana  --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}


Al final de la salida, se nos mostrará cómo obtener la contraseña de acceso:



kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo


Para solicitar certificados, instale cert-manager . Para instalarlo, consulte la documentación que ofrece los comandos adecuados para Helm:



helm repo add jetstack https://charts.jetstack.io

helm install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --version v0.16.0 \
  --set installCRDs=true


Para certificados autofirmados en uso doméstico, esto es suficiente. Si necesita recibir el mismo Let's Encrypt , debe configurar otro emisor de clúster. Puede encontrar más detalles en nuestro artículo " Certificados SSL de Let's Encrypt con cert-manager en Kubernetes ".



Yo mismo me decidí por la versión del ejemplo en la documentación , decidiendo que la versión provisional de LE sería suficiente. Cambie el correo electrónico en el ejemplo, guárdelo en un archivo y agréguelo al clúster ( cert-manager-cluster-issuer.yaml ):



kubectl create -f cert-manager-cluster-issuer.yaml


Ahora puede solicitar un certificado, por ejemplo, para Grafana. Esto requerirá un dominio y acceso externo al clúster. Tengo un dominio y configuré el tráfico reenviando los puertos 80 y 443 en mi enrutador doméstico de acuerdo con el servicio de controlador de ingreso creado:



kubectl -n ingress-nginx get svc
NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.2.206.61    <none>        80:31303/TCP,443:30498/TCP   23d


El puerto 80 en este caso se traduce a 31303 y 443 a 30498. (Los puertos se generan aleatoriamente, por lo que tendrá otros diferentes).



Aquí hay un certificado de ejemplo ( cert-manager-grafana-certificate.yaml ):



apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
  name: grafana
  namespace: monitoring
spec:
  dnsNames:
    - grafana.home.pi
  secretName: grafana-tls
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-staging


Agréguelo al clúster:



kubectl create -f cert-manager-grafana-certificate.yaml


Después de eso, aparecerá el recurso Ingress, a través del cual se validará Let's Encrypt:



root@pi-control:~# kubectl -n monitoring get ing
NAME                        CLASS    HOSTS                        ADDRESS         PORTS   AGE
cm-acme-http-solver-rkf8l   <none>   grafana.home.pi      192.168.88.31   80      72s
grafana                     <none>   grafana.home.pi      192.168.88.31   80      6d17h
prometheus-server           <none>   prometheus.home.pi   192.168.88.31   80      8d


Después de que pase la validación, veremos que el recurso está certificatelisto y el secreto anterior contiene el grafana-tlscertificado y la clave. Puede comprobar inmediatamente quién emitió el certificado:



root@pi-control:~# kubectl -n monitoring get certificate
NAME      READY   SECRET        AGE
grafana   True    grafana-tls   13m

root@pi-control:~# kubectl -n monitoring get secrets grafana-tls -ojsonpath="{.data['tls\.crt']}" | base64 -d | openssl x509 -issuer -noout
issuer=CN = Fake LE Intermediate X1


Volvamos a Grafana. Necesitamos arreglar un poco su versión de Helm, cambiando la configuración de TLS de acuerdo con el certificado generado.



Para hacer esto, descargue el gráfico, edite y actualice desde el directorio local:



helm pull --untar stable/grafana


Edite los grafana/values.yaml parámetros de TLS en el archivo :



  tls:
    - secretName: grafana-tls
      hosts:
        - grafana.home.pi


Aquí puede configurar inmediatamente el Prometheus instalado como datasource:



datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server:80
      access: proxy
      isDefault: true


Ahora actualice el gráfico de Grafana desde el directorio local:



helm upgrade grafana --namespace monitoring ./grafana  --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}


Comprobamos que grafanase ha añadido el puerto 443 a Ingress y que hay acceso vía HTTPS:



root@pi-control:~# kubectl -n monitoring get ing grafana
NAME      CLASS    HOSTS                     ADDRESS         PORTS     AGE
grafana   <none>   grafana.home.pi           192.168.88.31   80, 443   63m

root@pi-control:~# curl -kI https://grafana.home.pi
HTTP/2 302
server: nginx/1.19.1
date: Tue, 28 Jul 2020 19:01:31 GMT
content-type: text/html; charset=utf-8
cache-control: no-cache
expires: -1
location: /login
pragma: no-cache
set-cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax
x-frame-options: deny
strict-transport-security: max-age=15724800; includeSubDomains


Para demostrar Grafana en acción, puede descargar y agregar un panel para kube-state-metrics . Así es como se ve:







También recomiendo agregar un tablero para el exportador de nodos: mostrará en detalle lo que sucede con las "frambuesas" (carga de CPU, memoria, red, uso del disco, etc.).



Después de eso, creo que el clúster está listo para recibir y ejecutar aplicaciones.



Nota de montaje



Hay al menos dos opciones para crear aplicaciones para la arquitectura ARM. Primero, puede construir sobre un dispositivo ARM. Sin embargo, después de mirar la disposición actual de las dos Raspberry Pi, me di cuenta de que tampoco sobrevivirían al ensamblaje. Por lo tanto, me pedí una nueva Raspberry Pi 4 (es más potente y tiene 4 GB de memoria); planeo construirla en ella.



La segunda opción es crear una imagen de Docker de arquitectura múltiple en una máquina más potente. Hay una extensión de docker buildx para eso . Si la aplicación está en un lenguaje compilado, entonces se requiere la compilación cruzada para ARM. No describiré todas las configuraciones para esta ruta. esto conducirá a un artículo separado. Al implementar este enfoque, puede lograr imágenes "universales": Docker que se ejecuta en una máquina ARM cargará automáticamente la imagen correspondiente a la arquitectura.



Conclusión



El experimento realizado superó todas mis expectativas: [al menos] Kubernetes "vainilla" con la base necesaria se siente bien en ARM, y con su configuración, solo surgieron un par de matices.



Las Raspberry Pi 3B + mantienen ocupada la CPU, pero sus tarjetas SD son un claro cuello de botella. Los colegas sugirieron que en algunas versiones es posible arrancar desde USB, donde puede conectar un SSD: entonces la situación probablemente mejorará.



Aquí hay un ejemplo de carga de CPU al instalar Grafana:







Para experimentos y "para probar", en mi opinión, el clúster de Kubernetes en "frambuesas" transmite mucho mejor las sensaciones de funcionamiento que el mismo Minikube, porque todos los componentes del clúster están instalados y funcionan "De una manera adulta".



En el futuro, existe la idea de agregar al clúster todo el ciclo de CI / CD, implementado completamente en la Raspberry Pi. Y también me alegrará que alguien comparta su experiencia sobre la configuración de K8 en AWS Gravitons.



PD: Sí, la "producción" puede estar más cerca de lo que pensaba:







PPS



Lea también en nuestro blog:






All Articles