
Recientemente, una conocida empresa anunció que trasladará su línea de laptops a la arquitectura ARM. Al escuchar esta noticia, recordé que al revisar los precios de EC2 en AWS, noté los Graviton con un precio muy atractivo. La trampa, por supuesto, era que era ARM. En ese momento, no se me pasó por la cabeza que ARM es algo bastante serio...
Para mí, esta arquitectura siempre había sido territorio de dispositivos móviles y otros gadgets de IoT. Los "servidores reales" en ARM son un poco inusuales, incluso un poco extraños... Sin embargo, esta nueva idea se me quedó en la cabeza, así que en uno de los fines de semana decidí comprobar qué se puede ejecutar hoy en ARM. Y para ello decidí comenzar con algo cercano y familiar: un clúster de Kubernetes. No simplemente algún "clúster" circunstancial, sino uno "serio", para que fuera lo más similar posible a cómo estoy acostumbrado a verlo en producción.
En mi idea, el clúster debe ser accesible desde Internet, debe ejecutar alguna aplicación web y también debe tener al menos monitoreo. Para realizar esta idea, se necesitarán un par (o más) de Raspberry Pi al menos del modelo 3B+. El entorno para experimentos podría ser AWS, pero me interesaba especialmente la "frambuesa" (que de todos modos estaba sin uso). Entonces, desplegaremos en ellas un clúster de Kubernetes con Ingress, Prometheus y Grafana.
Preparación de las "frambuesas"
Instalación del SO y SSH
No me compliqué mucho con la elección del SO para la instalación: simplemente tomé la versión más reciente de Raspberry Pi OS Lite con . Allí también está disponible , todas las acciones que deben realizarse en todos los nodos del futuro clúster. A continuación, serán necesarias las siguientes manipulaciones (también en todos los nodos).
Conectando un monitor y teclado, es necesario configurar previamente la red y SSH:
- Para que el clúster funcione, el maestro debe tener una dirección IP estática, y en los nodos de trabajo, a consideración. Preferí direcciones estáticas en todas partes por razones de conveniencia en la configuración.
- La dirección estática se puede configurar en el SO (en el archivo
/etc/dhcpcd.confhay un ejemplo adecuado) o fijando el lease en el servidor DHCP del enrutador usado (en mi caso, doméstico). - El servidor SSH se activa simplemente en raspi-config (opciones de interfacing → ssh).
Después de esto, ya se puede iniciar sesión por SSH (por defecto, el nombre de usuario es pi, y la contraseña es raspberry o el que se cambió) y continuar con la configuración.
Otras configuraciones
- Establezcamos el nombre del host. En mi ejemplo se utilizarán
pi-controlypi-worker. - Verificaremos que el sistema de archivos está expandido a todo el disco (
df -h /). Si es necesario, se puede ampliar usando raspi-config. - Cambiaremos la contraseña del usuario por defecto en raspi-config.
- Desactivaremos el archivo swap (esto es un requisito de Kubernetes; si te interesa más sobre este tema, consulta ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - Actualizaremos los paquetes a las últimas versiones:
apt-get update && apt-get dist-upgrade -y - Instalaremos Docker y paquetes adicionales:
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentAl instalar
iptables-persistentserá necesario guardar la configuración de iptables para ipv4, y en el archivo/etc/iptables/rules.v4— añadir reglas a la cadenaFORWARD, de esta manera:# 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 todo está listo para la instalación del clúster de Kubernetes.
Instalación de Kubernetes
En esta etapa, dejé deliberadamente de lado todo mi trabajo previo y el de nuestra empresa para automatizar la instalación y configuración del clúster K8s. En su lugar, usaremos la documentación oficial de (ligeramente complementada con comentarios y abreviaciones).
Agregaremos 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 updateA continuación, la documentación sugiere instalar el CRI (interfaz de tiempo de ejecución de contenedores). Dado que Docker ya está instalado, avanzamos y instalamos los componentes principales:
sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni En la etapa de instalación de los componentes principales, añadí de inmediato kubernetes-cni, que es necesario para el funcionamiento del clúster. Y hay un punto importante: el paquete kubernetes-cni por alguna razón no crea el directorio por defecto para la configuración de interfaces CNI, así que tuve que crearlo manualmente:
mkdir -p /etc/cni/net.dPara el funcionamiento del backend de red, del que hablaremos más adelante, es necesario instalar un plugin para CNI. Elegí el plugin portmap, que me resulta familiar y comprensible. (puedes ver la lista completa en ):
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/ ./portmapConfiguración de Kubernetes
Nodo con el plano de control
La instalación del propio clúster se realiza de manera bastante sencilla. Y para acelerar este proceso y verificar que las imágenes de Kubernetes están disponibles, se puede ejecutar previamente:
kubeadm config images pullAhora realizamos 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-certsTenga en cuenta que las subredes para los servicios y los pod no deben cruzarse entre sí ni con las redes existentes.
Al final, se mostrará un mensaje indicando que todo está bien y además se sugerirá cómo unir nodos de trabajo al plano de control:
¡Su plano de control de Kubernetes se ha inicializado correctamente!
Para comenzar a usar su clúster, necesita ejecutar lo siguiente como un usuario normal:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Ahora debe implementar una red de pod en el clúster.
Ejecute "kubectl apply -f [podnetwork].yaml" con una de las opciones listadas en:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Ahora puede unir cualquier número de nodos del plano de control ejecutando el siguiente comando en cada uno como root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050
--contrl-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Tenga en cuenta que el certificate-key otorga acceso a datos sensibles del clúster, ¡manténgalo en secreto!
Como medida de seguridad, los certificados subidos se eliminarán en dos horas; si es necesario, puede usar
"kubeadm init phase upload-certs --upload-certs" para volver a cargar los certificados después.
Luego puede unir cualquier número de nodos de trabajo ejecutando lo siguiente en cada uno como root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Sigamos las recomendaciones para agregar la configuración para el usuario. Y también recomiendo agregar la autocompletación para kubectl desde ya:
kubectl completion bash > ~/.kube/completion.bash.inc
printf "
# Autocompletado en shell para Kubectl
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profileEn esta etapa, ya se puede ver el primer nodo en el clúster (aunque aún no está listo):
root@pi-control:~# kubectl get no
NOMBRE ESTATUS ROLES EDAD VERSIÓN
pi-control NotReady maestro 29s v1.18.6Configuración de red
A continuación, como se mencionó en el mensaje después de la instalación, será necesario instalar la red en el clúster. La documentación ofrece elegir entre Calico, Cilium, contiv-vpp, Kube-router y Weave Net… Aquí me desvié de la instrucción oficial y elegí una opción que me resulta más familiar y comprensible: en modo host-gw (más detalles sobre los backends disponibles en ).
Instalarlo en el clúster es bastante sencillo. Primero, descargamos los manifiestos:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Luego cambiamos en la configuración el tipo de vxlan en host-gw:
sed -i 's/vxlan/host-gw/' kube-flannel.yml… y la subred de los pod — del valor por defecto al que se especificó durante la inicialización del clúster:
sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.ymlDespués de eso, creamos los recursos:
kubectl create -f kube-flannel.yml ¡Listo! En un momento, el primer nodo K8s cambiará a estado Listo:
NOMBRE ESTADO ROLES EDAD VERSIÓN
pi-control Listo master 2m v1.18.6Agregar un nodo de trabajo
Ahora podemos añadir un worker. Para ello, en él — después de instalar Kubernetes según el guion descrito anteriormente — solo hay que ejecutar el comando obtenido anteriormente:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Con esto, podemos considerar que el clúster está listo:
root@pi-control:~# kubectl get no
NOMBRE ESTADO ROLES EDAD VERSIÓN
pi-control Listo master 28m v1.18.6
pi-worker Listo 2m8s v1.18.6Solo tenía dos Raspberry Pi a mano, así que no quería entregar una de ellas solo para el control plane. Por eso, quité el taint automáticamente instalado del nodo pi-control, ejecutando:
root@pi-control:~# kubectl edit node pi-control… y eliminé las siguientes líneas:
- effect: NoSchedule
key: node-role.kubernetes.io/masterLlenando el clúster con lo mínimo necesario
En primer lugar, necesitaremos Helm. Claro, se puede hacer todo sin él, pero Helm permite configurar algunos componentes a su gusto sin modificar archivos. Y de hecho, es simplemente un archivo binario que "no pide mucho".
Así que, entramos en en la sección docs/installation y ejecutamos el comando de allí:
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashDespués de esto, añadimos el repositorio de charts:
helm repo add stable https://kubernetes-charts.storage.googleapis.com/Ahora instalaremos los componentes de infraestructura de acuerdo con lo planeado:
- Controlador de Ingress;
- Prometheus;
- Grafana;
- cert-manager.
Controlador de Ingress
El primer componente — Controlador de Ingress — se instala bastante fácilmente y está listo para usar "de inmediato". Para ello, es suficiente con acceder a y ejecutar el comando de instalación de allí:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yamlSin embargo, en ese momento, la "frambuesa" comenzó a estresarse y a llegar al límite de IOPS del disco. La cuestión es que junto con el controlador de Ingress se instalan una gran cantidad de recursos, se realizan muchas solicitudes a la API y, por lo tanto, se escriben muchos datos en etcd. En resumen, o la tarjeta de memoria de clase 10 no es muy eficiente, o las tarjetas SD simplemente no son suficientes para tal carga. Sin embargo, después de unos 5 minutos, todo se puso en marcha.
Se creó un namespace y en él apareció el controlador y todo lo que necesitaba:
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 48sPrometheus
Los siguientes dos componentes son bastante fáciles de instalar a través de Helm desde el repositorio de charts.
Buscamos Prometheus, creamos el namespace y lo instalamos 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 solicita 2 discos: uno para los datos de Prometheus y otro para los datos de AlertManager. Como no se ha creado una clase de almacenamiento en el clúster, los discos no se solicitarán y los pod no se iniciarán. Para instalaciones bare metal de Kubernetes, generalmente utilizamos Ceph rbd, sin embargo, para Raspberry Pi esto es evidentemente excesivo.
Por lo tanto, crearemos un simple almacenamiento local en hostpath. Los manifiestos PV (volumen persistente) para prometheus-server y prometheus-alertmanager están combinados en el archivo prometheus-pv.yaml en . La carpeta para el PV debe ser creada de antemano en el disco del nodo al que queremos vincular Prometheus: en el ejemplo se especifica nodeAffinity por hostname pi-worker y se han creado directorios en él /data/localstorage/prometheus-server y /data/localstorage/prometheus-alertmanager.
Descargamos (clonamos) el manifiesto y lo añadimos a Kubernetes:
kubectl create -f prometheus-pv.yamlEn esta etapa me encontré por primera vez con el problema de la arquitectura ARM. Kube-state-metrics, que se instala por defecto en el chart de Prometheus, se negó a iniciarse. Mostraba el 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 problema es que para kube-state-metrics se utiliza una imagen del proyecto CoreOS, que no se compila 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.7Tuve que buscar un poco y encontrar, por ejemplo, . Para usarla, actualizaremos 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.6Verificamos que todo se haya iniciado:
root@pi-control:~# kubectl -n monitoring get po
NAME READY STATUS RESTARTS AGE
prometheus-alertmanager-df65d99d4-6d27g 2/2 En ejecución 0 5m56s
prometheus-kube-state-metrics-5dc5fd89c6-ztmqr 1/1 En ejecución 0 5m56s
prometheus-node-exporter-49zll 1/1 En ejecución 0 5m51s
prometheus-node-exporter-vwl44 1/1 En ejecución 0 4m20s
prometheus-pushgateway-c547cfc87-k28qx 1/1 En ejecución 0 5m56s
prometheus-server-85666fd794-z9qnc 2/2 En ejecución 0 4m52sGrafana y cert-manager
Para gráficos y dashboards instalamos Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}Al final de la salida nos mostrarán cómo obtener la contraseña de acceso:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoPara solicitar certificados, instalaremos cert-manager. Para su instalación, consultamos a , que ofrece los comandos correspondientes 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=truePara certificados autofirmados en uso doméstico, esto es suficiente. Sin embargo, si se necesita obtener el mismo Let’s Encrypt, es necesario configurar también el cluster issuer. Puede encontrar detalles sobre esto en nuestro artículo «».
Yo elegí la opción del , decidiendo que la opción de staging de LE sería suficiente. Alteramos el correo electrónico en el ejemplo, lo guardamos en un archivo y lo añadimos al clúster ():
kubectl create -f cert-manager-cluster-issuer.yamlAhora se puede solicitar un certificado, por ejemplo, para Grafana. Para ello se requiere un dominio y acceso externo al clúster. Tengo un dominio y configuré el tráfico a través de los puertos 80 y 443 en mi enrutador doméstico en consonancia con el servicio ingresado creado:
kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller NodePort 10.2.206.61 80:31303/TCP,443:30498/TCP 23dEl puerto 80 en este caso se mapea a 31303, y el 443 a 30498. (Los puertos se generan aleatoriamente, por lo que los suyos serán diferentes.)
Aquí hay un ejemplo de certificado ():
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-stagingLo añadimos al clúster:
kubectl create -f cert-manager-grafana-certificate.yamlDespués de eso, aparecerá el recurso Ingress, a través del cual se realizará la validación de Let’s Encrypt:
root@pi-control:~# kubectl -n monitoring get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
cm-acme-http-solver-rkf8l grafana.home.pi 192.168.88.31 80 72s
grafana grafana.home.pi 192.168.88.31 80 6d17h
prometheus-server prometheus.home.pi 192.168.88.31 80 8d Después de que la validación se complete, veremos que el recurso certificate está listo, y en el secreto mencionado anteriormente grafana-tls — certificado y clave. Se puede verificar 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 X1Volvamos a Grafana. Necesitaremos hacer algunos ajustes en su lanzamiento de Helm, modificando la configuración para TLS de acuerdo con el certificado creado.
Para ello, descargamos el chart, lo editamos y actualizamos desde un directorio local:
helm pull --untar stable/grafana Editamos en el archivo grafana/values.yaml los parámetros de TLS:
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi Aquí también podemos configurar Prometheus ya instalado como datasource:
datasources:
datasources.yaml:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus-server:80
access: proxy
isDefault: trueAhora actualizamos el chart de Grafana desde el directorio local:
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Verificamos que en el Ingress grafana se añadió el puerto 443 y hay acceso por HTTPS:
root@pi-control:~# kubectl -n monitoring get ing grafana
NAME CLASS HOSTS ADDRESS PORTS AGE
grafana 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=; Path=/; HttpOnly; SameSite=Lax
x-frame-options: deny
strict-transport-security: max-age=15724800; includeSubDomainsPara demostrar Grafana en acción, puedes descargar y agregar un . Así es como se ve:

También recomiendo agregar un dashboard para node exporter: mostrará detalladamente lo que está sucediendo con las "frambuesas" (carga de CPU, uso de memoria, red, disco, etc.).
Después de esto, considero que el clúster está listo para recibir y ejecutar aplicaciones.
Nota sobre la instalación
Para construir aplicaciones para la arquitectura ARM, hay al menos dos opciones. Primero, se puede compilar en un dispositivo ARM. Sin embargo, después de observar la actual utilización de dos Raspberry Pi, me di cuenta de que ni siquiera soportarían la compilación. Por eso, me pedí una nueva Raspberry Pi 4 (es más potente y tiene nada menos que 4 GB de memoria) — planeo compilar en ella.
La segunda opción es crear una imagen Docker multicardiaca en una máquina más potente. Para ello, existe . Si la aplicación está escrita en un lenguaje compilado, será necesaria la compilación cruzada para ARM. No describiré todas las configuraciones para este enfoque, ya que eso requeriría un artículo aparte. Al implementar este método, se pueden lograr imágenes "universales": Docker, ejecutándose en una máquina ARM, automáticamente descargará la imagen correspondiente a la arquitectura.
Conclusión
El experimento realizado superó todas mis expectativas: [como mínimo] el Kubernetes "vainilla" con la base necesaria se siente bastante bien en ARM, y durante su configuración surgieron solo un par de inconvenientes.
Las propias Raspberry Pi 3B+ soportan la carga de la CPU, sin embargo, sus tarjetas SD son un evidente cuello de botella. Mis colegas me hicieron notar que en algunas versiones hay la posibilidad de arrancar desde USB, donde se puede conectar un SSD: entonces, probablemente, la situación mejorará.
Aquí hay un ejemplo de carga de CPU al instalar Grafana:

Para experimentos y «para probar», en mi opinión, un clúster de Kubernetes en las "frambuesas" transmite una mejor sensación de uso que Minikube, porque todos los componentes del clúster se instalan y funcionan "de verdad".
En el futuro, hay una idea de agregar a el clúster todo el ciclo de CI/CD, totalmente implementado en Raspberry Pi. También estaré encantado si alguien comparte su experiencia en la configuración de K8s en AWS Graviton.
P.D. Sí, la "producción" puede estar más cerca de lo que pensaba:

P.P.D.
También puedes leer en nuestro blog:
- «».
Fuente: habr.com
