# 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 ```