Hoje estamos publicando uma tradução da terceira parte do Kubernetes Networking Guide. A
primeira parte foi sobre pods, a
segunda sobre serviços, e hoje falaremos sobre balanceamento de carga e recursos Kubernetes do tipo Ingress.
O roteamento não é balanceamento de carga
No artigo
anterior desta série, consideramos uma configuração que consiste em um par de lareiras e um serviço ao qual foi atribuído um endereço IP chamado "IP do cluster". As consultas destinadas a lares foram enviadas para este endereço. Aqui continuaremos a trabalhar em nosso sistema de treinamento, começando onde nos graduamos pela última vez. Lembre-se de que o endereço IP do cluster do serviço,
10.3.241.152
, pertence a um intervalo de endereços IP diferente do usado na rede da lareira e do usado na rede em que os nós estão localizados. Chamei a rede definida por esse espaço de endereço de "rede de serviço", embora dificilmente seja digna de um nome especial, pois nenhum dispositivo está conectado a essa rede e seu espaço de endereço, de fato, consiste inteiramente de regras de roteamento. Foi demonstrado anteriormente como essa rede é implementada com base no componente Kubernetes, chamado
kube-proxy, e interage com o
netfilter do módulo do kernel Linux para interceptar e redirecionar o tráfego enviado ao cluster IP para o qual trabalhar.
Diagrama de redeAté agora, conversamos sobre "conexões" e "solicitações" e até usamos o conceito difícil de interpretar "tráfego", mas para entender os recursos do mecanismo do Kubernetes Ingress, precisamos usar termos mais precisos. Portanto, as conexões e solicitações funcionam no 4º nível do
modelo OSI (tcp) ou no 7º nível (http, rpc e assim por diante). As regras do Netfilter são regras de roteamento, elas funcionam com pacotes IP no terceiro nível. Todos os roteadores, incluindo o netfilter, tomam decisões mais ou menos com base apenas nas informações contidas no pacote. Em geral, eles estão interessados em saber de onde o pacote vem e para onde vai. Portanto, para descrever esse comportamento em termos do terceiro nível do modelo OSI, deve-se dizer que cada pacote destinado ao serviço localizado em
10.3.241.152:80
, que chega na interface do nó
eth0
, é processado pelo netfilter e, de acordo com as regras definidas para o nosso serviço são redirecionadas para o endereço IP de uma lareira viável.
Parece bastante óbvio que qualquer mecanismo que usamos para permitir que clientes externos acessem pods deve usar a mesma infraestrutura de roteamento. Como resultado, esses clientes externos acessarão o endereço IP e a porta do cluster, pois são o "ponto de acesso" a todos os mecanismos sobre os quais falamos até agora. Eles nos permitem não nos preocupar com o local exato em que ele é executado em um determinado momento. No entanto, não é de todo óbvio como fazer tudo funcionar.
O serviço IP de cluster é alcançável apenas com a interface Ethernet do nó. Nada fora do cluster sabe o que fazer com endereços do intervalo ao qual esse endereço pertence. Como redirecionar o tráfego de um endereço IP público para um endereço acessível somente se o pacote já chegou ao host?
Se tentarmos encontrar uma solução para esse problema, uma das coisas que podem ser feitas no processo de encontrar uma solução será o estudo das regras do netfilter usando o utilitário
iptables . Se você fizer isso, poderá descobrir algo que, à primeira vista, possa parecer incomum: as regras para o serviço não se limitam a uma rede de origem específica. Isso significa que quaisquer pacotes gerados em qualquer lugar que cheguem à interface Ethernet do nó e tenham um endereço de destino
10.3.241.152:80
serão reconhecidos como em conformidade com a regra e serão redirecionados para o sub. Podemos apenas fornecer aos clientes um cluster IP, talvez vinculando-o a um nome de domínio adequado e, em seguida, configurar uma rota que permita organizar a entrega desses pacotes para um dos nós?
Cliente e cluster externosSe tudo estiver configurado dessa maneira, esse projeto provará estar funcionando. Os clientes acessam o IP do cluster, os pacotes seguem a rota que leva ao host e são redirecionados para a parte inferior. Neste momento, pode parecer que essa solução pode ser limitada, mas sofre de alguns problemas sérios. A primeira é que os nós, de fato, o conceito de efêmero, não são particularmente diferentes, a esse respeito, dos lares. Eles estão, é claro, um pouco mais próximos do mundo material do que os pods, mas podem migrar para novas máquinas virtuais, os clusters podem aumentar ou diminuir, e assim por diante. Os roteadores funcionam no terceiro nível do modelo OSI e os pacotes não conseguem distinguir entre os serviços normalmente funcionando e os que não funcionam corretamente. Eles esperam que a próxima transição na rota seja acessível e estável. Se o nó estiver inacessível, a rota ficará inoperante e permanecerá assim, na maioria dos casos, por muito tempo. Mesmo que a rota seja resistente a falhas, esse esquema levará ao fato de que todo o tráfego externo passará por um único nó, o que provavelmente não é o ideal.
Não importa como trazemos o tráfego do cliente para o sistema, precisamos fazer isso para que ele não dependa do estado de nenhum nó do cluster. E, de fato, não há uma maneira confiável de fazer isso usando apenas roteamento, sem alguns meios de gerenciar ativamente o roteador. De fato, é precisamente esse papel, o papel do sistema de controle, que o kube-proxy desempenha em relação ao netfilter. Estender a responsabilidade do Kubernetes de gerenciar um roteador externo provavelmente não fazia muito sentido para arquitetos de sistemas, especialmente porque já possuímos ferramentas comprovadas para distribuir o tráfego de clientes em vários servidores. Eles são chamados de balanceadores de carga e não é de surpreender que sejam a solução realmente confiável para o Kubernetes Ingress. Para entender exatamente como isso acontece, precisamos sair do terceiro nível do OSI e falar sobre conexões novamente.
Para usar o balanceador de carga para distribuir o tráfego do cliente entre os nós do cluster, precisamos de um endereço IP público ao qual os clientes possam se conectar e também precisamos dos endereços dos próprios nós para os quais o balanceador de carga pode redirecionar solicitações. Pelas razões acima, não podemos apenas criar uma rota estática estável entre o roteador do gateway e os nós usando uma rede baseada em serviço (cluster IP).
Entre os outros endereços com os quais você pode trabalhar, apenas os endereços da rede à qual as interfaces Ethernet dos nós estão conectados, ou seja, neste exemplo,
10.100.0.0/24
, podem ser observados. O roteador já sabe como encaminhar pacotes para essas interfaces e as conexões enviadas do balanceador de carga para o roteador irão para onde devem ir. Mas se o cliente quiser se conectar ao nosso serviço na porta 80, não podemos simplesmente enviar pacotes para essa porta nas interfaces de rede dos nós.
Balanceador de carga, tentativa malsucedida de acessar a porta 80 da interface de rede hostA razão pela qual isso não pode ser feito é completamente óbvia. Ou seja, estamos falando do fato de que não há processo aguardando conexões em
10.100.0.3:80
(e, se houver, esse definitivamente não é o mesmo processo) e as regras do netfilter, que, como esperávamos, interceptariam a solicitação e eles enviam para ele, não trabalham nesse endereço de destino. Eles respondem apenas a uma rede IP de cluster com base em serviços, ou seja, no endereço
10.3.241.152:80
. Como resultado, esses pacotes, após sua chegada, não podem ser entregues no endereço de destino, e o kernel emitirá uma resposta
ECONNREFUSED
. Isso nos coloca em uma posição confusa: não é fácil trabalhar com a rede para redirecionar pacotes para o qual o netfilter está configurado ao redirecionar dados do gateway para os nós, e uma rede para a qual o roteamento é fácil de configurar não é a rede para a qual o netfilter redireciona pacotes. Para resolver esse problema, você pode criar uma ponte entre essas redes. É exatamente isso que o Kubernetes faz usando um serviço como o NodePort.
Serviços como NodePort
O serviço que nós, por exemplo, criamos no artigo anterior, não recebe um tipo, por isso adotou o tipo padrão -
ClusterIP
. Existem mais dois tipos de serviços que diferem em recursos adicionais, e o que estamos interessados agora é o
NodePort
. Aqui está um exemplo de uma descrição de um serviço desse tipo:
kind: Service apiVersion: v1 metadata: name: service-test spec: type: NodePort selector: app: service_test_pod ports: - port: 80 targetPort: http
Serviços do tipo
NodePort
são serviços do tipo
ClusterIP
que têm uma oportunidade adicional: o acesso a eles pode ser obtido pelo endereço IP atribuído ao host e pelo endereço atribuído ao cluster na rede de serviço. Isso é conseguido de uma maneira bastante simples: quando o Kubernetes cria um serviço NodePort, o kube-proxy aloca uma porta no intervalo de 30000-32767 e abre essa porta na interface
eth0
de cada nó (daí o nome do tipo de serviço -
NodePort
). As conexões feitas a essa porta (chamaremos essas portas
NodePort
) são redirecionadas para o IP do cluster do serviço. Se criarmos o serviço descrito acima e executarmos o
kubectl get svc service-test
, podemos ver a porta atribuída a ele.
$ kubectl get svc service-test NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE service-test 10.3.241.152 <none> 80:32213/TCP 1m
Nesse caso, o serviço é atribuído ao NodePort
32213
. Isso significa que agora podemos nos conectar ao serviço através de qualquer nó em nosso cluster experimental em
10.100.0.2:32213
ou em
10.100.0.3:32213
. Nesse caso, o tráfego será redirecionado para o serviço.
Após essa parte do sistema ter ocorrido, temos todos os fragmentos do pipeline para equilibrar a carga criada pelas solicitações do cliente para todos os nós do cluster.
Serviço NodePortNa figura anterior, o cliente se conecta ao balanceador de carga por meio de um endereço IP público, o balanceador de carga seleciona o nó e se conecta a ele em
10.100.0.3:32213
, o kube-proxy aceita essa conexão e a redireciona para o serviço acessível via IP
10.3.241.152:80
cluster . Aqui, a solicitação é processada com sucesso de acordo com as regras definidas pelo netfilter e é redirecionada para o pod do servidor no endereço
10.0.2.2:8080
. Talvez tudo isso possa parecer um pouco complicado, e até certo ponto seja, mas não é fácil encontrar uma solução mais simples que suporte todos os recursos maravilhosos que nos fornecem pods e redes com base em serviços.
Esse mecanismo, no entanto, não deixa de ter seus próprios problemas. O uso de serviços como o
NodePort
oferece aos clientes acesso a serviços usando uma porta não padrão. Geralmente, isso não é um problema, pois o balanceador de carga pode fornecer a eles uma porta regular e ocultar o
NodePort
dos usuários finais. Mas, em alguns cenários, por exemplo, ao usar um balanceador de carga externo da plataforma Google Cloud, pode ser necessário implantar o
NodePort
clientes. Deve-se notar que essas portas, além disso, representam recursos limitados, embora 2768 portas sejam provavelmente suficientes, mesmo para os maiores clusters. Na maioria dos casos, você pode permitir que o Kubernetes selecione números de porta aleatoriamente, mas você mesmo pode configurá-los, se necessário. Outro problema são algumas limitações relacionadas ao armazenamento de endereços IP de origem nas solicitações. Para descobrir como resolver esses problemas, você pode consultar
este material na documentação do Kubernetes.
Portas
NodePorts
é o mecanismo fundamental pelo qual todo o tráfego externo entra no cluster Kubernetes. No entanto, eles próprios não nos apresentam uma solução pronta. Pelas razões acima, antes do cluster, se os clientes são entidades internas ou externas localizadas em uma rede pública, é sempre necessário ter algum tipo de balanceador de carga.
Os arquitetos da plataforma, percebendo isso, forneceram duas maneiras de configurar o balanceador de carga a partir da própria plataforma Kubernetes. Vamos discutir isso.
Serviços como LoadBalancer e recursos do tipo Ingress
Serviços como o
LoadBalancer
e recursos do tipo
Ingress
são alguns dos mecanismos mais complexos do Kubernetes. No entanto, não gastaremos muito tempo com eles, pois seu uso não leva a mudanças fundamentais em tudo o que falamos até agora. Todo o tráfego externo, como antes, entra no cluster através do
NodePort
.
Os arquitetos podem parar por aqui, permitindo que aqueles que criam clusters se importem apenas com endereços IP públicos e balanceadores de carga. De fato, em certas situações, como iniciar um cluster em servidores regulares ou em casa, é exatamente isso que eles fazem. Mas em ambientes que suportam configurações de recursos de rede controladas por API, o Kubernetes permite configurar tudo o que você precisa em um só lugar.
A primeira abordagem para resolver esse problema, a mais simples, é usar serviços do Kubernetes, como o
LoadBalancer
. Esses serviços têm todos os recursos de serviços como
NodePort
e, além disso, têm a capacidade de criar caminhos completos para o tráfego recebido, com base na suposição de que o cluster está sendo executado em ambientes como GCP ou AWS que suportam a configuração de recursos de rede por meio da API.
kind: Service apiVersion: v1 metadata: name: service-test spec: type: LoadBalancer selector: app: service_test_pod ports: - port: 80 targetPort: http
Se excluirmos e recriarmos o serviço do nosso exemplo no Google Kubernetes Engine, logo depois disso, usando o
kubectl get svc service-test
, poderemos verificar se o IP externo está atribuído.
$ kubectl get svc service-test NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE openvpn 10.3.241.52 35.184.97.156 80:32213/TCP 5m
Foi dito acima que seremos capazes de verificar o fato de atribuir um endereço IP externo "em breve", apesar de a atribuição de um IP externo demorar alguns minutos, o que não é surpreendente, dada a quantidade de recursos que precisam ser trazidos para um estado íntegro. Na plataforma GCP, por exemplo, isso exige que o sistema crie um endereço IP externo, regras de redirecionamento de tráfego, um servidor proxy de destino, um serviço de back-end e, possivelmente, uma instância de um grupo. Depois de alocar um endereço IP externo, você pode se conectar ao serviço através desse endereço, atribuir um nome de domínio e informar os clientes. Até que o serviço seja destruído e recriado (para fazer isso, raramente quando houver um bom motivo), o endereço IP não será alterado.
Serviços como o
LoadBalancer
têm algumas limitações. Esse serviço não pode ser configurado para descriptografar o tráfego HTTPS. Você não pode criar hosts virtuais ou configurar o roteamento baseado em caminho; portanto, usando configurações práticas, não pode usar um único balanceador de carga com muitos serviços. Essas limitações levaram à introdução do Kubernetes 1.1. Um recurso especial para configurar balanceadores de carga. Este é um recurso do tipo
Ingress . Serviços como o
LoadBalancer
visam expandir os recursos de um único serviço para dar suporte a clientes externos. Por outro lado, os recursos do
Ingress
são recursos especiais que permitem configurar flexivelmente balanceadores de carga. A API do Ingress suporta descriptografia do tráfego TLS, hosts virtuais e roteamento baseado em caminho. Usando essa API, o balanceador de carga pode ser facilmente configurado para funcionar com vários serviços de back-end.
A API do recurso do tipo
Ingress
é muito grande para discutir seus recursos aqui; além disso, não afeta particularmente como os recursos do Ingress funcionam no nível da rede. A implementação desse recurso segue o padrão usual do Kubernetes: existe um tipo de recurso e um controlador para controlar esse tipo. O recurso nesse caso é o recurso
Ingress
, que descreve solicitações para recursos de rede. Veja como pode ser a descrição de um recurso do
Ingress
.
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress annotations: kubernetes.io/ingress.class: "gce" spec: tls: - secretName: my-ssl-secret rules: - host: testhost.com http: paths: - path: /* backend: serviceName: service-test servicePort: 80
O controlador do Ingress é responsável por executar essas solicitações, trazendo outros recursos para o estado desejado. Ao usar o Ingress, são criados serviços como o
NodePort
, após o qual o controlador do Ingress pode tomar decisões sobre como direcionar o tráfego para os nós. Existe uma implementação do controlador Ingress para balanceadores de carga GCE, balanceadores da AWS, para servidores proxy populares como nginx e haproxy. Observe que a mistura de recursos e serviços do Ingress como o
LoadBalancer
pode causar problemas menores em alguns ambientes. Eles são fáceis de manusear, mas, em geral, é melhor usar o Ingress mesmo para serviços simples.
HostPort e HostNetwork
O que falaremos agora, a saber,
HostPort
e
HostNetwork
, pode ser atribuído à categoria de raridades interessantes, e não a ferramentas úteis. De fato, comprometo-me a afirmar que, em 99,99% dos casos, seu uso pode ser considerado um antipadrão, e qualquer sistema no qual eles são usados deve passar por uma verificação obrigatória de sua arquitetura.
Eu pensei que não valia a pena falar sobre eles, mas eles são algo parecido com as ferramentas usadas pelos recursos do Ingress para processar o tráfego recebido, então decidi que valia a pena mencioná-los, pelo menos brevemente.
Primeiro,
HostPort
falar sobre o
HostPort
. Esta é uma propriedade de contêiner (declarada na estrutura
ContainerPort
). Quando um determinado número de porta é gravado nele, isso leva à abertura dessa porta no nó e ao seu redirecionamento diretamente para o contêiner. Não há mecanismos de proxy e a porta é aberta apenas nos nós nos quais o contêiner está em execução. Nos primeiros dias da plataforma, antes que os mecanismos
DaemonSet e
StatefulSet aparecessem, o
HostPort
era um truque que permitia que apenas um contêiner de um determinado tipo fosse iniciado em qualquer nó. Por exemplo, uma vez eu usei isso para criar um cluster do
HostPort
configurando o
HostPort
para
9200
e especificando quantas réplicas havia nós. , Kubernetes, -
HostPort
.
NostNetwork
, , Kubernetes ,
HostPort
.
true
, -
network=host
docker run
. , .
eth0
. , . , , , Kubernetes, - .
Sumário
Kubernetes, , Ingress. , , , Kubernetes.
Caros leitores! Ingress?
