Cluster Kubernetes Multimaster com Kind e Docker

Em um post anteriormente ensinei como criar um Cluster Kubernetes Multimaster utilizando máquinas virtuais. Desta vez faremos quase a mesma coisa só que desta vez utilizando o nosso amiguinho Kind. O Kind funciona subindo as imagens do Control Plane e dos Workers Nodes em containers. Desta forma você pode ter um mini cluster para realizar os seus testes, sem ter que ter 6 maquinas virtuais. Nesse tutorial ainda iremos configurar o Ingress e o Load Balancer para você ter um cluster ainda mais completo.

Instalando o Kind

O Kind está disponível para Linux, Mac e Windows e você pode baixá-lo diretamente do site. A instalação é muito simples e sem segredo, para ilustrar vou colocar aqui os passos para Linux, mas se você estiver utilizando outro sistema operacional no link que eu deixei na parte de cima tem as instruções.

# Baixa o Kind utilizando o curl
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.11.1/kind-linux-amd64

# Troca a permissão do arquivo para permitir execução
chmod +x ./kind

# Move o binário do Kind para um diretório que esteja no $PATH
mv ./kind ~/.local/bin

Pronto instalado!

Se você der uma olhada na documentação para se criar o Cluster Kubernetes basicamente rodamos:

$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.21.1) 🖼
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂

Após a criação do Cluster o Kind já altera o seu arquivo de configuração do kubectl adicionando um novo contexto com o nome do cluster, que por padrão, se chamará kind.

Para vermos algumas informações do Cluster vamos rodar:

$ kubectl get all -A
NAMESPACE            NAME                                             READY   STATUS    RESTARTS   AGE
kube-system          pod/coredns-558bd4d5db-rs5jj                     1/1     Running   0          2m
kube-system          pod/coredns-558bd4d5db-vjjtn                     1/1     Running   0          2m
kube-system          pod/etcd-kind-control-plane                      1/1     Running   0          2m3s
kube-system          pod/kindnet-2pf9g                                1/1     Running   0          2m
kube-system          pod/kube-apiserver-kind-control-plane            1/1     Running   0          2m3s
kube-system          pod/kube-controller-manager-kind-control-plane   1/1     Running   0          2m3s
kube-system          pod/kube-proxy-44knv                             1/1     Running   0          2m
kube-system          pod/kube-scheduler-kind-control-plane            1/1     Running   0          2m3s
local-path-storage   pod/local-path-provisioner-547f784dff-2vsvb      1/1     Running   0          2m

NAMESPACE     NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
default       service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP                  2m16s
kube-system   service/kube-dns     ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   2m14s

NAMESPACE     NAME                        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
kube-system   daemonset.apps/kindnet      1         1         1       1            1           <none>                   2m13s
kube-system   daemonset.apps/kube-proxy   1         1         1       1            1           kubernetes.io/os=linux   2m14s

NAMESPACE            NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
kube-system          deployment.apps/coredns                  2/2     2            2           2m14s
local-path-storage   deployment.apps/local-path-provisioner   1/1     1            1           2m13s

NAMESPACE            NAME                                                DESIRED   CURRENT   READY   AGE
kube-system          replicaset.apps/coredns-558bd4d5db                  2         2         2       2m
local-path-storage   replicaset.apps/local-path-provisioner-547f784dff   1         1         1       2m

Perceba que por padrão o Kind vai criar um Cluster Kubernetes Single Node, ou seja, tudo fica um container apenas.

$ kubectl get nodes
NAME                 STATUS   ROLES                  AGE     VERSION
kind-control-plane   Ready    control-plane,master   4m17s   v1.21.1

Criando cluster multi master

Como vimos no passo anterior o Kind por padrão sobre apenas um Single Node porém, é possível definir em um arquivo simples de configuração yaml de como queremos que seja criado o nosso cluster.

Para termos um cluster com 3 masters e 3 workers crie um arquivo com o nome multi-master-kind.yaml com o conteúdo abaixo:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: control-plane
- role: control-plane
- role: worker
- role: worker
- role: worker

Depois de salvar o arquivo rode novamente o kind só que com as opções abaixo:

$ kind create cluster --name multi \
--config multi-master-kind.yaml

Creating cluster "multi" ...
 ✓ Ensuring node image (kindest/node:v1.21.1) 🖼
 ✓ Preparing nodes 📦 📦 📦 📦 📦 📦  
 ✓ Configuring the external load balancer ⚖️ 
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
 ✓ Joining more control-plane nodes 🎮 
 ✓ Joining worker nodes 🚜 
Set kubectl context to "kind-multi"
You can now use your cluster with:

kubectl cluster-info --context kind-multi

Thanks for using kind! 😊

Agora você tem dois clusters rodando na sua maquina. O primeiro que criamos Single Node e este novo Multi Master e Multi Node.

Para chavear o kubectl entre um cluster e outro basta que você utilize a opção use-context, dessa forma:

# Lista os contextos existentes, no nosso caso os clusters do Kind
kubectl get contexts
URRENT   NAME         CLUSTER      AUTHINFO     NAMESPACE
          kind-kind    kind-kind    kind-kind    
*         kind-multi   kind-multi   kind-multi   
# Note que o * seta para qual o seu contexto está configurado.

# Para trocar por exemplo para o contexto do Cluster anterior
kubectl use-context kind-kind

# Listando novamente note que o * mudou
CURRENT   NAME         CLUSTER      AUTHINFO     NAMESPACE
*         kind-kind    kind-kind    kind-kind    
          kind-multi   kind-multi   kind-multi   

Instalando Ingress com Nginx

Você viu que até agora conseguimos criar um cluster multi master de forma bem rápida e tranquila. Agora vamos melhorar a nossa configuração e adicionar um Ingress no nosso cluster.

As instruções abaixo foram criadas baseadas na própria documentação do Kind que está disponível em: https://kind.sigs.k8s.io/

Para começar precisaremos criar um novo arquivo de configuração e adicionar mais alguns parâmetros.

Crie um arquivo chamado kind-ingress.yaml com o conteúdo abaixo:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
- role: control-plane
- role: control-plane
- role: worker
- role: worker
- role: worker

Agora vamos criar o nosso cluster. Para que você não fique muitos clusters rodando na sua máquina sugiro que você delete os clusters que criamos até o momento e fique com apenas um, para isso:

$ kind delete cluster --name kind
Deleting cluster "kind" ...

$ kind delete cluster --name kind-multi
Deleting cluster "multi" ...

# Para verificar que todos os clusters foram apagados
$ kind get clusters
No kind clusters found.

Rode o comando abaixo para criar o novo cluster

kind create cluster --name multi --config kind-ingress.yaml
Creating cluster "ingres" ...
 ✓ Ensuring node image (kindest/node:v1.21.1) 🖼
 ✓ Preparing nodes 📦 📦 📦 📦 📦 📦  
 ✓ Configuring the external load balancer ⚖️ 
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
 ✓ Joining more control-plane nodes 🎮 
 ✓ Joining worker nodes 🚜 
Set kubectl context to "kind-ingres"
You can now use your cluster with:

kubectl cluster-info --context kind-multi

Have a nice day! 👋

Agora vamos fazer o deploy propriamente dito do Ingress. Existem diversas implementações de Ingress disponíveis para o Kubernetes. No nosso caso vamos utilizar a do NGinx.

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

namespace/ingress-nginx created
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created

Aguarde até que pod do ingress controller esteja em Running. Para verificar o andamento é só rodar:

kubectl get pods -n ingress-nginx

NAME                                       READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-9g94b       0/1     Completed   0          65s
ingress-nginx-admission-patch-4vh6g        0/1     Completed   1          65s
ingress-nginx-controller-b7b74c7b7-qc4f9   1/1     Running     0          65s

Testando o Ingress

Agora que temos o Ingress rodando vamos realizar alguns testes para verificar o seu funcionamento. Crie um arquivo com o nome de test-ingress.yaml com o conteúdo abaixo:

kind: Namespace
apiVersion: v1
metadata:
  name: test-nginx
  labels:
    app: test-nginx
---
kind: Pod
apiVersion: v1
metadata:
  name: foo-app
  namespace: test-nginx
  labels:
    app: foo
spec:
  containers:
  - name: foo-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=foo"
---
kind: Service
apiVersion: v1
metadata:
  name: foo-service
  namespace: test-nginx
spec:
  selector:
    app: foo
  ports:
  # Default port used by the image
  - port: 5678
---
kind: Pod
apiVersion: v1
metadata:
  name: bar-app
  namespace: test-nginx
  labels:
    app: bar
spec:
  containers:
  - name: bar-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=bar"
---
kind: Service
apiVersion: v1
metadata:
  name: bar-service
  namespace: test-nginx
spec:
  selector:
    app: bar
  ports:
  # Default port used by the image
  - port: 5678
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  namespace: test-nginx
spec:
  rules:
  - http:
      paths:
      - pathType: Prefix
        path: "/foo"
        backend:
          service:
            name: foo-service
            port:
              number: 5678
      - pathType: Prefix
        path: "/bar"
        backend:
          service:
            name: bar-service
            port:
              number: 5678

Para deixar mais organizado o cluster criamos um novo namespace: test-nginx (linhas destacadas acima)

Aplique o manifesto no cluster:

$ kubectl apply -f test-ingress.yaml
namespace/test-nginx created
pod/foo-app created
service/foo-service created
pod/bar-app created
service/bar-service created
ingress.networking.k8s.io/example-ingress created

Vamos verificar se os pods subiram sem problemas

kubectl get pods -n test-nginx
NAME      READY   STATUS    RESTARTS   AGE
bar-app   1/1     Running   0          10s
foo-app   1/1     Running   0          10s

Agora vamos verificar se os serviços e o ingress foram criados corretamente:

# Checando os serviços
$ kubectl get svc -n test-nginx
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
bar-service   ClusterIP   10.96.6.226     <none>        5678/TCP   93s
foo-service   ClusterIP   10.96.195.110   <none>        5678/TCP   93s

# Checando o Ingress
$ kubectl get ingress -n test-nginx
NAME              CLASS    HOSTS   ADDRESS   PORTS   AGE
example-ingress   <none>   *                 80      28s

Basicamente o que este serviço faz é imprimir um texto com o nome do serviço que foi chamado:

$ curl localhost/foo
foo

$ curl localhost/bar
bar

Pronto já temos o nosso Ingress configurado e testado.

Instalando um Load Balancer

Para que o nosso cluster fique ainda mais completo vamos instalar um load balancer. Isso permitirá que você acesse através de um endereço IP que será atribuído e não mais para localhost. Esse normalmente é o cenário que se tem quando queremos que uma aplicação seja acessada de fora do Cluster.

Até o momento que escrevo este tutorial o único load balancer disponível Kubernetes em bare metal (máquinas físicas) e que podemos utilizar no Kind é o Metallb.

Para fazer a instalação basta seguirmos a própria documentação do Kind novamente que está no site.

Primeiro criamos um namespace chamado

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/master/manifests/namespace.yaml
namespace/metallb-system created

Depois criamos uma secret:

kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)" 
secret/memberlist created

Por fim aplicamos o manifesto de deploy do Metallb

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/master/manifests/metallb.yaml

Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
role.rbac.authorization.k8s.io/pod-lister created
role.rbac.authorization.k8s.io/controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
rolebinding.rbac.authorization.k8s.io/pod-lister created
rolebinding.rbac.authorization.k8s.io/controller created
daemonset.apps/speaker created
deployment.apps/controller created

Aguarde até que os pods estejam em Running:

kubectl get pods -n metallb-system
NAME                          READY   STATUS    RESTARTS   AGE
controller-7cb54c8c7c-gdkk9   1/1     Running   0          69s
speaker-22q92                 1/1     Running   0          69s
speaker-2rggk                 1/1     Running   0          69s
speaker-hsccq                 1/1     Running   0          69s
speaker-m2c4m                 1/1     Running   0          69s
speaker-sb9nj                 1/1     Running   0          69s
speaker-xnsss                 1/1     Running   0          69s

Para que Metallb funcione corretamente ele requer ainda uma última configuração que informa qual será o range de endereços IP estará disponível para uso do Load Balancer. Lembrando que será um destes IPs que será atribuído a um novo serviço quando criamos um serviço e informarmos Type como LoadBalancer.

Tecnicamente o que vamos fazer será perguntar ao Docker qual é a rede que está configurada para o uso do Kind e então reservamos uma parte dela para ser utilizada pelo Load Balancer.

$ docker network inspect -f '{{.IPAM.Config}}' kind
[{172.20.0.0/16  172.20.0.1 map[]} {fc00:f853:ccd:e793::/64   map[]}]

No meu caso a rede que tenho é 172.20.0.0/16 o que significa que os IPs de: 172.20.0.1 até 172.20.0.255 estão reservados para o Kind. O que iremos fazer agora e pegar uma parte destes range de IPs para o Load Balancer. No nosso caso vou reservar 5 IPs contados a partir do final.

172.20.0.249 até 172.20.0.254

Um ponto importante aqui, nunca utilize o final do range de IP *.255 pois ele está reservado para realizar o Broadcast de rede.

Agora vamos criar um arquivo chamado metallb-configmap.yaml para fazer a configuração final do Metallb com o conteúdo abaixo:

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 172.20.0.249-172.20.0.254

Lembre-se que aqui você vai substituir pelo valores relativos a sua rede, linha destacada acima.

Aplique o manifesto no Cluster:

kubectl apply -f metallb-configmap.yaml
configmap/config created

Com isso concluímos a configuração do Metallb como Load Balancer.

Testando o Load Balancer

Vamos pegar emprestado o mesmo exemplo que utilizamos para testar o Ingress e adaptá-lo para testarmos o Load Balancer.

Crie um arquivo chamado loadbalancer-test.yaml com o conteúdo abaixo:

kind: Namespace
apiVersion: v1
metadata:
  name: test-lb
  labels:
    app: test-lb
---
kind: Pod
apiVersion: v1
metadata:
  name: foo-app
  namespace: test-lb
  labels:
    app: http-echo
spec:
  containers:
  - name: foo-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=foo"
---
kind: Pod
apiVersion: v1
metadata:
  name: bar-app
  namespace: test-lb
  labels:
    app: http-echo
spec:
  containers:
  - name: bar-app
    image: hashicorp/http-echo:0.2.3
    args:
    - "-text=bar"
---
kind: Service
apiVersion: v1
metadata:
  name: foo-service
  namespace: test-lb
spec:
  type: LoadBalancer
  selector:
    app: http-echo
  ports:
  # Default port used by the image
  - port: 5678

Aplique o manifesto no cluster:

$ kubectl apply -f loadbalancer-test.yaml
namespace/test-lb created
pod/foo-app created
pod/bar-app created
service/foo-service created

Vamos verificar se o deploy foi aplicado corretamente:

# Checando os Pods
$ kubectl get pods -n test-lb
NAME      READY   STATUS    RESTARTS   AGE
bar-app   1/1     Running   0          2m23s
foo-app   1/1     Running   0          2m23s

# Checando os serviços
$ kubectl get svc -n test-lb
NAME          TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
foo-service   LoadBalancer   10.96.215.129   172.20.0.249   5678:32256/TCP   3m

Para verificar se o Load Balancer está realmente funcionando vamos executar um pequeno scriptlet que fará 10 chamadas para o IP atribuído pelo Load balancer. Em cada uma das chamadas hora cairá no serviço foo e hora no serviço bar.

$ for _ in {1..10}; do
  curl 172.20.0.249:5678
done
foo
bar
foo
bar
bar
foo
bar
bar
foo
bar

E chegamos ao final de mais um tutorial. O que achou do Kind? Bacana né? Quebra um puta galho para testes rápidos ou para desenvolvedores que precisam avaliar comportamentos ou simplesmente fazer o deploy e avaliar a sua aplicação.

Espero que tenham gostado!

Abraço

1 Comment

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s