# Multipass e Kubespray
O [multipass](https://multipass.run/) é uma ferramenta excelente para realizar testes que precisam simular máquinas virtuais, pois é mais simples e rápido de inicializar em comparação com o VirtualBox ou lxc/lxd.
Nesse documento vamos utilizar o Multipass para realizar a instalação de um cluster kubernetes utilizando o [kubespray](https://kubespray.io/#/).
## Multipass
### Instalação do multipass
Se estiver usando um ambiente baseado em arch-linux, pode utilizar o comando abaixo. Para outras opções, acesse a [documentação oficial](https://multipass.run/install).
```shell=
yay -S canonical-multipass
```
Após a instalação, provavelmente será necessário habilitar e inicializar o serviço.
```shell=
# Execute o comando abaixo para verificar se o serviço está ativo e rodando, provavelmente não estará
$ systemctl status multipassd
○ multipassd.service - Multipass is a mini-cloud on your workstation using native hypervisors
Loaded: loaded (/usr/lib/systemd/system/multipassd.service; disabled; preset: disabled)
Active: inactive (dead)
# Habilite e inicie o serviço:
$ sudo systemctl enable --now multipassd
Created symlink /etc/systemd/system/multi-user.target.wants/multipassd.service → /usr/lib/systemd/system/multipassd.service.
# Execute o status novamente pra validar se está ok
$ systemctl status multipassd
● multipassd.service - Multipass is a mini-cloud on your workstation using native hypervisors
Loaded: loaded (/usr/lib/systemd/system/multipassd.service; enabled; preset: disabled)
Active: active (running) since Fri 2024-05-31 15:28:24 -03; 8s ago
Main PID: 32822 (multipassd)
Tasks: 45 (limit: 77040)
Memory: 120.7M (peak: 121.4M)
CPU: 424ms
CGroup: /system.slice/multipassd.service
├─32822 /usr/bin/multipassd
└─32934 /usr/bin/dnsmasq --keep-in-foreground --strict-order --bind-interfaces --pid-file --domain=multipass --local=/multipass/ --except-interface=lo --interface=mpqemubr0 --listen-address=10.183.57.1 --dhcp-no-overri>
mai 31 15:28:24 roberto-desktop dnsmasq-dhcp[32934]: read /var/lib/multipassd/.local/share/multipassd/network/dnsmasq.hosts
mai 31 15:28:24 roberto-desktop multipassd[32822]: # Warning: iptables-legacy tables present, use iptables-legacy to see them
mai 31 15:28:24 roberto-desktop multipassd[32822]: # Warning: iptables-legacy tables present, use iptables-legacy to see them
mai 31 15:28:24 roberto-desktop multipassd[32822]: # Warning: iptables-legacy tables present, use iptables-legacy to see them
mai 31 15:28:24 roberto-desktop multipassd[32822]: # Warning: iptables-legacy tables present, use iptables-legacy to see them
mai 31 15:28:24 roberto-desktop multipassd[32822]: Using iptables-legacy for firewall rules.
mai 31 15:28:24 roberto-desktop multipassd[32822]: gRPC listening on unix:/run/multipass_socket
mai 31 15:28:24 roberto-desktop multipassd[32822]: Starting Multipass 1.13.0-dev.0+g55d5eed55
mai 31 15:28:24 roberto-desktop multipassd[32822]: Daemon arguments: /usr/bin/multipassd
```
Execute o comando abaixo para validar se está tudo ok.
```shell=
$ multipass list
```
### Criação do cluster
Agora nós vamos criar um cluster com 3 nós para simular um ambiente de alta disponibilidade do kubernetes.
Para que o cluster seja inicializado sem problemas, é necessário configurar pelo menos 8GB de memória RAM para cada instância.
```shell=
multipass launch 24.04 --name mp1 --memory 10G --disk 30G --cpus 4
multipass launch 24.04 --name mp2 --memory 10G --disk 30G --cpus 4
multipass launch 24.04 --name mp3 --memory 10G --disk 30G --cpus 4
```
### Criação do usuário kubespray
Vamos criar um usuário específico para o kubespray realizar a instalação. Como esse usuário precisará executar comandos de root, vamos adicioná-lo no grupo sudoers.
```shell=
multipass exec mp1 -- \
sudo useradd --base-dir /home \
--shell /bin/bash \
--gid 0 \
--create-home \
--password $(perl -e 'print crypt(kubespray, "password")') \
kubespray
multipass exec mp2 -- \
sudo useradd --base-dir /home \
--shell /bin/bash \
--gid 0 \
--create-home \
--password $(perl -e 'print crypt(kubespray, "password")') \
kubespray
multipass exec mp3 -- \
sudo useradd --base-dir /home \
--shell /bin/bash \
--gid 0 \
--create-home \
--password $(perl -e 'print crypt(kubespray, "password")') \
kubespray
# Adiciona o usuário no grupo sudo
multipass exec mp1 -- \
sudo usermod -a -G sudo kubespray
multipass exec mp2 -- \
sudo usermod -a -G sudo kubespray
multipass exec mp3 -- \
sudo usermod -a -G sudo kubespray
```
```shell=
# Talvez precise adicionar o usuário kubespray no sudoers para permitir comandos root sem pedir a senha
$ echo "kubespray ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/91-kubespray
```
### Configuração do ssh
O kubespray (ansible) acessa as máquinas via ssh, por isso é necessário configurar o acesso a partir do usuário `kubespray` que criamos no passo anterior.
```shell=
# mp1
multipass exec mp1 -- sudo sed -ri 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config.d/60-cloudimg-settings.conf
multipass exec mp1 -- sudo sed -ri 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config
multipass exec mp1 -- sudo systemctl reload ssh
# mp2
multipass exec mp2 -- sudo sed -ri 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config.d/60-cloudimg-settings.conf
multipass exec mp2 -- sudo sed -ri 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config
multipass exec mp2 -- sudo systemctl reload ssh
# mp3
multipass exec mp3 -- sudo sed -ri 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config.d/60-cloudimg-settings.conf
multipass exec mp3 -- sudo sed -ri 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config
multipass exec mp3 -- sudo systemctl reload ssh
#PermitRootLogin prohibit-password
```
Para testar a conexão ssh, execute o comando abaixo
```shell=
# mp1
ssh kubespray@$(multipass info --format json mp1 | jq -r .info.[].ipv4[0])
# mp2
ssh kubespray@$(multipass info --format json mp2 | jq -r .info.[].ipv4[0])
# mp3
ssh kubespray@$(multipass info --format json mp3 | jq -r .info.[].ipv4[0])
```
Todos os comandos executas acima foram concentrados no arquivo abaixo para facilitar a execução.
```shell=
$ cat multipass-create.sh
#!/bin/bash
name=${1}
if [[ -z ${name} ]]; then
echo "Invalid name: '${name}'"
exit 1
fi
echo "Creating multipass with name ${name}"
# multipass launch
multipass launch 24.04 --name ${name} --memory 10G --disk 30G --cpus 4
# kubespray user
multipass exec ${name} -- \
sudo useradd --base-dir /home \
--shell /bin/bash \
--gid 0 \
--create-home \
--password $(perl -e 'print crypt(kubespray, "password")') \
kubespray
# add kubespray user to sudoers
multipass exec ${name} -- sudo usermod -a -G sudo kubespray
# Configure ssh to allow ssh with password
multipass exec ${name} -- sudo sed -ri 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config.d/60-cloudimg-settings.conf
multipass exec ${name} -- sudo sed -ri 's/#PasswordAuthentication yes/PasswordAuthentication yes/g' /etc/ssh/sshd_config
multipass exec ${name} -- sudo systemctl reload ssh
# Connect via ssh
ssh kubespray@$(multipass info --format json ${name} | jq -r .info.[].ipv4[0])
```
Execute passando o nome da instância, por exemplo:
```shell=
./multipass-create.sh mp1
```
## Kubespray
O kubespray pode ser executado a partir de qualquer local que possua acesso as máquinas que serão utilizadas para a instalação. Para mais detalhes [veja](https://kubespray.io/#/docs/getting_started/setting-up-your-first-cluster?id=set-up-kubespray).
```shell=
git clone https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
# Veja a página de releases para pegar a última versão
# https://github.com/kubernetes-sigs/kubespray/releases
git checkout release-2.25
```
<!-- Vamos criar um ambiente virtual python para instalar as dependências.
```shell=
conda create --name tce2-kubespray python=3.9 --yes
conda activate tce2-kubespray
pip install -r requirements.txt
``` -->
Agora nós vamos criar nosso arquivo de configuração baseado no arquivo `sample`.
```shell=
cp -rfp inventory/sample inventory/multipass
```
O comando abaixo vai configurar os IPs no arquivo `inventory/multipass/hosts.yaml`.
```shell=
declare -a IPS=$(multipass info --format json | jq -r .info.[].ipv4[0] | grep -v null | tr "\n" " ")
CONFIG_FILE=inventory/multipass/hosts.yaml python contrib/inventory_builder/inventory.py $IPS[@]
```
Observe que o comando acima não funciona com o `fish`, caso esteja usando, execute o comando `bash` antes do `declare`.
Faça um cat no arquivo `inventory/multipass/hosts.yaml` pra ver como ficou, abaixo está um exemplo.
```shell=
$ cat inventory/multipass/hosts.yaml
all:
hosts:
node1:
ansible_host: 10.183.57.178
ip: 10.183.57.178
access_ip: 10.183.57.178
node2:
ansible_host: 10.183.57.7
ip: 10.183.57.7
access_ip: 10.183.57.7
node3:
ansible_host: 10.183.57.85
ip: 10.183.57.85
access_ip: 10.183.57.85
children:
kube_control_plane:
hosts:
node1:
node2:
kube_node:
hosts:
node1:
node2:
node3:
etcd:
hosts:
node1:
node2:
node3:
k8s_cluster:
children:
kube_control_plane:
kube_node:
calico_rr:
hosts: {}
```
<!-- Nós precisamos editar o arquivo `inventory/multipass/group_vars/k8s_cluster/k8s-cluster.yml` para adicionar os IPs dos controllers para que depois seja possível conectar no cluster a partir de outros nós.
Abaixo é um exemplo de como ficou essa configuração.
```yaml=
## Supplementary addresses that can be added in kubernetes ssl keys.
## That can be useful for example to setup a keepalived virtual IP
supplementary_addresses_in_ssl_keys: [10.183.57.178, 10.183.57.7, 10.183.57.85]
``` -->
### sshpass
Antes de executar o playbook do ansible, garanta que o utilitário `sshpass` está instalado.
```shell=
yay -S sshpass
```
Comando para fazer a instalação. Observe o parâmetro `--extra-vars`, que nós passamos a senha criada para o usuário `kubespray`.
```shell=
# Não se esqueça de habilitar o environment que foi usado para instalar o kubespray/ansible
$ conda activate tce2-kubespray
$ ansible-playbook --inventory inventory/multipass/hosts.yaml \
--user kubespray \
--become --verbose \
--extra-vars "ansible_password=kubespray ansible_sudo_pass=kubespray" \
cluster.yml
```
## Após instalação
Para poder acessar o cluster, é necessário utilizar o arquivo de configuração, o comando abaixo disponibiliza um arquivo no local padrão.
```shell=
# Entre em algum node do multipass
$ multipass exec mp1 -- bash
mkdir ~/.kube
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $USER ~/.kube/config
```
Lembrando que esse arquivo só estará disponível nos servidores que são do tipo `control-plane`.
Execute o comando abaixo para validar o cluster.
```shell=
alias k=kubectl
$ k get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
node1 Ready control-plane 40m v1.29.5 10.183.57.178 <none> Ubuntu 24.04 LTS 6.8.0-31-generic containerd://1.7.16
node2 Ready control-plane 39m v1.29.5 10.183.57.7 <none> Ubuntu 24.04 LTS 6.8.0-31-generic containerd://1.7.16
node3 Ready <none> 39m v1.29.5 10.183.57.85 <none> Ubuntu 24.04 LTS 6.8.0-31-generic containerd://1.7.16
```
Por padrão, o `container runtime` utilizado é o cri-o, portanto o utilitário de linha de comando é o `crictl`. Você pode configurar um alias para o comando docker, caso esteja mais habituado a ele.
```shell=
# É necessário dar permissão ao socket do containerd.
$ sudo chmod 777 /var/run/containerd/containerd.sock
$ crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
3e8c29870f5a4 e579eb50f57be 32 minutes ago Running kube-scheduler 2 51e864b9b391f kube-scheduler-node1
372695cc2813b 2242ad7f7c41a 32 minutes ago Running kube-controller-manager 2 291a7d7168628 kube-controller-manager-node1
4ad009ded79e3 59d295ba73230 42 minutes ago Running node-cache 0 5bfe47d64d882 nodelocaldns-8xr4z
8ccaa17273a96 cbb01a7bd410d 42 minutes ago Running coredns 0 860bc98cee928 coredns-69db55dd76-vkxbx
d33407fe62c4d 5c6ffd2b2a1d0 42 minutes ago Running calico-node 0 b8b6941557a32 calico-node-x75kn
93de5e2096273 2019bbea5542a 42 minutes ago Running kube-proxy 0 f78c312d7e10f kube-proxy-6gbsk
26c7d93f61e2f b36112597a5f1 43 minutes ago Running kube-apiserver 0 d1a7d37df66bb kube-apiserver-node1
$ alias docker=crictl
$ docker ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
3e8c29870f5a4 e579eb50f57be 32 minutes ago Running kube-scheduler 2 51e864b9b391f kube-scheduler-node1
372695cc2813b 2242ad7f7c41a 32 minutes ago Running kube-controller-manager 2 291a7d7168628 kube-controller-manager-node1
4ad009ded79e3 59d295ba73230 42 minutes ago Running node-cache 0 5bfe47d64d882 nodelocaldns-8xr4z
8ccaa17273a96 cbb01a7bd410d 42 minutes ago Running coredns 0 860bc98cee928 coredns-69db55dd76-vkxbx
d33407fe62c4d 5c6ffd2b2a1d0 42 minutes ago Running calico-node 0 b8b6941557a32 calico-node-x75kn
93de5e2096273 2019bbea5542a 43 minutes ago Running kube-proxy 0 f78c312d7e10f kube-proxy-6gbsk
26c7d93f61e2f b36112597a5f1 44 minutes ago Running kube-apiserver 0 d1a7d37df66bb kube-apiserver-node1
```
Abaixo tem alguns comandos para iniciar um deployment e um service para validar melhor o funcionamento.
Esses comandos foram extraídos da [documentação oficial](https://kubernetes.io/docs/tutorials/hello-minikube/) do k8s.
```shell=
# Cria um deployment
$ kubectl create deployment hello-node --image=registry.k8s.io/e2e-test-images/agnhost:2.39 -- /agnhost netexec --http-port=8080
deployment.apps/hello-node created
# Lista os pods
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
hello-node-ccf4b9788-xzc2z 1/1 Running 0 13s 10.233.71.3 node3
# Cria um service
$ kubectl expose deployment hello-node --port=8080
service/hello-node exposed
# Lista os services
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-node ClusterIP 10.233.42.89 <none> 8080/TCP 24s
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 50m
# Abaixo estão todas as possibilidades de acesso ao serviço
# A partir do nome do serviço
$ curl -w '\n' http://hello-node:8080
NOW: 2024-06-01 22:21:41.489750998 +0000 UTC m=+258.170006187
# A partir do IP do service
$ curl -w '\n' http://10.233.42.89:8080
NOW: 2024-06-01 22:22:27.379604952 +0000 UTC m=+304.059860151
# A partir do IP do pod
$ curl -w '\n' http://10.233.71.3:8080
NOW: 2024-06-01 22:23:32.921088652 +0000 UTC m=+369.601343851
```
É possível acessar o serviço de fora do cluster kubernetes, vamos testar utilizando o serviço do tipo `NodePort`.
```shell=
# Vamos recriar o service com o novo tipo
$ kubectl delete svc hello-node
service "hello-node" deleted
$ kubectl expose deployment hello-node --type=NodePort --port=8080
service/hello-node exposed
$ kubectl get svc hello-node
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-node NodePort 10.233.27.128 <none> 8080:32047/TCP 18s
```
Observe que o service agora está listado com type=NodePort e a coluna port(s) contém um outro valor após o `8080`, nesse caso a porta 32047, essa porta será utilizada para acessar o serviço de fora do cluster.
```shell=
$ curl -w '\n' http://10.183.57.85:32047
NOW: 2024-06-01 22:28:36.225014115 +0000 UTC m=+672.905269304
```
O IP utilizado acima é o do `node3` do cluster.
### etcd
Caso você queira validar o cluster de etcd, utilize o comando abaixo como exemplo:
```shell=
etcdctl member list \
--endpoints 10.183.57.141:2379 \ # IP do pod que tem o etcd
--cert /etc/kubernetes/ssl/etcd/server.crt \
--key /etc/kubernetes/ssl/etcd/server.key \
--cacert /etc/kubernetes/ssl/etcd/ca.crt
```
## Clean up
```shell=
$ multipass delete mp1 mp2 mp3
$ multipass purge
```