Compreenda os Conceitos Essenciais do Kubernetes com um Exemplo Prático em Go Lang
Este artigo tem como objetivo simplificar sua jornada com o Kubernetes, adotando uma abordagem prática e direta. Vamos explorar os principais conceitos dessa tecnologia enquanto realizamos o deploy de uma aplicação Go Lang utilizando o Kind (Kubernetes in Docker).
Por que usar o Kind?
O Kind é uma excelente alternativa para configurar uma infraestrutura local. Ele permite:
- Executar serviços em clusters Kubernetes dentro de um container Docker.
- Configurações simples e rápidas para um ambiente de desenvolvimento local.
- Ideal para desenvolvedores iniciantes e experientes que desejam experimentar novas funcionalidades sem complexidades iniciais.
Para quem este artigo é indicado?
- Novatos em Kubernetes: Desenvolvedores que estão se familiarizando com a tecnologia.
- Desenvolvedores experientes: Aqueles que desejam iniciar um projeto com a mentalidade de "deploy first", criando os contêineres e o setup do cluster Kubernetes, mas sem as complexidades de, por exemplo, uma cloud, em um primeiro momento.
O que você aprenderá?
Ao longo deste artigo, vamos dissecar os principais conceitos do Kubernetes, passo a passo. A cada seção, aprofundaremos um pouco mais na prática, detalhando os principais componentes do Kubernetes e como eles interagem entre si.
Projeto Base
O objetivo deste artigo é nos aprofundarmos em temas relacionados ao Kubernetes. No entanto, para garantir maior fluidez e praticidade, os exemplos serão aplicados em uma aplicação simples desenvolvida em Go Lang, que pode ser encontrada no seguinte repositório: https://github.com/diegomagalhaes-dev/gst-app.git.
Containerização e Kind: Construindo e Gerenciando Nosso Ambiente Kubernetes
A containerização revolucionou a forma como as aplicações são construídas, entregues e implantadas. Ao isolar aplicações e suas dependências em pacotes leves e autossuficientes, os containers garantem um comportamento consistente em diferentes ambientes, desde o desenvolvimento até a produção. Vamos explorar os conceitos fundamentais da containerização e como utilizamos o Kind (Kubernetes in Docker) para configurar nosso ambiente Kubernetes.
Containerização: Uma Abordagem Moderna para Implantação de Aplicações
O que é Containerização?
A containerização envolve encapsular uma aplicação e suas dependências em um "container", um ambiente leve, portátil e autossuficiente que pode ser executado de forma consistente em várias infraestruturas.
Benefícios da Containerização:
- Isolamento: Containers fornecem um ambiente isolado, prevenindo conflitos entre aplicações que rodam no mesmo host.
- Portabilidade: Containers podem rodar em qualquer sistema que suporte o runtime de container, garantindo implantação consistente em ambientes de desenvolvimento, teste e produção.
- Escalabilidade: Containers podem ser facilmente escalados para cima ou para baixo, tornando-os ideais para aplicações dinâmicas e nativas da nuvem.
Exemplo no Nosso Projeto: Configuração do Dockerfile
No nosso projeto, o dockerfile.todo
define a imagem Docker para o serviço todo-api
:
FROM golang:1.23.0 AS build_todo-api
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o todo-api ./main.go
FROM alpine:3.18
RUN apk --no-cache add postgresql-client
RUN addgroup -g 1000 -S todo && \
adduser -u 1000 -h /app -G todo -S todo
WORKDIR /app
COPY --from=build_todo-api --chown=todo:todo /app/todo-api /app/todo-api
USER todo
EXPOSE 8000
CMD ["./todo-api"]
LABEL org.opencontainers.image.title="todo-api" \
org.opencontainers.image.authors="Diêgo <diegomagalhaes.contact@gmail.com>" \
org.opencontainers.image.source="https://github.com/diegomagalhaes-dev/gst-app" \
org.opencontainers.image.version="1.0.0"
Este Dockerfile consiste em duas etapas:
- Etapa de Build (golang:1.23.0 AS build_todo-api): A aplicação é compilada usando Go em um ambiente limpo. Isso garante que a imagem final contenha apenas os binários necessários, reduzindo seu tamanho.
- Etapa de Execução (FROM alpine:3.18): O binário compilado é copiado para uma imagem minimalista do Alpine Linux, fornecendo um ambiente de runtime leve. Esta etapa também instala quaisquer dependências de runtime necessárias, como o
postgresql-client
.
Ao separar as etapas de build e execução, otimizamos a imagem tanto em termos de tamanho quanto de segurança, seguindo as melhores práticas de containerização.
Kind: Simulando um Cluster Kubernetes no Docker
O que é Kind?
Kind (Kubernetes in Docker) é uma ferramenta para executar clusters Kubernetes locais usando containers Docker como Nodes. É uma excelente escolha para desenvolvimento e testes locais, pois permite que os desenvolvedores criem clusters de múltiplos Nodes sem a necessidade de várias máquinas físicas ou virtuais.
Exemplo no Nosso Projeto: Configuração do Kind
O arquivo kind.config.yaml
define a configuração para o nosso cluster Kind:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
# Todo-Api
- containerPort: 8000
hostPort: 8000
# Postgres
- containerPort: 5432
hostPort: 5432
Esta configuração especifica um cluster Kind de nó único com o papel de Control Plane. Além disso, mapeia as portas 8000 e 5432 do host para as portas 8000 (para o todo-api
) e 5432 (para o banco de dados PostgreSQL) do container, respectivamente. Este setup nos permite acessar os serviços que estão rodando dentro do cluster a partir da nossa máquina local.
Integrando Containerização com Kind: Construindo e Executando o Serviço
Configuração do Makefile para Automação
O Makefile
em nosso projeto automatiza várias tarefas relacionadas à construção e implantação do serviço todo-api
usando Docker e Kind:
# Define dependencies
GOLANG := golang:1.22.2
ALPINE := alpine:3.18
KIND := kindest/node:v1.27.3
POSTGRES := postgres:15.4
# Building containers
service:
docker build \
-f infra/docker/dockerfile.todo \
-t $(SERVICE_IMAGE) \
--build-arg BUILD_REF=$(VERSION) \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
.
# Running from within k8s/kind
dev-up:
kind create cluster \
--image $(KIND) \
--name $(KIND_CLUSTER) \
--config infra/k8s/dev/kind/kind.config.yaml
kubectl config use-context kind-$(KIND_CLUSTER)
kubectl wait --timeout=120s --namespace=local-path-storage --for=condition=Available deployment/local-path-provisioner
kind load docker-image $(POSTGRES) --name $(KIND_CLUSTER)
Principais alvos do Makefile:
service
: Constrói a imagem Docker para o serviçotodo-api
usando o Dockerfile localizado eminfra/docker/dockerfile.todo
.dev-up
: Cria um cluster Kind usando o arquivo de configuração especificado (kind.config.yaml
) e carrega as imagens Docker necessárias no cluster.
Ao aproveitar Docker e Kind, nossa configuração assegura um fluxo de trabalho de desenvolvimento simplificado que reflete - dadas as limitações - um ambiente de produção. Isso nos permite construir, implantar e testar nossa aplicação Go Lang em um cluster Kubernetes local, proporcionando um ambiente de alta fidelidade para desenvolvimento e testes.
Componentes Essenciais do Kubernetes: O Que São e Como Usá-los
Compreender os componentes principais do Kubernetes é fundamental para o deploy e a gestão eficaz de aplicações. Vamos explorar os componentes-chave que formam a base do Kubernetes, utilizando nossa aplicação em Go Lang como um exemplo prático.
1. Nodes: Os Trabalhadores dos Clusters Kubernetes
O que são Nodes?
Nodes são as máquinas de trabalho nos clusters Kubernetes. Eles são responsáveis por executar as aplicações containerizadas e fornecer os recursos computacionais necessários para manter suas aplicações funcionando sem problemas. Os Nodes podem ser servidores físicos ou máquinas virtuais, dependendo da configuração do cluster.
Papel Arquitetural:
- Ambiente de Execução: Nodes servem como o ambiente de execução para seus Pods. Cada Node executa pelo menos um kubelet (um agente responsável por se comunicar com o Control Plane do Kubernetes), um runtime de container (como Docker ou containerd) e um kube-proxy (que mantém as regras de rede nos Nodes).
- Gerenciamento de Recursos: Nodes fornecem CPU, memória, armazenamento e recursos de rede para a execução de containers. O Kubernetes gerencia esses recursos de forma eficiente, garantindo que cada Pod receba os recursos necessários conforme especificado em sua configuração.
Componentes de um Node:
- Kubelet: O kubelet é um agente que roda em cada Node e garante que os containers estejam rodando em um Pod. Ele monitora continuamente o status dos Pods e se comunica com o servidor de API do Kubernetes para manter o estado desejado dos Pods.
- Container Runtime: O runtime de container é o software responsável por executar os containers. Runtimes populares incluem Docker, containerd e CRI-O. O Kubernetes suporta qualquer runtime que implemente a Interface de Runtime de Container do Kubernetes (CRI).
- Kube-proxy: O kube-proxy é um proxy de rede que roda em cada Node e gerencia a comunicação de rede entre Pods em diferentes Nodes. Ele implementa os serviços de rede do Kubernetes em cada Node, garantindo que os Pods possam se comunicar entre si e com serviços externos.
Exemplo no Nosso Projeto:
No nosso projeto, os Nodes são representados pelos containers Docker que executam o Kubernetes quando usamos o Kind (Kubernetes in Docker). Cada Node em um cluster Kind é um container Docker, o que nos permite simular um cluster Kubernetes de múltiplos Nodes localmente.
Embora não tenhamos um manifesto YAML específico para definir Nodes (já que os Nodes são gerenciados pelo Control Plane), dependemos deles para fornecer o ambiente necessário para nossos Pods e serviços. Por exemplo, ao implantarmos o banco de dados PostgreSQL ou a aplicação todo-api
, o Kubernetes agenda esses Pods nos Nodes disponíveis, utilizando seus recursos computacionais.
Conceitos-Chave Relacionados aos Nodes:
- Afinidade e Anti-Afinidade de Nodes: O Kubernetes oferece mecanismos para controlar como os Pods são agendados nos Nodes. A afinidade de Node permite definir regras que atraem Pods para certos Nodes, enquanto a anti-afinidade garante que os Pods sejam distribuídos entre Nodes para melhorar a tolerância a falhas.
- Taints e Tolerations: Taints e tolerations são usados para evitar que certos Pods sejam agendados em determinados Nodes. Por exemplo, um Node pode ser marcado (tainted) para permitir apenas cargas de trabalho específicas, como aquelas que exigem GPUs para computação, garantindo que apenas Pods compatíveis sejam agendados lá.
Compreendendo o Gerenciamento de Nodes:
- Status de Node: Cada Node mantém um status que fornece informações essenciais, como a saúde do Node, capacidade (CPU, memória, etc.) e condições (por exemplo, Ready, DiskPressure, MemoryPressure).
- Manutenção de Nodes: Nodes podem ser marcados como não agendáveis (unschedulable) quando precisam de manutenção. Isso impede que novos Pods sejam agendados no Node, permitindo que os Pods existentes continuem a rodar ou sejam reprogramados.
2. Pods: O Bloco de Construção Fundamental do Kubernetes
O que são Pods?
Pods são as unidades mais básicas de implantação no Kubernetes, representando uma única instância de um processo em execução. Um Pod pode encapsular um ou mais containers que compartilham o mesmo namespace de rede e armazenamento. Os containers dentro de um Pod podem se comunicar entre si usando localhost e compartilhar volumes de armazenamento.
Papel Arquitetural:
- Natureza Efêmera: Pods são projetados para serem efêmeros. Quando um Pod falha, o Kubernetes cria automaticamente um novo Pod para substituí-lo, em vez de repará-lo.
- Co-Localização de Containers: Containers que precisam compartilhar recursos (como armazenamento ou rede) ou que sempre devem ser implantados juntos são agrupados em um único Pod.
Exemplo no nosso projeto:
No nosso projeto, a configuração de StatefulSet
no arquivo dev-database.yaml
define um template de Pod para executar um container PostgreSQL:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database
namespace: simple-go-todo
spec:
replicas: 1
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
spec:
containers:
- name: postgres
image: 'postgres:15.4'
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
Essa configuração garante que um Pod executando um banco de dados PostgreSQL seja criado e mantido, com armazenamento persistente montado em /var/lib/postgresql/data
.
3. Deployments: Gerenciando o Estado Desejado da Sua Aplicação
O que são Deployments?
Deployments são abstrações que gerenciam Pods e ReplicaSets. Eles fornecem atualizações declarativas, garantindo que o número especificado de Pods esteja sempre em execução e podem lidar com tarefas como escalonamento, atualizações contínuas e reversões.
Papel Arquitetural:
- Escalabilidade e Resiliência: Deployments permitem o escalonamento horizontal de aplicações (aumentando o número de réplicas) para lidar com tráfego ou carga de trabalho aumentados.
- Atualizações Contínuas e Reversões: Suportam atualizações sem tempo de inatividade ao atualizar incrementalmente os Pods com novas versões de uma aplicação e podem reverter para uma versão anterior, se necessário.
Exemplo no nosso projeto:
O arquivo base-service.yaml
especifica um Deployment para nossa aplicação todo:
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo
namespace: simple-go-todo
spec:
replicas: 1
selector:
matchLabels:
app: todo
template:
metadata:
labels:
app: todo
spec:
containers:
- name: todo-api
image: service-image
Este Deployment gerencia o ciclo de vida do Pod todo-api
, garantindo que uma instância esteja sempre em execução e possa ser escalada conforme necessário.
4. Services: Rede Estável para Seus Pods
O que são Services?
Services fornecem pontos de acesso de rede estáveis para acessar Pods dentro de um cluster Kubernetes. Eles abstraem o acesso à rede para os Pods, permitindo a comunicação dentro do cluster e com clientes externos.
Tipos de Services:
- ClusterIP: Expõe o Service em um IP interno do cluster, acessível apenas dentro do cluster.
- NodePort: Expõe o Service em uma porta estática no IP de cada node.
- LoadBalancer: Provisona um IP externo para balancear o tráfego entre os nodes.
- ExternalName: Mapeia um Service para um nome DNS externo.
Papel Arquitetural:
- Desacoplamento: Services desacoplam o cliente dos endereços IP subjacentes dos Pods, que podem mudar se os Pods forem recriados ou reprogramados.
- Descoberta de Serviços: Eles fornecem uma interface consistente para descoberta de serviços, permitindo que outras aplicações descubram e comuniquem-se com os Pods de maneira confiável.
Exemplo no nosso projeto:
O arquivo dev-todo-patch-service.yaml
cria um Service para o todo-api
:
apiVersion: v1
kind: Service
metadata:
name: todo-api
namespace: simple-go-todo
spec:
type: ClusterIP
ports:
- name: todo-api
port: 8000
targetPort: todo-api
Este Service permite a comunicação interna dentro do cluster para acessar o todo-api
em um IP e porta estáveis.
5. ConfigMaps e Secrets: Gerenciando Configurações e Dados Sensíveis
O que são ConfigMaps e Secrets?
- ConfigMaps: Armazenam dados de configuração não sensíveis em pares chave-valor.
- Secrets: Armazenam dados sensíveis, como senhas, tokens OAuth e chaves SSH, codificados em base64.
Papel Arquitetural:
- Separação de Configuração e Código: Permitem a separação da configuração do código da aplicação, tornando a aplicação portátil e mais fácil de gerenciar.
- Gerenciamento Seguro e Flexível: Secrets garantem que dados sensíveis sejam gerenciados com segurança, enquanto ConfigMaps fornecem uma maneira flexível de gerenciar configurações sem hardcoding de valores no código da aplicação.
Exemplo no nosso projeto:
Utilizamos um ConfigMap para configurar as definições do PostgreSQL no dev-database.yaml
:
apiVersion: v1
kind: ConfigMap
metadata:
name: pghbaconf
namespace: simple-go-todo
data:
pg_hba.conf: |
local all all trust
# IPv4 local connections:
host all all 0.0.0.0/0 trust
# IPv6 local connections:
host all all ::1/128 trust
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all trust
host replication all 0.0.0.0/0 trust
host replication all ::1/128 trust
Este ConfigMap armazena a configuração de controle de acesso do PostgreSQL, que é montada como um arquivo no Pod.
5. StatefulSets: Gerenciando Aplicações Stateful
O que são StatefulSets?
StatefulSets gerenciam a implantação e o escalonamento de um conjunto de Pods, proporcionando garantias sobre a ordem e a unicidade desses Pods.
Papel Arquitetural:
- Gerenciamento de Aplicações Stateful: Ideais para gerenciar aplicações stateful, onde cada Pod deve ter uma identidade única e armazenamento persistente estável.
- Identidade de Rede e Armazenamento Estável: Garante que cada Pod tenha uma identidade de rede única e estável e possa manter armazenamento persistente entre reinicializações.
Exemplo no nosso projeto:
O arquivo dev-database.yaml
usa um StatefulSet para implantar uma instância PostgreSQL:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: database
namespace: simple-go-todo
spec:
selector:
matchLabels:
app: database
replicas: 1
template:
metadata:
labels:
app: database
Esta configuração fornece uma identidade estável e armazenamento persistente para nosso banco de dados PostgreSQL.
O Modelo Declarativo do Kubernetes
O Kubernetes utiliza um modelo declarativo onde você define o estado desejado da sua aplicação, e o Kubernetes trabalha continuamente para manter esse estado. Definindo os componentes da sua aplicação como manifests YAML, você pode gerenciar e escalar facilmente suas aplicações em um cluster Kubernetes. Essa abordagem contrasta com os modelos imperativos, onde cada etapa é executada manualmente, oferecendo uma maneira mais escalável e resiliente de gerenciar aplicações.
Compreender esses objetos do Kubernetes e sua arquitetura é crucial para aproveitar todo o potencial do Kubernetes. Em nosso projeto, aplicamos esses conceitos para implantar uma aplicação Go Lang, proporcionando um exemplo prático de como cada componente se encaixa na arquitetura geral.
Colocando em prática: executando o projeto com comandos Makefile
Nesta seção final, vamos guiá-lo pelo processo passo a passo de construção, implantação e execução da nossa aplicação Go Lang usando os comandos definidos no Makefile. Isso proporcionará uma compreensão abrangente de como cada comando contribui para o processo geral de implantação e garantirá que tudo esteja funcionando corretamente em seu ambiente Kubernetes.
O Makefile simplifica o fluxo de trabalho automatizando tarefas repetitivas. Vamos detalhar os comandos principais e o que acontece quando você executa cada um deles.
1. Instalando Dependências
O primeiro passo para configurar nosso ambiente é instalar todas as dependências necessárias. O Makefile
fornece um alvo para instalar essas dependências usando o Homebrew (fique a vontade para adaptar para algum gerenciador de sua preferência).
dev-brew:
brew update
brew list kind || brew install kind
brew list kubectl || brew install kubectl
brew list kustomize || brew install kustomize
brew list pgcli || brew install pgcli
Este passo garante que todas as ferramentas necessárias estejam disponíveis em sua máquina para interagir com o cluster Kubernetes e gerenciar configurações.
2. Baixando Imagens Docker
Antes de construir nossa imagem Docker personalizada, precisamos garantir que temos as imagens base necessárias.
dev-docker:
docker pull $(GOLANG)
docker pull $(ALPINE)
docker pull $(KIND)
docker pull $(POSTGRES)
O script baixa as imagens Docker especificadas para Go, Alpine, Node Kind e PostgreSQL. Essas imagens são a base para construir nossa imagem de aplicação personalizada e executar nosso cluster Kubernetes local.
3. Construindo a Imagem Docker
O Makefile
inclui um comando para construir a imagem Docker para nosso serviço todo-api
.
service:
docker build \
-f infra/docker/dockerfile.todo \
-t $(SERVICE_IMAGE) \
--build-arg BUILD_REF=$(VERSION) \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
.
- Construção da Imagem Docker: Este comando constrói a imagem Docker usando o Dockerfile localizado em
infra/docker/dockerfile.todo
. Ele marca a imagem com a versão especificada na variávelVERSION
(comotodo-api:0.0.1
). - Argumentos de Build:
BUILD_REF
eBUILD_DATE
são passados como argumentos de build para incorporar informações de versionamento e metadados de build na imagem.
A imagem Docker resultante contém a aplicação Go Lang compilada, pronta para ser implantada no nosso cluster Kubernetes.
4. Criando o Cluster Kind
Para simular um ambiente Kubernetes localmente, usamos o Kind para criar um novo cluster.
dev-up:
kind create cluster \
--image $(KIND) \
--name $(KIND_CLUSTER) \
--config infra/k8s/dev/kind/kind.config.yaml
kubectl config use-context kind-$(KIND_CLUSTER)
kubectl wait --timeout=120s --namespace=local-path-storage --for=condition=Available deployment/local-path-provisioner
kind load docker-image $(POSTGRES) --name $(KIND_CLUSTER)
- Criação do Cluster Kind: O comando
kind create cluster
cria um novo cluster Kubernetes chamadosgt-kind-cluster
usando a imagem especificada do node Kind (kindest/node:v1.27.3
) e o arquivo de configuração (kind.config.yaml
). - Definição do Contexto Kubernetes:
kubectl config use-context
alterna o contexto atual do Kubernetes para o novo cluster Kind, permitindo que comandoskubectl
subsequentes interajam com ele. - Aguardando o Provisionador de Armazenamento: O comando
kubectl wait
aguarda até que o deploymentlocal-path-provisioner
esteja disponível, garantindo que o cluster esteja pronto para provisionar volumes de armazenamento. - Carregar Imagem Docker no Cluster:
kind load docker-image
carrega a imagem Docker do PostgreSQL no cluster Kind, tornando-a disponível para uso pela nossa aplicação.
5. Implantando a Aplicação no Kubernetes
Com o cluster configurado e as imagens carregadas, agora podemos implantar nossa aplicação e suas dependências.
Usando Kustomize para Gerenciar Configurações Kubernetes
O que é Kustomize?
Kustomize é uma ferramenta nativa do Kubernetes que permite customizar configurações de recursos Kubernetes sem alterar os arquivos YAML originais. Ele é especialmente útil para gerenciar ambientes diferentes (como desenvolvimento, teste e produção) a partir de uma base comum de arquivos de configuração. Ao usar o Kustomize, podemos gerar automaticamente os manifests personalizados para nosso cluster, aplicando overlays específicos que ajustam as configurações conforme necessário.
dev-apply:
kustomize build infra/k8s/dev/database | kubectl apply -f -
kubectl rollout status --namespace=$(NAMESPACE) --watch --timeout=120s sts/database
kustomize build infra/k8s/dev/service | kubectl apply -f -
kubectl wait pods --namespace=$(NAMESPACE) --selector app=$(APP) --timeout=120s --for=condition=Ready
- Aplicar Configuração do Banco de Dados:
kustomize build
gera os manifests Kubernetes para a configuração do banco de dados a partir dos arquivos YAML base.kubectl apply -f -
aplica essas configurações ao cluster, criando os recursos necessários (por exemplo,StatefulSet
para PostgreSQL). - Aguardar Implantação do Banco de Dados: O comando
kubectl rollout status
espera que oStatefulSet
do PostgreSQL esteja totalmente implantado e em execução antes de prosseguir. - Aplicar Configuração do Serviço: O processo é repetido para o serviço
todo-api
, garantindo que o serviço e suas dependências sejam implantados no cluster. - Aguardar que os Pods Fiquem Prontos:
kubectl wait pods
garante que todos os Pods associados à aplicaçãotodo-api
estejam em execução e prontos antes de concluir o processo de implantação.
6. Testando os Endpoints da Aplicação
Finalmente, podemos usar o Makefile para testar os endpoints da API REST da nossa aplicação e verificar se tudo está funcionando como esperado.
test_all: create get_all get_one update delete
Seguindo esses passos, você pode construir, implantar e testar com sucesso sua aplicação Go Lang em um cluster Kubernetes local usando Docker e Kind. O Makefile automatiza grande parte desse processo, facilitando o gerenciamento e reduzindo o risco de erros.
Conclusão
Ao combinar containerização, Kubernetes e Kind, podemos criar um ambiente de desenvolvimento local poderoso e flexível que se assemelha - dadas as limitações - a um setup de produção. Esta abordagem permite um desenvolvimento, teste e iteração eficientes, garantindo que suas aplicações sejam robustas, escaláveis e prontas para implantação em ambientes reais.