Aventuras com um cluster Kubernetes em casa

Nota perev. : O autor do artigo, Marshall Brekka, ocupa o cargo de diretor de design de sistemas na Fair.com, que oferece sua aplicação para locação de automóveis. Em seu tempo livre, ele gosta de usar sua vasta experiência para resolver problemas "caseiros" que dificilmente surpreenderão qualquer nerd (portanto, a pergunta "Por quê?" - com relação às ações descritas abaixo - é omitida a priori). Então, em sua publicação, Marshall compartilha os resultados da recente implantação do Kubernetes em ... placas ARM.



Como muitos outros geeks, ao longo dos anos acumulei uma variedade de placas de desenvolvimento como o Raspberry Pi. E, como muitos nerds, eles se espanaram nas prateleiras com o pensamento de que algum dia seriam úteis. E agora para mim este dia finalmente chegou!

Nas férias de inverno, várias semanas fora do trabalho apareceram, nas quais houve tempo suficiente para inventariar todo o ferro acumulado e decidir o que fazer com ele. Aqui está o que eu tinha:

  • Gabinete RAID de 5 unidades com conexão USB3;
  • Raspberry Pi Modelo B (modelo OG);
  • CubbieBoard 1;
  • Banana Pi M1;
  • Netbook HP (2012?).

Dos 5 componentes de ferro listados, usei, a menos que RAID e um netbook como um NAS temporário. No entanto, devido à falta de suporte a USB3 no netbook, o RAID não utilizou o potencial de velocidade total.

Objetivos de vida


Como trabalhar com RAID não era o ideal ao usar um netbook, estabeleci os seguintes objetivos para obter a melhor configuração:

  1. NAS com USB3 e Ethernet gigabit;
  2. A melhor maneira de gerenciar o software no seu dispositivo
  3. (bônus) a capacidade de transmitir conteúdo multimídia do RAID para o Fire TV.

Como nenhum dos dispositivos disponíveis suportava USB3 e Ethernet de gigabit, infelizmente, tive que fazer compras adicionais. A escolha recaiu no ROC-RK3328-CC . Ela possuía todas as especificações necessárias e suporte suficiente para sistemas operacionais.

Tendo resolvido minhas necessidades de hardware (e aguardando a chegada dessa solução), mudei para o segundo objetivo.

Gerenciando o software no dispositivo


Em parte, meus projetos anteriores relacionados às placas de desenvolvimento falharam devido à atenção insuficiente aos problemas de reprodutibilidade e documentação. Ao criar a próxima configuração para minhas necessidades atuais, não me preocupei em anotar as etapas seguidas ou os links para as postagens que segui. E quando, depois de meses ou anos, algo deu errado e eu tentei resolver o problema, não entendi como tudo estava organizado originalmente.

Então eu disse a mim mesma que desta vez tudo será diferente!



E ele se voltou para o fato de que eu sei o suficiente - para Kubernetes.

Embora o K8s seja uma solução muito difícil para um problema bastante simples, depois de quase três anos gerenciando clusters usando várias ferramentas (minhas próprias, kops etc.) no meu trabalho principal, estou muito familiarizado com esse sistema. Além disso, a implantação de K8s fora de um ambiente em nuvem e até em dispositivos ARM - tudo isso parecia uma tarefa interessante.

Eu também pensei que, como o hardware disponível não atende aos requisitos necessários para o NAS, tentarei pelo menos montar um cluster a partir dele e, possivelmente, algum software que não exige tanto recursos poderá funcionar em dispositivos mais antigos.

Kubernetes no ARM


No trabalho, não tive a oportunidade de usar o utilitário kubeadm para implantar clusters, então decidi que agora era a hora de experimentá-lo em ação.

O Raspbian foi escolhido como sistema operacional, porque é famoso pelo melhor suporte para minhas placas.

Encontrei um bom artigo sobre como configurar o Kubernetes em um Raspberry Pi usando o HypriotOS. Como não tinha certeza da disponibilidade do HypriotOS para todos os meus painéis, adaptei essas instruções ao Debian / Raspbian.

Componentes Necessários


Primeiro, era necessária a instalação das seguintes ferramentas:

  • Docker,
  • kubelet
  • kubeadm,
  • kubectl.

O Docker deve ser instalado usando um script especial - script de conveniência (conforme indicado no caso de uso do Raspbian).

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

Depois disso, instalei os componentes do Kubernetes de acordo com as instruções do blog Hypriot, adaptando-os para que versões específicas sejam usadas para todas as dependências:

 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 

Raspberry pi b


A primeira dificuldade surgiu ao tentar inicializar um cluster no Raspberry Pi B:

 $ kubeadm init Illegal instruction 

Acontece que o Kubernetes removeu o suporte ao ARMv6 . Bem, eu também tenho CubbieBoard e Banana Pi.

Banana pi


Inicialmente, a mesma sequência de ações para o Banana Pi parecia ter mais sucesso; no entanto, o comando kubeadm init enquanto tentava esperar o plano de controle funcionar:

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

Descobrindo com o docker ps que estava acontecendo com os contêineres, vi que o kube-controller-manager e o kube-scheduler estavam trabalhando por pelo menos 4-5 minutos, mas o kube-api-server se levantou há apenas 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, o api-server estava morrendo ou o processo de estrôncio estava matando e reiniciando.

Verificando os logs, vi procedimentos de inicialização muito padrão - houve um registro do início da escuta da porta segura e uma longa pausa antes do aparecimento de vários erros nos handshakes 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 

E logo depois disso, o servidor encerra seu trabalho. A pesquisa no Google levou a esse problema , indicando uma possível razão para a operação lenta de algoritmos criptográficos em alguns dispositivos ARM.

Fui mais longe e pensei que talvez o api-server recebendo muitas solicitações repetidas do scheduler e do controller-manager .

A remoção desses arquivos do diretório de manifesto instruirá o kubelet a interromper a execução dos pods correspondentes:

 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/ 

A visualização dos logs mais recentes api-server mostrou que agora o processo foi além, no entanto, ele ainda morreu após cerca de 2 minutos. Lembrei-me de que o manifesto poderia conter uma amostra de vivacidade com tempos limite com valores muito baixos para um dispositivo tão lento.

Portanto, verifiquei /etc/kubernetes/manifests/kube-api-server.yaml - e nele, é claro ...

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

O pod foi timeoutSeconds após 135 segundos ( timeoutSeconds + timeoutSeconds * failureThreshold ). Aumentar initialDelaySeconds para 120 ...

Sucesso! Bem, ainda ocorrem erros de handshake (presumivelmente do kubelet), no entanto, o lançamento ainda ocorreu:

 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 

Quando o api-server se levantou, mudei os arquivos YAML para o controlador e o planejador de volta ao diretório do manifesto, após o qual eles também começaram normalmente.

Agora é hora de garantir que o download seja bem-sucedido se você deixar todos os arquivos no diretório de origem: basta alterar o atraso permitido na inicialização do 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 

Sim, tudo funciona, embora esses dispositivos antigos, aparentemente, não tenham a intenção de iniciar o plano de controle, uma vez que conexões TLS repetidas causam freios significativos. De uma forma ou de outra - é recebida uma instalação funcional do K8s no ARM! Vamos mais longe ...

Montagem RAID


Como os cartões SD não são adequados para gravação a longo prazo, decidi usar um armazenamento mais confiável para as partes mais voláteis do sistema de arquivos - nesse caso, RAID. 4 seções foram destacadas nele:

  • 50 GB;
  • 2 × 20 GB;
  • 3,9 Tb.

Ainda não propus uma finalidade específica para partições de 20 gigabytes, mas queria deixar oportunidades adicionais para o futuro.

No /etc/fstab para a partição de 50 GB, o ponto de montagem foi especificado como /mnt/root e para 3,9 TB - /mnt/raid . Depois disso, montei os diretórios com o etcd e o docker na partição 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 

Chegada ROC-RK3328-CC


Quando a nova placa foi entregue, instalei os componentes necessários para os K8s (consulte o início do artigo) e kubeadm init . Alguns minutos de espera são o sucesso e a saída do comando join para executar em outros nós.

Ótimo! Sem problemas com intervalos.

E como o RAID também será usado nesta placa, as montagens precisarão ser configuradas novamente. Para resumir todas as etapas:

1. Monte discos no / 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. Instalando os binários Docker e 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. Configurando um nome de host exclusivo (importante como muitos nós foram adicionados)


 hostnamectl set-hostname k8s-master-1 

4. Inicialização do Kubernetes


Eu omito a fase com o plano de controle, porque eu quero poder planejar pods normais neste nó:

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

5. Instalando o Plug-in de Rede


As informações sobre isso no artigo Hypriot eram um pouco datadas, porque o plug-in de rede Weave agora também é suportado no 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. Adicionando rótulos de host


Neste nó, vou iniciar o servidor NAS, por isso vou marcá-lo com rótulos para possível uso futuro no planejador:

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

Conectando outros nós ao cluster


A configuração de outros dispositivos (Banana Pi, CubbieBoard) também foi fácil. Para eles, é necessário repetir as 3 primeiras etapas (alterando as configurações para montar discos / mídia flash, dependendo da disponibilidade) e executar o comando kubeadm join vez do kubeadm init .

Localizando contêineres do Docker para ARM


A maioria dos contêineres do Docker necessários é criada normalmente em um Mac, mas para o ARM é um pouco mais complicado. Tendo encontrado muitos artigos sobre como usar o QEMU para esses fins, cheguei à conclusão de que a maioria dos aplicativos de que eu preciso já está montada e muitos deles estão disponíveis no servidor linux .

Próximas etapas


Ainda não obtendo a configuração inicial dos dispositivos em uma forma automatizada / com scripts, como eu gostaria, pelo menos compus um conjunto de comandos básicos (montagens, kubeadm docker e kubeadm ) e os documentei no repositório Git. O restante dos aplicativos usados ​​também recebeu configurações YAML para K8s armazenadas no mesmo repositório; portanto, agora é muito fácil obter a configuração necessária do zero.

No futuro, gostaria de obter o seguinte:

  1. Tornar os sites principais altamente disponíveis
  2. adicione monitoramento / notificações para saber sobre falhas em qualquer componente;
  3. Altere as configurações de DCHP do roteador para usar um servidor DNS do cluster para simplificar a descoberta de aplicativos (quem deseja se lembrar dos endereços IP internos?);
  4. execute o MetalLB para encaminhar os serviços de cluster para uma rede privada (DNS, etc.).


PS do tradutor


Leia também em nosso blog:

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


All Articles