Criando e atualizando um cluster Kubernetes com kubeadm

Criando e atualizando um cluster Kubernetes com kubeadm

Introdução

Este post faz parte de uma série sobre a certificação CKACertified Kubernetes Administrator [1].

Dentre os tópicos que possuem mais peso na certificação, o item Cluster Architeture, Instalation & Configuration chama atenção por dois motivos:

  1. Faz referência a preparação de um cluster mínimo viável
  2. Apresenta uma forma canônica de instalação através da ferramenta kubeadm

Com a finalidade de apresentar uma forma de preparar um cluster para iniciar os estudos sobre Kubernetes, e ao mesmo tempo abordar itens que são cobrados nesta certificação, será apresentado a seguir um hands on que:

  1. Prepara uma infraestrutura local através do uso de máquinas virtuais com VirtualBox e Vagrant
  2. Utiliza o kubeadm para preparar um cluster kubernetes em uma determinada versão
  3. Performa a atualização do cluster para versão mais atualizada até o momento -v.1.23.5-00

Trata-se de um post de maior fôlego, com exemplos e a apresentação das saídas dos comandos: tudo isso para ficar mais fácil o entendimento e esclarecer qualquer dúvida durante a execução dos passos.

Pré Requisitos

Virtualização

Para simular o cluster de forma local, será preciso ter configurado um hypervisor em seu sistema operacional. O Virtualbox está presente em todos os sistemas operacionais, e por isso será a escolha deste hands on.

Além do Virtualbox, para não precisar realizar etapas de instalação de sistemas linux, será utilizado o Vagrant. Para instalar esses softwares acessem:

Requisitos computacionais

Do ponto de vista de recursos computacionais, na documentação oficial do kubernetes [2] é exigido de cada nó do cluster as seguintes configurações:

  • Um host linux compatível
  • 2GB de RAM
  • 2CPUs
  • Conectividade de rede entre os nós

Tendo em vista que um dos objetivos do laboratório é simular uma atualização do cluster, para termos uma experiência comportamental mais próxima da realidade, será preparado um cluster com 3 nós: o primeiro com a finalidade de ser o gerenciador, que na nomenclatura do kubernetes é chamado de control-plane e os demais como nós responsáveis por abrigar os workloads, chamados de worker nodes.

Nesse sentido, teremos que ter livre ao menos 6GB de RAM para subir essa infraestrutura. Entendemos que não são todos que possuem uma máquina com essas configurações e há alternativas possíveis utilizando recursos de cloud provider – quem optar por essa forma, não é necessário seguir os passos da seção a seguir, mas tenha em mente que algumas configurações de rede serão necessárias, e isso pode variar de acordo com o provedor escolhido e não será abordada nesse post.

Preparando as máquinas virtuais

Com o Virtuabox e Vagrant instalado em seu sistema, iremos instalar 3 máquinas com Linux, com a distribuição Debian na versão 11 (Bullseye).

É possível utilizar o kubernetes em outras distribuições, especialmente aquelas baseadas em Debian e Red Hat, para maiores detalhes, ver a documentação oficial para diretrizes específicas de acordo com a distribuição desejada.

Para facilitar a reprodutibilidade, o Vagrant será nosso gerenciador para implementar as máquinas virtuais – ele nos permite declarar nossa infraestrutura como código na linguagem ruby. O script a seguir será responsável por indicar a quantidade máquinas virtuais a ser criada, os recursos computacionais alocados para cada um, endereços de rede, entre outros elementos importantes.

Em sua máquina local, crie um diretório específico e um arquivo em branco chamado Vagrantfile. O conteúdo deste arquivo será o seguinte:

# -*- mode: ruby -*-
# vi: set ft=ruby :
## 1. Definindo as características das máquinas virtuais 
vms = {
  'control-plane' => {'memory' => '2048', 'cpus' => 2, 'ip' => '10', 'box' => 'geerlingguy/debian11'},
  'worker-1' => {'memory' => '2048', 'cpus' => 1, 'ip' => '20', 'box' => 'geerlingguy/debian11'},
  'worker-2' => {'memory' => '2048', 'cpus' => 1, 'ip' => '30', 'box' => 'geerlingguy/debian11'},
}
## 1. Aplicando as configurações no virtualbox
Vagrant.configure('2') do |config|
  config.vm.box_check_update = false ## opcional: adiar a checagem de updates
  vms.each do |name, conf| ## para cada item do dicionário de vms, faça:
    config.vm.define "#{name}" do |my|
      my.vm.box = conf['box'] ## seleciona a box com a distribuição linux a ser utilizada
      my.vm.hostname = "#{name}.example.com" ## define o hostname da máquina
      my.vm.network 'private_network', ip: "192.168.56.#{conf['ip']}" ## define o ip de cada máquina de acordo com o range do Virtualbox
      my.vm.provider 'virtualbox' do |vb|
        vb.memory = conf['memory'] ## define quantidade de recurso de memória
        vb.cpus = conf['cpus']     ## define quantidade de vCPUs 
      end
    end
  end
end

O vagrant possui um repositório de boxes, que nada mais são que imagens de sistemas operacionais já instalados prontos para serem importados em hypervisors quaisquer. No exemplo em questão estamos utilizando uma box mínima do Debian 11, criada pelo Jeff Geerling [3].

Não se preocupe em entender cada linha desse arquivo, apenas salve em seu diretório e em seguida digite na sua linha de comando vagrant up. Essa diretiva será responsável em ler as definições do arquivo Vagrantfile e aplicar no virtualbox – por ser 3 máquinas, pode ser que leve um tempo para criar e configurar as máquinas virtuais.

treinamento@4linux:~/infra/cka$ ls
Vagrantfile
treinamento@4linux:~/infra/cka$ vagrant up
Bringing machine 'control-plane' up with 'virtualbox' provider...
Bringing machine 'worker-1' up with 'virtualbox' provider...
Bringing machine 'worker-2' up with 'virtualbox' provider...
==> control-plane: Importing base box 'geerlingguy/debian11'...
(...continua)

Para checar se todas as máquinas foram criadas, após o término da execução do comando, utilize a diretiva vagrant status.

