
Recentemente, uma empresa famosa anunciou que estava transferindo sua linha de notebooks para a arquitetura ARM. Ao ouvir essa notícia, lembrei que, ao verificar novamente os preços do EC2 na AWS, prestei atenção nos Gravitons com um preço muito interessante. A pegadinha, claro, era que se tratava de ARM. Naquela época, eu nem imaginava que ARM era algo sério...
Para mim, essa arquitetura sempre foi associada a dispositivos móveis e outras coisinhas IoT. Servidores ‘de verdade’ em ARM pareciam um pouco incomuns, quase estranhos... No entanto, uma nova ideia se alojou na minha cabeça, e então, em um dos fins de semana, decidi verificar o que realmente poderia ser executado em ARM hoje. Para isso, decidi começar com algo próximo e familiar: o cluster Kubernetes. E não apenas qualquer ‘cluster’, mas tudo ‘de forma profissional’, para que fosse o mais parecido possível com o que estou acostumado a ver em produção.
Na minha visão, o cluster deveria ser acessível pela internet, executar um certo aplicativo web e pelo menos ter um monitoramento. Para realizar essa ideia, seria necessário um par (ou mais) de Raspberry Pis, não inferiores ao modelo 3B+. A AWS poderia ser um bom local para a experiência, mas eu estava realmente interessado nas ‘frambuesas’ (que de qualquer forma estavam parada). Portanto, vamos implementar um cluster Kubernetes com Ingress, Prometheus e Grafana.
Preparação das ‘frambuesas’
Instalação do SO e SSH
Quanto à escolha do SO para instalação, não me preocupei muito: simplesmente peguei o mais recente Raspberry Pi OS Lite de . Também está disponível , cujas ações precisam ser executadas em todos os nós do futuro cluster. Em seguida, será necessário realizar as seguintes manipulações (também em todos os nós).
Conectando um monitor e teclado, é necessário pré-configurar a rede e o SSH:
- Para o funcionamento do cluster, o mestre deve ter um endereço IP estático, e nos nós de trabalho, isso fica a critério. Eu preferi endereços estáticos em todos os lugares por questões de conveniência de configuração.
- Um endereço estático pode ser configurado no SO (no arquivo
/etc/dhcpcd.confhá um exemplo adequado) ou fixando o lease no servidor DHCP do roteador usado (no meu caso, o doméstico). - O ssh-server é ativado simplesmente no raspi-config (opções de interface → ssh).
Após isso, já é possível fazer login via SSH (por padrão, o login é pi, e a senha é raspberry ou aquela que foi alterada) e continuar as configurações.
Outras configurações
- Vamos configurar o nome do host. No meu exemplo, usaremos
pi-controlepi-worker. - Vamos verificar se o sistema de arquivos está expandido para todo o disco (
df -h /). Caso seja necessário, ele pode ser expandido utilizando o raspi-config. - Vamos alterar a senha do usuário padrão no raspi-config.
- Desativaremos o arquivo de swap (isso é um requisito do Kubernetes; se você está interessado em mais detalhes sobre este assunto, veja ):
dphys-swapfile swapoff systemctl disable dphys-swapfile - Vamos atualizar os pacotes para as versões mais recentes:
apt-get update && apt-get dist-upgrade -y - Vamos instalar o Docker e pacotes adicionais:
apt-get install -y docker docker.io apt-transport-https curl bridge-utils iptables-persistentAo instalar
iptables-persistenté necessário salvar as configurações do iptables para ipv4, e no arquivo/etc/iptables/rules.v4— adicionar regras à cadeiaFORWARD, assim:# 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 - Agora, só precisamos reiniciar.
Agora tudo está pronto para a instalação do cluster Kubernetes.
Instalação do Kubernetes
Neste ponto, deixei de lado todas as minhas e nossas implementações corporativas de automação para a instalação e configuração do cluster K8s. Em vez disso, utilizaremos a documentação oficial com (ligeiramente complementada com comentários e abreviações).
Vamos adicionar 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 updateEm seguida, a documentação sugere instalar o CRI (interface de tempo de execução de contêiner). Como o Docker já está instalado, vamos em frente e instalamos os componentes principais:
sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni Na etapa de instalação dos componentes principais, eu já acrescentei kubernetes-cni, que é necessário para o funcionamento do cluster. E aqui há um ponto importante: o pacote kubernetes-cni por algum motivo não cria o diretório padrão para as configurações dos interfaces CNI, então eu tive que criá-lo manualmente:
mkdir -p /etc/cni/net.dPara o funcionamento do backend de rede, sobre o qual falaremos mais adiante, é necessário instalar o plugin para CNI. Eu escolhi o plugin portmap que já estou acostumado e que me é familiar (veja a lista completa em ):
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/ ./portmapConfiguração do Kubernetes
Nó com control plane
A instalação do próprio cluster é bastante simples. E para acelerar esse processo e verificar se as imagens do Kubernetes estão disponíveis, podemos executar previamente:
kubeadm config images pullAgora vamos para a instalação propriamente dita — inicializamos o control plane do cluster:
kubeadm init --pod-network-cidr=10.1.0.0/16 --service-cidr=10.2.0.0/16 --upload-certsObserve que as sub-redes para serviços e pods não devem se sobrepor entre si e com as redes existentes.
No final, será exibida uma mensagem informando que tudo está bem e também serão fornecidas instruções sobre como conectar os nós de trabalho ao control plane:
Seu control-plane do Kubernetes foi inicializado com sucesso!
Para começar a usar seu cluster, você precisa executar o seguinte como um usuário comum:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Agora você deve implantar uma rede de pods no cluster.
Execute "kubectl apply -f [podnetwork].yaml" com uma das opções listadas em:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Você pode agora juntar qualquer número de nós do control-plane executando o seguinte comando em cada um como root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050
--control-plane --certificate-key 72a3c0a14c627d6d7fdade1f4c8d7a41b0fac31b1faf0d8fdf9678d74d7d2403
Por favor, note que a chave do certificado dá acesso a dados sensíveis do cluster, mantenha-a em segredo!
Como medida de segurança, os certificados carregados serão excluídos em duas horas; Se necessário, você pode usar
"kubeadm init phase upload-certs --upload-certs" para recarregar os certificados posteriormente.
Em seguida, você pode juntar qualquer número de nós de trabalho executando o seguinte em cada um como root:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050Vamos seguir as recomendações para adicionar a configuração para o usuário. E também recomendo adicionar a conclusão automática para kubectl:
kubectl completion bash > ~$HOME/.kube/completion.bash.inc
printf "
# Completação para o shell do Kubectl
source '$HOME/.kube/completion.bash.inc'
" >> $HOME/.bash_profile
source $HOME/.bash_profileNesta etapa, já é possível ver o primeiro nó no cluster (embora ele ainda não esteja pronto):
root@pi-control:~# kubectl get no
NAME STATUS ROLES AGE VERSION
pi-control NotReady master 29s v1.18.6Configuração da rede
Em seguida, como mencionado na mensagem após a instalação, será necessário configurar a rede no cluster. A documentação sugere opções como Calico, Cilium, contiv-vpp, Kube-router e Weave Net… Aqui, eu me afastei da instrução oficial e escolhi uma opção mais familiar e compreensível para mim: no modo host-gw (mais detalhes sobre os backends disponíveis em ).
Instalá-lo no cluster é bastante simples. Para começar — faça o download dos manifests:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Em seguida, altere nas configurações o tipo de vxlan para host-gw:
sed -i 's/vxlan/host-gw/' kube-flannel.yml… e a sub-rede dos pods — de seu valor padrão para o que foi especificado durante a inicialização do cluster:
sed -i 's#10.244.0.0/16#10.1.0.0/16#' kube-flannel.ymlDepois disso, crie os recursos:
kubectl create -f kube-flannel.yml Pronto! Em pouco tempo, o primeiro nó K8s mudará para o status Pronto:
NAME STATUS ROLES AGE VERSION
pi-control Ready master 2m v1.18.6Adicionando um nó de trabalho
Agora é possível adicionar um worker. Para isso, após a instalação do próprio Kubernetes conforme descrito acima, basta executar o comando obtido anteriormente:
kubeadm join 192.168.88.30:6443 --token a485vl.xjgvzzr2g0xbtbs4
--discovery-token-ca-cert-hash sha256:9da6b05aaa5364a9ec59adcc67b3988b9c1b94c15e81300560220acb1779b050A partir daqui, podemos considerar 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 2m8s v1.18.6Eu só tinha duas Raspberry Pi à disposição, então não queria ceder uma delas apenas para o control plane. Portanto, removi o taint instalado automaticamente do nó pi-control, executando:
root@pi-control:~# kubectl edit node pi-control… e removi as linhas:
- effect: NoSchedule
key: node-role.kubernetes.io/masterPreenchendo o cluster com o mínimo necessário
Primeiro, precisaremos de Helm. Claro, é possível fazer tudo sem ele, mas o Helm permite configurar alguns componentes à sua maneira sem editar arquivos. E, na verdade, é apenas um arquivo binário que "não pede nada".
Então, vamos ao site na seção docs/installation e executamos o comando de lá:
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashDepois disso, adicionamos o repositório de charts:
helm repo add stable https://kubernetes-charts.storage.googleapis.com/Agora vamos instalar os componentes de infraestrutura conforme planejado:
- Ingress controller;
- Prometheus;
- Grafana;
- cert-manager.
Ingress controller
O primeiro componente — Ingress controller — é instalado de forma bastante simples e está pronto para uso "pronto para uso". Para isso, basta acessar e executar o comando de instalação de lá:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yamlNo entanto, neste momento, a "framboesa" começou a ficar sobrecarregada e a esbarrar no IOPS do disco. O fato é que junto com o Ingress controller, muitos recursos são instalados, muitas requisições ao API são feitas e, consequentemente, muitos dados são gravados no etcd. Em suma, ou o cartão SD de classe 10 não é tão eficiente, ou os cartões SD não são suficientes para essa carga. De qualquer forma, após cerca de 5 minutos, tudo iniciou.
Um namespace foi criado e nele apareceu o controller e tudo que ele precisava:
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
Os dois componentes a seguir são bastante simples de instalar via Helm a partir do repositório de charts.
Encontramos Prometheus, criamos o namespace e instalamos 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 solicita 2 discos: um para os dados do próprio Prometheus e outro para os dados do AlertManager. Como a storage class não foi criada no cluster, os discos não serão solicitados e os pods não iniciarão. Para instalações bare metal do Kubernetes, costumamos usar Ceph rbd, porém, no caso do Raspberry Pi, isso é um exagero.
Portanto, criaremos um simples armazenamento local no hostpath. Os manifestos PV (persistent volume) para prometheus-server e prometheus-alertmanager estão juntos no arquivo prometheus-pv.yaml em . O diretório para o PV deve ser criado previamente no disco do nó ao qual queremos vincular o Prometheus: no exemplo, está especificado nodeAffinity pelo hostname pi-worker e nele foram criados os diretórios /data/localstorage/prometheus-server e /data/localstorage/prometheus-alertmanager.
Baixamos (clonamos) o manifesto e adicionamos ao Kubernetes:
kubectl create -f prometheus-pv.yamlNesse estágio, enfrentei pela primeira vez o problema da arquitetura ARM. O kube-state-metrics, que é instalado por padrão no chart do Prometheus, se recusou a iniciar. Ele apresentou o 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 problema é que o kube-state-metrics usa uma imagem do projeto CoreOS, que não é construída 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.7Tive que pesquisar um pouco e encontrei, por exemplo, . Para utilizá-la, atualizaremos a versão, especificando qual imagem usar para o 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 se tudo subiu:
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 4m52sGrafana e cert-manager
Para gráficos e dashboards, instalamos Grafana:
helm install grafana --namespace monitoring stable/grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"}No final da saída, vamos mostrar como obter a senha de acesso:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echoPara solicitar certificados, vamos instalar o cert-manager. Para sua instalação, vamos nos referir ao , que oferece os comandos correspondentes 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=truePara certificados autoassinados em uso doméstico, isso é mais que suficiente. No entanto, se precisar obter o mesmo Let’s Encrypt, será necessário configurar também o cluster issuer. Detalhes sobre isso podem ser encontrados em nosso artigo "».
Eu optei pela opção do , decidindo que a versão staging do LE seria suficiente. Alteramos no exemplo o e-mail, salvamos no arquivo e adicionamos ao cluster ():
kubectl create -f cert-manager-cluster-issuer.yamlAgora pode-se solicitar um certificado, por exemplo, para o Grafana. Para isso, será necessário um domínio e acesso ao cluster externamente. Eu tenho o domínio, e configurei o tráfego redirecionando as portas 80 e 443 no meu roteador doméstico de acordo com o serviço criado do ingress-controller:
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 23dA porta 80, neste caso, é transmitida para 31303, enquanto a 443 é transmitida para 30498. (As portas são geradas aleatoriamente, portanto, as suas serão diferentes.)
Aqui está um exemplo 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-stagingAdicionamos-no ao cluster:
kubectl create -f cert-manager-grafana-certificate.yamlDepois disso, aparecerá o recurso Ingress, pelo qual a validação será feita pelo 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 Após a validação, veremos que o recurso certificate está pronto, e no segredo mencionado acima, grafana-tls está o certificado e a chave. Pode-se 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 X1Vamos voltar ao Grafana. Precisamos fazer algumas correções em seu release do Helm, alterando as configurações para TLS de acordo com o certificado criado.
Para isso, baixamos o chart, editamos e atualizamos a partir do diretório local:
helm pull --untar stable/grafana Editamos o arquivo grafana/values.yaml as configurações de TLS:
tls:
- secretName: grafana-tls
hosts:
- grafana.home.pi Aqui também podemos 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: trueAgora, atualizamos o chart do Grafana a partir do diretório local:
helm upgrade grafana --namespace monitoring ./grafana --set ingress.enabled=true --set ingress.hosts={"grafana.home.pi"} Verificamos se no Ingress grafana foi adicionado a porta 443 e se o acesso via HTTPS está disponível:
root@pi-control:~# kubectl -n monitoring get ing grafana
NOME CLASSE HOSTS ENDEREÇO PORTAS IDADE
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
data: Ter, 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 demonstrar o Grafana em ação, podemos baixar e adicionar . Veja como isso fica:

Recomendo também adicionar um dashboard para o node exporter: ele mostrará detalhadamente o que está acontecendo com as "malinas" (carga da CPU, uso de memória, rede, disco, etc.).
Após isso, considero que o cluster está pronto para receber e executar aplicativos!
Nota sobre a construção
Para construir aplicativos para a arquitetura ARM, existem pelo menos duas opções. Primeiro, você pode compilar em um dispositivo ARM. No entanto, olhando para a utilização atual de duas Raspberry Pi, percebi que elas não suportariam a compilação. Portanto, encomendei uma nova Raspberry Pi 4 (ela é mais poderosa e tem 4 GB de memória) — planejo compilar nela.
A segunda opção é a construção de uma imagem Docker multi-arquitetura em uma máquina mais potente. Para isso, existe . Se a aplicação estiver em um linguagem compilada, será necessário fazer a cross-compilação para ARM. Não vou descrever todas as configurações para esse caminho, pois isso renderia um artigo separado. Com essa abordagem, é possível alcançar imagens "universais": o Docker, rodando em uma máquina ARM, irá automaticamente baixar a imagem correspondente à arquitetura.
Conclusão
O experimento realizado superou todas as minhas expectativas: [no mínimo] o Kubernetes "vanilla" com a base necessária se comporta bem em ARM, e durante sua configuração surgiram apenas alguns detalhes.
Os próprios Raspberry Pi 3B+ conseguem lidar com a carga da CPU, no entanto, seus cartões SD são um claro gargalo. Colegas sugeriram que em algumas versões há a possibilidade de inicializar a partir de USB, onde é possível conectar um SSD: então, provavelmente, a situação melhorará.
Aqui está um exemplo de carga da CPU durante a instalação do Grafana:

Para experimentos e "para testar", na minha opinião, um cluster Kubernetes em Raspberry Pi transmite muito melhor a sensação de operação do que o mesmo Minikube, pois todos os componentes do cluster são instalados e funcionam "de maneira real".
A longo prazo, há a ideia de adicionar todo o ciclo de CI/CD ao cluster, totalmente implementado em Raspberry Pi. Eu também ficaria feliz se alguém compartilhasse sua experiência na configuração do K8s em AWS Graviton.
P.S. Sim, o "produção" pode estar mais perto do que eu pensava:

P.P.S.
Confira também em nosso blog:
- «».
Fonte: habr.com
