Kubernetes Básico

Kubernetes Básico

Você está ouvindo as pessoas comentarem a respeito de Kubernetes mas não faz idéia do que seja? Então está no lugar certo! Abaixo procurarei explicar da maneira mais sucinta e simples possível para que você possa começar a dar seus primeiros passos na ferramenta. Vamos começar!

O que é Kubernetes?

Kuberentes é um software utilizado para fazer o gerenciamento de aplicações que rodam através de contêineres. Uma das grandes vantagens do Kubernetes é a centralização do gerenciamento e regras de controle de acesso conhecidas como RBAC.

O que são contêineres?

Contêineres são aplicações que rodam isoladamente no sistema operacional, tão isoladas que possúem praticamente todas suas dependências dentro delas mesmas. Quase sempre, quando estão falando sobre contêineres, estão falando sobre Docker. Um bom resumo para quem não conhece contêineres está aqui em Docker - Resumo.

Como começar?

Diferente do Docker que provavelmente você está acostumado, para começar a utilizar o Kubernetes precisaremos criar toda a sua infraestrutura, "somente o binário" não é suficiente.

Mas você não precisa de nenhuma cloud para começar a utilizar o Kubernetes, mesmo em ambientes produtivos. Vamos tentar simplificar ao máximo o nosso ambiente, e para isso vamos utilizar a sua própria máquina e o Minikube.

Sim, minikube! O minikube é um software que utilizará um gerenciador de máquinas virtuais, como por exemplo o VirtualBox, para criar uma máquina virtual com todos os componentes necessários, pronta para começar a testar o Kubernetes.

Além do minikube você precisará do binário utilizado para se comunicar com um cluster Kubernetes, e este binário se chama kubectl. Você pode encontrá-lo em https://kubernetes.io/docs/tasks/tools/install-kubectl/.

Então, vamos revisar do que precisaremos:

Se você deseja um cluster mais avançado, entender mais profundamente, provavelmente está lendo o artigo errado, neste caso eu recomendo o meu Guia de Estudo.

Iniciando o Minikube

Uma vez com tudo instalado, você precisa ir ao terminal e digitar:

minikube start

Vai levar um tempinho, o minikube irá baixar uma pequena imagem .iso e através dessa imagem provisionará sua máquina virtual. Após isso, ele passará a baixar os componentes do próprio Kubernetes.

O processo é bastante direto e não deve apresentar problemas. Caso você tenha problemas, provavelmente está na comunicação do minikube com o VirtualBox ou qualquer outro gerenciador que você possa ter escolhido.

Testando a conexão

Assim que o minikube terminar o seu trabalho você verá uma mensagem parecida com a seguinte:

"minikube" IP address is 192.168.39.2
Configuring Docker as the container runtime ...
Version of container runtime is 18.06.2-ce
Waiting for image downloads to complete ...
Preparing Kubernetes environment ...
Downloading kubeadm v1.14.0
Downloading kubelet v1.14.0
Pulling images required by Kubernetes v1.14.0 ...
Launching Kubernetes v1.14.0 using kubeadm ... 
Waiting for pods: apiserver proxy etcd scheduler controller dns
Configuring cluster permissions ...
Verifying component health .....
kubectl is now configured to use "minikube"
For best results, install kubectl: https://kubernetes.io/docs/tasks/tools/install-kubectl/
Done! Thank you for using minikube!

Execute um comando para puxar informações a respeito do cluster e verificar se a comunicação acontece:

kubectl cluster-info

Informações a respeito do cluster serão escritas na tela:

Kubernetes master is running at https://192.168.39.2:8443
KubeDNS is running at https://192.168.39.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

Parece que tudo está funcionando!

Vamos executar mais um comando para verificar as máquinas do nosso cluster - é claro que há apenas uma, mas não custa verificar:

kubectl get nodes

E a saída será semelhante a esta:

NAME       STATUS   ROLES    AGE   VERSION
minikube   Ready    master   12m   v1.14.0

Sempre que conversarmos com o cluster do Kubernetes, devemos nos direcionar a uma máquina master. Nosso cluster é composto de apenas uma máquina e ela é justamente a máquina master, então está tudo certo. Em ambientes mais complexos poderemos ver dezenas ou até centenas de máquinas.

Parando o Minikube

Caso você deseje parar a máquina virtual do Kubernetes - para jogar algum jogo por exemplo - você pode digitar no terminal:

minikube stop

Da próxima vez que executar o minikube start a inicialização será muito mais rápida, pois todos os componentes já foram baixados e instalados.

Primeira Aplicação

Existem muitas "coisas" que podemos criar dentro do Kubernetes, vamos ver algumas adiante. Mas a principal delas é o Pod. O pod é a unidade mínima de uma aplicação dentro do Kubernetes, e dentro de um pod eu posso ter um ou mais contêineres.

Isso significa que você não pode rodar um contêiner dentro do Kubernetes, você precisa rodar um pod, mesmo que esse pod tenha apenas um contêiner.

Não há uma forma mais simples de criar um pod, precisaremos criar um arquivo do tipo YAML e então adicioná-lo ao Kubernetes. Você pode utilizar qualquer editor, desde salve o arquivo em algum lugar que consiga acessar pelo terminal:

meu-pod.yml

apiVersion: v1
kind: Pod
metadata:
  name: meu-pod
spec:
  containers:
    - name: cgi
      image: hectorvido/sh-cgi
      ports:
      - containerPort: 80

O arquivo é bastante auto-explicativo, mas explicarei linha a linha:

  1. apiVersion indica a categoria daquele determinado recurso, v1 são os mais simples.
  2. kind é o tipo do recurso, pode ser um Pod, um Deployment, um Service, etc.
  3. metadata aqui é definido informações diversas como nome, labels e namespace.
  4. name indica o nome do pod.
  5. spec indica o início das definições específicas daquele kind.
  6. containers indica o início da lista de definição de cada contêiner dentro deste pod.
  7. name o nome que daremos à aquele contêiner dentro do pod.
  8. image a imagem do contêiner, neste caso a imagem está em hub.docker.com. Esta imagem é um pequeno servidor Lighttpd.
  9. ports indica o início da lista de portas que podemos abrir para aquele contêiner
  10. containerPort indica a porta que o contêiner irá abrir.

Muito bom! Com o arquivo pronto e - parcialmente - entendido, podemos enviá-lo para o Kubernetes da seguinte forma:

kubectl create -f meu-pod.yml

Funcionou?! Se funcionou, o Kubernetes devolveu a seguinte mensagem:

pod/meu-pod created

Se não funcionou, veja a identação do seu arquivo, os campos e inclusive as letras maiúsculas, tudo precisa ser exatamente como está digitado - não utilize tab utilize apenas espaços.

Com o nosso pod funcionando, vamos descobrir qual é o seu IP através do subcomando get:

kubectl get pods -o wide

E poderemos ver um pouco mais de detalhes do que veríamos sem o -o wide:

NAME      READY   STATUS    RESTARTS   AGE     IP           NODE     
meu-pod   1/1     Running   0          2m51s   172.17.0.4   minikube

Também podemos obter mais detalhes sobre o nosso pod através do subcomando describe. A saída é relativamente extensa, então não vou colocar aqui:

kubectl describe pod meu-pod

Acessando a Aplicação

Dentro de um cluster Kubernetes, a partir do momento em que um pod ganha um IP, qualquer máquina pertencente ao cluster pode acessar a aplicação. Sendo assim, vamos pedir para o minikube executar um comando chamando curl, muito utilizado para fazer chamadas HTTP ou simular algum navegador web pelo terminal. Ou seja, é como se entrássemos na máquina e executássemos o comando por lá:

minikube ssh curl 172.17.0.4

E então, a aplicação retorna seus dados, conforme você pode ver em seu terminal:

Version:   0.1
Webserver: lighttpd/1.4.54
Hostname:  meu-pod
Address:   172.17.0.4
Method:    GET

Pronto, subimos a nossa primeira aplicação! Vamos removê-la para dar lugar a outras mais complexas:

kubectl delete pod meu-pod

Como exercício você pode tentar criar outros pods com outras imagens de servidores web para testar os mais variados aspectos de cada uma.

Obs: Note que o comando kubectl é composto de um verbo e um objeto. Opcionalmente podemos filtrar pelo nome desse objeto.

Entrando no Contêiner

Se você removeu o pod do exercício anterior, significa que estava seguindo todos os passos, parabéns! Mas agora precisaremos readicioná-lo:

kubectl create -f meu-pod.yml

Com certeza você já conhece um pouco de Docker, não? Então deve se lembrar como entramos em um contêiner com o Docker:

docker exec -ti nome-do-container sh

Muito bom! Você se lembrou! E veja que no Kubernetes, o comando é exatamente a mesma coisa, mas no lugar do nome do contêiner, eu utilizo o nome do Pod:

kubectl exec -ti meu-pod sh

Funcionou! Vamos verificar se estamos mesmo dentro do contêiner consultando o arquivo que exibe informações sobre a distribuição, o /etc/os-release:

cat /etc/os-release

E a saída será a seguinte:

NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.10.2
PRETTY_NAME="Alpine Linux v3.10"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"  

Para sair do contêiner basta digitar exit ou a sequência CTRL + D.

Verificando logs

Se você entendeu como criar e consumir o serviço de um pod através de um endereço de IP tente criar um Pod chamado apache baseado na imagem httpd:alpine. Se não conseguir, não tem problema, a resposta estará logo abaixo das explicações.

Feito? Agora acesse o endereço desse pod algumas vezes com o curl, e veja os logs do pod através do comando:

kubectl logs apache

A saída será os logs de inicialização e acesso pertencentes ao servidor web do contêiner:

AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.4. Set the 'ServerName' directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.4. Set the 'ServerName' directive globally to suppress this message
[Sat Oct 19 23:36:09.477539 2019] [mpm_event:notice] [pid 1:tid 140393866915144] AH00489: Apache/2.4.41 (Unix) configured -- resuming normal operations
[Sat Oct 19 23:36:09.477575 2019] [core:notice] [pid 1:tid 140393866915144] AH00094: Command line: 'httpd -D FOREGROUND'
172.17.0.1 - - [19/Oct/2019:23:36:18 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [19/Oct/2019:23:36:19 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [19/Oct/2019:23:36:19 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [19/Oct/2019:23:36:20 +0000] "GET / HTTP/1.1" 200 45

Se você não conseguiu, não desista, aqui está o pod chamado apache:

apache.yml

apiVersion: v1
kind: Pod
metadata:
  name: apache
spec:
  containers:
    - name: container-apache
      image: httpd:alpine
      ports:
      - containerPort: 80

Adicione o pod ao Kubernetes, veja seu endereço de ip e utilize o comando o curl:

kubectl apply -f apache.yml
kubectl get pods -o wide
...
minikube ssh curl 172.17.0.4

Obs: O subcomando apply também pode ser utilizado no lugar de create.

E quando há mais de um contêiner dentro do Pod?

Todo mundo me faz essa pergunta, e provavelmente é porque eu demoro a dizer.

Neste caso, você precisa especificar o nome do contêiner com --container ou -c além do nome do pod.

Deployment

Deployment é uma palavra da lingua inglesa que pode ser traduzida como provisionamento. No Kubernetes raramente criaremos pods isolados, quando criamos nossas aplicações elas geralmente estão ligadas a um deployment.

Isso significa que eu posso esquecer os pods?

Pelo contrário! Um deployment cuidara dos pods para você, ao custo de algumas regras:

O deployment é um objeto do Kubernetes que controla a forma como sua aplicação será provisionada e atualizada dentro do cluster, essas definições são conhecidas como strategy, mas não vamos abordar este tema aqui neste pequeno artigo. Além disso o deployment também é responsável por definir a quantidade de réplicas de uma determinada aplicação. Quanto mais réplicas, mais pods. O objeto responsável por adicionar ou remover essas réplicas é conhecido como ReplicaSet, mas não se preocupe, não mexeremos diretamente com eles.

A ordem é esta:

Deployment

Felizmente é possível criar deployments pela linha de comando! Vamos simplificar nosso aprendizado fazendo justamente isso:

kubectl create deployment meu-deploy --image=hectorvido/sh-cgi

Pronto! Vamos ver todos os objetos que foram criados:

kubectl get all

Ignore aquele Service, veja que o ReplicaSet e o Pod receberam nomes aleatórios:

NAME                              READY   STATUS    RESTARTS   AGE
pod/meu-deploy-6d5f44f6d5-kmkbf   1/1     Running   0          21s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   73m

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/meu-deploy   1/1     1            1           21s

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/meu-deploy-6d5f44f6d5   1         1         1       21s

Poxa Hector, mas e as réplicas?!

Pois é, não é que eu esqueci, mas é que pela linha de comando só podemos criar deployments com apenas 1 réplica.

Vamos corrigir isso com o subcomando edit. Não vamos conseguir fugir do YAML, cedo ou tarde é preciso ajustar alguma coisa:

kubectl edit deploy meu-deploy

O editor padrão geralmente é o vim, e não há vergonha em preferir o nano:

EDITOR=nano kubectl edit deploy meu-deploy

O subcomando edit abre o objeto - neste caso o deploy - em formato YAML para edição, ao salvar as alterações elas são automaticamente atualizadas no Kubernetes.

Veja quantas linhas um deploy possuí! Vamos adicionar uma porta com containerPort e crescer o número de réplicas para três:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: meu-deploy
  name: meu-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: meu-deploy
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: meu-deploy
    spec:
      containers:
      - name: sh-cgi
        image: hectorvido/sh-cgi
        ports:
        - containerPort: 80

Obs: o deployment foi simplificado para facilitar a leitura, o original possuí praticamente o dobro de linhas deste.

Antes de continuar, vou explicar linha a linha assim como fiz com o Pod:

  1. apiVersion é categoria daquele deste recurso, apps/v1 são os objetos de mais alto nível.
  2. kind é o tipo do recurso, pode ser um Pod, um Deployment, um Service, etc.
  3. metadata aqui é definido informações diversas como nome, labels e namespace
  4. labels indica o início das definições dos labels
  5. app: meu-deploy é a label que criamos para identificar o Deploy
  6. name indica o nome do pod.
  7. spec indica o início das definições específicas daquele kind.
  8. replicas especifica a quantidade de cópias daquele pod dentro do cluster
  9. selector indica o início da definição dos seletores
  10. matchLabels indica que utilizaremos labels para seleção
  11. app: meu-deploy é o label indica o ReplicaSet que o Deploy controlará
  12. strategy indica o início da definição da estratégia de provisionamento
  13. rollingUpdate define as configurações da atualização gradativa
  14. maxSurge o máximo de réplicas a mais que podem coexistir duarante as atualizações
  15. maxUnavailable o máximo de réplicas a menos que podem coexistir durante as atualizações
  16. type define se será RollingUpdate - gradativo - ou Recreate - de uma vez
  17. template indica o início da definição do Pod
  18. metadata aqui é definido informações diversas como nome, labels e namespace
  19. labels indica o início das definições dos labels do Pod
  20. app: meu-deploy é a label que criamos para identificar os Pods
  21. spec indica o início das definições específicas dos Pods
  22. containers indica o início da lista de definição de cada contêiner dentro destes Pods
  23. name o nome que daremos à aqueles contêineres dentro dos Pods.
  24. image a imagem do contêiner, neste caso a imagem está em hub.docker.com. Esta imagem é um pequeno servidor Lighttpd.
  25. ports indica o início da lista de portas que podemos abrir para aquele contêiner
  26. containerPort indica a porta que o contêiner irá abrir.

Se você conseguiu salvar e a tela voltou para o terminal sem abrir o editor de texto novamente, significa que deu tudo certo! Se não conseguiu, tente refazer. As vezes o arquivo fica tão estragado que é mais fácil fechar sem salvar e executar o edit novamente.

Tudo certo? Legal, veja as três réplicas do pod:

kubectl get pods -o wide

Existem três pods!

NAME                          READY   STATUS    RESTARTS   AGE   IP           NODE    
meu-deploy-85db559d9d-5gmxb   1/1     Running   0          59s   172.17.0.7   minikube
meu-deploy-85db559d9d-hs4pn   1/1     Running   0          66s   172.17.0.6   minikube
meu-deploy-85db559d9d-qldvz   1/1     Running   0          56s   172.17.0.5   minikube

Muito bom! Podemos agora acessar os três pods através do minikube:

minikube ssh curl 172.17.0.5
minikube ssh curl 172.17.0.6
minikube ssh curl 172.17.0.7

Tente remover algum dos pods, você verá que o Kuberentes automaticamente provisionará um novo em seu lugar.

Mas agora existem três Pods

É verdade, não parece mas isso é um problema, agora precisaremos centralizar o acesso a essa aplicação. Poderíamos colocar os ips em um balanceador externo, mas e se as réplicas aumentassem? Ficaria muito difícil lembrar todos os ips e capturar todos os novos, mas para isso existem os Services dentro do Kubernetes!

Service

O Service é o objeto dentro do Kubernetes que faz o balanceamento entro vários pods, por exemplo, vários pods de um mesmo deployment.

Como criamos o nosso deployment, fica muito fácil criar um serviço para todos os pods e inclusive pods novos, caso alteremos novamente a quantidade de réplicas:

kubectl expose deploy meu-deploy

Para listar o serviço, lembre-se kubectl verbo objeto:

kubectl get service

E você verá uma saída semelhante a seguinte:

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP   95m
meu-deploy   ClusterIP   10.106.236.204   <none>        80/TCP    66s

Assim, podemos testar o balanceamento nativo da mesma forma como fizemos com os pods, porém em apenas um endereço:

minikube ssh curl 10.106.236.204

Obs: Se você executar diversas vezes, verá que existe um balanceamento entre os pods disponíveis.

O serviço fica entre as requisições e os pods, fornecendo um único ponto de acesso, facilitando nosso trabalho:

Service

Expor a Aplicação

Existem algumas formas de expor a aplicação, e todas elas dependem de um serviço. O tipo de serviço que criamos é o serviço padrão, conhecido como ClusterIP.

Um serviço do tipo ClusterIP é acessível somente de dentro do cluster. Para que possamos acessar a aplicação de nossa máquina diretamente, precisaremos utilizar um tipo de serviço chamado NodePort.

Remova o serviço e adicione um novo com o tipo certo e uma porta específica:

kubectl delete service meu-deploy
kubectl expose deploy meu-deploy --type NodePort

Liste novamente os serviços com kubectl get svc e veja que existe uma porta maior ou igual a 30000:

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        101m
meu-deploy   NodePort    10.109.152.16   <none>        80:31140/TCP   26s

A minha é 31140, vamos pegar o ip do minikube e abrir o endereço em nosso navegador:

minikube ip

A minha saída foi a seguinte:

192.168.39.2

Então eu vou abrir em meu navegador o endereço http://192.168.39.2:31140. O que apareceu no navegador foi uma tabela relativamente bem formatada:

- -
Version: 0.1
Webserver: lighttpd/1.4.54
Hostname: meu-deploy-85db559d9d-5gmxb
Address: 172.17.0.7
Method: GET

Com as teclas CTRL + F5 é possível atualizar o cache e ver o load balance agindo. Também é possível utilizar o curl da própria máquina:

curl http://192.168.39.2:31140/

Mas como o Kubernetes sabe quais pods utilizar para o balanceamento do Service? Ele faz isso através de Labels.

Labels

Muitas coisas no Kubernetes funcionam através de Labels, e o Service é uma delas.

Não ficou explícito, mas ao criar um deployment com o comando:

kubectl create deployment meu-deploy --image=hectorvido/sh-cgi

Uma label de nome app e valor meu-deploy foi adicionado ao deployment, veja:

kubectl get deploy meu-deploy -o yaml

Este comando escreve na tela a definição do objeto em YAML, procure pelo metadata, você verá o label lá dentro:

metadata:
  labels:
    app: meu-deploy

Este mesmo label também está presente em matchLabels e dentro do template, aí neste mesmo arquivo.

Através destes labels o Kubernetes sabe quais pods controlar em relação as suas réplicas. Também é através destes labels que o Kubernetes sabe para quais pods um service deve apontar.

Mas e o Service?

Conforme eu disse alí em baixo, o service também utiliza estes labels, e cada pod novo também terá os mesmos labels. Assim, quando um pod novo aparecer dentro do Kubernetes o próprio service se encarregará de adicioná-lo ao balanceamento.

Tem Limite?

Podemos adicionar quantos labels quisermos aos nossos objetos, inclusive fazer seleções mais complexas.

Também podemos utilizar os labels para selecionar objetos durante o get ou mesmo remover todo um conjunto de objetos, como pode exemplo:

kubectl delete all -l app=meu-deploy

Pronto, todos os objetos que compartilham o label app=meu-deploy foram removidos.

Fim!

Espero que esta introdução tenha sido útil! O Kubernetes é um universo, e esta foi apenas uma pequena luz sobre uma superfície imensa. Apenas quatro recursos foram abordados:

  1. Pod
  2. Deployment
  3. ReplicaSet
  4. Service

Mas existem muitos outros, e eu aconselho que vá atrás dos seguintes, na ordem:

Se você deseja avançar mais os estudos, leia meu guia, lá eu abordo todos estes temas.

Desligue o Minikube

Para desligar o minikube:

minikube stop

Para removê-lo:

minikube delete

Outras opções

Você pode utilizar os mini-cursos ou playgrounds do Katacoda para praticar ou utilizar o cluster provisionado através do Vagrant em https://github.com/hector-vido/kubernetes.