treinamento@4linux:~/infra/cka$ vagrant status
Current machine states:
control-plane             running (virtualbox)
worker-1                  running (virtualbox)
worker-2                  running (virtualbox)

This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.

Note que o vagrant irá associar os nomes definidos no Vagrantfile com as máquinas virtuais criadas. Isso será importante para diferenciar as máquinas entre si e realizar ações como acesso via secure shell.

Sobre a arquitetura de um cluster Kubernetes e o utilitário Kubeadm

Sob a perspectiva de software, o kubernetes é um sistema distribuído que possui diversos componentes, que em conjunto, tem a finalidade de orquestrar ambientes contêinerizados. Dentre estes componentes, existem aqueles que fazem parte da configuração de nó de gerenciamento (control plane), e também aqueles que são indispensáveis para comunicação e provisionamento de infraestrutura de contêineres.

O kubeadm visa instalar e configurar alguns destes componentes, a fim de facilitar a administração, ingresso de nós, atualização, entre outras funcionalidades. Ele serve tanto para nós com papel de control plane quanto para worker nodes.

No que se refere ao control plane, o kubeadm irá preparar a infraestrutura PKI para comunicação TLS entre os componentes, e a instalação dos componentes:

  • kubeapi-server
  • kube-controller-manager
  • kube-scheduler
  • etcd

Os componentes runtime estão de fora do escopo do kubeadm por serem requisitos para o funcionamento do cluster, e por isso será preciso instalar antes, de forma manual.

Configurações a nível de sistema operacional

Além dos requisitos computacionais mencionados anteriormente, cada nó do cluster precisa de algumas configurações específicas a nível de sistema operacional. Uma delas está relacionada à configuração do iptables para receber tráfego de pacotes no modo bridge, e a outra está ligada à remoção do suporte ao swap no sistema.

Deste modo, precisamos acessar a máquina através do comando vagrant ssh:

treinamento@4linux:~/infra/cka$ vagrant ssh control-plane
Linux control-plane 5.10.0-11-amd64 #1 SMP Debian 5.10.92-1 (2022-01-18) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
vagrant@control-plane:~$

Em seguida aplicaremos a configuração de swap:

vagrant@control-plane:~$ sudo swapoff -a

Para habilitar o tráfego em modo bridge no iptables, é preciso antes verificar se o módulo br_netfilter está carregado no sistema através do comando sudo lsmod | grep br_nefilter. Caso não esteja, será necessário carregar o módulo através do comando modprobe.

Para persistir a configuração no nó, é preciso adicionar no caminho /etc/modules-load.d/ um arquivo de configuração com os nomes dos módulos a serem carregados na inicialização:

cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
br_netfilter
EOF

sudo modprobe br_netfilter

Após carregar o módulo e adicionar a configuração, é preciso modificar os parâmetros do kernel atual para ativar a configuração:

# Adiciona parametrização
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

# Atualiza parâmetros sem precisar reiniciar
sudo sysctl --system

Container runtime

O Kubernetes precisa de um container runtime presente, seja um host de control-plane ou um worker node. Para este laboratório utilizaremos um container runtime diferente do Docker, tendo em vista que seu suporte será descontinuado a partir da versão v1.25+. Nesse sentido utilizaremos o CRI-O [4].

O processo de instalação do container runtime é bem parecido como qualquer outra instalação de serviço no Linux – é necessário 1) verificar a fonte dos pacotes; 2) adicionar o repositório na lista de repositórios do gerenciador de pacotes da sua distribuição; 3) atualizar os metadados dos pacotes e por fim 4) instalar os pacotes a partir do gerenciador de pacotes em questão.

No caso atual, estamos em uma distribuição Debian, e por isso utilizaremos o apt.

De acordo com as instruções no site do cri-o, é preciso adicionar o repositório de acordo com a versão do seu sistema. Para descobrir a versão da sua distribuição, basta executar o comando cat /etc/os-release. Como estamos utilizamos o Debian 11, podemos informar de forma direta.

Além da versão do sistema operacional, é preciso também informar a versão do cri-o. Em geral, as versões acompanham a versão do kubernetes. Iremos instalar a versão v.1.22 haja vista que simularemos uma atualização do cluster.

Com isso, de acordo com a informações de instalação oficial do cri-o, temos os seguintes comandos a serem executados:

# adiciona repositório nas listas de pacotes
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list

echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list

# adiciona as chaves dos repositórios
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION/$OS/Release.key | apt-key add -

curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | apt-key add -

# atualiza a lista de pacotes e instala o cri-o
apt-get update
apt-get install cri-o cri-o-runc

Note que devemos substituir as variáveis $OS e $VERSION por Debian_11 e 1.22 respectivamente. Isso pode ser feito de duas formas, a primeira exportando as variáveis com as diretivas export OS='Debian_11'; export VERSION='1.22' ou trocando manualmente:

vagrant@control-plane:~$ sudo su -c 'echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_11/ /"  > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list'                                                                
vagrant@control-plane:~$ sudo su -c 'echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.22/Debian_11/ /"> /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:1.22.list'
vagrant@control-plane:~$ curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:1.22/Debian_11/Release.key | sudo apt-key add -                            
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
100   389  100   389    0     0    325      0  0:00:01  0:00:01 --:--:--   325
100   390  100   390    0     0    244      0  0:00:01  0:00:01 --:--:--     0
100   391  100   391    0     0    194      0  0:00:02  0:00:02 --:--:--   194
100   392  100   392    0     0    161      0  0:00:02  0:00:02 --:--:--     0
100   393  100   393    0     0    138      0  0:00:02  0:00:02 --:--:--   138
100  1093  100  1093    0     0    337      0  0:00:03  0:00:03 --:--:--   337
OK
vagrant@control-plane:~$ curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/Debian_11/Release.key | sudo apt-key add -  
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
100  1093  100  1093    0     0    852      0  0:00:01  0:00:01 --:--:--   853
OK
vagrant@control-plane:$ sudo apt update && sudo apt install cri-o cri-o-runc
...

Após a concluída a instalação, podemos inspecionar o status do serviço do cri-o através do comando systemctl status crio.service :

