Projeto projet11-kubernetes-services · Documento de referência aprofundado.
Este documento vai muito mais longe do que o corrigé: detalha todos os tipos de Services, as noções internas (kube-proxy, Endpoints, EndpointSlices, DNS), os campos YAML importantes, as políticas de tráfego, a session affinity, o multi-porto, as armadilhas clássicas e as boas práticas.
Um Pod é efémero: pode ser recriado a qualquer momento, com um novo IP. Portanto não se pode apoiar no IP de um Pod para comunicar.
Um Service é uma abstração estável que:
Ideia mestra: o Service não « contém » os Pods. Encontra-os em contínuo graças ao seletor de labels, e mantém a lista dos respetivos endereços nos Endpoints.
apiVersion: v1
kind: Service
metadata:
name: mon-service
labels:
app: demo
annotations: {} # metadados (muitas vezes usados pelos LoadBalancer cloud)
spec:
type: ClusterIP # ClusterIP | NodePort | LoadBalancer | ExternalName
selector: # que Pods este Service visa (por labels)
app: demo
ports:
- name: http # nome do porto (útil se houver vários portos)
protocol: TCP # TCP (defeito) | UDP | SCTP
port: 80 # porto do Service (o que os clientes veem)
targetPort: 5000 # porto do contentor (ou nome de porto do contentor)
nodePort: 30080 # (NodePort/LoadBalancer) porto aberto no nó
clusterIP: 10.96.0.10 # (opcional) IP fixo ; "None" = headless
sessionAffinity: None # None | ClientIP
externalTrafficPolicy: Cluster # Cluster | Local (NodePort/LoadBalancer)
internalTrafficPolicy: Cluster # Cluster | Local
ipFamilyPolicy: SingleStack # SingleStack | PreferDualStack | RequireDualStack
externalIPs: [] # IP externos encaminhados para este Service (avançado)Cada campo é detalhado mais abaixo. Pode-se criar um Service mínimo em 8 linhas; todos os outros campos têm valores por defeito razoáveis.
O tipo por defeito. Atribui um IP virtual interno (na gama Service CIDR, ex. 10.96.0.0/12), acessível apenas a partir do interior do cluster.
apiVersion: v1
kind: Service
metadata:
name: demo-clusterip
spec:
type: ClusterIP
selector:
app: demo-back
ports:
- port: 80
targetPort: 5000Características:
EXTERNAL-IP).http://demo-clusterip (ver §13).Quando usar: para tudo o que fica no cluster. É o tipo mais frequente.
Faz tudo o que o ClusterIP faz (obtém um IP interno), mais: abre um porto estático em cada nó do cluster (gama por defeito 30000–32767).
apiVersion: v1
kind: Service
metadata:
name: demo-nodeport
spec:
type: NodePort
selector:
app: demo-back
ports:
- port: 80 # porto do Service (interno)
targetPort: 5000 # porto do contentor
nodePort: 30082 # porto aberto em CADA nóAcesso: http://<ip-de-n-importe-quel-noeud>:30082 (com Docker Desktop: http://localhost:30082).
Pontos importantes:
nodePort, o Kubernetes escolhe um na gama.Faz tudo o que o NodePort faz, mais: pede à infraestrutura (a cloud) que provisione um equilibrador de carga externo com um IP público.
apiVersion: v1
kind: Service
metadata:
name: demo-lb
spec:
type: LoadBalancer
selector:
app: demo-back
ports:
- port: 8090
targetPort: 5000Conforme o ambiente:
| Ambiente | Comportamento |
|---|---|
| AWS / GCP / Azure | Cria um verdadeiro LB gerido (ELB/NLB, GCP LB…) e preenche EXTERNAL-IP |
| Docker Desktop | EXTERNAL-IP = localhost → http://localhost:8090 |
| minikube | minikube tunnel fornece o IP externo |
| kind / bare-metal | Fica <pending> sem um controlador como MetalLB |
Cadeia completa: LoadBalancer → NodePort → ClusterIP → Endpoints → Pods.
Annotations (específicas do fornecedor) pilotam o LB, por ex. na AWS:
metadata:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
service.beta.kubernetes.io/aws-load-balancer-internal: "true"Um LoadBalancer por serviço = caro na cloud. Em produção, prefere-se muitas vezes um único ponto de entrada (Ingress/Gateway) à frente de vários serviços (ver §17).
Caso particular: nenhum seletor, nenhum Pod, nenhum IP. Cria simplesmente um alias DNS (registo CNAME) para um nome externo.
apiVersion: v1
kind: Service
metadata:
name: base-externe
spec:
type: ExternalName
externalName: db.exemple.com # os Pods que chamam "base-externe" são reencaminhados para aquiUso: fazer apontar um nome interno estável (base-externe) para um serviço fora do cluster (uma base gerida, uma API de terceiros). Se o endereço mudar, altera-se um único sítio.
Limite: é DNS puro, sem repartição de carga nem controlo de porto. Não convém se o serviço externo espera um
HostHTTP particular.
Ao pôr clusterIP: None, obtém-se um Service sem IP virtual. O DNS devolve então diretamente os IP de todos os Pods (uma lista de registos A), em vez de um único IP.
apiVersion: v1
kind: Service
metadata:
name: demo-headless
spec:
clusterIP: None # <-- headless
selector:
app: demo-back
ports:
- port: 80
targetPort: 5000Para que serve:
pod-0.demo-headless, pod-1.demo-headless…), útil para bases de dados replicadas (Cassandra, Kafka, etc.).| ClusterIP normal | Headless (clusterIP: None) | |
|---|---|---|
| IP virtual | Sim (um só) | Não |
| Resposta DNS | 1 IP (o do Service) | N IP (os dos Pods) |
| Repartição | Por kube-proxy | À responsabilidade do cliente |
| Caso de uso | Web/API sem estado | Bases replicadas, StatefulSet |
Um Service pode não ter seletor. Nesse caso, o Kubernetes não preenche os Endpoints sozinho: tu defines-os à mão. Prático para expor um recurso externo sob um IP interno estável.
apiVersion: v1
kind: Service
metadata:
name: api-legacy
spec:
ports:
- port: 80
targetPort: 8080
---
apiVersion: v1
kind: Endpoints # (ou EndpointSlice, mais moderno)
metadata:
name: api-legacy # o MESMO nome que o Service
subsets:
- addresses:
- ip: 192.168.1.50 # servidor externo
ports:
- port: 8080Diferença em relação ao ExternalName: aqui encaminha-se por IP (com load balancing possível em vários IP), não por CNAME DNS.
Esta é a fonte de confusão. Três portos diferentes, três papéis:
| Campo | Onde | Significado |
|---|---|---|
port | No Service | O porto que os clientes usam para contactar o Service |
targetPort | No contentor | O porto onde a aplicação escuta de facto no Pod |
nodePort | No nó | (NodePort/LoadBalancer) o porto aberto na máquina |
Exemplo lido em voz alta: « os clientes batem no porto 80 do Service, que transmite para o porto 5000 do contentor; em NodePort, entra-se também pelo porto 30082 da máquina ».
targetPortpode referenciar um nome de porto definido no contentor (ver §10), o que evita escrever o número em duro.
Um Service pode expor vários portos (ex. HTTP + métricas). Nesse caso, cada entrada deve ter um name.
spec:
selector:
app: demo
ports:
- name: http
port: 80
targetPort: web # referencia um porto NOMEADO do contentor
- name: metrics
port: 9090
targetPort: 9090Do lado do contentor, nomeiam-se os portos:
containers:
- name: app
ports:
- name: web # <-- reutilizado por targetPort: web
containerPort: 5000
- name: metrics
containerPort: 9090Vantagem dos portos nomeados: se o porto do contentor mudar, não há nada a alterar no Service.
O Service é um objeto abstrato: não é um processo que recebe o tráfego. A magia é feita pelo kube-proxy, um componente presente em cada nó, que programa as regras de rede do kernel para reencaminhar « IP:porto do Service » para « IP:porto de um Pod ».
Modos do kube-proxy:
| Modo | Princípio | Notas |
|---|---|---|
| iptables (defeito) | Regras iptables, seleção aleatória de um Pod | Simples, robusto, muito difundido |
| IPVS | Tabela de hash do kernel, verdadeiros algos de LB (rr, lc, sh…) | Mais performante em clusters grandes |
| nftables | Sucessor do iptables | Mais recente |
Consequências práticas:
O elo Service ↔ Pods materializa-se em objetos:
IP:port dos Pods prontos.kubectl get endpoints demo-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=demo-clusteripQuem atualiza a lista? O endpoint controller: assim que um Pod fica Ready (readinessProbe OK) e corresponde ao seletor, o IP entra; se cair, sai.
Um Pod não Ready é retirado dos Endpoints → não recebe tráfego. Por isso a readinessProbe é essencial: controla quem está « dentro » do Service. (Exceção:
publishNotReadyAddresses: truepublica também os Pods não prontos — uso headless específico.)
O Kubernetes faz correr o CoreDNS. Cada Service recebe um nome DNS determinista:
<service> # mesmo namespace
<service>.<namespace> # outro namespace
<service>.<namespace>.svc.cluster.local # FQDN completoExemplo a partir de um Pod:
curl http://demo-clusterip # mesmo namespace
curl http://demo-clusterip.default # explícito
curl http://demo-clusterip.default.svc.cluster.localRegistos produzidos:
_http._tcp.demo-clusterip….Historicamente, o Kubernetes também injetava variáveis de ambiente (
DEMO_CLUSTERIP_SERVICE_HOST,..._PORT) nos Pods criados depois do Service. O DNS continua a ser o método recomendado (funciona independentemente da ordem de criação).
| Valor | Efeito | Compromisso |
|---|---|---|
| Cluster (defeito) | O tráfego pode ser reencaminhado para outro nó para alcançar um Pod | Boa repartição, mas o IP de origem do cliente fica mascarado (SNAT) e há um salto de rede extra |
| Local | Só serve os Pods do nó que recebe o pacote | Preserva o IP de origem do cliente, sem salto; mas desequilíbrio se os Pods estiverem mal repartidos |
| Valor | Efeito |
|---|---|
| Cluster (defeito) | Encaminha para qualquer Pod do Service |
| Local | Encaminha apenas para os Pods do mesmo nó (útil para a latência / a localidade) |
externalTrafficPolicy: Localé o ajuste-chave quando precisas de conhecer o verdadeiro IP do cliente (logs, segurança, geolocalização).
Por defeito, cada pedido pode ir para qualquer Pod. Para « colar » um cliente ao mesmo Pod:
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3 hNone (defeito): repartição a cada pedido.ClientIP: todos os pedidos de um mesmo IP vão para o mesmo Pod (sticky sessions básicas L4).Para sessões HTTP mais finas (por cookie), usa-se antes um Ingress (L7).
protocol: TCP (defeito), UDP (DNS, jogos, streaming), SCTP (telecoms).appProtocol (indicativo) precisa o protocolo aplicacional (http, https, grpc) para as ferramentas/LB.ports:
- name: dns-udp
port: 53
protocol: UDP
targetPort: 53
- name: dns-tcp
port: 53
protocol: TCP
targetPort: 53Um Service trabalha em L4 (IP/porto). Não sabe encaminhar segundo o URL, o nome de anfitrião, nem gerir o TLS. Para isso:
| Objeto | Camada | Papel |
|---|---|---|
| Service (ClusterIP/NodePort/LB) | L3/L4 | Endereço estável + LB simples para Pods |
| Ingress | L7 (HTTP/HTTPS) | Encaminhamento por host e caminho, TLS, um único ponto de entrada para vários serviços |
| Gateway API | L7 (sucessor do Ingress) | Mais expressivo, separação de papéis, multi-protocolos |
Modelo típico em produção: um único LoadBalancer → Ingress → vários ClusterIP internos. Poupa-se nos LB caros e centraliza-se TLS/encaminhamento.
| Tipo | IP interno | Acesso externo | DNS | Seletor | Caso de uso |
|---|---|---|---|---|---|
| ClusterIP | Sim | Não | 1 A (ClusterIP) | Sim | Comunicação interna (o mais frequente) |
| NodePort | Sim | Porto do nó | 1 A | Sim | Dev/local, tijolo de um LB |
| LoadBalancer | Sim | IP público | 1 A | Sim | Serviço público na cloud |
| ExternalName | Não | — (CNAME) | CNAME | Não | Alias para um serviço externo |
Headless (clusterIP: None) | Não | Não | N A (Pods) | Sim | StatefulSet, bases replicadas |
| Sem seletor | Sim | conforme o tipo | 1 A | Não | Endpoints manuais (recurso externo) |
| Sintoma | Causa frequente | Solução |
|---|---|---|
| O Service não responde | Seletor ≠ labels dos Pods | Alinhar spec.selector e os labels do template de Pod |
| Endpoints vazios | Nenhum Pod Ready ou nenhum Pod correspondente | kubectl get endpoints <svc>; verificar readinessProbe e labels |
| Ligação recusada internamente | targetPort errado | targetPort = porto real do contentor |
EXTERNAL-IP fica <pending> | Sem controlador LB (kind/bare-metal) | Docker Desktop OK; senão MetalLB / port-forward |
| IP de origem do cliente mascarado | externalTrafficPolicy: Cluster | Passar a Local |
| NodePort inacessível | Porto fora da gama / ocupado | Usar 30000–32767, alterar nodePort |
| O DNS não resolve | Namespace errado / CoreDNS KO | Testar o FQDN; kubectl -n kube-system get pods (coredns) |
Comandos de diagnóstico:
kubectl get svc <nom> -o wide
kubectl describe svc <nom>
kubectl get endpoints <nom>
kubectl get endpointslices -l kubernetes.io/service-name=<nom>
kubectl run test --rm -it --image=busybox:1.36 -- sh # nslookup <svc>, wget -qO- http://<svc>targetPort por nome → desacoplamento).app, tier, version).externalTrafficPolicy: Local quando o verdadeiro IP do cliente importa.Retira type: NodePort (ou põe ClusterIP), reaplica e depois prova que já não está acessível a partir do anfitrião mas está pelo nome a partir de um Pod (curl http://demo-nodeport... renomeado). Observa kubectl get svc: já não há coluna nodePort.
Altera o seletor do Service para app: inexistant, reaplica e constata kubectl get endpoints vazio + serviço inatingível. Volta a pôr app: demo-back: os Endpoints regressam.
Cria um Service com clusterIP: None e depois, a partir de um Pod: nslookup demo-headless. Deves ver vários IP (um por Pod) em vez de um só.
Acrescenta um porto metrics (9090) nomeado no contentor e no Service. Verifica com kubectl describe svc que os dois portos aparecem e que targetPort referencia bem o nome do porto.
Cria um Service ExternalName para example.com. A partir de um Pod: nslookup mon-alias deve devolver um CNAME para example.com.
Regresso ao corrigé do projeto · Conceitos de base: 01-CONCEPTS-SERVICES.md · Comandos: 02-COMMANDES.md.
Curso criado pelo Dr. Haythem REHOUMA — Desenvolvimento e implantação de soluções de dados