Aventuras con un grupo de Kubernetes casero

Nota perev. : El autor del artículo, Marshall Brekka, ocupa el puesto de director de diseño de sistemas en Fair.com, que ofrece su aplicación para el arrendamiento de automóviles. En su tiempo libre, le gusta usar su amplia experiencia para resolver problemas de "hogar" que probablemente no sorprendan a ningún geek (por lo tanto, la pregunta "¿Por qué?", ​​Con respecto a las acciones que se describen a continuación, se omite a priori). Entonces, en su publicación, Marshall comparte los resultados del reciente despliegue de Kubernetes en ... tableros ARM.



Como muchos otros geeks, a lo largo de los años he acumulado una variedad de placas de desarrollo como la Raspberry Pi. Y como muchos geeks, se sacudieron el polvo en los estantes con la idea de que algún día serían útiles. ¡Y ahora para mí este día finalmente ha llegado!

Durante las vacaciones de invierno, aparecieron varias semanas fuera del trabajo, dentro de las cuales había suficiente tiempo para inventariar todo el hierro acumulado y decidir qué hacer con él. Aquí está lo que tenía:

  • Caja RAID de 5 unidades con conexión USB3;
  • Raspberry Pi Modelo B (modelo OG);
  • CubbieBoard 1;
  • Banana Pi M1;
  • Netbook HP (2012?).

De los 5 componentes de hierro enumerados, utilicé a menos que RAID y una netbook como un NAS temporal. Sin embargo, debido a la falta de compatibilidad con USB3 en el netbook, RAID no utilizó el potencial de velocidad máxima.

Metas de la vida


Dado que trabajar con RAID no era óptimo al usar una netbook, establecí los siguientes objetivos para obtener la mejor configuración:

  1. NAS con USB3 y Gigabit Ethernet;
  2. La mejor manera de administrar el software en su dispositivo
  3. (bono) la capacidad de transmitir contenido multimedia de RAID a Fire TV.

Dado que ninguno de los dispositivos disponibles admitía USB3 y Gigabit Ethernet, desafortunadamente, tuve que hacer compras adicionales. La elección recayó en el ROC-RK3328-CC . Ella poseía todas las especificaciones necesarias y suficiente soporte para los sistemas operativos.

Habiendo resuelto mis necesidades de hardware (y esperando la llegada de esta solución), cambié al segundo objetivo.

Administrar software en el dispositivo


En parte, mis proyectos anteriores relacionados con las juntas de desarrollo han fallado debido a una atención insuficiente a los problemas de reproducibilidad y documentación. Al crear la siguiente configuración para mis necesidades actuales, no me molesté en escribir los pasos tomados o los enlaces a las publicaciones de blog que seguí. Y cuando, después de meses o años, algo salió mal y traté de solucionar el problema, no entendí cómo estaba todo arreglado originalmente.

¡Entonces me dije que esta vez todo será diferente!



Y se volvió hacia el hecho de que yo sé bastante bien: a Kubernetes.

Aunque K8s es una solución demasiado difícil para un problema bastante simple, después de casi tres años de administrar clústeres utilizando varias herramientas (la mía, la de Kops, etc.) en mi trabajo principal, estoy muy familiarizado con este sistema. Además, desplegar K8 fuera de un entorno de nube, e incluso en dispositivos ARM, todo esto parecía una tarea interesante.

También pensé que, dado que el hardware disponible no cumple con los requisitos necesarios para el NAS, intentaré al menos ensamblar un clúster a partir de él y, tal vez, algún software que no requiera tantos recursos pueda funcionar en dispositivos más antiguos.

Kubernetes en ARM


En el trabajo, no tuve la oportunidad de usar la utilidad kubeadm para implementar clústeres, así que decidí que ahora era el momento de probarlo en acción.

Raspbian fue elegido como sistema operativo, porque es famoso por el mejor soporte para mis tableros.

Encontré un buen artículo sobre cómo configurar Kubernetes en una Raspberry Pi usando HypriotOS. Como no estaba seguro de la disponibilidad de HypriotOS para todos mis tableros, adapté estas instrucciones para Debian / Raspbian.

Componentes requeridos


Primero, se requirió la instalación de las siguientes herramientas:

  • Docker
  • kubelet
  • kubeadm,
  • kubectl.

Docker debe instalarse usando un script especial: script de conveniencia (como se indica para el caso de usar Raspbian).

 curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh 