vagrant@control-plane:~$ sudo systemctl status crio.service 
● crio.service - Container Runtime Interface for OCI (CRI-O)
     Loaded: loaded (/lib/systemd/system/crio.service; disabled; vendor preset: enabled)
     Active: inactive (dead)
       Docs: https://github.com/cri-o/cri-o

Note que o serviço está parado. Será necessário habilitá-lo para ser inicializado conjuntamente com o sistema e ao mesmo tempo iniciar o servico. Para tal, utilizamos sudo systemctl enable crio.service && sudo systemctl start crio.service.

vagrant@control-plane:~$ sudo systemctl enable crio.service && sudo systemctl start crio.service 
Created symlink /etc/systemd/system/cri-o.service → /lib/systemd/system/crio.service.
Created symlink /etc/systemd/system/multi-user.target.wants/crio.service → /lib/systemd/system/crio.service.
vagrant@control-plane:~$ systemctl status crio.service
● crio.service - Container Runtime Interface for OCI (CRI-O)
     Loaded: loaded (/lib/systemd/system/crio.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2022-04-22 13:21:12 UTC; 9s ago
       Docs: https://github.com/cri-o/cri-o
   Main PID: 3536 (crio)
      Tasks: 9
     Memory: 11.9M
        CPU: 110ms
     CGroup: /system.slice/crio.service
             └─3536 /usr/bin/crio
vagrant@control-plane:~$

Com isso temos todas as dependências satisfeitas e podemos prosseguir com a instalação de componentes do kubernetes e o próprio kubeadm.

kubeadm, kubelet e kubectl

Antes de performar as instalações do binários, é importante certificar se algumas dependências estão satisfeitas. De acordo com a documentação oficial é necessário instalar os seguintes pacotes:

  • apt-transport-https
  • ca-certificates
  • curl

Para tal:

vagrant@controlplane:~/$ sudo apt update && sudo apt install apt transport-https ca-certificates curl

Depois de instalado as dependências, vamos adicionar a chave publica do repositório do google referente aos componentes do kubernetes em questão:

vagrant@control-plane:~/$ sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
vagrant@control-plane:~/$ sudo su -c 'echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list'

Em seguida atualizamos a lista de pacotes e instalamos os binários kubeadm, kubectl e kubelet:

vagrant@control-plane:~/$ sudo apt update && sudo apt install kubelet=1.22.0-00 kubeadm=1.22.0-00 kubectl=1.22.0-00

Uma vez instalado os binários é importando aplicar uma flag que previne que esses pacotes sejam modificados e/ou atualizados automaticamente. Isso porque pode acontecer problemas no cluster se este processo não for feito de acordo com alguns protocolos. Para efetuar esse tipo de marcação, utilizamos o utilitário do apt apt-mark :

vagrant@control-plane:~/$ sudo apt-mark hold kubectl kubeadm kubelet
kubelet set on hold.
kubeadm set on hold.
kubectl set on hold.

Desta forma estamos prontos para performar o bootstrap do control-plane.

Bootstrap do Control Plane

Para performar o bootstrap do control-plane certifique-se que as configurações a nível de sistema operacional foram satifeitas. O kubeadm possui uma API específica para esta tarefa, que é a kubeadm init.

Existem algumas opções importantes, tendo em vista que estamos em um ambiente de máquinas virtuais utilizando Virtualbox – uma delas é referente ao IP que será exposto para o control-plane. Por padrão ele pega o endereço da interface padrão da máquina virtual em questão e isso pode ocasionar alguns conflitos haja vista que o Virtuabox cria uma interface NAT para que as máquinas possam sair para internet. Para evitar conflitos , utilizaremos o ip definido no Vagrant – ou seja, 192.168.56.10. Da mesma forma, definiremos o range dos PODs para que não tenha nenhum tipo de conflito com as configurações do virtuabox.

Para indicar isso no comando kubeadm init utilizamos os parâmetros --apiserver-advertise-address e --pod-network-cidr:

vagrant@control-plane:~/$ sudo kubeadm init --apiserver-advertise-address 192.168.56.10 --pod-network-cidr 10.244.0.0/16

A execução do kubeadm init pode levar alguns minutos, e sua saída é bem interessante pois lista as checagens e o que está sendo realizado. Ao final da execução, caso tudo tenha ocorrido de forma satisfatória, a seguinte mensagem será apresentada:

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

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

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/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.56.10:6443 --token bgkad3.sdumad8yig8thife \
    --discovery-token-ca-cert-hash sha256:1959fbfddb23f869b4e1f60f2a5d89b2f94e12df998c7da53e393855a0934b63

Guarde essas informações, especificamente a linha que contém o comando kubeadm join – ela será utilizada para ingressar os demais nós em nosso cluster.

IMPORTANTE: as informações de token e hash serão diferentes para cada execução. Na hora do ingresso dos outros nós, certifique-se de utilizar as informações que foram apresentados na sua saída padrão ao invés da apresentada neste post.

Com isso estamos quase prontos para ingressar os demais nós em nosso cluster, basta apenas configurar o plugin de rede. Existem diversos sendo desenvolvidos para o Kubernetes, e um dos mais famosos é o Calico [5]. Para aplicar em seu cluster utilize as seguintes diretivas:

vagrant@control-plane:~/$ sudo export KUBECONFIG=/etc/kubernetes/admin.conf
vagrant@control-plane:~/$ curl https://projectcalico.docs.tigera.io/manifests/calico.yaml -O > calico.yaml
vagrant@control-plane:~/$ kubectl apply -f calico.yaml
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/caliconodestatuses.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipreservations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
daemonset.apps/calico-node created
serviceaccount/calico-node created
deployment.apps/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
Warning: policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+; use policy/v1 PodDisruptionBudget
poddisruptionbudget.policy/calico-kube-controllers created

Pode demorar alguns minutos até que os pods do Calico estejam prontos, você pode acompanhar esse processo através do comando watch kubectl get nodes

Ao final desse processo, o control-plane deverá apresentar o seguinte status:

vagrant@control-plane:~$ kubectl get nodes
NAME            STATUS   ROLES                  AGE   VERSION
control-plane   Ready    control-plane,master   10m   v1.22.0

Com isso o control-plane está pronto. Para sair do watch utilize a tecla de atalho `CRTL + C`  retorne a sua máquina local através das teclas de atalho CRTL + D .

Ingressando Worker Nodes

Os worker nodes possuem quase as mesmas dependências em relação ao control-plane. Isso significa que será necessário:

  1. desabilitar o swap
  2. habilitar o módulo br_netfilter
  3. configurar o kernel para aceitar o tráfego
  4. instalar o container runtime
  5. instalar kubeadm, kubelet e kubectl
  6. aplicar a flag hold nos binários em questão

Para facilitar esse processo, acesse a máquina virtual worker-1 com o comando vagrant ssh worker-1 e crie um arquivo chamado setup-worker-node.sh com o seguinte conteúdo:

#!/bin/bash

#1: desabilitando swap

sudo swapoff -a

#2: configurações bridge e parâmetros do kernel
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
br_netfilter
EOF

sudo modprobe br_netfilter

cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sudo sysctl --system

#3: instalação do CRI-O
OS=Debian_11
CRIO_VERSION=1.22

echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /"|sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$CRIO_VERSION/$OS/ /"|sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION.list

curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$CRIO_VERSION/$OS/Release.key | sudo apt-key add -
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | sudo apt-key add -

sudo apt update && sudo apt install -y cri-o cri-o-runc

sudo systemctl enable crio.service

sudo systemctl start crio.service

#4: Instalação kubeadm, kubelet, kubectl

sudo apt-get install -y apt-transport-https ca-certificates curl

sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg

echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update
sudo apt-get install -y kubelet=1.22.0-00 kubeadm=1.22.0-00 kubectl=1.22.0-00
sudo apt-mark hold kubelet kubeadm kubectl

 

Após criado o arquivo, modifique a permissão do arquivo para se tornar executável com a diretiva chmod +x setup-worker-node.sh` e em seguida execute com o comando ./setup-worker-node.sh:


vagrant@worker-1:~/$ chmod +x setup-worker-node.sh
vagrant@worker-1:~/$ ./setup-worker-node.sh

 

Ao final da execução,  ingresse o nó no cluster com o comando que apareceu na saída do kubeadm init no processo de bootstrap do control-plane:

vagrant@worker1:~/$ sudo kubeadm join 192.168.56.10:6443 --token bgkad3.sdumad8yig8thife \
    --discovery-token-ca-cert-hash sha256:1959fbfddb23f869b4e1f60f2a5d89b2f94e12df998c7da53e393855a0934b63

Ao final do processo a seguinte mensagem será apresentada:


[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

Para o segundo worker node, repita os mesmos passos executados – copie o conteúdo do script, execute na máquina worker-2 e execute o comando kubeadm join informado na saída do bootstrap do control-plane.

Ao final, retorne ao nó do control-plane e execute a diretiva kubectl get nodes e verifique se todos os nós estão com status Ready:


root@control-plane:/home/vagrant# kubectl get nodes
NAME            STATUS   ROLES                  AGE     VERSION
control-plane   Ready    control-plane,master   93m     v1.22.0
worker-1        Ready    <none>                 44m     v1.22.0
worker-2        Ready    <none>                 3m18s   v1.22.0

Lembre-se de exportar o kubeconfig de admin: export KUBECONFIG=/etc/kubernetes/admin.conf

Testando o cluster

Para testar o nosso cluster, subiremos 2 aplicações idênticas, com configurações de replicas distintas para vermos a distribuição dos pods no cluster.

Para tal, utilizaremos o próprio nginx. Na documentação oficial sobre deployment há um exemplo de um manifest do nginx:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx1-deployment
  labels:
    app: nginx-1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-1
  template:
    metadata:
      labels:
        app: nginx-1
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx1-service
spec:
  type: NodePort
  selector:
    app: nginx-1
  ports:
  - port: 80
    protocol: TCP
    nodePort: 31000

Crie um arquivo chamado app1.yaml com o conteúdo acima – não se preocupe em entender cada linha do manifest, apenas estamos testando as funcionalidades do cluster. Para criar os pods no cluster use a diretiva kubectl apply -f app1.yaml.

Você pode inspecionar o local de criação dos pods através do comando kubectl get pods -o wide:

NAME                                 READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS GATES
nginx1-deployment-645c6857b7-b4vx6   1/1     Running   0          11m   10.244.226.65    worker-1   <none>           <none>
nginx1-deployment-645c6857b7-bm9w7   1/1     Running   0          11m   10.244.133.193   worker-2   <none>           <none>
nginx1-deployment-645c6857b7-rtm7m   1/1     Running   0          11m   10.244.133.194   worker-2   <none>           <none>

Você pode acessar diretamente através do comando curl http://192.168.56.20:31000

root@control-plane:/home/vagrant# curl 192.168.56.20:31000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Dica: você também pode acessar essa página através do seu navegador

Com a aplicação de pé, vamos criar mais duas apps, só que agora trocando o número de réplicas, nome e a porta para não termos conflito. Basta copiar o arquivo app1.yaml para app2.yaml e fazer as alterações:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx2-deployment
  labels:
    app: nginx-2
spec:
  replicas: 5
  selector:
    matchLabels:
      app: nginx-2
  template:
    metadata:
      labels:
        app: nginx-2
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx2-service
spec:
  type: NodePort
  selector:
    app: nginx-2
  ports:
  - port: 80
    protocol: TCP
    nodePort: 31002

Ao aplicar o manifest desta nova aplicação, teremos a seguinte disposição dos pods no cluster:

root@control-plane:/home/vagrant# kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS   AGE     IP               NODE       NOMINATED NODE   READINESS GATES
nginx1-deployment-645c6857b7-b4vx6   1/1     Running   0          17m     10.244.226.65    worker-1   <none>           <none>
nginx1-deployment-645c6857b7-bm9w7   1/1     Running   0          17m     10.244.133.193   worker-2   <none>           <none>
nginx1-deployment-645c6857b7-rtm7m   1/1     Running   0          17m     10.244.133.194   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-bx9ln   1/1     Running   0          2m46s   10.244.226.67    worker-1   <none>           <none>
nginx2-deployment-7cd5cb6d4b-nks2s   1/1     Running   0          2m46s   10.244.226.66    worker-1   <none>           <none>
nginx2-deployment-7cd5cb6d4b-nwt8q   1/1     Running   0          2m46s   10.244.133.195   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-s7ccg   1/1     Running   0          2m46s   10.244.133.196   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-wb4v9   1/1     Running   0          2m46s   10.244.226.68    worker-1   <none>           <none>

Com isso temos um cluster funcional com duas aplicações em execução. Veremos na seção a seguir como atualizar o cluster de forma a não impactar a execução das aplicações bem como manter a consistência do cluster.

Atualização de um cluster através do kubeadm

Antes de atualizar o cluster, é preciso ter alguns cuidados em mente:

  1. Verificar a versão atual do cluster
  2. Identificar qual a versão alvo para atualização
  3. Ver se é possível fazer a atualização direta ou se vai precisar atualizar para uma versão intermediária

Em nosso cenário, estamos na versão v1.22.0-00 . A versão mais atual do kubernetes no momento em que esse post foi escrito é v.1.23.5-00. De acordo com a documentação oficial, não é necessário fazer quaquer tipo de atualização intermediária tendo em vista que não estamos pulando alguma versão minor (estamos indo da v1.22 para 1.23*, se estivéssemos na versão v1.21 isso não seria possível de forma direta).

Os passos para update de um cluster de forma segura é realizar a atualização da versão de forma individual em cada nó, um de cada vez, sendo que durante a atualização do kubelet e kubectl, é necessário atualizar o estado do nó para modo de manutenção.

Atualizando o control plane

Para iniciarmos a atualização do control-plane, acesse sua máquina através do comando vagrant ssh control-plane. Em seguida torne-se root através da diretiva sudo su.

O primeiro passo para atualizar o cluster é fazer a atualização do kubeadm. Para tal, desmarque o kubeadm através da diretiva apt-mark unhold kubeadm e sem seguida atualize sua lista de pacotes e performe a atualização através do comando apt-mark unhold kubeadm && apt update && apt install kubeadm=1.23.5-00:

root@control-plane:/home/vagrant# sudo apt install kubeadm=1.23.5-00
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following held packages will be changed:
  kubeadm
The following packages will be upgraded:
  kubeadm
1 upgraded, 0 newly installed, 0 to remove and 52 not upgraded.
Need to get 8,582 kB of archives.
After this operation, 623 kB disk space will be freed.
Do you want to continue? [Y/n]

Após confirmar com Y, o gerenciador de pacotes irá realizar o processo de atualização. Após esse processo é possível verificar a versão do kubeadm com a diretiva kubeadm version

root@control-plane:/home/vagrant# kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.5", GitCommit:"c285e781331a3785a7f436042c65c5641ce8a9e9", GitTreeState:"clean", BuildDate:"2022-03-16T15:57:37Z", GoVersion:"go1.17.8", Compiler:"gc", Platform:"linux/amd64"}

Para verificar o plano de atualização do nó, podemos utilizar a diretiva kubeadm upgrade plan – desta forma irá verificar se há alguma pendência no nó, além de mostrar as possíveis atualizações e as ações necessárias pós atualização:

root@control-plane:/home/vagrant# kubeadm upgrade plan
[upgrade/config] Making sure the configuration is correct:
[upgrade/config] Reading configuration from the cluster...
[upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[preflight] Running pre-flight checks.
[upgrade] Running cluster health checks
[upgrade] Fetching available versions to upgrade to
[upgrade/versions] Cluster version: v1.22.9
[upgrade/versions] kubeadm version: v1.23.5
[upgrade/versions] Target version: v1.23.6
[upgrade/versions] Latest version in the v1.22 series: v1.22.9

Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT   CURRENT       TARGET
kubelet     3 x v1.22.0   v1.23.6

Upgrade to the latest stable version:

COMPONENT                 CURRENT   TARGET
kube-apiserver            v1.22.9   v1.23.6
kube-controller-manager   v1.22.9   v1.23.6
kube-scheduler            v1.22.9   v1.23.6
kube-proxy                v1.22.9   v1.23.6
CoreDNS                   v1.8.4    v1.8.6
etcd                      3.5.0-0   3.5.1-0

You can now apply the upgrade by executing the following command:

    kubeadm upgrade apply v1.23.6

Note: Before you can perform this upgrade, you have to update kubeadm to v1.23.6.

_____________________________________________________________________


The table below shows the current state of component configs as understood by this version of kubeadm.
Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or
resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually
upgrade to is denoted in the "PREFERRED VERSION" column.

API GROUP                 CURRENT VERSION   PREFERRED VERSION   MANUAL UPGRADE REQUIRED
kubeproxy.config.k8s.io   v1alpha1          v1alpha1            no
kubelet.config.k8s.io     v1beta1           v1beta1             no
_____________________________________________________________________

Note que durante este processo, foi identificado uma versão mais atualizada – v1.23.6. E há um aviso importate que, para atualizar para última versão estável, é necessário atualizar o kubeadm para a versão v1.23.6-00:

root@control-plane:~/$ apt update && apt install kubeadm=v1.23.6-00
(...)

Após a atualização, podemos verificar novamente se o plano de atualização é plausível com o comando kubeadm upgrade plan. Desta vez não aparecerá nenhum impedimento direto, inclusive aparecendo a mensagem com o comando a ser executado via kubeadm:

(...)
You can now apply the upgrade by executing the following command:

    kubeadm upgrade apply v1.23.6
(...)

Note que há também um aviso sobre o componente kubelet que deverá ser feita manualmente:

Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT   CURRENT       TARGET
kubelet     3 x v1.22.0   v1.23.6

Nesse sentido vamos aplicar a seguinte estratégia:

  1. atualizar o cluster com o kubeadm upgrade apply v1.23.6
  2. colocar o nó em modo de manutenção através da diretiva kubectl drain control-plane --ignore-daemonsets
  3. atualizar o kubelet para versão desejada
  4. retornar do modo de manutenção através do comando kubectl uncordon control-plane.

Para atualizar o cluster utilizamos o comando conforme a saída do comando do kubeadm upgrade plan:

root@control-plane:/home/vagrant# kubeadm upgrade apply v1.23.6
[upgrade/config] Making sure the configuration is correct:
[upgrade/config] Reading configuration from the cluster...
[upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[preflight] Running pre-flight checks.
[upgrade] Running cluster health checks
[upgrade/version] You have chosen to change the cluster version to "v1.23.6"
[upgrade/versions] Cluster version: v1.22.9
[upgrade/versions] kubeadm version: v1.23.6
[upgrade/confirm] Are you sure you want to proceed with the upgrade? [y/N]: y  
[upgrade/prepull] Pulling images required for setting up a Kubernetes cluster
[upgrade/prepull] This might take a minute or two, depending on the speed of your internet connection
[upgrade/prepull] You can also perform this action in beforehand using 'kubeadm config images pull'
[upgrade/apply] Upgrading your Static Pod-hosted control plane to version "v1.23.6"...
Static pod: kube-apiserver-control-plane hash: dbf53049950d64d0d51c2ac8821a8048
Static pod: kube-controller-manager-control-plane hash: 2330aca433980402f4f49d6603f2d898
Static pod: kube-scheduler-control-plane hash: 7fdfa0d609e3fd3c5a02e41c2c025484
[upgrade/etcd] Upgrading to TLS for etcd
Static pod: etcd-control-plane hash: e190aab32386f2b82b392ff88a0c43d9
[upgrade/staticpods] Preparing for "etcd" upgrade
[upgrade/staticpods] Renewing etcd-server certificate
[upgrade/staticpods] Renewing etcd-peer certificate
[upgrade/staticpods] Renewing etcd-healthcheck-client certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/etcd.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2022-04-22-17-56-13/etcd.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s)
Static pod: etcd-control-plane hash: e190aab32386f2b82b392ff88a0c43d9
Static pod: etcd-control-plane hash: 9b5786e55fd0df43b304d665a9ea0e35
[apiclient] Found 1 Pods for label selector component=etcd
[upgrade/staticpods] Component "etcd" upgraded successfully!
[upgrade/etcd] Waiting for etcd to become available
[upgrade/staticpods] Writing new Static Pod manifests to "/etc/kubernetes/tmp/kubeadm-upgraded-manifests324761829"
[upgrade/staticpods] Preparing for "kube-apiserver" upgrade
[upgrade/staticpods] Renewing apiserver certificate
[upgrade/staticpods] Renewing apiserver-kubelet-client certificate
[upgrade/staticpods] Renewing front-proxy-client certificate
[upgrade/staticpods] Renewing apiserver-etcd-client certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/kube-apiserver.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2022-04-22-17-56-13/kube-apiserver.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s)
Static pod: kube-apiserver-control-plane hash: dbf53049950d64d0d51c2ac8821a8048
Static pod: kube-apiserver-control-plane hash: 380f1d7b85b2a93ee6253013a4c90921
[apiclient] Found 1 Pods for label selector component=kube-apiserver
[upgrade/staticpods] Component "kube-apiserver" upgraded successfully!
[upgrade/staticpods] Preparing for "kube-controller-manager" upgrade
[upgrade/staticpods] Renewing controller-manager.conf certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/kube-controller-manager.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2022-04-22-17-56-13/kube-controller-manager.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s)
Static pod: kube-controller-manager-control-plane hash: 2330aca433980402f4f49d6603f2d898
Static pod: kube-controller-manager-control-plane hash: 1006a360e1a440e1c59344829009a790
[apiclient] Found 1 Pods for label selector component=kube-controller-manager
[upgrade/staticpods] Component "kube-controller-manager" upgraded successfully!
[upgrade/staticpods] Preparing for "kube-scheduler" upgrade
[upgrade/staticpods] Renewing scheduler.conf certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/kube-scheduler.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2022-04-22-17-56-13/kube-scheduler.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s)
Static pod: kube-scheduler-control-plane hash: 7fdfa0d609e3fd3c5a02e41c2c025484
Static pod: kube-scheduler-control-plane hash: 11441b8e7cd4bbf5926a2836d512e88f
[apiclient] Found 1 Pods for label selector component=kube-scheduler
[upgrade/staticpods] Component "kube-scheduler" upgraded successfully!
[upgrade/postupgrade] Applying label node-role.kubernetes.io/control-plane='' to Nodes with label node-role.kubernetes.io/master='' (deprecated)
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.23" in namespace kube-system with the configuration for the kubelets in the cluster
NOTE: The "kubelet-config-1.23" naming of the kubelet ConfigMap is deprecated. Once the UnversionedKubeletConfigMap feature gate graduates to Beta the default name will become just "kubelet-config". Kubeadm upgrade will handle this transition transparently.
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.23.6". Enjoy!

[upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.

Note essa etapa pode demorar alguns minutos pois algumas imagens de contêineres atualizadas serão baixadas, e a conexão da sua internet pode interferir no processo.

Com a atualização do cluster, precisamos atualizar o kubelet. Só que esse é um serviço essencial e pode influenciar no comportamento do cluster, e por isso é necessário aplicar o modo de manutenção no nó em questão através do comando kubectl drain control-plane --ignore-daemonsets:

root@control-plane:/home/vagrant# kubectl drain control-plane --ignore-daemonsets
node/control-plane cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-xjrhf, kube-system/kube-proxy-kr55r
evicting pod kube-system/calico-kube-controllers-65898446b5-ns9ht
pod/calico-kube-controllers-65898446b5-ns9ht evicted
node/control-plane evicted

Agora é possível atualizar o serviço do kubelet com o gerenciador de pacotes. Como tínhamos sinalizado anteriomente, é preciso desmarcar os pacotes kubelet e kubectl :

root@control-plane:/home/vagrant# apt-mark unhold kubelet kubectl
Canceled hold on kubelet.
Canceled hold on kubectl.

Agora é possível atualizar os pacotes normalmente através da diretiva apt-get install:

root@control-plane:/home/vagrant# apt install kubelet=1.23.6-00 kubectl=1.23.6-00
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages will be upgraded:
  kubectl kubelet
2 upgraded, 0 newly installed, 0 to remove and 50 not upgraded.
Need to get 28.4 MB of archives.
After this operation, 29.1 MB disk space will be freed.
Get:1 https://packages.cloud.google.com/apt kubernetes-xenial/main amd64 kubectl amd64 1.23.6-00 [8,933 kB]
Get:2 https://packages.cloud.google.com/apt kubernetes-xenial/main amd64 kubelet amd64 1.23.6-00 [19.5 MB]
Fetched 28.4 MB in 18s (1,538 kB/s)                                                                            
Reading changelogs... Done
(Reading database ... 37737 files and directories currently installed.)
Preparing to unpack .../kubectl_1.23.6-00_amd64.deb ...
Unpacking kubectl (1.23.6-00) over (1.22.0-00) ...
Preparing to unpack .../kubelet_1.23.6-00_amd64.deb ...
Unpacking kubelet (1.23.6-00) over (1.22.0-00) ...
Setting up kubectl (1.23.6-00) ...
Setting up kubelet (1.23.6-00) ...

Ao final da atualização retornamos o estado com o apt-mark hold kubeadm kubectl kubelet. Além de impedir a atualização por softwares terceiros, é necessário reinicializar o serviço do kubelet:

root@control-plane:/home/vagrant# systemctl daemon-reload
root@control-plane:/home/vagrant# systemctl restart kubelet
root@control-plane:/home/vagrant# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    Drop-In: /etc/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Fri 2022-04-22 18:05:40 UTC; 59s ago
       Docs: https://kubernetes.io/docs/home/
   Main PID: 113952 (kubelet)
      Tasks: 14 (limit: 2336)
     Memory: 32.1M
        CPU: 1.721s

Com isso o nó está pronto para retornar ao estado normal. Para tal utilize o comando kubectl uncordon control-plane:

root@control-plane:/home/vagrant# kubectl uncordon control-plane
node/control-plane uncordoned
root@control-plane:/home/vagrant# kubectl get nodes -o wide
NAME            STATUS   ROLES                  AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION    CONTAINER-RUNTIME
control-plane   Ready    control-plane,master   3h7m   v1.23.6   10.0.2.15     <none>        Debian GNU/Linux 11 (bullseye)   5.10.0-11-amd64   cri-o://1.22.3
worker-1        Ready    <none>                 138m   v1.22.0   10.0.2.15     <none>        Debian GNU/Linux 11 (bullseye)   5.10.0-11-amd64   cri-o://1.22.3
worker-2        Ready    <none>                 97m    v1.22.0   10.0.2.15     <none>        Debian GNU/Linux 11 (bullseye)   5.10.0-11-amd64   cri-o://1.22.3

Com isso o control-plane foi atualizado. Agora é preciso fazer o mesmo processo nos worker nodes, um de cada vez, como veremos à seguir.

Atualizando Worker nodes

O processo é semelhante ao control-plane, porém é mais simples tendo em vista que há menos componenetes nos workers do que no control-plane.

Acesse em outra aba do seu terminal a máquina worker-1 com o comando vagrant ssh worker-1.

As etapas serão as mesmas:

  1. atualizar o kubeadm para a versão alvo
  2. aplicar a atualização
  3. colocar o nó worker-1 em modo de manutenção
  4. atualizar o kubelet
  5. sair do modo de manutenção

É importante dizer que as configurações de acesso estão restritas ao nó control plane. A criação de kubeconfigs e service accounts estão fora do escopo deste post, e por isso, as diretivas kubectl serão executadas na máquina virtual do control-plane.

Na máquina worker node, atualize o kubeadm com o seguinte comando:

root@worker-1:/home/vagrant# sudo apt-mark unhold kubeadm
Canceled hold on kubeadm.
root@worker-1:/home/vagrant# apt update && apt install -y kubeadm=1.23.6-00

Para atualizar o nó com o kubeadm o comando se modifica:

root@worker-1:/home/vagrant# kubeadm upgrade node
[upgrade] Reading configuration from the cluster...
[upgrade] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[preflight] Running pre-flight checks
[preflight] Skipping prepull. Not a control plane node.
[upgrade] Skipping phase. Not a control plane node.
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[upgrade] The configuration for this node was successfully updated!
[upgrade] Now you should go ahead and upgrade the kubelet package using your package manager.

Pronto, agora é necessário colocar o nó em manutenção para poder atualizar o kubelet. Você pode estar se perguntando o que acontece com as aplicações que estão ativas no nó. O comando kubectl drain realiza a realocação dos pods que estão no nó alvo de manutenção, mantendo assim a sua aplicação de pé. podemos ver esse comportamento inspecionando os pods antes e depois:

# antes do modo de manutenção
root@control-plane:/home/vagrant# kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS GATES
nginx1-deployment-645c6857b7-b4vx6   1/1     Running   0          87m   10.244.226.65    worker-1   <none>           <none>
nginx1-deployment-645c6857b7-bm9w7   1/1     Running   0          87m   10.244.133.193   worker-2   <none>           <none>
nginx1-deployment-645c6857b7-rtm7m   1/1     Running   0          87m   10.244.133.194   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-bx9ln   1/1     Running   0          72m   10.244.226.67    worker-1   <none>           <none>
nginx2-deployment-7cd5cb6d4b-nks2s   1/1     Running   0          72m   10.244.226.66    worker-1   <none>           <none>
nginx2-deployment-7cd5cb6d4b-nwt8q   1/1     Running   0          72m   10.244.133.195   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-s7ccg   1/1     Running   0          72m   10.244.133.196   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-wb4v9   1/1     Running   0          72m   10.244.226.68    worker-1   <none>           <none>
root@control-plane:/home/vagrant# kubectl drain worker-1 --ignore-daemonsets
node/worker-1 cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-fvxbh, kube-system/kube-proxy-5wqpg
evicting pod default/nginx1-deployment-645c6857b7-b4vx6
evicting pod kube-system/coredns-64897985d-nw2xb
evicting pod default/nginx2-deployment-7cd5cb6d4b-nks2s
evicting pod default/nginx2-deployment-7cd5cb6d4b-bx9ln
evicting pod default/nginx2-deployment-7cd5cb6d4b-wb4v9
evicting pod kube-system/calico-kube-controllers-65898446b5-hfgks
pod/nginx2-deployment-7cd5cb6d4b-nks2s evicted
pod/nginx1-deployment-645c6857b7-b4vx6 evicted
pod/nginx2-deployment-7cd5cb6d4b-bx9ln evicted
pod/calico-kube-controllers-65898446b5-hfgks evicted
pod/nginx2-deployment-7cd5cb6d4b-wb4v9 evicted
pod/coredns-64897985d-nw2xb evicted
node/worker-1 drained

# inspecionando o nós depois do modo drain
root@control-plane:/home/vagrant# kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS GATES
nginx1-deployment-645c6857b7-7j8f4   1/1     Running   0          43s   10.244.133.202   worker-2   <none>           <none>
nginx1-deployment-645c6857b7-bm9w7   1/1     Running   0          88m   10.244.133.193   worker-2   <none>           <none>
nginx1-deployment-645c6857b7-rtm7m   1/1     Running   0          88m   10.244.133.194   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-674fp   1/1     Running   0          43s   10.244.133.203   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-c7kfr   1/1     Running   0          43s   10.244.133.201   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-nwt8q   1/1     Running   0          73m   10.244.133.195   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-s7ccg   1/1     Running   0          73m   10.244.133.196   worker-2   <none>           <none>
nginx2-deployment-7cd5cb6d4b-z8x22   1/1     Running   0          43s   10.244.133.200   worker-2   <none>           <none>

Veja que todos os pods foram direcionados para o nó worker-2 e o estado atual do nó é o seguinte:

NAME            STATUS                     ROLES                  AGE     VERSION
control-plane   Ready                      control-plane,master   3h26m   v1.23.6
worker-1        Ready,SchedulingDisabled   <none>                 156m    v1.22.0
worker-2        Ready                      <none>                 115m    v1.22.0

Note que o control-plane irá evitar de alocar pods no worker-1 tendo em vista que o scheduling está desabilitado. Com isso conseguimos prosseguir com a atualização do kubelet:

root@worker-1:/home/vagrant# apt-mark unhold kubelet
Canceled hold on kubelet.
root@worker-1:/home/vagrant# apt install kubelet=1.23.6-00
Reading package lists... Done
(...)
root@worker-1:/home/vagrant# systemctl daemon-reload
root@worker-1:/home/vagrant# systemctl restart kubelet
root@worker-1:/home/vagrant# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    Drop-In: /etc/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Fri 2022-04-22 18:29:22 UTC; 7s ago
       Docs: https://kubernetes.io/docs/home/
   Main PID: 55547 (kubelet)
      Tasks: 12 (limit: 2336)
     Memory: 29.3M
        CPU: 310ms
root@worker-1:/home/vagrant# apt-mark hold kubeadm kubelet
kubeadm set on hold.
kubelet set on hold.

Após essa etapa podemos retirar o modo manutenção no control-plane:

root@control-plane:/home/vagrant# kubectl uncordon worker-1
node/worker-1 uncordoned
root@control-plane:/home/vagrant# kubectl get nodes
NAME            STATUS   ROLES                  AGE     VERSION
control-plane   Ready    control-plane,master   3h30m   v1.23.6
worker-1        Ready    <none>                 161m    v1.23.6
worker-2        Ready    <none>                 120m    v1.22.0

Com isso temos o segundo nó atualizado. Repita os procedimentos no worker-2 e teremos o cluser atualizado:

root@control-plane:/home/vagrant# kubectl get nodes
NAME            STATUS   ROLES                  AGE     VERSION
control-plane   Ready    control-plane,master   3h30m   v1.23.6
worker-1        Ready    <none>                 161m    v1.23.6
worker-2        Ready    <none>                 120m    v1.23.6

E assimencerramos esse hands on sobre preparação, instalação e atualização de um cluster Kubernetes através do kubeadm.

Fique atento ao nosso blog pois mais posts sobre a CKA virão por aí.

Até lá!

Notas

[1]: Ver ementa oficial em: https://training.linuxfoundation.org/certification/certified-kubernetes-administrator-cka/

[2]: Ver : https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#before-you-begin

[3]: O Jeff Geerling ou GeerlingGuy como é conhecido, é um membro ativo da comunidade open source, já fez parte do conselho técnico da ferramenta Ansible da Red Hat. Sobre a box, o motivo de sua escolha é pela curadoria apurada e por ser uma das menores em termos de tamanho em disco, o que facilita no processo de download.

[4]: Para maiores detalhes ver: https://cri-o.io/

[5]: Para saber mais sobre o projeto calico acesse: https://www.tigera.io/project-calico/

Referências

SPENNEBERG, R. Bridgewalling- Using Netfilter in Bridge Mode.

Líder em Treinamento e serviços de Consultoria, Suporte e Implantação para o mundo open source. Conheça nossas soluções:

CURSOSCONSULTORIA

Anterior PGCONF 2022: Maior conferência de PostgreSQL do Brasil retorna presencialmente
Próxima React Native ou Flutter: Qual é a melhor tecnologia para desenvolvimento mobile?

About author

Guilherme Zanelato
Guilherme Zanelato 5 posts

Guilherme Zanelato é formado em Ciências Sociais pela universidade de São Paulo e atualmente cursa Análise e Desenvolvimento de Sistemas na FATEC. Possui mais de 2 anos de experiência em desenvolvimento e 4 anos lidando com tecnologias open source. Possui a certificação PCAP - Certified Associate in Python Programming.

View all posts by this author →

Você pode gostar também

Infraestrutura TI

Descubra como o Puppet pode otimizar a administração de sistemas Open Source

  Durante muito tempo o cotidiano do sysadmin de soluções Open Source foi regido por scripts caseiros que, aparentemente, eram capazes de solucionar todas as questões do dia a dia

Containers

Descubra como o Skaffold pode otimizar seu trabalho com Kubernetes

Olá pessoal! A ideia para este post surgiu quando me deparei com o problema, que todos que trabalham ou vão trabalhar com Kubernetes enfrentam: a necessidade de a cada simples

Containers

Descubra o poder do Kubernetes e como ele pode impulsionar suas aplicações

    Kubernetes ou “k8s” para os íntimos, é uma ferramenta de código aberto originalmente desenvolvido pela Google, utilizado para a orquestração de containers, ou seja, para implantação, dimensionamento e