Os tipos de Serviços Kubernetes — guia ultra-exaustivo

13 min

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.

Índice

  1. Lembrete: o papel de um Service
  2. Anatomia de um Service (todos os campos)
  3. Tipo 1 — ClusterIP
  4. Tipo 2 — NodePort
  5. Tipo 3 — LoadBalancer
  6. Tipo 4 — ExternalName
  7. Service Headless (sem ClusterIP)
  8. Service sem seletor (Endpoints manuais)
  9. port vs targetPort vs nodePort
  10. Multi-porto e portos nomeados
  11. Como funciona por baixo: kube-proxy
  12. Endpoints e EndpointSlices
  13. O DNS dos Services (CoreDNS)
  14. Políticas de tráfego (externalTrafficPolicy / internalTrafficPolicy)
  15. Session affinity
  16. Protocolos: TCP, UDP, SCTP, appProtocol
  17. Service vs Ingress vs Gateway API
  18. Tabela recapitulativa dos tipos
  19. Armadilhas clássicas e resolução de problemas
  20. Boas práticas
  21. Mini-exercícios

1. Lembrete: o papel de um Service

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:

  • fornece uma identidade de rede permanente (um IP virtual e/ou um nome DNS);
  • seleciona um conjunto de Pods através dos respetivos labels;
  • reparte o tráfego (load balancing) entre esses Pods;
  • atualiza-se automaticamente quando os Pods aparecem ou desaparecem.

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.


2. Anatomia de um Service (todos os campos)

yaml
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.


3. Tipo 1 — ClusterIP

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.

yaml
apiVersion: v1
kind: Service
metadata:
  name: demo-clusterip
spec:
  type: ClusterIP
  selector:
    app: demo-back
  ports:
    - port: 80
      targetPort: 5000

Características:

  • Inacessível a partir do exterior (sem EXTERNAL-IP).
  • Base da comunicação interna (frontend → backend, app → base de dados, microserviços entre si).
  • Acessível por nome DNS: http://demo-clusterip (ver §13).

Quando usar: para tudo o que fica no cluster. É o tipo mais frequente.


4. Tipo 2 — NodePort

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).

yaml
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:

  • Se não precisares nodePort, o Kubernetes escolhe um na gama.
  • O mesmo porto é aberto em todos os nós (graças ao routing mesh / kube-proxy), mesmo os que não alojam nenhum Pod do Service.
  • Pouco elegante para produção (portos não standard, gestão manual), mas perfeito em dev/local e muitas vezes o tijolo por baixo de um LoadBalancer.

5. Tipo 3 — LoadBalancer

Faz tudo o que o NodePort faz, mais: pede à infraestrutura (a cloud) que provisione um equilibrador de carga externo com um IP público.

yaml
apiVersion: v1
kind: Service
metadata:
  name: demo-lb
spec:
  type: LoadBalancer
  selector:
    app: demo-back
  ports:
    - port: 8090
      targetPort: 5000

Conforme o ambiente:

AmbienteComportamento
AWS / GCP / AzureCria um verdadeiro LB gerido (ELB/NLB, GCP LB…) e preenche EXTERNAL-IP
Docker DesktopEXTERNAL-IP = localhosthttp://localhost:8090
minikubeminikube tunnel fornece o IP externo
kind / bare-metalFica <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:

yaml
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).


6. Tipo 4 — ExternalName

Caso particular: nenhum seletor, nenhum Pod, nenhum IP. Cria simplesmente um alias DNS (registo CNAME) para um nome externo.

yaml
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 aqui

Uso: 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 Host HTTP particular.


7. Service Headless (sem ClusterIP)

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.

yaml
apiVersion: v1
kind: Service
metadata:
  name: demo-headless
spec:
  clusterIP: None        # <-- headless
  selector:
    app: demo-back
  ports:
    - port: 80
      targetPort: 5000

Para que serve:

  • Quando o cliente quer ver cada Pod individualmente (sem LB centralizado).
  • Indispensável para os StatefulSet: cada Pod obtém um nome DNS estável (pod-0.demo-headless, pod-1.demo-headless…), útil para bases de dados replicadas (Cassandra, Kafka, etc.).
ClusterIP normalHeadless (clusterIP: None)
IP virtualSim (um só)Não
Resposta DNS1 IP (o do Service)N IP (os dos Pods)
RepartiçãoPor kube-proxyÀ responsabilidade do cliente
Caso de usoWeb/API sem estadoBases replicadas, StatefulSet

8. Service sem seletor (Endpoints manuais)

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.

yaml
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: 8080

Diferenç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.


9. port vs targetPort vs nodePort

Esta é a fonte de confusão. Três portos diferentes, três papéis:

CampoOndeSignificado
portNo ServiceO porto que os clientes usam para contactar o Service
targetPortNo contentorO porto onde a aplicação escuta de facto no Pod
nodePortNo (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 ».

targetPort pode referenciar um nome de porto definido no contentor (ver §10), o que evita escrever o número em duro.


10. Multi-porto e portos nomeados

Um Service pode expor vários portos (ex. HTTP + métricas). Nesse caso, cada entrada deve ter um name.

yaml
spec:
  selector:
    app: demo
  ports:
    - name: http
      port: 80
      targetPort: web          # referencia um porto NOMEADO do contentor
    - name: metrics
      port: 9090
      targetPort: 9090

Do lado do contentor, nomeiam-se os portos:

yaml
containers:
  - name: app
    ports:
      - name: web              # <-- reutilizado por targetPort: web
        containerPort: 5000
      - name: metrics
        containerPort: 9090

Vantagem dos portos nomeados: se o porto do contentor mudar, não há nada a alterar no Service.


11. Como funciona por baixo: kube-proxy

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:

ModoPrincípioNotas
iptables (defeito)Regras iptables, seleção aleatória de um PodSimples, robusto, muito difundido
IPVSTabela de hash do kernel, verdadeiros algos de LB (rr, lc, sh…)Mais performante em clusters grandes
nftablesSucessor do iptablesMais recente

Consequências práticas:

  • A repartição iptables é aleatória (não é um verdadeiro round-robin ordenado).
  • O kube-proxy não vê a camada HTTP: é L3/L4 (IP/porto). Para encaminhamento HTTP (por caminho, por host), é preciso um Ingress (§17).

12. Endpoints e EndpointSlices

O elo Service ↔ Pods materializa-se em objetos:

  • Endpoints (histórico): um único objeto a listar todos os IP:port dos Pods prontos.
  • EndpointSlices (moderno, recomendado): a lista é dividida em fatias (máx. ~100 endpoints cada) → escalabilidade bem melhor nos serviços grandes.
bash
kubectl get endpoints demo-clusterip
kubectl get endpointslices -l kubernetes.io/service-name=demo-clusterip

Quem 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: true publica também os Pods não prontos — uso headless específico.)


13. O DNS dos Services (CoreDNS)

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 completo

Exemplo a partir de um Pod:

bash
curl http://demo-clusterip                       # mesmo namespace
curl http://demo-clusterip.default               # explícito
curl http://demo-clusterip.default.svc.cluster.local

Registos produzidos:

  • Service normal → um registo A para o ClusterIP.
  • Service headlessvários A, um por Pod.
  • Portos nomeados → registos SRV: _http._tcp.demo-clusterip….
  • ExternalNameCNAME para o alvo.

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).


14. Políticas de tráfego

externalTrafficPolicy (tráfego entrante externo, NodePort/LoadBalancer)

ValorEfeitoCompromisso
Cluster (defeito)O tráfego pode ser reencaminhado para outro nó para alcançar um PodBoa repartição, mas o IP de origem do cliente fica mascarado (SNAT) e há um salto de rede extra
LocalSó serve os Pods do nó que recebe o pacotePreserva o IP de origem do cliente, sem salto; mas desequilíbrio se os Pods estiverem mal repartidos

internalTrafficPolicy (tráfego interno, entre Pods)

ValorEfeito
Cluster (defeito)Encaminha para qualquer Pod do Service
LocalEncaminha 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).


15. Session affinity

Por defeito, cada pedido pode ir para qualquer Pod. Para « colar » um cliente ao mesmo Pod:

yaml
spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800     # 3 h
  • None (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).


16. Protocolos: TCP, UDP, SCTP, appProtocol

  • protocol: TCP (defeito), UDP (DNS, jogos, streaming), SCTP (telecoms).
  • Pode-se misturar vários protocolos no mesmo Service (portos distintos).
  • appProtocol (indicativo) precisa o protocolo aplicacional (http, https, grpc) para as ferramentas/LB.
yaml
ports:
  - name: dns-udp
    port: 53
    protocol: UDP
    targetPort: 53
  - name: dns-tcp
    port: 53
    protocol: TCP
    targetPort: 53

17. Service vs Ingress vs Gateway API

Um Service trabalha em L4 (IP/porto). Não sabe encaminhar segundo o URL, o nome de anfitrião, nem gerir o TLS. Para isso:

ObjetoCamadaPapel
Service (ClusterIP/NodePort/LB)L3/L4Endereço estável + LB simples para Pods
IngressL7 (HTTP/HTTPS)Encaminhamento por host e caminho, TLS, um único ponto de entrada para vários serviços
Gateway APIL7 (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.


18. Tabela recapitulativa dos tipos

TipoIP internoAcesso externoDNSSeletorCaso de uso
ClusterIPSimNão1 A (ClusterIP)SimComunicação interna (o mais frequente)
NodePortSimPorto do nó1 ASimDev/local, tijolo de um LB
LoadBalancerSimIP público1 ASimServiço público na cloud
ExternalNameNão— (CNAME)CNAMENãoAlias para um serviço externo
Headless (clusterIP: None)NãoNãoN A (Pods)SimStatefulSet, bases replicadas
Sem seletorSimconforme o tipo1 ANãoEndpoints manuais (recurso externo)

19. Armadilhas clássicas e resolução de problemas

SintomaCausa frequenteSolução
O Service não respondeSeletor ≠ labels dos PodsAlinhar spec.selector e os labels do template de Pod
Endpoints vaziosNenhum Pod Ready ou nenhum Pod correspondentekubectl get endpoints <svc>; verificar readinessProbe e labels
Ligação recusada internamentetargetPort erradotargetPort = 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 mascaradoexternalTrafficPolicy: ClusterPassar a Local
NodePort inacessívelPorto fora da gama / ocupadoUsar 30000–32767, alterar nodePort
O DNS não resolveNamespace errado / CoreDNS KOTestar o FQDN; kubectl -n kube-system get pods (coredns)

Comandos de diagnóstico:

bash
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>

20. Boas práticas

  • Por defeito, ClusterIP. Só exposes ao exterior o que deve sê-lo.
  • Um único LoadBalancer + Ingress à frente de vários ClusterIP (custo + TLS centralizado).
  • Nomeia os portos (multi-porto, e targetPort por nome → desacoplamento).
  • Cuida das readinessProbe: elas decidem quem está dentro dos Endpoints.
  • Coerência labels/seletores: é o erro n.º 1. Mantém labels estáveis (app, tier, version).
  • Usa externalTrafficPolicy: Local quando o verdadeiro IP do cliente importa.
  • Prefere os EndpointSlices (ativos por defeito nas versões recentes) para a escalabilidade.
  • Nunca escrevas um IP de Pod em duro: usa o nome DNS do Service.

21. Mini-exercícios

Exercício 1 — Transformar um NodePort em ClusterIP

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.

Exercício 2 — Partir e depois reparar os Endpoints

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.

Exercício 3 — Service headless

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ó.

Exercício 4 — Multi-porto

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.

Exercício 5 — ExternalName

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