Después de eso, instalé los componentes de Kubernetes de acuerdo con las instrucciones del blog de Hypriot, adaptándolos para que se usen versiones específicas para todas las dependencias:

 curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list apt-get update apt-get install -y kubelet=1.13.1-00 kubectl=1.13.1-00 kubeadm=1.13.1-00 

Frambuesa pi b


La primera dificultad surgió al intentar arrancar un clúster en la Raspberry Pi B:

 $ kubeadm init Illegal instruction 

Resultó que Kubernetes eliminó el soporte para ARMv6 . Bueno, también tengo CubbieBoard y Banana Pi.

Pi piña


Inicialmente, la misma secuencia de acciones para Banana Pi parecía ser más exitosa, sin embargo, el comando kubeadm init tiempo de espera al intentar que el avión de control funcionara:

 error execution phase wait-control-plane: couldn't initialize a Kubernetes cluster 

Al descubrir con docker ps lo que estaba sucediendo con los contenedores, vi que tanto kube-controller-manager como kube-scheduler habían estado trabajando durante al menos 4-5 minutos, pero kube-api-server se levantó hace solo 1-2 minutos:

 $ docker ps CONTAINER ID COMMAND CREATED STATUS de22427ad594 "kube-apiserver --au…" About a minute ago Up About a minute dc2b70dd803e "kube-scheduler --ad…" 5 minutes ago Up 5 minutes 60b6cc418a66 "kube-controller-man…" 5 minutes ago Up 5 minutes 1e1362a9787c "etcd --advertise-cl…" 5 minutes ago Up 5 minutes 

Obviamente, el api-server estaba muriendo o el proceso de estroncio lo estaba matando y reiniciando.

Al revisar los registros, vi procedimientos de inicio muy estándar: hubo un registro del comienzo de la escucha del puerto seguro y una larga pausa antes de la aparición de numerosos errores en los apretones de manos TLS:

 20:06:48.604881 naming_controller.go:284] Starting NamingConditionController 20:06:48.605031 establishing_controller.go:73] Starting EstablishingController 20:06:50.791098 log.go:172] http: TLS handshake error from 192.168.1.155:50280: EOF 20:06:51.797710 log.go:172] http: TLS handshake error from 192.168.1.155:50286: EOF 20:06:51.971690 log.go:172] http: TLS handshake error from 192.168.1.155:50288: EOF 20:06:51.990556 log.go:172] http: TLS handshake error from 192.168.1.155:50284: EOF 20:06:52.374947 log.go:172] http: TLS handshake error from 192.168.1.155:50486: EOF 20:06:52.612617 log.go:172] http: TLS handshake error from 192.168.1.155:50298: EOF 20:06:52.748668 log.go:172] http: TLS handshake error from 192.168.1.155:50290: EOF 

Y poco después, el servidor finaliza su trabajo. Buscar en Google condujo a tal problema , lo que indica una posible razón para la lenta operación de los algoritmos criptográficos en algunos dispositivos ARM.

Fui más allá y pensé que tal vez el api-server recibiendo demasiadas solicitudes repetidas del scheduler y el controller-manager .

Eliminar estos archivos del directorio de manifiesto le indicará a kubelet que detenga la ejecución de los pods correspondientes:

 mkdir /etc/kubernetes/manifests.bak mv /etc/kubernetes/manifests/kube-scheduler.yaml /etc/kubernetes/manifests.bak/ mv /etc/kubernetes/manifests/kube-controller-mananger.yaml /etc/kubernetes/manifests.bak/ 

Ver los últimos registros de api-server mostró que ahora el proceso fue más allá, sin embargo, todavía murió después de unos 2 minutos. Entonces recordé que el manifiesto podría contener una muestra de vida con tiempos de espera que tienen valores demasiado bajos para un dispositivo tan lento.

Por lo tanto, revisé /etc/kubernetes/manifests/kube-api-server.yaml , y en él, por supuesto ...

 livenessProbe: failureThreshold: 8 httpGet: host: 192.168.1.155 path: /healthz port: 6443 scheme: HTTPS initialDelaySeconds: 15 timeoutSeconds: 15 

El pod se mató después de 135 segundos ( initialDelaySeconds + timeoutSeconds * failureThreshold ). Aumente initialDelaySeconds a 120 ...

