Como criar uma configuração básica de Kubernetes

Kubernetes é uma plataforma robusta de orquestração de contêineres. Pode gerenciar seus contêineres, programá-los em diferentes nós conforme os recursos disponíveis, escalar automaticamente sua infraestrutura em resposta à demanda e fazer muitas outras coisas.
Diante da tarefa de implantar seus aplicativos, a maioria dos desenvolvedores costuma procurar a solução com menos obstáculos, seja recorrendo a um provedor totalmente gerenciado (por exemplo, Heroku) ou criando seus próprios scripts simples de implantação (ou seja, conectar ao servidor por ssh, baixar imagens Docker e iniciar contêineres).
À medida que seu projeto cresce, você passa cada vez mais tempo ajustando seus scripts de implantação, adiciona mais servidores, precisa executar comandos antes ou depois da implantação, quer poder voltar a uma versão anterior do software se algo falhar e depois precisa de um sistema de verificação de integridade para todos esses serviços... Em algum momento, começa a perceber que seu script está se tornando uma plataforma de orquestração de contêineres por si só e que provavelmente deveria parar de trabalhar nele.
Talvez você ache que usar Kubernetes para um projeto de um ou dois contêineres seja exagerado ou desnecessário, mas não se pode subestimar a flexibilidade que ele oferece. Você terá uma forma padrão de gerenciar seu sistema, poderá ampliar facilmente seu conjunto de tecnologias se necessário e também poderá migrar de um provedor de nuvem para outro com mais facilidade.
Hoje, a maioria dos provedores de servidores em nuvem tem sua própria oferta de Kubernetes. Isso significa que colocar seus clusters Kubernetes em funcionamento é muito fácil. Usaremos DigitalOcean nos exemplos, mas você deve conseguir fazer o mesmo com Amazon Web Services e outros provedores.
Configuração
Vamos criar um novo cluster Kubernetes no DigitalOcean e configurar a ferramenta kubectl para se comunicar com ele.
- Entre na sua conta do DigitalOcean e clique em Kubernetes no painel à esquerda,
- Clique em "Create a Kubernetes Cluster"
- Selecione uma região e reduza o número de nós para 1, pois não precisaremos de mais do que isso por enquanto.
- Clique em "Create Cluster". Você verá uma barra de progresso no topo. Seu cluster estará pronto quando essa barra desaparecer.
- Instale Kubectl. Você precisará dessa ferramenta para gerenciar seu cluster Kubernetes.
- Siga este guia para configurar a ferramenta doctl e conectar kubectl ao seu cluster. Outra opção é clicar em "Download Config File" e passar esse arquivo ao kubectl para se conectar ao cluster:
$ kubectl --kubeconfig=path/to/config-file.yaml <commands>
Agora devemos conseguir usar o comando kubectl para nos comunicar com nosso cluster.
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
pool-dsayjvr1j-33ipn Ready <none> 8m44s v1.16.6
Se usamos a abordagem do arquivo de configuração, o comando anterior deve se parecer com isto:
$ kubectl --kubeconfig=path/to/k8s...kubeconfig.yaml get nodes
NAME STATUS ROLES AGE VERSION
pool-dsayjvr1j-33ipn Ready <none> 12m v1.16.6
Podemos ver que nosso cluster consiste em um nó (ou seja, uma máquina de trabalho do Kubernetes).
Se usamos o método do arquivo kubeconfig, não podemos esquecer de passar a opção --kubeconfig ao kubectl toda vez. De agora em diante, todas as chamadas de kubectl aparecerão sem a opção --kubeconfig, então devemos ajustá-las conforme necessário.
Pods e Deployments
Kubernetes define pods como «as menores unidades de computação implantáveis que podem ser criadas e gerenciadas no Kubernetes». Um Pod pode ser composto de um ou mais contêineres. Nossos Pods neste exemplo, como costuma acontecer, serão compostos de apenas um contêiner.
Os Deployments, por outro lado, descrevem o estado desejado do sistema em relação aos Pods; por exemplo, podemos criar um deployment que solicite duas instâncias de determinado Pod, e o Kubernetes tentará atender a esse requisito da melhor maneira possível conforme os recursos disponíveis.
Agora vamos fazer o Kubernetes implantar nossa aplicação frontend. Vamos criar um novo arquivo chamado "frontend.yaml" com o seguinte conteúdo:
kind: Deployment
apiVersion: apps/v1
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: sophilabs/k8s-demo-frontend
ports:
- name: http
containerPort: 80
As primeiras duas linhas informam ao Kubernetes que estamos definindo um Deployment usando a versão apps/v1 do esquema. Depois vem a seção "metadata", na qual damos um nome ao Deployment que estamos criando, neste caso “frontend”.
A seção "spec" é onde realmente definimos o estado desejado do nosso Deployment. Primeiro declaramos que queremos pelo menos uma réplica dos Pods que correspondam ao “selector” indicado. A seção “selector” especifica como o Deployment deve selecionar os Pods sobre os quais atua; neste caso, corresponderá a qualquer Pod rotulado como “app: frontend”.
A seção "template" é onde definimos nosso Pod. Assim como o próprio Deployment, contém uma seção “metadata”, na qual adicionamos o rótulo “app: frontend” ao Pod. Depois vem a seção “spec”, na qual definimos os contêineres que ficarão dentro desse Pod.
Neste caso, temos um contêiner chamado "frontend" usando a imagem Docker “sophilabs/k8s-demo-frontend”. A seção ports define as portas expostas pelo contêiner e lhes dá um nome; neste caso, “http” e a porta 80. Usaremos o nome da porta mais adiante.
Agora é hora de dizer ao Kubernetes para aplicar esse deployment.
$ kubectl apply -f frontend.yaml
deployment.apps/frontend created
Agora podemos inspecionar nosso cluster Kubernetes.
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
frontend 1/1 1 1 5s
Aqui podemos ver que nosso deployment "frontend" foi criado e está pronto. Vamos ver se nosso Pod frontend foi criado:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
frontend-84865ff7bd-d5w4w 1/1 Running 0 8s
Ótimo, nosso pod também está em funcionamento. Lembre-se de que esse processo leva alguns segundos. Continue executando os subcomandos "get deployments" e “get pods” até a coluna “ready” mostrar “1/1.”
Agora podemos executar comandos no pod frontend e anotar o nome do Pod que obtivemos com o comando "get pods".
$ kubectl exec frontend-84865ff7bd-d5w4w -- ls
bin
boot
dev
...
var
E assim executamos o comando "ls" no contêiner frontend sem precisar configurar chaves SSH, nomes de host ou qualquer outra coisa. Ótimo!
Se quisermos abrir um shell em um contêiner em execução, podemos fazer isso com:
$ kubectl exec -ti frontend-84865ff7bd-d5w4w -- sh
/ # ls
bin dev etc home lib media mnt opt
proc root run sbin srv sys tmp usr
var
/ #
Como faremos uma requisição a esse Pod agora? Antes de ver como fazer isso corretamente, vamos conhecer outro ótimo recurso do kubectl: "port-forward"
$ kubectl port-forward frontend-84865ff7bd-d5w4w 9080:80
Forwarding from 127.0.0.1:9080 -> 80
Forwarding from [::1]:9080 -> 80
Agora todas as requisições que fizermos a localhost:9080 serão enviadas à porta 80 do Pod!
Abra um navegador e acesse http://localhost:9080

