Study guide

Kubernetes, from zero to production.

What Kubernetes is, the problem it solves, the configuration files and their security options, small hands-on labs with Docker on your machine, and how to run it securely on AWS.

O que é o Kubernetes e que problema ele resolveWhat Kubernetes is and the problem it solves

Kubernetes (K8s: o 8 são as letras entre K e s) é um sistema open source, criado no Google e hoje mantido pela CNCF, que roda e cuida de contêineres em várias máquinas como se fossem uma só. O nome vem do grego e significa “timoneiro”: quem conduz o navio. Pense num maestro: cada músico (contêiner) sabe tocar o seu trecho, mas alguém precisa dizer quem toca, quando, e substituir quem faltar.Kubernetes (K8s: the 8 stands for the letters between K and s) is an open source system, created at Google and now maintained by the CNCF, that runs and looks after containers across many machines as if they were one. The name is Greek for “helmsman”: the one who steers the ship. Think of a conductor: each musician (container) knows their part, but someone has to say who plays, when, and replace whoever is missing.

Pré-requisito: saber o que é um contêiner. Uma imagem é o pacote pronto (programa + bibliotecas); um contêiner é essa imagem em execução, isolada do resto da máquina. O Docker constrói e roda contêineres em uma máquina. O Kubernetes entra quando são muitos contêineres em muitas máquinas.Prerequisite: knowing what a container is. An image is the ready-made package (program + libraries); a container is that image running, isolated from the rest of the machine. Docker builds and runs containers on one machine. Kubernetes enters when there are many containers on many machines.

O problemaThe problem

Rodar um contêiner com docker run é fácil. Operar dezenas deles em produção traz perguntas que o Docker sozinho não responde:Running one container with docker run is easy. Operating dozens in production raises questions Docker alone does not answer:

  • O contêiner caiu às 3h da manhã. Quem sobe de novo?The container died at 3 a.m. Who starts it again?
  • A máquina inteira morreu. Quem move os contêineres para outra?The whole machine died. Who moves the containers to another one?
  • Chegou mais tráfego. Como ter 10 cópias em vez de 2, e dividir os acessos entre elas?More traffic arrived. How do you get 10 copies instead of 2, and split the requests among them?
  • Versão nova. Como trocar sem derrubar o site, e voltar atrás se der errado?New version. How do you swap without downtime, and go back if it fails?
  • Configuração e segredos. Como entregar senhas e variáveis sem colocá-las na imagem?Configuration and secrets. How do you deliver passwords and variables without baking them into the image?
  • Quem pode fazer o quê? E quem pode falar com quem dentro do sistema?Who may do what? And who may talk to whom inside the system?
você + YAMLyou + YAML 3 cópias3 copies cluster KubernetesKubernetes cluster nó 1node 1 app app nó 2node 2 app ✗ caiu✗ died nó 3node 3 app recriadorecreated O Kubernetes recria o que caiu e mantém 3 cópias no arKubernetes recreates what died and keeps 3 copies running
Você descreve o resultado (3 cópias); o cluster distribui entre os nós e recria o que cair.You describe the result (3 copies); the cluster spreads them across nodes and recreates whatever dies.

A ideia central: declarativoThe core idea: declarative

Você não manda “suba o contêiner A na máquina 2”. Você escreve um arquivo YAML dizendo como quer que fique (“3 cópias do app A, na versão 1.27”) e o Kubernetes trabalha sem parar para que a realidade fique igual ao arquivo. É a mesma ideia do Terraform, só que para aplicações em execução.You do not say “start container A on machine 2”. You write a YAML file saying how you want things to be (“3 copies of app A, version 1.27”) and Kubernetes works non-stop to make reality match the file. It is the same idea as Terraform, but for running applications.

Quando usar, e quando não. Kubernetes vale quando você tem vários serviços, precisa de alta disponibilidade e de deploys frequentes. Para um site simples ou um único app, uma VM, o ECS/Fargate ou um PaaS custam muito menos esforço. K8s tem curva de aprendizado e custo operacional reais.When to use it, and when not. Kubernetes pays off when you have several services, need high availability and deploy often. For a simple site or a single app, a VM, ECS/Fargate or a PaaS take far less effort. K8s has a real learning curve and operating cost.

Como funciona por dentroHow it works inside

Um cluster é um conjunto de máquinas (chamadas nós). Um grupo delas, o control plane, decide; as outras, os workers, executam. Você só conversa com o control plane, pelo kubectl (a linha de comando) que chama o API server.A cluster is a set of machines (called nodes). Some of them, the control plane, decide; the others, the workers, execute. You only talk to the control plane, through kubectl (the command line) calling the API server.

kubectl control plane (o cérebro)control plane (the brain) API server porta de entradafront door etcd estado do clustercluster state scheduler escolhe o nó de cada Podpicks a node for each Pod controller manager compara desejado × atualcompares desired × actual nó (worker)node (worker) kubelet kube-proxy containerd Pod Pod Pod contêineres rodam dentro de Podscontainers run inside Pods nó (worker)node (worker) kubelet kube-proxy containerd Pod Pod Pod contêineres rodam dentro de Podscontainers run inside Pods API server ⇄ kubelet de cada nóAPI server ⇄ each node's kubelet
O control plane decide e guarda o estado; os nós executam. Tudo passa pelo API server.The control plane decides and stores state; the nodes execute. Everything goes through the API server.
PeçaPieceOndeWhereO que fazWhat it does
API serverAPI servercontrol planecontrol planeÚnica porta de entrada: autentica, autoriza, valida e grava. kubectl, nós e controllers só falam com ele.The only entry point: authenticates, authorizes, validates and stores. kubectl, nodes and controllers only talk to it.
etcdetcdcontrol planecontrol planeBanco chave-valor com todo o estado do cluster, inclusive os Secrets. Perdeu o etcd, perdeu o cluster.Key-value database with all cluster state, including Secrets. Lose etcd, lose the cluster.
schedulerschedulercontrol planecontrol planePara cada Pod novo, escolhe o nó que tem espaço (CPU, memória, regras).For each new Pod, picks the node with room (CPU, memory, rules).
controller managercontroller managercontrol planecontrol planeConjunto de “vigias”: cada um compara o estado desejado com o real e corrige.A set of “watchers”: each compares desired state with actual state and fixes it.
kubeletkubeletcada workereach workerAgente no nó: recebe a lista de Pods do API server e manda o runtime executá-los; reporta a saúde.Agent on the node: receives the Pod list from the API server and tells the runtime to run them; reports health.
kube-proxykube-proxycada workereach workerPrograma as regras de rede para que um Service chegue aos Pods certos.Programs the network rules so a Service reaches the right Pods.
container runtimecontainer runtimecada workereach workerQuem de fato roda os contêineres (containerd ou CRI-O).What actually runs the containers (containerd or CRI-O).

O laço de reconciliaçãoThe reconciliation loop

Tudo no Kubernetes segue o mesmo padrão: cada controller olha o estado desejado (o que você escreveu) e o estado atual (o que existe) e age sobre a diferença, para sempre. Por isso um Pod apagado volta, e por isso você nunca “inicia” coisas à mão.Everything in Kubernetes follows the same pattern: each controller looks at the desired state (what you wrote) and the current state (what exists) and acts on the difference, forever. That is why a deleted Pod comes back, and why you never “start” things by hand.

1. Você declara1. You declare replicas: 3 2. API grava2. API stores no etcdin etcd 3. Controller vê3. Controller sees tem 2, quer 3has 2, wants 3 4. Cria 1 Pod4. Creates 1 Pod e volta ao passo 3then back to step 3 repete para sempre: é isso que “autocura” significarepeats forever: this is what “self-healing” means
O coração do Kubernetes: um laço que compara o estado desejado com o real e corrige a diferença.The heart of Kubernetes: a loop that compares desired state with actual state and fixes the difference.
Em nuvem gerenciada (EKS, GKE, AKS) o control plane é da empresa de nuvem: você cuida só dos workers e do que roda neles. É o caminho recomendado para produção.On a managed cloud (EKS, GKE, AKS) the control plane belongs to the cloud provider: you only look after the workers and what runs on them. This is the recommended path for production.

Os objetos que você vai usarThe objects you will use

Tudo que você cria é um objeto descrito em YAML. Estes são os que cobrem 80% do dia a dia:Everything you create is an object described in YAML. These cover 80% of daily work:

