Redes Kubernetes: ingresso

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 rede

Até 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 externos

Se 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 host

A 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 NodePort

Na 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?

Source: https://habr.com/ru/post/pt442646/


All Articles