Você abre uma barraca com um único vendedor e um cartaz "API". O vendedor usa um crachá com seu nome, e a cada cliente diz "olá, sou jv2xw". Quando a fila alonga, você diz ao gerente "quero três vendedores", e dois novos crachás aparecem. Os clientes se apresentam no mesmo balcão, mas é alternadamente 4pl9z, nvshh quem responde: a carga se distribui. Se um vendedor sai, o gerente já chamou outro antes que você perceba, porque sua instrução é "três vendedores", não "esses três específicos". É exatamente o que você verá: traefik/whoami é um vendedor que exibe seu nome (o Pod), o Deployment é o gerente, o Service é o balcão, e localhost:8080 é a porta da barraca.
Quando você digita kubectl create deployment api --image=traefik/whoami:v1.10, não é criado um Pod mas três objetos encaixados. O Deployment api carrega sua intenção ("essa imagem, esse número de réplicas") e sabe gerenciar atualizações. Cria um ReplicaSet api-9bfb55fc6, cujo único trabalho é manter exatamente N Pods idênticos; o sufixo 9bfb55fc6 é um hash do modelo Pod (imagem, portas, labels), então uma nova imagem dará novo ReplicaSet. O ReplicaSet finalmente cria os Pods api-9bfb55fc6-jv2xw, -4pl9z… com um sufixo aleatório de cinco caracteres. Cada Pod recebe uma IP interna (10.1.0.210) inacessível de seu navegador.
Para acessá-la, dois caminhos. kubectl port-forward abre um túnel temporário de seu terminal para um Pod: perfeito para verificar, inútil para distribuir carga, e ele fecha com Ctrl+C. Um Service é o objeto permanente: uma IP estável no cluster (CLUSTER-IP), um nome DNS (api.primeiros-passos.svc), e uma lista de endereços alvo mantida atualizada por um controlador a partir dos labels (app=api). De tipo LoadBalancer, pede além uma adressIP externa; no Docker Desktop, o componente vpnkit-controller responde "localhost" e publica a porta do Service em sua máquina. É o que torna http://localhost:8080 possível sem túnel.
Esta lição trabalha de forma imperativa: cada comando diz ao cluster o que fazer, agora mesmo. É a forma certa de conseguir uma vitória rápida e ver os objetos aparecerem. A partir do módulo seguinte, você escreverá os mesmos objetos em YAML e os aplicará com kubectl apply: é o modo declarativo, repetível e versionável, aquele da vida real. Os dois produzem exatamente os mesmos objetos; kubectl get deployment api -o yaml já te mostra o que create deployment escreveu por você.
| Comando imperativo | O que cria ou muda | Equivalente declarativo (próximo módulo) |
|---|---|---|
kubectl create namespace primeiros-passos | um Namespace | kind: Namespace |
kubectl create deployment api --image=… --port=80 | um Deployment (+ ReplicaSet + Pod) | kind: Deployment, spec.template.spec.containers[0].image |
kubectl expose deployment api --type=LoadBalancer --port=8080 --target-port=80 | um Service que visa app=api | kind: Service, spec.type: LoadBalancer, ports[0].port: 8080, targetPort: 80 |
kubectl scale deployment api --replicas=3 | spec.replicas do Deployment | mudar replicas: 3 depois kubectl apply |
kubectl delete pod <nome> | deleta um Pod; o ReplicaSet o substitui | nada a escrever: é a reconciliação |
| Coluna | Em get deploy | Em get rs | Em get pods |
|---|---|---|---|
READY | Pods prontos / desejados (3/3) | Pods prontos | contêineres prontos / total (1/1) |
UP-TO-DATE | Pods no último modelo | — | — |
DESIRED / CURRENT | — | desejados / existentes | — |
STATUS | — | — | Running, ContainerCreating, ImagePullBackOff… |
Todos os comandos usam -n primeiros-passos. Se você usar alias k da lição 03, substitua kubectl por k.
Criar o namespace do módulo. Uma gaveta vazia na qual tudo que segue será guardado, e que a última etapa va deletar de uma vez.
kubectl create namespace primeiros-passosSaída real:
namespace/primeiros-passos createdO que observar: created. Se ler AlreadyExists, você o guardou de lição 03: sem problema, continue.
Criar o Deployment api. Uma imagem pública, uma tag fixa (v1.10, nunca latest: sem tag precisa, duas máquinas podem baixar duas versões diferentes e "funciona aqui" volta), e a porta que o contêiner escuta.
kubectl create deployment api --image=traefik/whoami:v1.10 --port=80 -n primeiros-passos
kubectl get deploy,rs,pods -n primeiros-passosSaída real (oito segundos após criação):
deployment.apps/api created
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/api 1/1 1 1 8s
NAME DESIRED CURRENT READY AGE
replicaset.apps/api-9bfb55fc6 1 1 1 8s
NAME READY STATUS RESTARTS AGE
pod/api-9bfb55fc6-jv2xw 1/1 Running 0 8sO que observar: três objetos por um comando. Leia o nome do Pod de direita a esquerda: jv2xw = esse Pod preciso (aleatório), 9bfb55fc6 = ReplicaSet, então modelo Pod, api = Deployment. Se STATUS diz ContainerCreating, imagem está baixando (alguns segundos para 10,6 MB); digite comando novamente.
Ver onde o Pod vive. -o wide adiciona IP do Pod e nó que o hospeda.
kubectl get pods -n primeiros-passos -o wideSaída real:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
api-9bfb55fc6-jv2xw 1/1 Running 0 8s 10.1.0.210 docker-desktop <none> <none>O que observar: IP em 10.1.0.x, rede interna do cluster, nó docker-desktop. Abra http://10.1.0.210 no navegador: nada responde, IP existe apenas na VM. Esse é todo o assunto dos dois passos seguintes.
Abrir túnel com port-forward. Terminal bloqueia enquanto túnel está aberto; é proposital.
kubectl port-forward deployment/api 8080:80 -n primeiros-passosSaída real:
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080Abra http://localhost:8080 no navegador (linha Handling connection aparece a cada requisição). Página exibida, como curl -s http://localhost:8080 a exibe:
Hostname: api-9bfb55fc6-jv2xw
IP: 127.0.0.1
IP: ::1
IP: 10.1.0.210
IP: fe80::48b:d4ff:fe47:6adb
RemoteAddr: 127.0.0.1:43450
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.21.0
Accept: */*O que observar: Hostname: api-9bfb55fc6-jv2xw, nome do Pod, e IP 10.1.0.210. Volta ao terminal e digita Ctrl+C: túnel fecha, http://localhost:8080 não responde. Túnel não é exposição.
Expor com Service LoadBalancer. --port é porta do Service (o que você digita no navegador), --target-port aquela do contêiner.
kubectl expose deployment api --type=LoadBalancer --port=8080 --target-port=80 -n primeiros-passos
kubectl get svc -n primeiros-passosSaída real:
service/api exposed
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api LoadBalancer 10.101.121.221 localhost 8080:30153/TCP 3sO que observar: EXTERNAL-IP vale localhost, assinatura do Docker Desktop (em nuvem você leria endereço público após minuto; em kind, <pending> para sempre). PORT(S) diz 8080:30153: 8080 é porta publicada em sua máquina, 30153 porta nó atribuída automaticamente cuja você não precisa aqui. Recarregue http://localhost:8080 sem qualquer port-forward: página whoami volta.
Passar para três réplicas e ver carga se distribuir. Deployment muda instrução; ReplicaSet cria dois Pods; Service adiciona a sua lista assim que prontos.
kubectl scale deployment api --replicas=3 -n primeiros-passos
kubectl get pods -n primeiros-passosSaída real (10 segundos após escalar):
NAME READY STATUS RESTARTS AGE
api-9bfb55fc6-4pl9z 1/1 Running 0 10s
api-9bfb55fc6-jv2xw 1/1 Running 0 15s
api-9bfb55fc6-nvshh 1/1 Running 0 10sO que observar: três Pods diferentes. Recarregue http://localhost:8080 cinco vezes e leia Hostname: alternadamente jv2xw, depois nvshh, depois 4pl9z. Service equilibra a carga redondinha. Se todos fossem jv2xw, o Service não estaria listando todos corretamente ou apenas um Pod.
Matar um Pod e vê-lo renascer. O ReplicaSet vê 2 em vez de 3 e imediatamente cria outro.
kubectl delete pod api-9bfb55fc6-4pl9z -n primeiros-passos
kubectl get pods -n primeiros-passos -wSaída real (a flag -w = "watch", fica conectado e mostra mudanças):
NAME READY STATUS RESTARTS AGE
api-9bfb55fc6-4pl9z 1/1 Running 0 10s
api-9bfb55fc6-jv2xw 1/1 Running 0 15s
api-9bfb55fc6-nvshh 1/1 Running 0 10s
api-9bfb55fc6-4pl9z 1/1 Terminating 0 15s
api-9bfb55fc6-xq7v6 0/1 Pending 0 0s
api-9bfb55fc6-xq7v6 0/1 ContainerCreating 0 1s
api-9bfb55fc6-4pl9z 1/1 Terminating 0 16s
api-9bfb55fc6-xq7v6 1/1 Running 0 3sO que observar: 4pl9z terminando, novo xq7v6 criando, após três segundos de novo 1/1 Running. Atualize http://localhost:8080 durante os três segundos: sem erro, Service redireciona para os outros dois Pods. Pressione Ctrl+C para sair do watch.
Deletar tudo do module com um comando. Deployment, ReplicaSet, Pods, Service — tudo na mesma gaveta.
kubectl delete namespace primeiros-passosSaída real:
namespace "primeiros-passos" deletedO que observar: tudo deletado de uma vez. Verifique:
kubectl get namespace primeiros-passosSaída real:
Error from server (NotFound): namespaces "primeiros-passos" not foundPerfeito: namespace e tudo dentro foi limpo.
ImagePullBackOff após 30 segundos → Kubernetes não consegue baixar traefik/whoami:v1.10. Possíveis causa: tag errada (verifique no Docker Hub), sem acesso a Docker Hub (rede corporativa), ou seu kubeconfig aponta para cluster errado (lição 03). Se for um verdadeiro problema de rede, você verá ErrImagePull depois ImagePullBackOff; tente docker pull traefik/whoami:v1.10 em seu terminal.<pending> → Kubernetes procura um balanceador externo. No Docker Desktop depois alguns segundos deve aparecer localhost. Se ficar <pending> para sempre, você pode estar em minikube ou kind: use kubectl port-forward permanentemente ou minikube tunnel / kind load docker-image.kubectl get endpoints -n primeiros-passos deve listar api com IPs. Se estiver vazio, o Service não encontrou Pods com labels app=api — etapa 2 talvez tenha falhado, veja kubectl describe pod <name> -n primeiros-passos | grep -i label.kubectl create deployment: Deployment, ReplicaSet, Pod(s).CLUSTER-IP, DNS <nome>.<namespace>.svc), opcionalmente IP ou nome externo.localhost; escala de 1 para N réplicas com kubectl scale; Pod morto é substituído automaticamente.A página Kubernetes Objects Lifecycle explica cascata de criação (Deployment → ReplicaSet → Pod). Deployments oferece referência completa de campos de um Deployment.