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:
- 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.
- Se puede configurar una dirección estática en el sistema operativo (
/etc/dhcpcd.conf
hay un ejemplo adecuado en el archivo ) o fijando el arrendamiento en el servidor DHCP del enrutador usado (en mi caso, doméstico). - 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
pi
y la contraseña es la raspberry
que cambió) y continuar con la configuración.
Otros ajustes
- Establezcamos el nombre de host. En mi ejemplo,
pi-control
y se utilizarápi-worker
. - Comprobemos que el sistema de archivos se expande a todo el disco (
df -h /
). Se puede ampliar si es necesario utilizando raspi-config. - Cambie la contraseña de usuario predeterminada en raspi-config.
- 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
- Actualicemos los paquetes a las últimas versiones:
apt-get update && apt-get dist-upgrade -y
- 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-persistent
deberá guardar la configuración de iptables para ipv4 y/etc/iptables/rules.v4
agregar las reglas a la cadena en el archivoFORWARD
, 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
- 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-cni
lo que se requiere para que el clúster funcione. Y aquí hay un punto importante: kubernetes-cni
por 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
vxlan
a 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.yaml
en 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-worker
y los directorios /data/localstorage/prometheus-server
y 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á
certificate
listo y el secreto anterior contiene el grafana-tls
certificado 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
grafana
se 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: