Conclua o Kubernetes do zero no Raspberry Pi





Mais recentemente, uma conhecida empresa anunciou que está transferindo sua linha de laptops para a arquitetura ARM. Tendo ouvido esta notícia, lembrei-me: ao olhar mais uma vez os preços do EC2 na AWS, notei Gravitons com um preço muito saboroso. O problema, claro, é que é o ARM. Nem me ocorreu então que o ARM era muito sério ...



Para mim, essa arquitetura sempre foi o lote de dispositivos móveis e outras coisas da IoT. Servidores "reais" no ARM são de alguma forma incomuns, em alguns aspectos até malucos ... No entanto, um novo pensamento ficou na minha cabeça, então um fim de semana decidi verificar o que poderia ser lançado no ARM hoje. E para isso eu decidi começar com um próximo e querido - o cluster Kubernetes. E não apenas algum “cluster” condicional, mas tudo “de uma forma adulta” para que seja o mais possível como estou habituado a ver em produção.



De acordo com a minha ideia, o cluster deve ser acessível pela Internet, alguma aplicação web deve rodar nele e deve haver pelo menos monitoramento. Para implementar essa ideia, você precisará de um par (ou mais) Raspberry Pi modelo 3B + ou superior. AWS também poderia se tornar uma plataforma para experimentos, mas eram as "framboesas" que eram interessantes para mim (que ainda estavam ociosas). Portanto, implantaremos um cluster Kubernetes com Ingress, Prometheus e Grafana neles.



Preparação de "framboesas"



Instalando o SO e SSH



Não me preocupei muito com a escolha do sistema operacional para instalação: acabei de pegar o Raspberry Pi OS Lite mais recente do site oficial . A documentação de instalação também está disponível lá , todas as etapas a partir das quais devem ser executadas em todos os nós do futuro cluster. Em seguida, você precisa realizar as seguintes manipulações (também em todos os nós).



Depois de conectar o monitor e o teclado, você deve primeiro configurar a rede e o SSH:



  1. Para que o cluster funcione, o mestre deve ter um endereço IP estático e os nós de trabalho devem ter um endereço IP estático. Eu preferia endereços estáticos em todos os lugares para facilitar a configuração.
  2. Um endereço estático pode ser configurado no sistema operacional ( /etc/dhcpcd.confhá um exemplo adequado no arquivo ) ou fixando o aluguel no servidor DHCP do roteador usado (no meu caso, doméstico).
  3. ssh-server está incluído apenas no raspi-config ( opções de interface -> ssh ).


Depois disso, você já pode fazer o login via SSH (por padrão, o login é pie a senha é a raspberryque você mudou) e continuar as configurações.



Outros ajustes



  1. Vamos definir o nome do host. No meu exemplo, pi-controle será usado pi-worker.
  2. Vamos verificar se o sistema de arquivos foi expandido para todo o disco ( df -h /). Ele pode ser estendido, se necessário, usando raspi-config.
  3. Altere a senha de usuário padrão em raspi-config.
  4. Desative o arquivo de troca (este é o requisito do Kubernetes; se você estiver interessado nos detalhes deste tópico, consulte o problema nº 53533 ):



    dphys-swapfile swapoff
    systemctl disable dphys-swapfile
  5. Vamos atualizar os pacotes para as versões mais recentes:



    apt-get update && apt-get dist-upgrade -y
  6. Instale o Docker e pacotes adicionais:



    apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistent


    Durante a instalação, você iptables-persistentprecisará salvar as configurações de iptables para ipv4 e /etc/iptables/rules.v4adicionar as regras à cadeia no arquivo FORWARD, como este:



    # 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
  7. Resta apenas reiniciar.


Agora você está pronto para instalar seu cluster Kubernetes.



Instalando Kubernetes



Neste estágio, adiei deliberadamente todos os meus desenvolvimentos corporativos e meus sobre a automação da instalação e configuração do cluster K8s. Em vez disso, usaremos a documentação oficial do kubernetes.io (ligeiramente aumentada com comentários e abreviações).



Adicione o repositório 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


Mais adiante na documentação, é proposta a instalação de CRI (interface de tempo de execução do contêiner). Como o Docker já está instalado, vamos prosseguir e instalar os componentes principais:



sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni


Na etapa de instalação dos componentes principais, adicionei imediatamente o kubernetes-cnique é necessário para o funcionamento do cluster. E aqui está um ponto importante: kubernetes-cnipor algum motivo o pacote não cria um diretório padrão para as configurações da interface CNI, então eu tive que criá-lo manualmente:



mkdir -p /etc/cni/net.d


Para que o back-end da rede funcione, o que será discutido a seguir, é necessário instalar o plugin para CNI. Eu escolhi o plugin portmap, que é familiar e claro para mim (veja a documentação para uma 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


Configurando Kubernetes



Nó de plano de controle



A configuração do próprio cluster é bastante simples. E para acelerar esse processo e verificar se as imagens do Kubernetes estão disponíveis, você pode primeiro executar:



kubeadm config images pull


Agora realizamos a própria instalação - inicializamos o plano de controle do cluster:



kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certs


Observe que as sub-redes de serviços e pods não devem se sobrepor umas às outras ou às redes existentes.



No final, será exibida uma mensagem informando que está tudo bem e, ao mesmo tempo, eles dirão como anexar nós de trabalho ao plano de controle:



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


Vamos seguir as recomendações para adicionar uma configuração para o usuário. E, ao mesmo tempo, recomendo adicionar o preenchimento automático para kubectl imediatamente:



 kubectl completion bash > ~/.kube/completion.bash.inc
 printf "
 # Kubectl shell completion
 source '$HOME/.kube/completion.bash.inc'
 " >> $HOME/.bash_profile
 source $HOME/.bash_profile


Neste estágio, você já pode ver o primeiro nó do cluster (embora ainda não esteja pronto):



root@pi-control:~# kubectl get no
NAME         STATUS     ROLES    AGE   VERSION
pi-control   NotReady   master   29s   v1.18.6


Configuração de rede



Além disso, como foi dito na mensagem após a instalação, você precisará instalar a rede no cluster. A documentação oferece uma escolha de Calico, Cilium, contiv-vpp, Kube-router e Weave Net ... Aqui eu me desviei das instruções oficiais e escolhi uma opção mais familiar e compreensível para mim: flanela no modo host-gw (para mais informações sobre back-ends disponíveis, consulte a documentação projeto ).



Instalá-lo em um cluster é muito simples. Primeiro, baixe os manifestos:



wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml


Em seguida, altere o tipo de vxlanpara nas configurações host-gw:



sed -i 's/vxlan/host-gw/' kube-flannel.yml


... e a sub-rede do pod - do valor padrão para o especificado durante a inicialização do cluster:



sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.yml


Depois disso, criamos recursos:



kubectl create -f kube-flannel.yml


Feito! Depois de um tempo, o primeiro nó K8s entrará no status Ready:



NAME         STATUS   ROLES    AGE   VERSION
pi-control   Ready    master   2m    v1.18.6


Adicionar um nó de trabalho



Agora você pode adicionar um trabalhador. Para fazer isso, após instalar o próprio Kubernetes de acordo com o cenário descrito acima, você só precisa executar o comando recebido anteriormente:



kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4 \
    --discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050


Sobre isso, podemos supor que o cluster está pronto:



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


Eu só tinha dois Raspberry Pi à mão, então não queria dar um deles apenas sob o avião de controle. Então, removi a contaminação de instalação automática do nó de controle pi executando:



root@pi-control:~# kubectl edit node pi-control


... e removendo as linhas:



 - effect: NoSchedule
   key: node-role.kubernetes.io/master


Preenchendo o cluster com o mínimo necessário



Em primeiro lugar, precisamos de Helm . Claro, você pode fazer tudo sem ele, mas o Helm permite que você personalize alguns componentes a seu critério, literalmente, sem editar arquivos. E na verdade é apenas um arquivo binário que "não pede pão".



Portanto, vá para helm.sh na seção docs / installation e execute o comando a partir daí:



curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash


Depois disso, adicione o repositório de gráficos:



helm repo add stable https://kubernetes-charts.storage.googleapis.com/


Agora vamos instalar os componentes da infraestrutura de acordo com a ideia:



  • Controlador de entrada;
  • Prometeu;
  • Grafana;
  • cert-manager.


Controlador de entrada



O primeiro componente, o controlador Ingress , é bastante fácil de instalar e pronto para usar fora da caixa. Para fazer isso, basta ir para a seção bare-metal no site e executar o comando de instalação a partir daí:



kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yaml


No entanto, neste momento, a "framboesa" começou a coar e entrar em IOPS de disco. O fato é que junto com o controlador de entrada, um grande número de recursos são instalados, muitas solicitações de API são feitas e, consequentemente, muitos dados são gravados no etcd. Em geral, ou um cartão de memória classe 10 não é muito produtivo ou um cartão SD basicamente não é suficiente para tal carga. No entanto, após 5 minutos, tudo começou.



Um namespace foi criado e um controlador apareceu nele e tudo o que ele precisa:



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


Prometeu



Os próximos dois componentes são bastante fáceis de instalar por meio do Helm a partir do repositório de gráficos.



Encontre o Prometheus , crie um namespace e instale nele:



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 padrão, o Prometheus pede 2 discos: para dados do Prometheus e para dados do AlertManager. Como nenhuma classe de armazenamento foi criada no cluster, os discos não serão solicitados e os pods não serão iniciados. Para instalações bare metal do Kubernetes, geralmente usamos Ceph rbd, mas no caso do Raspberry Pi, isso é um exagero.



Portanto, vamos criar um armazenamento local simples no caminho do host. Os manifestos PV (volume persistente) para prometheus-server e prometheus-alertmanager são mesclados em um arquivo prometheus-pv.yamlno repositório Git com exemplos para o artigo . O diretório para PV deve ser criado antecipadamente no disco do nó ao qual queremos vincular o Prometheus: no exemplo nodeAffinity, o nome do host é especificado pi-workere os diretórios /data/localstorage/prometheus-serversão criados nele /data/localstorage/prometheus-alertmanager.



Faça o download (clone) do manifesto e adicione-o ao Kubernetes:



kubectl create -f prometheus-pv.yaml


Nesse estágio, encontrei pela primeira vez o problema de arquitetura ARM. Kube-state-metrics, que é definido por padrão no gráfico do Prometheus, recusou-se a iniciar. Estava gerando um erro:



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"


O fato é que para kube-state-metrics é usada a imagem do projeto CoreOS, que não é 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


Tive que pesquisar um pouco no Google e encontrar, por exemplo, essa imagem . Para tirar proveito disso, vamos atualizar a versão, especificando qual imagem 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


Verificamos se tudo começou:



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 e cert-manager



Para gráficos e painéis, instale o Grafana :



helm install grafana --namespace monitoring stable/grafana  --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}