Não poderia ser mais fácil. Que tal verificar os logs do contêiner?
$ kubectl logs frontend-84865ff7bd-d5w4w
127.0.0.1 - - [23/Apr/2020:18:50:08 +0000] "GET / HTTP/1.1" 200 77 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0" "-"
2020/04/23 18:50:08 [error] 6#6: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost:9080"
127.0.0.1 - - [23/Apr/2020:18:50:08 +0000] "GET /favicon.ico HTTP/1.1" 404 154 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0" "-"
Agora você deve ver os logs que o nginx gerou quando visualizou a página!
Serviços
Agora temos um servidor frontend em execução no nosso cluster Kubernetes, mas não podemos acessá-lo sem executar o comando port-forward. Para expor nossa aplicação frontend ao mundo externo, precisamos criar um "Service"
A documentação do Kubernetes define um "Service" como «Uma forma abstrata de expor uma aplicação em execução em um conjunto de Pods como um serviço de rede».
Há várias maneiras de configurar Services no Kubernetes. Agora vamos nos concentrar nos serviços "NodePort". Um “NodePort Service” configura uma porta nos nós que encaminhará o tráfego de entrada ao Service.
Criamos um arquivo frontend-service.yaml com o seguinte conteúdo:
kind: Service
apiVersion: v1
metadata:
name: frontend
spec:
type: NodePort
selector:
app: frontend
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
Aqui definimos um Service chamado "frontend" do tipo “NodePort” para todos os Pods rotulados como “app: frontend”. Ele encaminha o tráfego da porta 80 à porta do contêiner chamada “http” (lembre-se de que demos o nome http à porta 80 do contêiner frontend).
Precisamos aplicar esse arquivo yaml:
$ kubectl apply -f frontend-service.yaml
service/frontend created
Vamos inspecionar o Service que acabamos de criar.
$ kubectl describe service frontend
Name: frontend
Namespace: default
Labels: <none>
Annotations: Selector: app=frontend
Type: NodePort
IP: 10.245.4.91
Port: http 80/TCP
TargetPort: http/TCP
NodePort: http 30695/TCP
Endpoints: 10.244.0.31:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
A saída mostra que o "NodePort" atribuído a esse Service é 30695. Agora precisamos do endereço ip externo do nosso nó.
$ kubectl describe nodes | grep ExternalIP
ExternalIP: 159.89.140.126
Agora você deve conseguir acessar o servidor frontend visitando: http://159.89.140.120:30695 (É claro, substitua esses números pelos que o kubectl retornar no seu caso).
Há outro aspecto importante de termos criado esse Service frontend. Agora o servidor frontend está acessível pelo nome frontend em todo o cluster; ou seja, podemos enviar uma requisição a [<http://frontend>](<http://frontend>) de dentro do cluster, e ela chegará a um dos Pods frontend em execução (por enquanto, apenas um). Vamos testar:
Abrimos um shell no contêiner frontend e executamos curl http://frontend.
$ kubectl exec -ti frontend-84865ff7bd-d5w4w -- sh
# curl http://frontend
<!DOCTYPE html>
<html>
<body>
<h1>Hello</h1>
</body>
</html>
Agora vamos excluir o serviço que acabamos de criar.
$ kubectl delete -f frontend-service.yaml
service "frontend" deleted
Vamos tentar executar o comando curl novamente:
$ kubectl exec -ti frontend-84865ff7bd-d5w4w -- sh
# curl <http://frontend>
curl: (6) Could not resolve host: frontend
Podemos ver que, sem a definição do serviço "frontend", já não conseguimos acessar o Pod frontend usando o nome de domínio “frontend”.
Controlador Ingress
Nossa aplicação frontend tem pouca utilidade se não podemos acessá-la facilmente por um nome de domínio legível. A forma habitual de fazer isso no DigitalOcean e na maioria dos outros provedores de nuvem é criar um balanceador de carga e deixar que o provedor gerencie nossas regras DNS. Kubernetes oferece Services do tipo Load Balancer, que podem criar um balanceador de carga usando a API do seu provedor de nuvem e configurá-lo para apontar para o Service dentro do cluster. Esse tipo de serviço funcionaria no nosso caso porque temos apenas um Service frontend, mas o que fazemos se precisamos de controle detalhado sobre como o tráfego é roteado dentro do cluster? Para isso, podemos usar um Ingress-Controller.
Controladores Ingress são servidores HTTP (como Nginx) modificados para saber que estão em execução dentro de um cluster Kubernetes. Isso permite que respondam a eventos do cluster, como a criação e exclusão de recursos Ingress; esses recursos são usados para configurar o controlador Ingress para implementar nossos requisitos de roteamento.
Usaremos a ferramenta Helm para instalar nosso controlador ingress.
Acesse o site do Helm e instale o programa.
Depois execute estes comandos:
$ helm repo add stable [<https://kubernetes-charts.storage.googleapis.com/>](<https://kubernetes-charts.storage.googleapis.com/>)
$ helm install nginx-ingress stable/nginx-ingress
Se você usa o parâmetro --kubeconfig para kubectl, adicione --kubeconfig=path/to/k8s-....-.yaml depois do comando helm, por exemplo, helm --kubeconfig=path/to/file.yaml install <etc,...>
Vamos ver o que mudou no nosso cluster:
$ kubectl get pods
NAME READY STATUS (...)
frontend-84865ff7bd-d5w4w 1/1 Running (...)
nginx-ingress-controller-676d7fcf55-5cwtq 1/1 Running (...)
nginx-ingress-default-backend-5b967cf596-w6kd4 1/1 Running (...)
Helm adicionou mais dois pods ao nosso cluster.
Vamos verificar nossos serviços:
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 27h
nginx-ingress-controller LoadBalancer 10.245.108.229 167.172.10.106 80:31122/TCP,443:31442/TCP 15m
nginx-ingress-default-backend ClusterIP 10.245.17.126 <none> 80/TCP 15m
Agora temos um serviço nginx-ingress-controller do tipo “LoadBalancer”, o que significa que o Kubernetes criou um LoadBalancer do DigitalOcean apontado para nosso novo controlador Ingress!
Entre na sua conta do DigitalOcean, clique no link Networking (painel à esquerda), depois na aba "Load balancers", e você deve ver seu novo balanceador de carga ali.

Observe que o endereço IP do balanceador de carga é o mesmo que obtivemos com kubectl: “167.172.10.106” no nosso caso.
Só falta reinstalar nosso serviço frontend e adicionar uma rota Ingress apontada para ele.
Abra o arquivo frontend-service.yaml que criamos antes e remova a linha type: NodePort. Isso tornará nosso serviço frontend acessível apenas dentro do cluster.
Aplique o arquivo do serviço:
$ kubectl apply -f frontend-service.yaml
Em seguida, crie um arquivo frontend-ingress.yaml com o seguinte conteúdo:
kind: Ingress
apiVersion: extensions/v1beta1
metadata:
name: frontend
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: frontend
servicePort: http
Você pode ver aqui que estamos definindo uma nova regra de roteamento. Toda requisição com o caminho / será encaminhada ao nosso serviço frontend na porta "http" (ou seja, a porta 80 definida em frontend-service.yaml).
Copiamos o endereço IP do nosso balanceador de carga e visitamos o site: http://167.172.10.106/ Devemos ver a página "Hello" novamente!
E é isso! Só falta apontar um nome de domínio para nosso novo balanceador de carga; podemos ler como fazer isso na documentação do nosso provedor de nuvem.
Leitura adicional
Este foi um pequeno guia para ajudar você a obter uma configuração básica, mas completa, de Kubernetes. Tenho certeza de que ainda há muitas perguntas sem resposta; você pode encontrar muito mais informações e guias em kubernetes.io. Recomendo começar pelos tutoriais interativos e avançar a partir daí.