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:
- NAS com USB3 e Ethernet gigabit;
- A melhor maneira de gerenciar o software no seu dispositivo
- (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:
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:
- Tornar os sites principais altamente disponíveis
- adicione monitoramento / notificações para saber sobre falhas em qualquer componente;
- 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?);
- 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: