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:
- Uma máquina com 2 GB de RAM livre
- VirtualBox
- Minikube
- kubectl
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:
- apiVersion indica a categoria daquele determinado recurso, v1 são os mais simples.
- kind é o tipo do recurso, pode ser um Pod, um Deployment, um Service, etc.
- metadata aqui é definido informações diversas como nome, labels e namespace.
- name indica o nome do pod.
- spec indica o início das definições específicas daquele kind.
- containers indica o início da lista de definição de cada contêiner dentro deste pod.
- name o nome que daremos à aquele contêiner dentro do pod.
- image a imagem do contêiner, neste caso a imagem está em hub.docker.com. Esta imagem é um pequeno servidor Lighttpd.
- ports indica o início da lista de portas que podemos abrir para aquele contêiner
- 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:
- Quando um pod apresentar problemas, ele será removido e um novo pod tomará seu lugar.
- O deployment exige pelo menos o dobro de linhas das configurações que escrevemos no pod.
- Haverá um objeto extra chamado ReplicaSet.
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:
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:
- apiVersion é categoria daquele deste recurso, apps/v1 são os objetos de mais alto nível.
- kind é o tipo do recurso, pode ser um Pod, um Deployment, um Service, etc.
- metadata aqui é definido informações diversas como nome, labels e namespace
- labels indica o início das definições dos labels
- app: meu-deploy é a label que criamos para identificar o
Deploy
- name indica o nome do pod.
- spec indica o início das definições específicas daquele kind.
- replicas especifica a quantidade de cópias daquele pod dentro do cluster
- selector indica o início da definição dos seletores
- matchLabels indica que utilizaremos labels para seleção
- app: meu-deploy é o label indica o
ReplicaSet
que oDeploy
controlará - strategy indica o início da definição da estratégia de provisionamento
- rollingUpdate define as configurações da atualização gradativa
- maxSurge o máximo de réplicas a mais que podem coexistir duarante as atualizações
- maxUnavailable o máximo de réplicas a menos que podem coexistir durante as atualizações
- type define se será
RollingUpdate
- gradativo - ouRecreate
- de uma vez - template indica o início da definição do
Pod
- metadata aqui é definido informações diversas como nome, labels e namespace
- labels indica o início das definições dos labels do
Pod
- app: meu-deploy é a label que criamos para identificar os
Pods
- spec indica o início das definições específicas dos
Pods
- containers indica o início da lista de definição de cada contêiner dentro destes
Pods
- name o nome que daremos à aqueles contêineres dentro dos
Pods
. - image a imagem do contêiner, neste caso a imagem está em hub.docker.com. Esta imagem é um pequeno servidor Lighttpd.
- ports indica o início da lista de portas que podemos abrir para aquele contêiner
- 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 entre 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:
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:
- Pod
- Deployment
- ReplicaSet
- Service
Mas existem muitos outros, e eu aconselho que vá atrás dos seguintes, na ordem:
- PersistentVolume
- PersistentVolumeClaim
- Ingress
- ConfigMap
- Secret
- HorizontalPodAutoscaling
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.