Como preservar o IP de origem da solicitação após o balanceamento de carga em um cluster K8s
Categories:
Introdução
O implantação de aplicativos nem sempre é simplesmente instalar e executar, às vezes também é necessário considerar problemas de rede. Este artigo introduz como fazer com que os serviços em um cluster k8s possam obter o IP de origem da solicitação.
Os aplicativos que fornecem serviços geralmente dependem de informações de entrada. Se as informações de entrada não dependem do quintuplo (IP de origem, porta de origem, IP de destino, porta de destino, protocolo), então o serviço tem baixa acoplamento com a rede e não precisa se preocupar com detalhes de rede.
Portanto, a maioria das pessoas não precisa ler este artigo. Se você estiver interessado em redes ou quiser expandir um pouco sua visão, pode continuar lendo para entender mais cenários de serviço.
Este artigo é baseado no k8s v1.29.4. Algumas descrições no artigo misturam pod e endpoint; neste cenário, eles podem ser considerados equivalentes.
Se houver erros, bem-vindo para corrigir, eu corrigirei prontamente.
Por que a informação do IP de origem é perdida?
Primeiro, esclarecemos o que é o IP de origem. Quando A envia uma solicitação para B, e B encaminha a solicitação para C, embora C veja o IP de origem do protocolo IP como o IP de B, este artigo considera o IP de A como o IP de origem.
Existem principalmente duas classes de comportamentos que levam à perda da informação de origem:
- Conversão de Endereço de Rede (NAT), o objetivo é economizar IPv4 público, balanceamento de carga etc. Isso fará com que o servidor veja o IP do dispositivo NAT como o IP de origem, não o IP de origem real.
- Proxy, Proxy Reverso (RP, Reverse Proxy) e Balanceador de Carga (LB, Load Balancer) pertencem a esta classe, abaixo denominados coletivamente servidor proxy. Este tipo de serviço proxy encaminhará a solicitação para o serviço backend, mas substituirá o IP de origem pelo seu próprio IP.
- NAT, em termos simples, é trocar espaço de porta por espaço de IP. Os endereços IPv4 são limitados. Um endereço IP pode mapear 65535 portas, e na maioria das vezes essas portas não são esgotadas, portanto, várias sub-redes IP podem compartilhar um IP público, distinguindo diferentes serviços pelas portas. Sua forma de uso é:
IP público:porta pública -> IP privado_1:porta privada. Para mais conteúdo, consulte Conversão de Endereço de Rede - O serviço proxy é para ocultar ou expor. O serviço proxy encaminhará a solicitação para o serviço backend e substituirá o IP de origem pelo seu próprio IP, ocultando assim o IP real do serviço backend e protegendo a segurança do serviço backend. A forma de uso do serviço proxy é:
IP do cliente -> IP do proxy -> IP do servidor. Para mais conteúdo, consulte Proxy
NAT e servidor proxy são muito comuns, e a maioria dos serviços não consegue obter o IP de origem da solicitação.
Estas são as duas principais vias comuns para modificar o IP de origem; bem-vindo para suplementar outras.
Como preservar o IP de origem?
A seguir está um exemplo de solicitação HTTP:
| Campo | Comprimento (bytes) | Deslocamento de bits | Descrição |
|---|---|---|---|
| Cabeçalho IP | |||
IP de origem | 4 | 0-31 | Endereço IP do remetente |
| IP de destino | 4 | 32-63 | Endereço IP do destinatário |
| Cabeçalho TCP | |||
| Porta de origem | 2 | 0-15 | Número da porta de envio |
| Porta de destino | 2 | 16-31 | Número da porta de recebimento |
| Número de sequência | 4 | 32-63 | Usado para identificar o fluxo de bytes enviado pelo remetente |
| Número de confirmação | 4 | 64-95 | Se o sinal ACK estiver definido, é o próximo número de sequência esperado |
| Deslocamento de dados | 4 | 96-103 | Número de bytes da posição inicial dos dados em relação ao cabeçalho TCP |
| Reservado | 4 | 104-111 | Campo reservado, não utilizado, definido como 0 |
| Bits de sinal | 2 | 112-127 | Vários sinais de controle, como SYN, ACK, FIN etc. |
| Tamanho da janela | 2 | 128-143 | Quantidade de dados que o receptor pode receber |
| Soma de verificação | 2 | 144-159 | Usado para detectar se os dados foram corrompidos durante a transmissão |
| Ponteiro urgente | 2 | 160-175 | Posição dos dados urgentes que o remetente espera que o receptor processe o mais rápido possível |
| Opções | Variável | 176-… | Pode incluir timestamp, comprimento máximo do segmento de mensagem etc. |
| Cabeçalho HTTP | |||
| Linha de solicitação | Variável | …-… | Inclui método de solicitação, URI e versão HTTP |
Campos de cabeçalho | Variável | …-… | Contém vários campos de cabeçalho, como Host, User-Agent etc. |
| Linha vazia | 2 | …-… | Usada para separar cabeçalho e corpo |
| Corpo | Variável | …-… | Corpo opcional da solicitação ou resposta |
Examinando a estrutura da solicitação HTTP acima, pode-se ver que opções TCP, linha de solicitação, campos de cabeçalho, corpo são variáveis. Entre elas, o espaço de opções TCP é limitado e geralmente não é usado para transmitir IP de origem. A linha de solicitação carrega informações fixas que não podem ser expandidas. O corpo HTTP não pode ser modificado após criptografado. Apenas os campos de cabeçalho HTTP são adequados para expansão e transmissão de IP de origem.
No cabeçalho HTTP, pode-se adicionar o campo X-REAL-IP para transmitir o IP de origem. Essa operação geralmente é colocada no servidor proxy, e então o servidor proxy enviará a solicitação para o serviço backend. O serviço backend pode obter a informação do IP de origem por meio desse campo.
Note que é necessário garantir que o servidor proxy esteja antes do dispositivo NAT, para que possa obter o whoami da origem real da solicitação. Podemos ver o produto balanceador de carga da Alibaba Cloud como uma categoria de produto separada, cuja posição na rede é diferente de um servidor de aplicativo comum.
Guia de Operação K8S
Usando o projeto whoami como exemplo para implantação.
Criar Deployment
Primeiro, crie o serviço:
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
spec:
replicas: 3
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: docker.io/traefik/whoami:latest
ports:
- containerPort: 8080
Esta etapa criará um Deployment contendo 3 Pods, cada pod contém um contêiner que executará o serviço whoami.
Criar Service
Pode-se criar serviços do tipo NodePort ou LoadBalancer para acesso externo, ou criar serviços do tipo ClusterIP para acesso apenas interno ao cluster, e adicionar serviços Ingress para expor acesso externo.
NodePort pode ser acessado tanto por NodeIP:NodePort quanto por serviços Ingress, conveniente para testes. Esta seção usa o serviço NodePort.
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
type: NodePort
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30002
Após criar o serviço, acesse com curl whoami.example.com:30002 e verá que o IP retornado é o NodeIP, não o whoami da origem da solicitação.
Por favor, note que este não é o IP do cliente correto, eles são IPs internos do cluster. Eis o que acontece:
- O cliente envia o pacote para node2:nodePort
- node2 substitui o IP de origem do pacote pelo seu próprio endereço IP (SNAT)
- node2 substitui o IP de destino do pacote pelo IP do Pod
- O pacote é roteado para node1 e depois para o endpoint
- A resposta do Pod é roteada de volta para node2
- A resposta do Pod é enviada de volta para o cliente
Representado em diagrama:

Configurar externalTrafficPolicy: Local
Para evitar isso, o Kubernetes tem uma funcionalidade que preserva o IP de origem do cliente. Se service.spec.externalTrafficPolicy for definido como Local, o kube-proxy só proxyará as solicitações para endpoints locais, sem encaminhar o tráfego para outros nós.
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
type: NodePort
externalTrafficPolicy: Local
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 8080
nodePort: 30002
Use curl whoami.example.com:30002 para testar. Quando whoami.example.com mapeia para IPs de múltiplos nodes do cluster, há uma certa probabilidade de falha no acesso. É necessário confirmar que o registro DNS contém apenas o IP do node onde está o endpoint (pod).
Essa configuração tem seu custo, que é a perda da capacidade de balanceamento de carga no cluster. O cliente só receberá resposta ao acessar o node onde o endpoint está implantado.

Quando o cliente acessa o Node 2, não haverá resposta.
Criar Ingress
A maioria dos serviços fornecidos aos usuários usa http/https. A forma https://ip:port pode parecer estranha para os usuários. Geralmente, usa-se Ingress para balancear o serviço NodePort criado acima para a porta 80/443 de um domínio.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
namespace: default
spec:
ingressClassName: external-lb-default
rules:
- host: whoami.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
Após aplicar, use curl whoami.example.com para testar o acesso e verá que o ClientIP é sempre o IP do Pod do Ingress Controller no node onde está o endpoint.
root@client:~# curl whoami.example.com
...
RemoteAddr: 10.42.1.10:56482
...
root@worker:~# kubectl get -n ingress-nginx pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-controller-c8f499cfc-xdrg7 1/1 Running 0 3d2h 10.42.1.10 k3s-agent-1 <none> <none>
Usar Ingress como proxy reverso para o serviço NodePort significa adicionar duas camadas de service antes do endpoint. O diagrama abaixo mostra a diferença entre eles.
graph LR
A[Client] -->|whoami.example.com:80| B(Ingress)
B -->|10.43.38.129:32123| C[Service]
C -->|10.42.1.1:8080| D[Endpoint]graph LR
A[Client] -->|whoami.example.com:30001| B(Service)
B -->|10.42.1.1:8080| C[Endpoint]No caminho 1, ao acessar o Ingress externamente, o tráfego primeiro chega ao endpoint Ingress Controller e depois ao endpoint whoami.
O Ingress Controller é essencialmente um serviço LoadBalancer,
kubectl -n ingress-nginx get svc
NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE
default echoip-ingress nginx ip.example.com 172.16.0.57,2408:4005:3de:8500:4da1:169e:dc47:1707 80 18h
default whoami-ingress nginx whoami.example.com 172.16.0.57,2408:4005:3de:8500:4da1:169e:dc47:1707 80 16h
Portanto, pode-se preservar o IP de origem configurando externalTrafficPolicy no Ingress Controller, como mencionado anteriormente.
Ao mesmo tempo, é necessário definir use-forwarded-headers como true no configmap do ingress-nginx-controller, para que o Ingress Controller possa reconhecer os campos X-Forwarded-For ou X-REAL-IP.
apiVersion: v1
data:
allow-snippet-annotations: "false"
compute-full-forwarded-for: "true"
use-forwarded-headers: "true"
enable-real-ip: "true"
forwarded-for-header: "X-Real-IP" # X-Real-IP or X-Forwarded-For
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.10.1
name: ingress-nginx-controller
namespace: ingress-nginx
A diferença principal entre o serviço NodePort e o serviço ingress-nginx-controller é que o backend do NodePort geralmente não é implantado em cada node, enquanto o backend do ingress-nginx-controller geralmente é implantado em cada node exposto externamente.
Diferente de definir externalTrafficPolicy no serviço NodePort, o que causa falha de resposta para solicitações entre nodes, o Ingress pode primeiro definir o HEADER na solicitação e depois proxyá-la, realizando as capacidades de preservar IP de origem e balanceamento de carga.
Resumo
- Conversão de Endereço (NAT), Proxy, Proxy Reverso, Balanceamento de Carga levarão à perda do IP de origem.
- Para evitar a perda do IP de origem, ao encaminhar no servidor proxy, defina o IP real no campo de cabeçalho HTTP
X-REAL-IPe transmita pelo serviço proxy. Se usar múltiplas camadas de proxy, pode-se usar o campoX-Forwarded-For, que registra a lista de IPs do IP de origem e o caminho do proxy em forma de pilha. - Definir
externalTrafficPolicy: Localno serviço NodePort do cluster pode preservar o IP de origem, mas perderá a capacidade de balanceamento de carga. - Sob a premissa de que o ingress-nginx-controller é implantado em forma de daemonset em todos os nodes com papel loadbalancer, definir
externalTrafficPolicy: Localpode preservar o IP de origem e manter a capacidade de balanceamento de carga.