Internet Ingress HTTP(S) de foraHTTP(S) from outside Service IP/DNS fixofixed IP/DNS Deployment → ReplicaSet → Pods Pod app: web Pod app: web Pod app: web o Deployment mantém N cópias iguaisthe Deployment keeps N identical copies e troca versões aos poucosand swaps versions gradually ConfigMap Secret configuraçãoconfiguration senhas e tokenspasswords and tokens
O caminho de uma requisição (Internet → Ingress → Service → Pods) e o que alimenta os Pods.A request's path (Internet → Ingress → Service → Pods) and what feeds the Pods.
ObjetoObjectEm uma fraseIn one sentenceAnalogiaAnalogy
PodPodA menor unidade: 1 ou mais contêineres que dividem rede e disco. É descartável: morreu, é substituído por outro (com IP novo).The smallest unit: 1 or more containers sharing network and disk. Disposable: when it dies it is replaced by another (with a new IP).um funcionárioone employee
ReplicaSetReplicaSetGarante que existam N Pods iguais. Raramente você o cria; o Deployment o faz.Ensures N identical Pods exist. You rarely create it; the Deployment does.o RH que mantém o quadroHR keeping headcount
DeploymentDeploymentDescreve o app: imagem, cópias, atualização gradual e rollback. É o objeto mais usado.Describes the app: image, copies, gradual update and rollback. The most-used object.o gerente do timethe team manager
ServiceServiceDá um nome e IP fixos a um grupo de Pods e divide o tráfego entre eles. Os Pods mudam; o Service não.Gives a fixed name and IP to a group of Pods and splits traffic among them. Pods change; the Service does not.o telefone da empresathe company phone number
Ingress / GatewayIngress / GatewayRegras HTTP(S) de entrada: domínio e caminho para qual Service, com TLS.Inbound HTTP(S) rules: domain and path to which Service, with TLS.a recepçãothe front desk
ConfigMap / SecretConfigMap / SecretConfiguração e dados sensíveis, fora da imagem. O Secret é “aviso de cuidado”, não cofre (veja segurança).Configuration and sensitive data, outside the image. A Secret is a “handle with care” label, not a vault (see security).o manual / o cofrethe manual / the safe
NamespaceNamespaceDivide o cluster em “pastas” isoladas (dev, prod, times), com regras e cotas próprias.Splits the cluster into isolated “folders” (dev, prod, teams), with their own rules and quotas.departamentosdepartments
PersistentVolumeClaimPersistentVolumeClaimPedido de disco que sobrevive ao Pod (bancos de dados, uploads).A request for disk that outlives the Pod (databases, uploads).um armário trancadoa locker
Job / CronJobJob / CronJobTarefa que roda até terminar (uma vez ou agendada).A task that runs to completion (once or scheduled).uma tarefa avulsaa one-off task
ServiceAccount, Role, RoleBindingServiceAccount, Role, RoleBindingIdentidade e permissões (RBAC) de Pods e pessoas dentro do cluster.Identity and permissions (RBAC) for Pods and people inside the cluster.crachá e catracabadge and turnstile
NetworkPolicyNetworkPolicyFirewall entre Pods: quem pode falar com quem.Firewall between Pods: who may talk to whom.portas internas trancadaslocked internal doors
O elo entre os objetos são os rótulos (labels). O Service acha seus Pods por selector: {app: web}; o Deployment acha os dele pelo mesmo mecanismo. Rótulo diferente entre os dois é o erro nº 1 de iniciante: o Service existe, mas aponta para ninguém.The glue between objects is labels. A Service finds its Pods by selector: {app: web}; the Deployment finds its own the same way. A label mismatch between the two is the #1 beginner error: the Service exists but points at nobody.

Os arquivos de configuraçãoThe configuration files

Você trabalha com manifestos: arquivos YAML, um ou vários objetos por arquivo (separados por ---), que vão no Git como código. O comando é sempre kubectl apply -f arquivo.yaml.You work with manifests: YAML files, one or several objects per file (separated by ---), kept in Git as code. The command is always kubectl apply -f file.yaml.

Anatomia: quatro campos em todo objetoAnatomy: four fields in every object

apiVersion: apps/v1        # which API group/version defines this kind
kind: Deployment           # the type of object
metadata:                  # identity
  name: web                #   unique name inside the namespace
  namespace: default       #   optional; defaults to "default"
  labels:                  #   free key/values other objects use to find this one
    app: web
spec:                      # the DESIRED state: this is what you write
  replicas: 3
  # ...
# status:                  # the ACTUAL state: filled in by the cluster, never by you

Não decore os campos: pergunte ao próprio cluster. kubectl explain deployment.spec.template.spec.securityContext mostra a documentação oficial de qualquer campo, e kubectl get deploy web -o yaml mostra o objeto completo como o cluster o enxerga.Do not memorise fields: ask the cluster itself. kubectl explain deployment.spec.template.spec.securityContext shows the official documentation of any field, and kubectl get deploy web -o yaml shows the whole object as the cluster sees it.

Mapa dos arquivosFile map