Ao final do resultado, veremos como obter a senha de acesso:



kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo


Para solicitar certificados, instale o cert-manager . Para instalá-lo, consulte a documentação , que oferece os comandos apropriados para o 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 autoassinados em uso doméstico, isso é suficiente. Se você precisar receber o mesmo Let's Encrypt , será necessário configurar outro emissor de cluster. Mais detalhes podem ser encontrados em nosso artigo " Certificados SSL de Let's Encrypt com cert-manager no Kubernetes ".



Eu mesmo decidi pela versão do exemplo na documentação , decidindo que a versão de teste do LE seria o suficiente. Altere o e-mail no exemplo, salve-o em um arquivo e adicione-o ao cluster ( cert-manager-cluster-issuer.yaml ):



kubectl create -f cert-manager-cluster-issuer.yaml


Agora você pode solicitar um certificado, por exemplo, para Grafana. Isso exigirá um domínio e acesso externo ao cluster. Eu tenho um domínio e configurei o tráfego encaminhando as portas 80 e 443 no meu roteador doméstico de acordo com o serviço de controlador de entrada criado:



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


Nesse caso, a porta 80 é convertida para 31303 e 443 para 30498. (As portas são geradas aleatoriamente, então você terá portas diferentes.)



Aqui está um exemplo de certificado ( 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


Adicione-o ao cluster:



kubectl create -f cert-manager-grafana-certificate.yaml


Depois disso, o recurso Ingress aparecerá, por meio do qual ocorrerá a validação do 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


Após a validação passar, veremos que o recurso está certificatepronto, e no segredo acima grafana-tls- o certificado e a chave. Você pode verificar imediatamente quem emitiu o 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


Voltemos a Grafana. Precisamos corrigir um pouco a versão do Helm, alterando as configurações do TLS de acordo com o certificado gerado.



Para fazer isso, baixe o gráfico, edite e atualize no diretório local:



helm pull --untar stable/grafana


Edite os grafana/values.yaml parâmetros TLS no arquivo :



  tls:
    - secretName: grafana-tls
      hosts:
        - grafana.home.pi


Aqui você pode configurar imediatamente o Prometheus instalado como datasource:



datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server:80
      access: proxy
      isDefault: true


Agora atualize o gráfico Grafana no diretório local:



helm upgrade grafana --namespace monitoring ./grafana  --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}


