Balanceamento de carga de serviços do Kubernetes com NGINX Plus

NGINX | 17 de novembro de 2015

O Kubernetes é um sistema de código aberto desenvolvido pelo Google para executar e gerenciar aplicações baseadas em microsserviços conteinerizados em um cluster. Quem usa o Kubernetes frequentemente precisa tornar os serviços criados nele acessíveis de fora do cluster do Kubernetes.

Embora o Kubernetes ofereça soluções integradas para expor serviços, descritas em Exposição de serviços do Kubernetes com soluções integradas abaixo, essas soluções limitam você ao balanceamento de carga da camada 4 ou ao balanceamento de carga HTTP round-robin.

Este post mostra como usar o NGINX Plus como uma solução avançada de balanceamento de carga de camada 7 para expor serviços do Kubernetes à Internet, seja executando o Kubernetes na nuvem ou em sua própria infraestrutura.

Partiremos do pressuposto de que você tenha conhecimentos básicos sobre o Kubernetes (pods, serviços, controladores de replicação e rótulos) e um cluster do Kubernetes em execução. Para saber mais sobre o Kubernetes, consulte o guia oficial do usuário do Kubernetes.

Expondo serviços do Kubernetes com soluções integradas

O Kubernetes oferece várias opções para expor serviços. Duas delas — NodePort e LoadBalancer — correspondem a um tipo específico de serviço. Uma terceira opção, a API Ingress, foi disponibilizada em versão beta no Kubernetes 1.1.

NodePort

Especificar o tipo de serviço como NodePort disponibiliza o serviço na mesma porta em cada nó do Kubernetes. Para expor o serviço à Internet, exponha um ou mais nós nessa porta. Para garantir alta disponibilidade, você pode expor vários nós e usar o balanceamento de carga baseado em DNS para distribuir o tráfego entre eles ou colocar os nós atrás de um balanceador de carga de sua escolha.

Quando o tráfego de entrada chega a um nó pela porta, ele é balanceado entre os pods do serviço. O balanceamento de carga realizado pelo proxy de rede do Kubernetes (kube-proxy), executado em cada nó, limita-se ao balanceamento de carga TCP/UDP.

Balanceador de carga

Especificar o tipo de serviço como LoadBalancer provisiona um balanceador de carga na nuvem que distribui o tráfego de entrada entre os pods do serviço.

A solução LoadBalancer é compatível apenas com determinados provedores de nuvem e com o Google Container Engine e não está disponível se você estiver executando o Kubernetes em sua própria infraestrutura. Além disso, o Kubernetes permite configurar apenas o balanceamento de carga TCP round-robin, mesmo que o balanceador de carga da nuvem tenha recursos avançados, como persistência de sessão ou mapeamento de solicitações.

API de Ingress

A criação de um recurso Ingress permite expor serviços à Internet em URLs personalizadas (por exemplo, o serviço A na URL /foo e o serviço B na URL /bar) e em vários nomes de hosts virtuais (por exemplo, foo.example.com para um grupo de serviços e bar.example.com para outro grupo). Um controlador de Ingress processa um recurso Ingress e configura um balanceador de carga externo.

Um controlador de Ingress não faz parte de uma implantação padrão do Kubernetes: você precisa escolher o controlador que melhor atenda às suas necessidades ou implementar um por conta própria e adicioná-lo ao seu cluster do Kubernetes. Espera-se que muitas implementações de controladores surjam em breve, mas, por enquanto, a única implementação disponível é o controlador para o balanceador de carga HTTP do Google Compute Engine, que funciona apenas se você estiver executando o Kubernetes no Google Compute Engine ou no Google Container Engine. A API Ingress oferece suporte apenas ao balanceamento de carga HTTP round-robin, mesmo que o balanceador de carga utilizado ofereça recursos avançados.

No momento da redação deste texto, tanto a API Ingress quanto o controlador do balanceador de carga HTTP do Google Compute Engine estão em versão beta.

Atualização – O NGINX Ingress Controller para NGINX e NGINX Plus já está disponível em nosso repositório do GitHub. Para obter detalhes sobre o produto, consulte NGINX Ingress Controller.

Embora as soluções mencionadas acima sejam fáceis de configurar e funcionem imediatamente, elas não oferecem recursos avançados, especialmente os relacionados ao balanceamento de carga da Camada 7.

Expondo serviços do Kubernetes com o NGINX Plus

[Editor – Esta seção foi atualizada para fazer referência à API do NGINX Plus, que substitui e torna obsoleto o módulo separado de configuração dinâmica discutido originalmente aqui.]

Para integrar o NGINX Plus ao Kubernetes, precisamos garantir que a configuração do NGINX Plus permaneça sincronizada com o Kubernetes, refletindo alterações nos serviços do Kubernetes, como a adição ou exclusão de pods. Com o NGINX Open Source, você modifica manualmente o arquivo de configuração do NGINX e recarrega a configuração. Com o NGINX Plus, há duas maneiras de atualizar a configuração dinamicamente:

  • Com APIs – Este método usa a API do NGINX Plus para adicionar e remover entradas de pods do Kubernetes na configuração do NGINX Plus e a API do Kubernetes para obter os endereços IP dos pods. Esse método exige que escrevamos algum código e não será abordado em detalhes aqui. Para saber mais, assista ao webinar de Kelsey Hightower, Levando o Kubernetes à borda com o NGINX Plus, no qual ele explora as APIs e cria uma aplicação que as utiliza.
  • Por meio da nova resolução de nomes DNS – Esse método requer apenas uma configuração inicial adequada do NGINX Plus, conforme descrito na seção a seguir.

Utilizando a reconfiguração baseada em DNS

Pressupomos que você já tenha um cluster Kubernetes em execução e um host com o utilitário kubectl disponível para gerenciar o cluster. Para obter instruções, consulte o guia de introdução ao Kubernetes referente ao seu tipo de cluster. Você também precisa ter criado uma imagem Docker do NGINX Plus. As instruções estão disponíveis no artigo Implantação do NGINX e do NGINX Plus com Docker em nosso blog.

Veja um resumo do que faremos:

  1. Configure um pod do NGINX Plus para expor e balancear a carga do serviço que estamos criando na Etapa 2.
  2. Crie uma aplicação web simples para servir como nosso serviço.
  3. Aumente e diminua a escala do serviço e observe como o NGINX Plus é reconfigurado automaticamente.

Observações: Testamos a solução descrita neste blog com o Kubernetes 1.0.6 em execução no Google Compute Engine e em um ambiente local configurado com o Vagrant, que é o que usamos abaixo.

Nos comandos, os valores que podem ser diferentes na sua configuração do Kubernetes aparecem em itálico.

Configurando o pod do NGINX Plus

Estamos colocando o NGINX Plus em um pod do Kubernetes em um nó que expomos à Internet. Nosso pod é criado por um controlador de replicação, que também estamos configurando. Nosso arquivo de configuração do NGINX Plus específico para Kubernetes fica em uma pasta compartilhada entre o pod do NGINX Plus e o nó, o que simplifica sua manutenção.

Escolhendo um nó para hospedar o pod do NGINX Plus

Para designar o nó em que o pod do NGINX Plus será executado, adicionamos um rótulo a esse nó. Para obter a lista de todos os nós, executamos:

$ kubectl get nodesNAME         LABELS                              STATUS
10.245.1.3   Kubernetes.io/hostname=10.245.1.3   Ready
10.245.1.4   Kubernetes.io/hostname=10.245.1.4   Ready
10.245.1.5   Kubernetes.io/hostname=10.245.1.5   Ready

Escolhemos o primeiro nó e adicionamos um rótulo a ele executando:

$ kubectl label node 10.245.1.3 role=nginxplus

Configurando o controlador de replicação para o pod do NGINX Plus

Não estamos criando um pod do NGINX Plus diretamente, mas sim por meio de um controlador de replicação. Configuramos o controlador de replicação do pod do NGINX Plus em um arquivo de declaração do Kubernetes chamado nginxplus-rc.yaml.

  • Definimos o número de réplicas como um, o que significa que o Kubernetes garante que um pod do NGINX Plus esteja sempre em execução: se o pod falhar, ele será substituído por um novo pod.
  • No campo nodeSelector, especificamos que o pod do NGINX Plus seja criado em um nó com o rótulo role: nginxplus.
  • Nosso contêiner do NGINX Plus expõe duas portas, 80 e 8080, e configuramos um mapeamento entre elas e as portas 80 e 8080 no nó.
  • Nosso contêiner do NGINX Plus também compartilha a pasta /etc/nginx/conf.d localizada no nó. Conforme explicado em mais detalhes na seção Configuração do NGINX Plus abaixo, o compartilhamento da pasta permite reconfigurar o NGINX Plus sem recriar a imagem do contêiner.
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginxplus-rc
spec:
  replicas: 1
  selector:
    app: nginxplus
  template:
    metadata:
      labels:
        app: nginxplus
    spec:
      nodeSelector:
        role: nginxplus
      containers:
      - name: nginxplus
        imagePullPolicy: IfNotPresent
        image: nginxplus
        ports:
          - name: http
            containerPort: 80
            hostPort: 80
          - name: http-alt
            containerPort: 8080
            hostPort: 8080
        volumeMounts:
          - mountPath: "/etc/nginx/conf.d"
            name: etc-nginx-confd
      volumes:
        - hostPath:
            path: "/etc/nginx/conf.d"
          name: etc-nginx-confd

Disponibilizando a imagem Docker do NGINX Plus no nó

Como dissemos acima, já criamos uma imagem Docker do NGINX Plus. Agora, vamos disponibilizá-la no nó. Para simplificar, não usamos um repositório Docker privado; apenas carregamos manualmente a imagem no nó.

No host em que criamos a imagem do Docker, executamos o seguinte comando para salvar a imagem em um arquivo:

$ docker save -o nginxplus.tar nginxplus

Transferimos o arquivo nginxplus.tar para o nó e executamos o seguinte comando nele para carregar a imagem a partir do arquivo:

$ docker load -i nginxplus.tar

Configurando o NGINX Plus

Na pasta /etc/nginx do contêiner do NGINX Plus, mantemos o arquivo de configuração principal padrão nginx.conf, que acompanha os pacotes do NGINX Plus. A diretiva include do arquivo padrão carrega outros arquivos de configuração da pasta /etc/nginx/conf.d. Conforme especificado no arquivo de declaração do controlador de replicação do NGINX Plus (nginxplus-rc.yaml), compartilhamos a pasta /etc/nginx/conf.d do nó do NGINX Plus com o contêiner. Esse compartilhamento permite alterar os arquivos de configuração armazenados na pasta (no nó) sem precisar recriar a imagem Docker do NGINX Plus, o que seria necessário se criássemos a pasta diretamente no contêiner. Colocamos nosso arquivo de configuração específico do Kubernetes (backend.conf) na pasta compartilhada.

Primeiro, vamos criar a pasta /etc/nginx/conf.d no nó.

$ sudo mkdir -p /etc/nginx/conf.d

Em seguida, criamos o arquivo backend.conf nesse diretório e incluímos estas diretivas:

  • resolver – Define o servidor DNS que o NGINX Plus usa para resolver novamente, de forma periódica, o nome de domínio usado para identificar nossos servidores upstream (na diretiva server dentro do bloco upstream, abordada no próximo item). Identificamos esse servidor DNS pelo nome de domínio kube-dns.kube-system.svc.cluster.local. O parâmetro valid instrui o NGINX Plus a enviar a solicitação de nova resolução a cada cinco segundos. (Observe que o processo de resolução dessa diretiva é diferente daquele usado para servidores upstream: esse nome de domínio é resolvido somente quando o NGINX é iniciado ou recarregado, e o NGINX Plus usa o servidor ou os servidores DNS do sistema definidos no arquivo /etc/resolv.conf para resolvê-lo.)
  • upstream – Cria um grupo de upstream chamado backend para conter os servidores que fornecem o serviço do Kubernetes que estamos expondo. Em vez de listar os servidores individualmente, nós os identificamos por meio de um nome de host totalmente qualificado em uma única diretiva server. O parâmetro resolve instrui o NGINX Plus a resolver novamente o nome do host em tempo de execução, de acordo com as configurações especificadas na diretiva resolver. Como o DNS do Kubernetes e o NGINX Plus (R10 e versões posteriores) são compatíveis com registros de serviço DNS (SRV), o NGINX Plus pode obter via DNS os números das portas dos servidores de upstream. Incluímos o parâmetro service para que o NGINX Plus solicite registros SRV, especificando o nome (_http) e o protocolo (_tcp) das portas expostas pelo nosso serviço. Declaramos esses valores no arquivo webapp-svc.yaml, abordado abaixo em Como criar o controlador de replicação para o serviço. Para obter mais informações sobre descoberta de serviços com DNS, consulte Como usar o DNS para descoberta de serviços com o NGINX e o NGINX Plus em nosso blog.
  • server (duas vezes) – Define dois servidores virtuais:
    • O primeiro servidor escuta na porta 80 e distribui as solicitações recebidas para /webapp (nosso serviço) entre os pods que executam instâncias do serviço. Também configuramos verificações ativas de integridade.
    • O segundo servidor escuta na porta 8080. Aqui, configuramos o monitoramento de atividades em tempo real do NGINX Plus. Posteriormente, vamos usá-lo para verificar se o NGINX Plus foi reconfigurado corretamente. [Editor – A configuração deste segundo servidor foi atualizada para usar a API do NGINX Plus, que substitui e torna obsoleto o módulo de status separado usado originalmente.]
resolver kube-dns.kube-system.svc.cluster.local valid=5s;

upstream backend {
    zone upstream-backend 64k;
    server webapp-svc.default.svc.cluster.local service=_http._tcp resolve;
}

server {
    listen 80;
    status_zone backend-servers;

    location /webapp {
        proxy_pass http://backend;
        health_check;
    }
}

server {
    listen 8080;
    root /usr/share/nginx/html;

    location = /dashboard.html { }

    location = / {
        return 302 /dashboard.html;
    }

    location /api {
        api write=on;
    }
}

Criando o controlador de replicação

Agora estamos prontos para criar o controlador de replicação executando este comando:

$ kubectl create -f nginxplus-rc.yaml

Para verificar se o pod do NGINX Plus foi criado, executamos:

$ kubectl get pods
NAME                 READY     STATUS    RESTARTS   AGE
nginxplus-rc-0ts5t   1/1       Running   0          17s

Estamos executando o Kubernetes em um ambiente local do Vagrant, portanto sabemos que o endereço IP externo do nosso nó é 10.245.1.3 e usaremos esse endereço no restante deste exemplo. Se você estiver executando o Kubernetes em um provedor de nuvem, poderá obter o endereço IP externo do seu nó executando:

$ kubectl get nodes node-name -o json | grep -i externalIP -A 1                "type": "ExternalIP",
                "address": XXX.XXX.XXX.XXX

Se você estiver executando em uma nuvem, não se esqueça de configurar uma regra de firewall para permitir que o nó do NGINX Plus aceite o tráfego de entrada. Consulte a documentação do seu provedor de nuvem.

Podemos verificar se o nosso pod do NGINX Plus está em execução consultando o painel de monitoramento de atividades em tempo real do NGINX Plus, disponível na porta 8080 do endereço IP externo do nó (portanto, http://10.245.1.3:8080/dashboard.html no nosso caso). No entanto, se consultarmos o painel neste momento, não veremos nenhum servidor para o nosso serviço, pois ainda não criamos o serviço.

O painel de monitoramento de atividades em tempo real do NGINX Plus antes de criarmos os serviços do Kubernetes

Criando um serviço simples do Kubernetes

Agora é hora de criar um serviço do Kubernetes. Nosso serviço consiste em dois servidores web, cada um disponibilizando uma página com informações sobre o contêiner em que está sendo executado.

Criando o controlador de replicação para o serviço

Primeiro, criamos um controlador de replicação para que o Kubernetes garanta que o número especificado de réplicas do servidor web (pods) esteja sempre em execução no cluster. Este é o arquivo de declaração (webapp-rc.yaml):

apiVersion: v1kind: ReplicationController
metadata:
  name: webapp-rc
spec:
  replicas: 2
  selector:
    app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: hello
        image: nginxdemos/hello 
        ports:
        - containerPort: 80

Nosso controlador consiste em dois servidores web. Declaramos um controlador composto por pods com um único contêiner, expondo a porta 80. A imagem nginxdemos/hello será baixada do Docker Hub.

Para criar o controlador de replicação, executamos o seguinte comando:

$ kubectl create -f webapp-rc.yaml

Para verificar se nossos pods foram criados, podemos executar o comando a seguir. Usamos o seletor de rótulo app=webapp para obter apenas os pods criados pelo controlador de replicação na etapa anterior:

$ kubectl get pods -l app=webapp
NAME             READY     STATUS    RESTARTS   AGE
webapp-rc-544f1   1/1       Running   0          2m
webapp-rc-uk6pm   1/1       Running   0          2m

Criando o serviço

Em seguida, criamos um serviço para os pods criados pelo nosso controlador de replicação. Declaramos o serviço com o seguinte arquivo (webapp-service.yaml):

apiVersion: v1kind: Service
metadata:
  name: webapp-svc
spec:
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http
  selector:
    app: webapp

Aqui, estamos declarando um serviço headless especial, definindo o campo ClusterIP como None. Com esse tipo de serviço, um endereço IP de cluster não é alocado, e o serviço não fica disponível por meio do kube-proxy. Uma consulta DNS ao DNS do Kubernetes retorna vários registros A (os endereços IP dos nossos pods).

Também declaramos a porta que o NGINX Plus usará para se conectar aos pods. Além de especificar os números da porta e da porta de destino, especificamos o nome (http) e o protocolo (TCP). Usamos esses valores no arquivo de configuração do NGINX Plus, no qual instruímos o NGINX Plus a obter os números das portas dos pods via DNS usando registros SRV.

Ao definir o campo selector como app: webapp, declaramos quais pods pertencem ao serviço, ou seja, os pods criados pelo nosso controlador de replicação do NGINX (definido em webapp-rc.yaml).

Executamos o comando a seguir, que cria o serviço:

$ kubectl create -f webapp-service.yaml

Agora, se atualizarmos a página do painel e clicarmos na aba Upstreams, no canto superior direito, veremos os dois servidores que adicionamos.

O painel de monitoramento de atividades em tempo real do NGINX Plus após a criação dos serviços do Kubernetes

Também podemos verificar se o NGINX Plus está balanceando o tráfego entre os pods do serviço. Se estiver, ao acessarmos http://10.245.1.3/webapp/ em um navegador, a página exibirá informações sobre o contêiner no qual o servidor web está sendo executado, como o nome do host e o endereço IP.

Se atualizarmos esta página várias vezes e observarmos o painel de status, veremos como as solicitações são distribuídas entre os dois servidores upstream.

Escalando o serviço do Kubernetes

[Editor – Esta seção foi atualizada para usar a API do NGINX Plus, que substitui e torna obsoleto o módulo de status separado usado originalmente.]

Agora, vamos adicionar mais dois pods ao nosso serviço e garantir que a configuração do NGINX Plus seja novamente atualizada de forma automática. Executamos este comando para aumentar o número de pods para quatro, escalando o controlador de replicação:

$ kubectl scale rc webapp-rc --replicas=4
scaled

Para verificar se o NGINX Plus foi reconfigurado, poderíamos consultar novamente o painel, mas desta vez usamos a API do NGINX Plus. Executamos o comando a seguir, em que 10.245.1.3 é o endereço IP externo do nosso nó do NGINX Plus e 3 é a versão da API do NGINX Plus. Para formatar adequadamente a saída JSON, nós a redirecionamos para jq.

$ curl -s 10.245.1.3:8080/api/3/http/upstreams/backend/servers | jq{
  "peers": [
    {
      "id": 1,
      "server": "10.0.0.1:80",
      "backup": false,
      "weight": 1,
      "state": "unhealthy",
      "active": 0,
      "requests": 1,
      "responses": {
        "1xx": 0,
        "2xx": 0,
        "3xx": 0,
        "4xx": 0,
        "5xx": 0,
        "total": 0
      },
      "sent": 0,
      "received": 0,
      "fails": 0,
      "unavail": 0,
      "health_checks": {
        "checks": 1,
        "fails": 1,
        "unhealthy": 1,
        "last_passed": false
      },
      "downtime": 33965,
      "downstart": 1445378182275,
      "selected": 1445378131000
    },
    {
      "id": 2,
      "server": "10.246.1.6:80",
      ...
    },
    {
      "id": 3,
      "server": "10.246.3.2:80",
       ...
    {
      "id": 4,
      "server": "10.0.0.2:80",
      ...
    }
  ],
  "keepalive": 0
}

A matriz peers na saída JSON tem exatamente quatro elementos, um para cada servidor web.

Agora, vamos reduzir o número de pods de quatro para um e verificar novamente o status do NGINX Plus:

$ kubectl scale rc webapp-rc --replicas=1scaled

$ curl -s 10.245.1.3:8080/api/3/http/upstreams/backend/servers | jq

Agora, a matriz peers na saída JSON contém apenas um elemento (a saída é a mesma que a do peer com ID 1 no comando de exemplo anterior).

Agora que o NGINX Plus está instalado e em execução, podemos começar a aproveitar seus recursos avançados, como persistência de sessão, terminação SSL/TLS, roteamento de solicitações, monitoramento avançado, entre outros.

Resumo

As opções de reconfiguração dinâmica disponíveis no NGINX Plus permitem integrá-lo facilmente ao Kubernetes, seja de forma programática por meio de uma API ou inteiramente via DNS. O uso do NGINX Plus para expor serviços do Kubernetes à Internet oferece muitos recursos que não estão disponíveis nas atuais soluções de balanceamento de carga integradas ao Kubernetes.

Para descobrir como o NGINX Plus funciona em conjunto com o Kubernetes, comece hoje mesmo sua avaliação gratuita de 30 dias ou entre em contato conosco para conversar sobre seu caso de uso.


Compartilhar

Publicações relacionadas no blog

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade
NGINX | 10/10/2024

Como o F5 NGINX One aprimora o gerenciamento e a observabilidade

Descubra como o F5 NGINX One aprimora o gerenciamento e a observabilidade de ambientes híbridos e multicloud ao oferecer um console web centralizado baseado em SaaS, com insights em tempo real, conformidade automatizada e gerenciamento simplificado de configurações.

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa
NGINX | 17/06/2024

Do OpenAPI ao NGINX como gateway de API usando uma API declarativa

Descubra como transformar uma definição de esquema OpenAPI em uma configuração NGINX totalmente funcional, operando como um API Gateway com a segurança de um Web Application Firewall e um Portal do Desenvolvedor, usando uma abordagem declarativa de API.

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor
NGINX | 14/07/2021

Como gerenciar picos repentinos de tráfego e sobrecarga do servidor

Picos de tráfego e sobrecarga de servidores são preocupações sérias para varejistas online em qualquer época, mas especialmente durante a “Cyber Season” de compras de fim de ano. Detalhamos como você pode aproveitar as soluções de entrega de aplicações de nível de produção da NGINX para se preparar para aumentos repentinos de tráfego.

Distribuição segura de chaves privadas SSL com o NGINX
NGINX | 02/04/2019

Distribuição segura de chaves privadas SSL com o NGINX

Descrevemos três maneiras progressivamente mais seguras de proteger chaves privadas SSL ao configurar o NGINX para processar tráfego HTTPS: permitir acesso de leitura apenas ao usuário root, criptografar as chaves com senhas armazenadas separadamente e distribuir as senhas a partir de um repositório central.

Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX
NGINX | 24/05/2018

Agrupando solicitações de API em lotes com NGINX Plus e o módulo JavaScript do NGINX

O módulo JavaScript do NGINX lançado com o NGINX Plus R15 oferece suporte a subsolicitações, possibilitando uma classe totalmente nova de casos de uso para o NGINX Plus. Este exemplo completo também usa a API do NGINX Plus e os módulos de chave-valor para converter solicitações de API dos clientes em solicitações separadas para as APIs dos microsserviços que compõem um aplicativo.

Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus
NGINX | 19/04/2016

Como superar o esgotamento de portas efêmeras no NGINX e no NGINX Plus

Confira dicas para evitar o esgotamento de portas efêmeras com ajustes no kernel do Linux, conexões keepalive e conexões vinculadas dinamicamente a endereços IP locais.