Arquivo (kind)File (kind)Para quêWhat forCampos de segurança que importamSecurity fields that matter
DeploymentDeploymento app: imagem, cópias, atualizaçãothe app: image, copies, rolloutsecurityContext, resources, image (tag/digest), serviceAccountName, automountServiceAccountTokensecurityContext, resources, image (tag/digest), serviceAccountName, automountServiceAccountToken
ServiceServiceendereço estável para os Podsstable address for the Podstype: prefira ClusterIP; NodePort/LoadBalancer expõem ao mundotype: prefer ClusterIP; NodePort/LoadBalancer expose to the world
IngressIngressentrada HTTP(S) por domínio/caminhoHTTP(S) entry by domain/pathspec.tls (HTTPS), anotações do controller (WAF, restrição de IP)spec.tls (HTTPS), controller annotations (WAF, IP restriction)
ConfigMapConfigMapconfiguração não sensívelnon-sensitive configurationnada de senha aquino passwords here
SecretSecretdados sensíveissensitive datasó base64 (não cifrado); nunca no Git; RBAC restritobase64 only (not encrypted); never in Git; tight RBAC
NamespaceNamespaceisolamento lógicological isolationrótulos pod-security.kubernetes.io/*pod-security.kubernetes.io/* labels
ServiceAccountServiceAccountidentidade do Podthe Pod's identityautomountServiceAccountToken: falseautomountServiceAccountToken: false
Role / ClusterRole + RoleBindingRole / ClusterRole + RoleBindingpermissões (RBAC)permissions (RBAC)verbs, resources: mínimo necessário, sem *verbs, resources: the bare minimum, no *
NetworkPolicyNetworkPolicyfirewall entre Podsfirewall between PodspodSelector, ingress, egresspodSelector, ingress, egress
ResourceQuota / LimitRangeResourceQuota / LimitRangetetos por namespace e por contêinercaps per namespace and per containerimpedem um time ou um invasor de consumir o cluster todostop a team or an intruder from eating the whole cluster
PodDisruptionBudget, HorizontalPodAutoscalerPodDisruptionBudget, HorizontalPodAutoscalerdisponibilidade e escalaavailability and scalingdisponibilidade também é segurançaavailability is security too
kubeconfig (~/.kube/config)kubeconfig (~/.kube/config)seu acesso: clusters, usuários, contextosyour access: clusters, users, contextsequivale a uma senha de administrador; não vai no Gitequals an admin password; never goes in Git
kustomization.yaml / Helm values.yamlkustomization.yaml / Helm values.yamlreaproveitar manifestos entre ambientesreuse manifests across environmentsvalores sensíveis fora do Gitkeep sensitive values out of Git

Kustomize e Helm: o mesmo app em dev e prodKustomize and Helm: the same app in dev and prod

Copiar YAML de ambiente em ambiente vira bagunça. O Kustomize (já dentro do kubectl) parte de uma base e aplica remendos por ambiente. O Helm usa modelos com values.yaml e é o formato da maioria dos pacotes prontos (Argo CD, ingress, cert-manager).Copying YAML from environment to environment gets messy. Kustomize (built into kubectl) starts from a base and applies per-environment patches. Helm uses templates with values.yaml and is the format of most ready-made packages (Argo CD, ingress, cert-manager).

# overlays/prod/kustomization.yaml
resources:
  - ../../base              # the shared Deployment + Service
replicas:
  - name: web
    count: 5                # prod runs 5 copies; dev keeps the base value
images:
  - name: nginxinc/nginx-unprivileged
    newTag: "1.27"          # change the version in ONE place

# kubectl apply -k overlays/prod

kubeconfig: sua chave de acessokubeconfig: your access key

kubectl config get-contexts            # which clusters you can reach (* = current)
kubectl config use-context kind-lab    # switch cluster
kubectl config current-context         # ALWAYS check before apply/delete: wrong cluster = incident

Segurança nos arquivosSecurity in the files

O Kubernetes vem aberto por padrão: um Pod roda como root se você não disser o contrário, fala com qualquer outro Pod, e recebe um token para falar com o API server. A boa notícia: quase toda proteção é uma linha de YAML. Pense em camadas:Kubernetes is open by default: a Pod runs as root unless you say otherwise, talks to any other Pod, and gets a token to talk to the API server. The good news: almost every protection is one line of YAML. Think in layers:

1. Nuvem e cluster1. Cloud and cluster IAM · endpoint da API · criptografia do etcd · nós atualizadosIAM · API endpoint · etcd encryption · patched nodes 2. Namespace Pod Security · RBAC · quotas · NetworkPolicy 3. Pod ServiceAccount sem token · securityContext do Pod · seccomptokenless ServiceAccount · Pod securityContext · seccomp 4. Contêiner4. Container imagem mínima e fixa · non-root · FS somente leituraminimal pinned image · non-root · read-only FS sem capabilities · sem escalada de privilégio · limites de CPU/memóriano capabilities · no privilege escalation · CPU/memory limits ← o que você escreve no YAML do app← what you write in the app's YAML
Defesa em profundidade: se uma camada falha, a de dentro ainda limita o estrago.Defence in depth: if one layer fails, the inner one still limits the damage.

Camada 4 e 3: o securityContext e o PodLayers 4 and 3: the securityContext and the Pod

spec:
  serviceAccountName: web
  automountServiceAccountToken: false   # the app does not call the K8s API: give it no token
  securityContext:                      # Pod level
    runAsNonRoot: true                  # refuses to start if the image wants root
    runAsUser: 101
    seccompProfile:
      type: RuntimeDefault              # blocks dangerous system calls
  containers:
    - name: web
      image: nginxinc/nginx-unprivileged:1.27   # fixed tag (better: @sha256:digest)
      securityContext:                  # container level
        allowPrivilegeEscalation: false # no setuid tricks to become root
        readOnlyRootFilesystem: true    # attacker cannot write or drop tools
        capabilities:
          drop: ["ALL"]                 # no special Linux powers
      resources:
        requests: { cpu: 50m, memory: 64Mi }
        limits:   { cpu: 200m, memory: 128Mi }   # a crypto-miner or a leak cannot eat the node
CampoFieldO que impedeWhat it stops
runAsNonRoot: truerunAsNonRoot: trueSe o invasor sair do app, ele não é root dentro do contêiner; fugir para o nó fica bem mais difícil.If the attacker breaks out of the app, they are not root inside the container; escaping to the node gets much harder.
allowPrivilegeEscalation: falseallowPrivilegeEscalation: falseProgramas com setuid ganhando mais privilégio do que o processo pai.setuid programs gaining more privilege than their parent process.
readOnlyRootFilesystem: truereadOnlyRootFilesystem: trueGravar malware ou editar arquivos do sistema. Dê um emptyDir só para as pastas que precisam de escrita (/tmp).Writing malware or editing system files. Give an emptyDir only to folders that need writes (/tmp).
capabilities.drop: [ALL]capabilities.drop: [ALL]Poderes de root fatiados do Linux (abrir portas baixas, mudar rede, montar discos). Adicione de volta só o indispensável.Root powers sliced up by Linux (low ports, network changes, mounting disks). Add back only what is indispensable.
seccompProfile: RuntimeDefaultseccompProfile: RuntimeDefaultChamadas de sistema perigosas usadas em exploits de kernel.Dangerous system calls used in kernel exploits.
resources.limitsresources.limitsUm Pod comprometido (ou com bug) esgotar CPU/memória do nó e derrubar os vizinhos.A compromised (or buggy) Pod exhausting the node's CPU/memory and taking neighbours down.
automountServiceAccountToken: falseautomountServiceAccountToken: falseO token em /var/run/secrets/… ser roubado e usado contra o API server.The token in /var/run/secrets/… being stolen and used against the API server.
image: repo:1.27 ou @sha256:…image: repo:1.27 or @sha256:…:latest muda sem aviso. O digest garante os mesmos bytes que você escaneou. Use registro privado e imagePullSecrets.:latest changes silently. A digest guarantees the same bytes you scanned. Use a private registry and imagePullSecrets.
privileged: true, hostNetwork, hostPID, hostPathprivileged: true, hostNetwork, hostPID, hostPathEvite. Cada um derruba o isolamento entre o contêiner e o nó. É o primeiro item de qualquer auditoria.Avoid. Each one tears down the isolation between container and node. The first item of any audit.

Camada 2: o namespace diz o que é permitido (Pod Security)Layer 2: the namespace says what is allowed (Pod Security)

O Pod Security Admission vem ligado no cluster e é controlado por rótulos no Namespace. Há três perfis: privileged (sem limites), baseline (bloqueia o pior) e restricted (as boas práticas acima, obrigatórias). Três modos: enforce (recusa), audit (registra) e warn (avisa o usuário). Ele substituiu o antigo PodSecurityPolicy, removido no Kubernetes 1.25.Pod Security Admission is built into the cluster and controlled by labels on the Namespace. There are three profiles: privileged (no limits), baseline (blocks the worst) and restricted (the best practices above, mandatory). Three modes: enforce (rejects), audit (logs) and warn (warns the user). It replaced the old PodSecurityPolicy, removed in Kubernetes 1.25.

apiVersion: v1
kind: Namespace
metadata:
  name: secure
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest

Camada 2: RBAC, o mínimo de permissãoLayer 2: RBAC, least privilege

RBAC liga quem (ServiceAccount, usuário, grupo) a o que pode fazer (verbos sobre recursos). Um Role vale em um namespace; um ClusterRole no cluster todo. Regras de ouro: nada de cluster-admin para apps, nada de verbs: ["*"], e secrets só para quem realmente precisa.RBAC ties who (ServiceAccount, user, group) to what they may do (verbs on resources). A Role applies in one namespace; a ClusterRole cluster-wide. Golden rules: no cluster-admin for apps, no verbs: ["*"], and secrets only for those who truly need them.

kind: Role
metadata: { name: pod-reader, namespace: secure }
rules:
  - apiGroups: [""]               # "" = the core API group
    resources: ["pods"]
    verbs: ["get", "list", "watch"]   # read only
---
kind: RoleBinding                 # ties the Role to a subject
metadata: { name: pod-reader, namespace: secure }
subjects:
  - { kind: ServiceAccount, name: pod-reader, namespace: secure }
roleRef: { kind: Role, name: pod-reader, apiGroup: rbac.authorization.k8s.io }

Camada 2: NetworkPolicy, o firewall internoLayer 2: NetworkPolicy, the internal firewall

Sem nenhuma política, todo Pod fala com todo Pod, de qualquer namespace. Um invasor que entra em um Pod passeia pelo cluster inteiro. O padrão seguro é negar tudo e liberar só o necessário. (O plugin de rede precisa suportar NetworkPolicy; o do EKS, o Calico e o Cilium suportam.)With no policy, every Pod talks to every Pod, from any namespace. An intruder who gets into one Pod roams the whole cluster. The safe default is to deny everything and allow only what is needed. (The network plugin must support NetworkPolicy; EKS's, Calico and Cilium do.)

client role=client other sem o rótulowithout the label NetworkPolicy só role=clientonly role=client entra na porta 8080gets in on port 8080 web app=web · :8080 ✓ passa✓ allowed ✗ bloqueado✗ blocked sem NetworkPolicy: todo Pod fala com todo Podwithout a NetworkPolicy: every Pod talks to every Pod
Com a política, só quem tem o rótulo certo chega ao web; o resto é descartado.With the policy, only whoever has the right label reaches web; the rest is dropped.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny, namespace: secure }
spec:
  podSelector: {}                   # {} = every Pod in the namespace
  policyTypes: ["Ingress"]          # no ingress rules listed = nothing may enter
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-client-to-web, namespace: secure }
spec:
  podSelector: { matchLabels: { app: web } }   # applies to the web Pods
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - podSelector: { matchLabels: { role: client } }
      ports:
        - port: 8080

Secrets: o que o base64 não fazSecrets: what base64 does not do

Um Secret guarda o valor em base64, que é só codificação, não criptografia: echo czNjcjN0 | base64 -d devolve a senha. Quem pode ler o Secret via API (ou ler o etcd) lê a senha. O que realmente protege: RBAC apertado, criptografia do etcd em repouso, e nunca versionar o YAML com o valor.A Secret stores the value in base64, which is just encoding, not encryption: echo czNjcjN0 | base64 -d returns the password. Whoever can read the Secret via the API (or read etcd) reads the password. What really protects it: tight RBAC, etcd encryption at rest, and never committing the YAML with the value.
  • Monte como arquivo (volumeMounts com readOnly: true) em vez de variável de ambiente: variáveis aparecem em dumps de erro, em /proc e em logs de depuração.Mount as a file (volumeMounts with readOnly: true) instead of an environment variable: variables show up in crash dumps, in /proc and in debug logs.
  • Em produção, mantenha o segredo fora do cluster (AWS Secrets Manager, Vault) e sincronize com o External Secrets Operator ou o Secrets Store CSI Driver. No Git, use Sealed Secrets ou SOPS (o valor vai cifrado).In production, keep the secret outside the cluster (AWS Secrets Manager, Vault) and sync it with the External Secrets Operator or the Secrets Store CSI Driver. In Git, use Sealed Secrets or SOPS (the value goes encrypted).
  • Rotacione: um segredo que muda é um segredo que, se vazar, expira.Rotate: a secret that changes is one that, if leaked, expires.

Camada 1: o cluster em siLayer 1: the cluster itself

  • API server não exposto à internet (ou restrito por IP), autenticação por identidade da nuvem, nada de usuário com senha estática.API server not exposed to the internet (or restricted by IP), authentication through cloud identity, no static-password users.
  • Logs de auditoria ligados: quem fez o quê, quando.Audit logs on: who did what, when.
  • Nós e Kubernetes atualizados: o projeto lança uma versão menor a cada ~4 meses e dá correções por cerca de 14 meses.Nodes and Kubernetes up to date: the project ships a minor version about every 4 months and backports fixes for roughly 14 months.
  • Imagens escaneadas (Trivy, ECR scan) e, idealmente, assinadas (cosign); políticas de admissão (Kyverno, OPA Gatekeeper ou ValidatingAdmissionPolicy) para barrar manifestos fora do padrão.Images scanned (Trivy, ECR scan) and ideally signed (cosign); admission policies (Kyverno, OPA Gatekeeper or ValidatingAdmissionPolicy) to reject manifests that break the standard.
Ferramentas para auditar o que você escreveu: kubescape scan e trivy config . (manifestos), kube-bench (CIS Benchmark do cluster) e kubeconform (valida o YAML). Rode no CI, como o plan do Terraform.Tools to audit what you wrote: kubescape scan and trivy config . (manifests), kube-bench (the cluster's CIS Benchmark) and kubeconform (validates the YAML). Run them in CI, like Terraform's plan.

Praticar na sua máquina com DockerPractise on your machine with Docker

Você vai usar o kind (“Kubernetes IN Docker”): ele cria um cluster real em que cada nó é um contêiner Docker. Sem nuvem, sem custo, e se quebrar basta apagar e criar de novo. As atividades são pequenas e cada uma se apoia na anterior. Todos os comandos desta seção foram executados de ponta a ponta num cluster kind.You will use kind (“Kubernetes IN Docker”): it creates a real cluster in which every node is a Docker container. No cloud, no cost, and if it breaks you just delete and recreate it. The activities are small and each builds on the previous one. Every command in this section was run end to end on a kind cluster.

##AtividadeActivityVocê aprendeYou learn
00Instalar e criar o clusterInstall and create the clusternós, kubectl, contextonodes, kubectl, context
11Seu primeiro PodYour first PodPod, logs, exec, e por que Pod solto não se curaPod, logs, exec, and why a bare Pod does not heal
22Deployment e ServiceDeployment and Servicecópias, autocura, escala, acesso pelo navegadorcopies, self-healing, scaling, browser access
33ConfigMap e SecretConfigMap and Secretconfiguração fora da imagem; base64 não é cifraconfiguration outside the image; base64 is not encryption
44Atualização gradual e rollbackRolling update and rollbacktrocar versão sem parar; voltar quando erraswap versions without stopping; go back when it fails
55Endurecer o PodHarden the PodPod Security, securityContext, limites, probesPod Security, securityContext, limits, probes
66RBACRBACpermissão mínima e can-ileast privilege and can-i
77NetworkPolicyNetworkPolicynegar tudo e liberar só o necessáriodeny everything and allow only what is needed
88LimparClean upapagar tudodelete everything

Atividade 0: instalar e criar o clusterActivity 0: install and create the cluster

Pré-requisito: Docker funcionando (docker ps responde). Instale o kind e o kubectl (no macOS: brew install kind kubectl; Linux, abaixo; Windows: winget ou use o WSL2).Prerequisite: Docker working (docker ps answers). Install kind and kubectl (on macOS: brew install kind kubectl; Linux below; Windows: winget or use WSL2).

# Linux (amd64)
curl -Lo kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kind kubectl && sudo mv kind kubectl /usr/local/bin/

mkdir k8s-lab && cd k8s-lab

Crie o arquivo kind.yaml: um nó de control plane e dois workers, e uma porta do seu computador (8088) ligada à porta 30080 do cluster, para abrir o app no navegador.Create the file kind.yaml: one control-plane node and two workers, and one port of your computer (8088) wired to the cluster's port 30080, so you can open the app in the browser.

# kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: lab
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30080
        hostPort: 8088
        listenAddress: "127.0.0.1"    # only your machine, not the whole network
  - role: worker
  - role: worker
kind create cluster --config kind.yaml
kubectl get nodes

Resultado esperado: três nós Ready (a versão depende do kind instalado).Expected result: three Ready nodes (the version depends on your kind).

NAME                STATUS   ROLES           AGE   VERSION
lab-control-plane   Ready    control-plane   34s   v1.37.0
lab-worker          Ready    <none>          19s   v1.37.0
lab-worker2         Ready    <none>          20s   v1.37.0

Curiosidade: docker ps mostra três contêineres, um por nó, e kubectl get pods -n kube-system mostra as peças da seção “Como funciona por dentro” rodando como Pods (etcd, kube-apiserver, kube-scheduler…).Curiosity: docker ps shows three containers, one per node, and kubectl get pods -n kube-system shows the pieces from “How it works inside” running as Pods (etcd, kube-apiserver, kube-scheduler…).

Se a porta 8088 já estiver em uso, o kind falha com “port is already allocated”. Troque o hostPort por outro número e use o mesmo nas atividades seguintes.If port 8088 is already in use, kind fails with “port is already allocated”. Change hostPort to another number and use it in the following activities.

Atividade 1: seu primeiro PodActivity 1: your first Pod

kubectl run hello --image=nginx:1.27
kubectl get pods -o wide                    # which node it landed on, and its IP
kubectl logs hello                          # the container's output
kubectl exec hello -- nginx -v              # run a command inside it
kubectl delete pod hello
kubectl get pods                            # gone for good

Resultado esperado: o Pod passa a Running, o exec mostra a versão do nginx, e depois de apagado ele não volta.Expected result: the Pod becomes Running, exec prints the nginx version, and once deleted it does not come back.

NAME    READY   STATUS    RESTARTS   AGE   IP           NODE
hello   1/1     Running   0          32s   10.244.2.2   lab-worker
nginx version: nginx/1.27.5
pod "hello" deleted from default namespace
No resources found in default namespace.

Lição: um Pod solto não tem ninguém cuidando dele. Para a autocura você precisa de um controller, o Deployment.Lesson: a bare Pod has nobody looking after it. For self-healing you need a controller, the Deployment.

Atividade 2: Deployment e ServiceActivity 2: Deployment and Service

Usaremos a imagem nginxinc/nginx-unprivileged, que já roda sem root e escuta na porta 8080 (a atividade 5 depende disso). Crie web.yaml:We will use the nginxinc/nginx-unprivileged image, which already runs without root and listens on port 8080 (activity 5 depends on that). Create web.yaml:

# web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 3                          # desired state: 3 copies
  selector:
    matchLabels:
      app: web                         # "my Pods are the ones labelled app=web"
  template:                            # the Pod template
    metadata:
      labels:
        app: web                       # MUST match the selector above
    spec:
      containers:
        - name: web
          image: nginxinc/nginx-unprivileged:1.27
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: NodePort                       # lab only: opens a port on the nodes
  selector:
    app: web                           # sends traffic to Pods labelled app=web
  ports:
    - port: 80                         # the Service's port
      targetPort: 8080                 # the container's port
      nodePort: 30080                  # the port kind forwards to localhost:8088
kubectl apply -f web.yaml
kubectl rollout status deploy/web
kubectl get deploy,rs,pods
curl -s -o /dev/null -w "%{http_code}\n" localhost:8088      # or open http://localhost:8088

Resultado esperado: 3/3 prontos, um ReplicaSet e três Pods, e 200 do curl.Expected result: 3/3 ready, one ReplicaSet and three Pods, and 200 from curl.

NAME                  READY   UP-TO-DATE   AVAILABLE
deployment.apps/web   3/3     3            3

NAME                       READY   STATUS    RESTARTS   AGE
pod/web-7bf55664b5-6qznz   1/1     Running   0          39s
pod/web-7bf55664b5-mcxwf   1/1     Running   0          39s
pod/web-7bf55664b5-vp2ws   1/1     Running   0          39s
200

Agora a autocura. Apague um Pod (use um nome da sua lista) e olhe de novo, rápido:Now self-healing. Delete a Pod (use a name from your list) and look again, quickly:

kubectl delete pod web-7bf55664b5-6qznz
kubectl get pods                          # a new Pod is already being created

kubectl scale deploy/web --replicas=5     # scale up
kubectl get pods                          # 5 Pods
kubectl scale deploy/web --replicas=3

Resultado esperado: assim que um Pod entra em Terminating, outro nasce em ContainerCreating: o laço de reconciliação em ação.Expected result: as soon as one Pod goes Terminating, another is born in ContainerCreating: the reconciliation loop in action.

NAME                   READY   STATUS              RESTARTS   AGE
web-7bf55664b5-6qznz   1/1     Terminating         0          41s
web-7bf55664b5-7tq5l   0/1     ContainerCreating   0          1s
web-7bf55664b5-mcxwf   1/1     Running             0          41s
web-7bf55664b5-vp2ws   1/1     Running             0          41s

Desafio: troque o rótulo do Service para app: errado, aplique e rode kubectl get endpoints web. A lista fica vazia e o curl falha: é o erro nº 1 de rótulos que vimos antes. Desfaça com kubectl apply -f web.yaml.Challenge: change the Service's label to app: wrong, apply and run kubectl get endpoints web. The list is empty and curl fails: the #1 label error we saw earlier. Undo with kubectl apply -f web.yaml.

Atividade 3: ConfigMap e SecretActivity 3: ConfigMap and Secret

kubectl create configmap web-config --from-literal=GREETING="hello from k8s"
kubectl create secret generic web-secret --from-literal=API_KEY=s3cr3t

# inject both as environment variables in the Deployment (this triggers a new rollout)
kubectl set env deploy/web --from=configmap/web-config
kubectl set env deploy/web --from=secret/web-secret
kubectl rollout status deploy/web

kubectl exec deploy/web -- printenv GREETING API_KEY
kubectl get secret web-secret -o jsonpath='{.data.API_KEY}'; echo
kubectl get secret web-secret -o jsonpath='{.data.API_KEY}' | base64 -d; echo

Resultado esperado: o Pod enxerga os dois valores; o Secret guardado é só base64, e qualquer um com acesso de leitura recupera a senha.Expected result: the Pod sees both values; the stored Secret is just base64, and anyone with read access recovers the password.

hello from k8s
s3cr3t
czNjcjN0
s3cr3t

Lição: a configuração ficou fora da imagem (a mesma imagem serve a dev e prod), mas o Secret não está protegido por ser Secret. Quem protege é o RBAC (atividade 6) e, em produção, o cofre externo.Lesson: the configuration lives outside the image (the same image serves dev and prod), but the Secret is not protected just for being a Secret. RBAC (activity 6) and, in production, an external vault protect it.

Atividade 4: atualização gradual e rollbackActivity 4: rolling update and rollback

kubectl set image deploy/web web=nginxinc/nginx-unprivileged:1.28
kubectl rollout status deploy/web           # old Pods leave as new ones become ready
kubectl rollout history deploy/web

# now ship a broken version on purpose
kubectl set image deploy/web web=nginxinc/nginx-unprivileged:nao-existe
kubectl get pods                            # one new Pod in ErrImagePull; the 3 old ones still serve
curl -s -o /dev/null -w "%{http_code}\n" localhost:8088      # still 200

kubectl rollout undo deploy/web             # go back to the last good version
kubectl rollout status deploy/web

Resultado esperado: a versão quebrada nunca derruba o site, porque o Kubernetes só tira um Pod antigo quando o novo fica pronto. O undo restaura a versão boa.Expected result: the broken version never takes the site down, because Kubernetes only removes an old Pod once the new one is ready. undo restores the good version.

NAME                   READY   STATUS         RESTARTS   AGE
web-6b6f99c975-86zgm   0/1     ErrImagePull   0          16s
web-7db97c8d9-2blnr    1/1     Running        0          65s
web-7db97c8d9-nn5t6    1/1     Running        0          41s
web-7db97c8d9-t9256    1/1     Running        0          18s

Em equipe, o undo manual é o plano B: o normal é reverter o commit no Git e deixar o pipeline reaplicar.On a team, the manual undo is plan B: normally you revert the commit in Git and let the pipeline reapply.

Atividade 5: endurecer o PodActivity 5: harden the Pod

Crie um namespace que só aceita Pods que seguem o perfil restricted e tente subir um Pod comum:Create a namespace that only accepts Pods following the restricted profile and try to start an ordinary Pod:

# secure-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: secure
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
kubectl apply -f secure-ns.yaml
kubectl run bad --image=nginx:1.27 -n secure

Resultado esperado: o cluster recusa e diz exatamente o que faltou, o que é um roteiro do que escrever.Expected result: the cluster refuses and says exactly what is missing, which is a to-do list of what to write.

Error from server (Forbidden): pods "bad" is forbidden: violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false (container "bad" must set securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "bad" must set securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "bad" must set securityContext.runAsNonRoot=true),
seccompProfile (pod or container "bad" must set securityContext.seccompProfile.type to
"RuntimeDefault" or "Localhost")

Agora a versão endurecida, web-secure.yaml, com os campos da seção de segurança mais probes (o Kubernetes só manda tráfego quando o readinessProbe responde, e reinicia o contêiner se o livenessProbe falhar):Now the hardened version, web-secure.yaml, with the fields from the security section plus probes (Kubernetes only sends traffic once the readinessProbe answers, and restarts the container if the livenessProbe fails):

# web-secure.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: web
automountServiceAccountToken: false
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      serviceAccountName: web
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 101
        runAsGroup: 101
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: web
          image: nginxinc/nginx-unprivileged:1.27
          ports:
            - containerPort: 8080
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi
          readinessProbe:
            httpGet:
              path: /
              port: 8080
            initialDelaySeconds: 2
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 10
          volumeMounts:
            - name: tmp
              mountPath: /tmp          # the only writable folder
      volumes:
        - name: tmp
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080               # ClusterIP (default): reachable only inside the cluster
kubectl apply -n secure -f web-secure.yaml
kubectl rollout status deploy/web -n secure
kubectl exec -n secure deploy/web -- sh -c 'id; touch /etc/x; touch /tmp/ok && echo tmp-ok; ls /var/run/secrets'

Resultado esperado: o Pod passa pelo namespace restricted; roda como usuário 101 (não root); não consegue escrever fora de /tmp; e não tem token do Kubernetes.Expected result: the Pod passes the restricted namespace; runs as user 101 (not root); cannot write outside /tmp; and has no Kubernetes token.

uid=101(nginx) gid=101(nginx) groups=101(nginx)
touch: cannot touch '/etc/x': Read-only file system
tmp-ok
ls: cannot access '/var/run/secrets': No such file or directory
command terminated with exit code 2        # the last error is expected: that folder does not exist
Dica: ao aplicar um Deployment inseguro num namespace restrito, o Deployment é criado, mas os Pods são recusados. Se um Deployment mostra 0/3, rode kubectl describe rs ou kubectl get events para ver o motivo.Tip: when you apply an insecure Deployment to a restricted namespace, the Deployment is created but its Pods are rejected. If a Deployment shows 0/3, run kubectl describe rs or kubectl get events to see why.

Atividade 6: RBACActivity 6: RBAC

# rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: pod-reader
  namespace: secure
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: secure
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-reader
  namespace: secure
subjects:
  - kind: ServiceAccount
    name: pod-reader
    namespace: secure
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f rbac.yaml
SA=system:serviceaccount:secure:pod-reader
kubectl auth can-i list pods     -n secure  --as=$SA
kubectl auth can-i delete pods   -n secure  --as=$SA
kubectl auth can-i get secrets   -n secure  --as=$SA
kubectl auth can-i list pods     -n default --as=$SA

Resultado esperado: só a ação liberada, no namespace liberado, responde yes.Expected result: only the allowed action, in the allowed namespace, answers yes.

yes
no
no
no

kubectl auth can-i --list --as=$SA -n secure mostra tudo o que uma identidade pode fazer. É o primeiro comando de uma revisão de permissões.kubectl auth can-i --list --as=$SA -n secure shows everything an identity can do. It is the first command of a permissions review.

Atividade 7: NetworkPolicyActivity 7: NetworkPolicy

Suba dois Pods de teste no namespace secure: um com o rótulo role=client e outro sem. Como o namespace é restricted, eles também precisam do securityContext. Crie clients.yaml:Start two test Pods in the secure namespace: one with the label role=client and one without. Since the namespace is restricted, they also need the securityContext. Create clients.yaml:

# clients.yaml
apiVersion: v1
kind: Pod
metadata:
  name: client
  namespace: secure
  labels:
    role: client
spec:
  securityContext: { runAsNonRoot: true, runAsUser: 1000, seccompProfile: { type: RuntimeDefault } }
  containers:
    - name: client
      image: busybox:1.37
      command: ["sleep", "3600"]
      securityContext: { allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] } }
---
apiVersion: v1
kind: Pod
metadata:
  name: other
  namespace: secure
spec:
  securityContext: { runAsNonRoot: true, runAsUser: 1000, seccompProfile: { type: RuntimeDefault } }
  containers:
    - name: other
      image: busybox:1.37
      command: ["sleep", "3600"]
      securityContext: { allowPrivilegeEscalation: false, capabilities: { drop: ["ALL"] } }
kubectl apply -f clients.yaml
kubectl wait -n secure --for=condition=Ready pod/client pod/other

# BEFORE any policy: both reach web
kubectl exec -n secure client -- wget -qO- -T 3 http://web | head -2
kubectl exec -n secure other  -- wget -qO- -T 3 http://web | head -2

Salve as duas políticas da seção de segurança em netpol.yaml (a default-deny e a allow-client-to-web) e aplique:Save the two policies from the security section as netpol.yaml (default-deny and allow-client-to-web) and apply them:

kubectl apply -f netpol.yaml
kubectl exec -n secure client -- wget -qO- -T 3 http://web | head -2    # allowed
kubectl exec -n secure other  -- wget -qO- -T 3 http://web | head -2    # blocked

Resultado esperado: antes, os dois recebem o HTML; depois, só o client recebe, e o other fica sem resposta até o tempo esgotar.Expected result: before, both get the HTML; after, only client does, and other gets no answer until it times out.

# client (allowed)
<!DOCTYPE html>
<html>
# other (blocked)
wget: download timed out
command terminated with exit code 1

Bônus: fechar também a saída (egress)Bonus: also close the outbound side (egress)

Negar a saída impede um Pod comprometido de “ligar para casa” (exfiltrar dados, baixar ferramentas). Mas ao negar tudo você também bloqueia o DNS, e nada mais resolve nomes (o erro é bad address 'web'). Por isso liberamos o DNS e depois cada destino necessário:Denying outbound traffic stops a compromised Pod from “phoning home” (exfiltrating data, downloading tools). But denying everything also blocks DNS, and nothing resolves names any more (the error is bad address 'web'). So we allow DNS first and then each needed destination:

# egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny-egress, namespace: secure }
spec:
  podSelector: {}
  policyTypes: ["Egress"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-dns, namespace: secure }
spec:
  podSelector: {}
  policyTypes: ["Egress"]
  egress:
    - to:
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: kube-system }
          podSelector:
            matchLabels: { k8s-app: kube-dns }
      ports:
        - { port: 53, protocol: UDP }
        - { port: 53, protocol: TCP }
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: client-egress-to-web, namespace: secure }
spec:
  podSelector: { matchLabels: { role: client } }
  policyTypes: ["Egress"]
  egress:
    - to:
        - podSelector: { matchLabels: { app: web } }
      ports:
        - port: 8080
kubectl apply -f egress.yaml
kubectl exec -n secure client -- wget -qO- -T 3 http://web | head -2          # still works
kubectl exec -n secure client -- wget -qO- -T 3 http://example.com | head -2  # blocked

Atividade 8: limparActivity 8: clean up

kind delete cluster --name lab      # removes the three node containers; nothing else is left behind

E depois? Um roteiro de estudoWhat next? A study path

  1. Repita as atividades sem copiar, escrevendo os YAMLs com a ajuda de kubectl explain e kubectl create ... --dry-run=client -o yaml.Repeat the activities without copying, writing the YAMLs with the help of kubectl explain and kubectl create ... --dry-run=client -o yaml.
  2. Quebre de propósito: imagem inexistente, rótulo errado, limite de memória baixo demais (OOMKilled), probe errado. Aprenda a ler kubectl describe, logs e get events.Break things on purpose: missing image, wrong label, memory limit too low (OOMKilled), wrong probe. Learn to read kubectl describe, logs and get events.
  3. Suba um app seu: um Deployment, Service, ConfigMap e um PersistentVolumeClaim para o banco (use o Postgres da outra nota).Deploy an app of your own: a Deployment, Service, ConfigMap and a PersistentVolumeClaim for the database (use the Postgres from the other note).
  4. Instale um Ingress/Gateway e um cert-manager; empacote o app com Kustomize ou Helm.Install an Ingress/Gateway and cert-manager; package the app with Kustomize or Helm.
  5. Passe os manifestos por kubescape ou trivy config e corrija os achados.Run the manifests through kubescape or trivy config and fix the findings.
  6. Preparação formal: as provas CKA, CKAD e CKS (segurança) da Linux Foundation, e o simulador killer.sh, são o melhor treino prático.Formal preparation: the Linux Foundation's CKA, CKAD and CKS (security) exams, and the killer.sh simulator, are the best hands-on practice.

Colocar em produção com segurança na AWS (EKS)Running it in production, securely, on AWS (EKS)

O serviço gerenciado da AWS é o EKS (Elastic Kubernetes Service): a AWS opera o control plane (alta disponibilidade em várias zonas, atualizações, backup do etcd) e você cuida dos nós e dos apps. O modelo de responsabilidade compartilhada vale aqui: a AWS protege o control plane; a configuração do seu cluster e dos seus Pods é com você.AWS's managed service is EKS (Elastic Kubernetes Service): AWS runs the control plane (multi-zone high availability, upgrades, etcd backups) and you look after the nodes and the apps. The shared-responsibility model applies: AWS protects the control plane; your cluster's and Pods' configuration is on you.

Internet HTTPS conta AWS · regiãoAWS account · region VPC subnets públicaspublic subnets ALB AWS LB Controller NAT gateway saída para a internetoutbound internet subnets privadas (2+ zonas)private subnets (2+ zones) nó · zona anode · zone a Pod Pod managed node group IMDSv2 · SG · disco cifradoIMDSv2 · SG · encrypted disk sem IP públicono public IP nó · zona bnode · zone b Pod Pod managed node group IMDSv2 · SG · disco cifradoIMDSv2 · SG · encrypted disk sem IP públicono public IP EKS control plane gerenciado pela AWSmanaged by AWS ECR imagensimages Secrets Manager segredossecrets KMS chaveskeys CloudWatch logs e alarmeslogs & alarms Pods acessam a AWS com uma IAM role própria (Pod Identity / IRSA), sem chaves fixas.Pods reach AWS with their own IAM role (Pod Identity / IRSA), with no static keys. Control plane e nós se falam por ENIs dentro da sua VPC.The control plane and nodes talk through ENIs inside your VPC.
Nós em subnets privadas, só o ALB exposto; o resto da AWS é acessado por IAM, não por senha.Nodes in private subnets, only the ALB exposed; the rest of AWS is reached through IAM, not passwords.
Custo. O EKS cobra pelo control plane por hora (cerca de US$ 0,10/h, uns US$ 73 por mês, consulte a tabela de preços atual), mais os nós EC2, o NAT gateway e o ALB. Crie o cluster de estudo, pratique e apague no mesmo dia (eksctl delete cluster). Defina um alerta de orçamento (AWS Budgets) antes de começar.Cost. EKS charges for the control plane by the hour (about US$ 0.10/h, roughly US$ 73 a month, check the current price list), plus the EC2 nodes, the NAT gateway and the ALB. Create the study cluster, practise and delete it the same day (eksctl delete cluster). Set a budget alert (AWS Budgets) before you begin.

Passo 1: o cluster, já com os controles de segurançaStep 1: the cluster, with security controls from day one

O jeito mais curto de aprender é o eksctl, que lê um arquivo e cria VPC, control plane e nós. Em produção real, descreva a mesma coisa em Terraform (módulos terraform-aws-modules/eks). Os comentários mostram o que cada linha protege:The shortest way to learn is eksctl, which reads a file and creates the VPC, control plane and nodes. In real production, describe the same thing in Terraform (the terraform-aws-modules/eks modules). The comments show what each line protects:

# cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: demo
  region: us-east-1
  version: "1.35"                      # use a version currently in standard support

accessConfig:
  authenticationMode: API              # access via IAM "access entries"; no legacy aws-auth ConfigMap

vpc:
  clusterEndpoints:
    privateAccess: true
    publicAccess: true                 # best for production: false + VPN/bastion
  publicAccessCIDRs: ["203.0.113.10/32"]   # replace with YOUR IP: only it can reach the API server

secretsEncryption:
  keyARN: arn:aws:kms:us-east-1:111122223333:key/REPLACE-ME   # your own KMS key for Secrets

cloudWatch:
  clusterLogging:
    enableTypes: ["api", "audit", "authenticator", "controllerManager", "scheduler"]

iam:
  withOIDC: true                       # lets Pods assume IAM roles (IRSA)

addons:
  - name: vpc-cni
    configurationValues: '{"enableNetworkPolicy": "true"}'   # NetworkPolicy enforcement
  - name: eks-pod-identity-agent       # IAM roles for Pods (Pod Identity)

managedNodeGroups:
  - name: ng-private
    instanceType: t3.medium
    desiredCapacity: 2
    minSize: 2
    maxSize: 4
    privateNetworking: true            # nodes in private subnets, no public IP
    volumeEncrypted: true              # encrypted disks
eksctl create cluster -f cluster.yaml                       # ~15-20 minutes
aws eks update-kubeconfig --name demo --region us-east-1    # writes your kubeconfig (short-lived tokens via IAM)
kubectl get nodes
O kubectl do EKS não usa senha: o kubeconfig chama aws eks get-token e o acesso segue as suas credenciais da AWS (SSO, de preferência). Quem entra no cluster é decidido por access entries do IAM, com políticas como AmazonEKSViewPolicy; evite dar cluster-admin a todo mundo.EKS's kubectl uses no password: the kubeconfig calls aws eks get-token and access follows your AWS credentials (SSO, ideally). Who gets into the cluster is decided by IAM access entries, with policies such as AmazonEKSViewPolicy; avoid giving cluster-admin to everyone.

Passo 2: imagens no ECRStep 2: images in ECR

aws ecr create-repository --repository-name web \
  --image-scanning-configuration scanOnPush=true \
  --image-tag-mutability IMMUTABLE \
  --encryption-configuration encryptionType=KMS

aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 111122223333.dkr.ecr.us-east-1.amazonaws.com
docker build -t 111122223333.dkr.ecr.us-east-1.amazonaws.com/web:1.0.0 .
docker push 111122223333.dkr.ecr.us-east-1.amazonaws.com/web:1.0.0

Tags imutáveis impedem que a 1.0.0 seja sobrescrita; o scan na publicação aponta CVEs conhecidas. No manifesto, referencie a imagem pelo digest (web@sha256:…). Em CI, o próprio pipeline faz o push, com OIDC (veja a nota GitHub to AWS with OIDC), sem chave guardada.Immutable tags stop 1.0.0 from being overwritten; scan-on-push flags known CVEs. In the manifest, reference the image by digest (web@sha256:…). In CI, the pipeline itself does the push, with OIDC (see the GitHub to AWS with OIDC note), with no stored key.

Passo 3: o app com identidade própria na AWSStep 3: the app with its own AWS identity

Um Pod que precisa ler um bucket S3 ou um segredo não deve usar as credenciais do nó (que serviriam a todos os Pods) nem chaves fixas. Crie uma IAM role só com o que ele precisa e ligue-a à ServiceAccount:A Pod that must read an S3 bucket or a secret should not use the node's credentials (which would serve every Pod) nor static keys. Create an IAM role with only what it needs and tie it to the ServiceAccount:

# IRSA: the ServiceAccount carries the role to assume
apiVersion: v1
kind: ServiceAccount
metadata:
  name: web
  namespace: prod
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/web-s3-read
automountServiceAccountToken: true     # IRSA needs the projected token; set false only for Pods with no AWS access

A alternativa mais nova, o EKS Pod Identity, dispensa a anotação: aws eks create-pod-identity-association --cluster-name demo --namespace prod --service-account web --role-arn …. Nos dois casos a role aceita só aquela ServiceAccount daquele namespace.The newer alternative, EKS Pod Identity, drops the annotation: aws eks create-pod-identity-association --cluster-name demo --namespace prod --service-account web --role-arn …. In both cases the role accepts only that ServiceAccount in that namespace.

Passo 4: expor com HTTPSStep 4: expose it with HTTPS

Instale o AWS Load Balancer Controller (Helm). Ele transforma um Ingress em um ALB. Certificado no ACM, HTTPS e, se quiser, WAF:Install the AWS Load Balancer Controller (Helm). It turns an Ingress into an ALB. Certificate in ACM, HTTPS and, if you want, WAF:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  namespace: prod
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:111122223333:certificate/REPLACE-ME
    alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
    # alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:...   # optional WAF
spec:
  ingressClassName: alb
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: web, port: { number: 80 } }

Mantenha os Services como ClusterIP e as subnets dos nós privadas: só o ALB fica na internet. Aponte seu domínio para o ALB (Route 53 ou Cloudflare).Keep Services as ClusterIP and the node subnets private: only the ALB faces the internet. Point your domain at the ALB (Route 53 or Cloudflare).

Passo 5: entregar com pipelineStep 5: deliver through a pipeline

git push PR revisadoreviewed PR CI build+teste+scanbuild+test+scan ECR imagem por digestimage by digest GitOps / deploy Argo CD · kubectl EKS admission barraadmission blocks O CI assume um papel da AWS por OIDC (nenhuma chave guardada no GitHub).CI assumes an AWS role through OIDC (no key stored in GitHub). O cluster recusa manifestos inseguros (Pod Security, Kyverno ou Gatekeeper).The cluster rejects insecure manifests (Pod Security, Kyverno or Gatekeeper).
Do commit ao cluster: cada etapa checa algo, e o cluster é o último portão.From commit to cluster: every stage checks something, and the cluster is the last gate.

O ideal é GitOps: o Git é a fonte da verdade e uma ferramenta dentro do cluster (Argo CD ou Flux) aplica o que está lá. Ninguém precisa de kubectl com permissão de escrita em produção, e todo deploy vira um commit auditável.The ideal is GitOps: Git is the source of truth and a tool inside the cluster (Argo CD or Flux) applies what is there. Nobody needs write-enabled kubectl in production, and every deploy becomes an auditable commit.

Lista de verificação de produçãoProduction checklist

ÁreaAreaO que fazerWhat to do
Acesso ao clusterCluster accessEndpoint do API server privado ou restrito por IP; access entries do IAM com menor privilégio; SSO; nenhum usuário com chave fixa.Private or IP-restricted API server endpoint; least-privilege IAM access entries; SSO; no static-key users.
RedeNetworkNós em subnets privadas, em 2+ zonas; só o ALB público; Security Groups mínimos; NetworkPolicy default deny em cada namespace; VPC endpoints para ECR/S3/STS.Nodes in private subnets, in 2+ zones; only the ALB public; minimal Security Groups; default deny NetworkPolicy in every namespace; VPC endpoints for ECR/S3/STS.
NósNodesManaged node groups com AMI atualizada; IMDSv2 obrigatório e hop limit = 1 (Pod não alcança as credenciais do nó); discos cifrados; sem SSH (use SSM).Managed node groups with a current AMI; IMDSv2 required and hop limit = 1 (Pods cannot reach the node's credentials); encrypted disks; no SSH (use SSM).
PodsPodsPod Security restricted como enforce nos namespaces de apps; securityContext, limites e probes em todo Deployment; PodDisruptionBudget e spread entre zonas.Pod Security restricted as enforce on app namespaces; securityContext, limits and probes on every Deployment; PodDisruptionBudget and zone spread.
Identidade dos PodsPod identityUma ServiceAccount e uma IAM role por app (Pod Identity/IRSA), com política mínima; nunca chaves de acesso em Secret.One ServiceAccount and one IAM role per app (Pod Identity/IRSA), minimal policy; never access keys in a Secret.
SegredosSecretsValores no Secrets Manager/Parameter Store, sincronizados por External Secrets ou CSI Driver; envelope encryption do EKS com sua chave KMS; RBAC sem get secrets amplo.Values in Secrets Manager/Parameter Store, synced by External Secrets or the CSI Driver; EKS envelope encryption with your KMS key; RBAC without broad get secrets.
ImagensImagesECR privado, tags imutáveis, scan na publicação (e contínuo com Inspector), digest nos manifestos, imagens mínimas (distroless/alpine), assinatura com cosign + política de admissão.Private ECR, immutable tags, scan on push (and continuous with Inspector), digests in manifests, minimal images (distroless/alpine), cosign signing + an admission policy.
PolíticasPoliciesKyverno, Gatekeeper ou ValidatingAdmissionPolicy barrando :latest, privileged, hostPath e imagens de fora do seu ECR.Kyverno, Gatekeeper or ValidatingAdmissionPolicy blocking :latest, privileged, hostPath and images from outside your ECR.
Observabilidade e detecçãoObservability and detectionLogs do control plane (principalmente audit) no CloudWatch; GuardDuty com proteção para EKS; métricas e alertas (Prometheus/CloudWatch Container Insights).Control-plane logs (especially audit) in CloudWatch; GuardDuty with EKS protection; metrics and alerts (Prometheus/CloudWatch Container Insights).
AtualizaçõesUpgradesAcompanhe o calendário de versões do EKS; atualize o control plane e depois os nós, em dev antes de prod; versões fora de suporte padrão custam mais e deixam de receber correções.Follow the EKS version calendar; upgrade the control plane and then the nodes, dev before prod; versions out of standard support cost more and stop getting fixes.
Dados e recuperaçãoData and recoveryManifestos no Git (recria o cluster); Velero ou AWS Backup para volumes; bancos preferencialmente em RDS, fora do cluster; teste a restauração.Manifests in Git (recreates the cluster); Velero or AWS Backup for volumes; databases preferably on RDS, outside the cluster; test the restore.
CustoCostTags de custo, requests/limits realistas, autoscaling (Karpenter ou Cluster Autoscaler + HPA), AWS Budgets.Cost tags, realistic requests/limits, autoscaling (Karpenter or Cluster Autoscaler + HPA), AWS Budgets.
O erro mais comum em produção não é um ataque sofisticado: é um painel, um API server ou um Service LoadBalancer exposto sem querer, e um Pod root com token de ServiceAccount poderoso. As três linhas que mais valem: NetworkPolicy default deny, Pod Security restricted e RBAC mínimo.The most common production failure is not a sophisticated attack: it is a dashboard, API server or LoadBalancer Service exposed by accident, and a root Pod with a powerful ServiceAccount token. The three lines that pay off most: default-deny NetworkPolicy, restricted Pod Security and minimal RBAC.
# When you are done studying: remove everything (control plane, nodes, VPC, NAT) to stop the billing
eksctl delete cluster -f cluster.yaml

Cola e revisãoCheat sheet and review

Comandos do dia a diaEveryday commands

Quero…I want…UsoI use
aplicar um arquivo / uma pastaapply a file / a folderkubectl apply -f arquivo.yaml / -f pasta/ / -k overlay/kubectl apply -f file.yaml / -f folder/ / -k overlay/
ver recursossee resourceskubectl get pods,svc,deploy -n NS (-A = todos os namespaces, -o wide, -o yaml)kubectl get pods,svc,deploy -n NS (-A = all namespaces, -o wide, -o yaml)
entender por que não sobeunderstand why it does not startkubectl describe pod X · kubectl get events --sort-by=.lastTimestampkubectl describe pod X · kubectl get events --sort-by=.lastTimestamp
ver logssee logskubectl logs X · -f (seguir) · --previous (contêiner que reiniciou)kubectl logs X · -f (follow) · --previous (the container that restarted)
entrar no contêinerget inside the containerkubectl exec -it X -- shkubectl exec -it X -- sh
testar sem exportest without exposingkubectl port-forward svc/web 8080:80kubectl port-forward svc/web 8080:80
escalar / atualizar / voltarscale / update / roll backkubectl scale · set image · rollout status|history|undokubectl scale · set image · rollout status|history|undo
gerar um YAML-basegenerate a starter YAMLkubectl create deploy web --image=nginx --dry-run=client -o yamlkubectl create deploy web --image=nginx --dry-run=client -o yaml
ver a diferença antes de aplicarsee the diff before applyingkubectl diff -f arquivo.yamlkubectl diff -f file.yaml
consultar a documentação de um campolook up a field's docskubectl explain pod.spec.containers.securityContextkubectl explain pod.spec.containers.securityContext
checar permissõescheck permissionskubectl auth can-i VERBO RECURSO --as=…kubectl auth can-i VERB RESOURCE --as=…
estado de erro comum: CrashLoopBackOffcommon error: CrashLoopBackOffo app morre ao iniciar: logs --previousthe app dies on start: logs --previous
ImagePullBackOff / Pending / OOMKilledImagePullBackOff / Pending / OOMKilledimagem/credencial errada · sem recurso ou sem nó que sirva · limite de memória baixowrong image/credential · no resources or no fitting node · memory limit too low

Perguntas de revisãoReview questions

  • Que problema o Kubernetes resolve que o docker run sozinho não resolve?What problem does Kubernetes solve that docker run alone does not?
  • Por que dizemos que ele é declarativo e o que é o laço de reconciliação?Why do we say it is declarative, and what is the reconciliation loop?
  • Qual a diferença entre Pod, Deployment e Service? Por que um Pod solto não se cura?What is the difference between a Pod, a Deployment and a Service? Why does a bare Pod not heal?
  • O que acontece se o selector do Service não casa com os rótulos dos Pods?What happens if the Service's selector does not match the Pods' labels?
  • Por que um Secret não é seguro só por ser Secret? O que de fato o protege?Why is a Secret not safe just for being a Secret? What actually protects it?
  • Cite cinco campos de securityContext/Pod e o ataque que cada um dificulta.Name five securityContext/Pod fields and the attack each one hinders.
  • O que o Pod Security Admission faz e quais são seus três perfis?What does Pod Security Admission do and what are its three profiles?
  • Por que usar NetworkPolicy default deny, e por que ao negar a saída é preciso liberar o DNS?Why use a default deny NetworkPolicy, and why must DNS be allowed when egress is denied?
  • No EKS, como um Pod acessa o S3 sem chave de acesso guardada?On EKS, how does a Pod reach S3 without a stored access key?
  • Por que os nós ficam em subnets privadas e só o ALB é público?Why do the nodes sit in private subnets while only the ALB is public?