Verificamos se a grafanaporta 443 foi adicionada ao Ingress e se há acesso via 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 demonstrar o Grafana em ação, você pode baixar e adicionar um painel para kube-state-metrics . Veja como fica: Eu







também recomendo adicionar um painel para o exportador de nós: ele mostrará em detalhes o que acontece com as "framboesas" (carga da CPU, memória, rede, uso do disco, etc.).



Depois disso, acredito que o cluster está pronto para receber e executar aplicativos!



Nota de montagem



Existem pelo menos duas opções para construir aplicativos para a arquitetura ARM. Primeiro, você pode construir em um dispositivo ARM. No entanto, depois de olhar a disposição atual dos dois Raspberry Pi, percebi que eles também não sobreviveriam à montagem. Portanto, comprei um novo Raspberry Pi 4 (é mais poderoso e tem 4 GB de memória) - pretendo construí-lo.



A segunda opção é construir uma imagem Docker de arquitetura múltipla em uma máquina mais poderosa. Existe uma extensão docker buildx para isso . Se o aplicativo estiver em uma linguagem compilada, a compilação cruzada para ARM será necessária. Não vou descrever todas as configurações para este caminho. isso levará a um artigo separado. Ao implementar essa abordagem, você pode obter imagens "universais": o Docker em execução em uma máquina ARM carregará automaticamente a imagem correspondente à arquitetura.



Conclusão



O experimento realizado superou todas as minhas expectativas: [pelo menos] Kubernetes "vanilla" com a base necessária se sente bem no ARM e, com sua configuração, apenas algumas nuances surgiram.



O próprio Raspberry Pi 3B + mantém a CPU ocupada, mas seus cartões SD são um claro gargalo. Colegas sugeriram que em algumas versões é possível inicializar a partir de USB, onde você pode conectar um SSD: então a situação provavelmente vai melhorar.



Aqui está um exemplo de carga da CPU ao instalar o Grafana:







Para experimentos e "para tentar", na minha opinião, o cluster Kubernetes em "framboesas" transmite muito melhor as sensações de operação do que o mesmo Minikube, porque todos os componentes do cluster estão instalados e funcionam "De uma maneira adulta."



Futuramente, existe a ideia de agregar ao cluster todo o ciclo de CI / CD, implementado inteiramente no Raspberry Pi. E também ficarei feliz se alguém compartilhar sua experiência na configuração de K8s no AWS Gravitons.



PS Sim, a "produção" pode estar mais perto do que eu pensava:







PPS



Leia também em nosso blog:






All Articles