Éxito! Bueno, todavía ocurren errores de apretón de manos (presumiblemente de kubelet), sin embargo, el lanzamiento aún tuvo lugar:

 20:06:54.957236 log.go:172] http: TLS handshake error from 192.168.1.155:50538: EOF 20:06:55.004865 log.go:172] http: TLS handshake error from 192.168.1.155:50384: EOF 20:06:55.118343 log.go:172] http: TLS handshake error from 192.168.1.155:50292: EOF 20:06:55.252586 cache.go:39] Caches are synced for autoregister controller 20:06:55.253907 cache.go:39] Caches are synced for APIServiceRegistrationController controller 20:06:55.545881 controller_utils.go:1034] Caches are synced for crd-autoregister controller ... 20:06:58.921689 storage_rbac.go:187] created clusterrole.rbac.authorization.k8s.io/cluster-admin 20:06:59.049373 storage_rbac.go:187] created clusterrole.rbac.authorization.k8s.io/system:discovery 20:06:59.214321 storage_rbac.go:187] created clusterrole.rbac.authorization.k8s.io/system:basic-user 

Cuando el api-server se levantó, moví los archivos YAML para el controlador y el planificador nuevamente al directorio de manifiesto, después de lo cual también comenzaron normalmente.

Ahora es el momento de asegurarse de que la descarga se realizará correctamente si deja todos los archivos en el directorio de origen: ¿es suficiente para cambiar el retraso permitido en la inicialización de livenessProbe ?

 20:29:33.306983 reflector.go:134] k8s.io/client-go/informers/factory.go:132: Failed to list *v1.Service: Get https://192.168.1.155:6443/api/v1/services?limit=500&resourceVersion=0: dial tcp 192.168.1.155:6443: i/o timeout 20:29:33.434541 reflector.go:134] k8s.io/client-go/informers/factory.go:132: Failed to list *v1.ReplicationController: Get https://192.168.1.155:6443/api/v1/replicationcontrollers?limit=500&resourceVersion=0: dial tcp 192.168.1.155:6443: i/o timeout 20:29:33.435799 reflector.go:134] k8s.io/client-go/informers/factory.go:132: Failed to list *v1.PersistentVolume: Get https://192.168.1.155:6443/api/v1/persistentvolumes?limit=500&resourceVersion=0: dial tcp 192.168.1.155:6443: i/o timeout 20:29:33.477405 reflector.go:134] k8s.io/client-go/informers/factory.go:132: Failed to list *v1beta1.PodDisruptionBudget: Get https://192.168.1.155:6443/apis/policy/v1beta1/poddisruptionbudgets?limit=500&resourceVersion=0: dial tcp 192.168.1.155:6443: i/o timeout 20:29:33.493660 reflector.go:134] k8s.io/client-go/informers/factory.go:132: Failed to list *v1.PersistentVolumeClaim: Get https://192.168.1.155:6443/api/v1/persistentvolumeclaims?limit=500&resourceVersion=0: dial tcp 192.168.1.155:6443: i/o timeout 20:29:37.974938 controller_utils.go:1027] Waiting for caches to sync for scheduler controller 20:29:38.078558 controller_utils.go:1034] Caches are synced for scheduler controller 20:29:38.078867 leaderelection.go:205] attempting to acquire leader lease kube-system/kube-scheduler 20:29:38.291875 leaderelection.go:214] successfully acquired lease kube-system/kube-scheduler 

Sí, todo funciona, aunque tales dispositivos antiguos, aparentemente, no estaban destinados a lanzar el avión de control, ya que las conexiones TLS repetidas causan frenos significativos. De una forma u otra: se recibe una instalación funcional de K8 en ARM. Vamos más allá ...

Montaje RAID


Como las tarjetas SD no son adecuadas para grabar a largo plazo, decidí usar un almacenamiento más confiable para las partes más volátiles del sistema de archivos, en este caso, RAID. Se destacaron 4 secciones:

  • 50 GB;
  • 2 × 20 GB;
  • 3.9 Tb.

Todavía no se me ocurrió un propósito específico para las particiones de 20 gigabytes, pero quería dejar oportunidades adicionales para el futuro.

En el /etc/fstab para la partición de 50 GB, el punto de montaje se especificó como /mnt/root y para 3.9 TB - /mnt/raid . Después de eso, monté los directorios con etcd y docker en la partición de 50 GB:

 UUID=655a39e8-9a5d-45f3-ae14-73b4c5ed50c3 /mnt/root ext4 defaults,rw,user,auto,exec 0 0 UUID=0633df91-017c-4b98-9b2e-4a0d27989a5c /mnt/raid ext4 defaults,rw,user,auto 0 0 /mnt/root/var/lib/etcd /var/lib/etcd none defaults,bind 0 0 /mnt/root/var/lib/docker /var/lib/docker none defaults,bind 0 0 

Llegada ROC-RK3328-CC


Cuando se entregó la nueva placa, instalé los componentes necesarios para los K8 en ella (vea el comienzo del artículo) y kubeadm init . Unos minutos de espera es el éxito y la salida del comando de join para ejecutarse en otros nodos.

Genial Sin problemas con los tiempos de espera.

Y dado que RAID también se usará en esta placa, los montajes deberán configurarse nuevamente. Para resumir todos los pasos:

1. Monte los discos en / etc / fstab


 UUID=655a39e8-9a5d-45f3-ae14-73b4c5ed50c3 /mnt/root ext4 defaults,rw,user,auto,exec 0 0 UUID=0633df91-017c-4b98-9b2e-4a0d27989a5c /mnt/raid ext4 defaults,rw,user,auto 0 0 /mnt/root/var/lib/etcd /var/lib/etcd none defaults,bind 0 0 /mnt/root/var/lib/docker /var/lib/docker none defaults,bind 0 0 

2. Instalación de los binarios de Docker y K8s


 curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh 

 curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list apt-get update apt-get install -y kubelet=1.13.1-00 kubectl=1.13.1-00 kubeadm=1.13.1-00 

3. Configuración de un nombre de host único (importante ya que se agregan muchos nodos)


 hostnamectl set-hostname k8s-master-1 

4. Inicialización de Kubernetes


Omito la fase con el plano de control, porque quiero poder planificar pods normales en este nodo:

 kubeadm init --skip-phases mark-control-plane 

5. Instalar el complemento de red


La información sobre esto en el artículo de Hypriot estaba un poco anticuada porque el complemento de red Weave ahora también es compatible con ARM :

 export KUBECONFIG=/etc/kubernetes/admin.conf kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')" 

6. Agregar etiquetas de host


En este nodo, voy a iniciar el servidor NAS, así que lo marcaré con etiquetas para un posible uso futuro en el programador:

 kubectl label nodes k8s-master-1 marshallbrekka.raid=true kubectl label nodes k8s-master-1 marshallbrekka.network=gigabit 

Conectar otros nodos al clúster


Configurar otros dispositivos (Banana Pi, CubbieBoard) fue igual de fácil. Para ellos, debe repetir los primeros 3 pasos (cambiar la configuración para montar discos / medios flash, según su disponibilidad) y ejecutar el comando kubeadm join lugar de kubeadm init .

Encontrar contenedores Docker para ARM


La mayoría de los contenedores Docker necesarios se crean normalmente en una Mac, pero para ARM es un poco más complicado. Después de haber encontrado muchos artículos sobre cómo usar QEMU para estos fines, llegué a la conclusión de que la mayoría de las aplicaciones que necesito ya están ensambladas, y muchas de ellas están disponibles en linuxserver .

Próximos pasos


Aún sin obtener la configuración inicial de los dispositivos en una forma tan automatizada / programada como me gustaría, al menos compuse un conjunto de comandos básicos (monturas, kubeadm docker y kubeadm ) y los documenté en el repositorio de Git. El resto de las aplicaciones utilizadas también recibieron configuraciones YAML para K8 almacenados en el mismo repositorio, por lo que ahora es muy fácil obtener la configuración necesaria desde cero.

En el futuro, me gustaría lograr lo siguiente:

  1. Hacer sitios maestros altamente disponibles
  2. agregar monitoreo / notificaciones para saber sobre fallas en cualquier componente;
  3. Cambie la configuración de DCHP del enrutador para usar un servidor DNS del clúster para simplificar el descubrimiento de aplicaciones (¿quién quiere recordar las direcciones IP internas?);
  4. ejecute MetalLB para reenviar servicios de clúster a una red privada (DNS, etc.).


PD del traductor


Lea también en nuestro blog:

Source: https://habr.com/ru/post/es435526/


All Articles