Istio, injeção de sidecar: ativando injeção automática, adicionando exceções e depuração


Ei pessoal. Hoje vamos falar sobre um pouco sobre injeção de sidecar do Istio em Kubernetes.

O Istio no Kubernetes funciona usando um modelo de deployment de sidecar, onde um contêiner auxiliar (sidecar) é anexado ao seu contêiner principal (serviço) em um único Pod. Ao fazer isso, seu serviço e o contêiner de sidecar compartilham a mesma rede e podem ser vistos como dois processos em um único host. Assim, o Istio pode interceptar todas as chamadas de rede de e para o seu contêiner principal e fazer sua mágica para melhorar a comunicação de serviço a serviço.

Esse contêiner de sidecar, denominado istio-proxy, pode ser injetado em seu pod de serviço de duas maneiras: manual e automaticamente. Mesmo essa técnica manual não é 100% feita à mão. Vamos ver.

Injeção manual

O Istio vem com uma ferramenta chamada istioctl. Sim, parece que é inspirado em alguma outra ferramenta amada :). Uma de suas funcionalidades é a capacidade de injetar o sidecar istio-proxy em seu pod de serviço. Vamos usá-la, usando um simples pod de busybox como exemplo:

$ cat busybox.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: busybox-test
spec:
  containers:
  - name: busybox-container
    image: busybox
    command: ['sh', '-c', 'echo Hello Kubernetes! && sleep infinity']

$ istioctl kube-inject -f busybox.yaml > busybox-injected.yaml

[jwendell@jw-laptop ~]$ cat busybox-injected.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
  name: busybox-test
spec:
  containers:
  - command:
    - sh
    - -c
    - echo Hello Kubernetes! && sleep infinity
    image: busybox
    name: busybox-container

  - image: docker.io/istio/proxyv2:1.0.2
    imagePullPolicy: IfNotPresent
    name: istio-proxy
    args:
      - proxy
      - sidecar
   ...

Como você pode ver acima, esse comando gerou outro arquivo yaml, semelhante ao input (pod busybox), mas com o sidecar (istio-proxy) adicionado ao pod. Então, de alguma forma, isso não é um trabalho manual de 100%, certo? Isso nos poupa um monte de digitação. Você está pronto para aplicar este yaml modificado ao seu cluster do kubernetes:

$ kubectl apply -f busybox-injected.yaml
# ou, se você não quiser ter um arquivo intermediário, aplique diretamente usando o arquivo original:
$ kubectl apply -f <(istioctl kube-inject -f busybox.yaml)

Uma pergunta natural que pode surgir é: de onde vêm esses dados? Como ele sabe que a imagem do sidecar é docker.io/istio/proxyv2:1.0.2? A resposta é simples: Todos os dados vêm de um ConfigMap que vive no plano de controle do Istio, no namespace istio-system:

$ kubectl -n istio-system describe configmap istio-sidecar-injector
Name:         istio-sidecar-injector
Namespace:    istio-system

Data
====
config:
----
policy: enabled
template: |-
  initContainers:
  - name: istio-init
    image: "docker.io/istio/proxy_init:1.0.2"
...

Você pode editar este ConfigMap com os valores que deseja injetar nos seus pods. Como você pode ver, esse é basicamente um modelo que será adicionado à sua definição de pod. Se você quiser usar outra imagem para o contêiner istio-proxy, use outra tag ou deseje ajustar qualquer coisa que será injetada, essa é a coisa que você precisa editar. Lembre-se que este ConfigMap é usado para injeção em todos os seus pods na malha de serviço. Seja cuidadoso 🙂

Porque istioctl lê um ConfigMap para saber o que injetar, isso significa que você precisa ter acesso a um cluster do Kubernetes em funcionamento com o Istio devidamente instalado. Se por algum motivo você não tiver tal acesso, você ainda pode usar istioctl, fornecendo um arquivo de configuração local:

# execute isso antes, com acesso apropriado ao cluster k8s
$ kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml
# sinta-se à vontade para modificar esse arquivo, e você pode executar a qualquer momento depois:
$ istioctl kube-inject --injectConfigFile inject-config.yaml ...

Injeção automática

A outra maneira de ter istio-proxy injetado em seus pods é dizendo ao Istio para fazer isso automaticamente por você. Na verdade, isso é ativado por padrão para todos os namespaces com o rótulo istio-injection=enabled. Isso significa que, se um namespace tiver esse rótulo, todos os pods dentro dele receberão o sidecar istio-proxy automaticamente. Você não precisa executar istioctl ou fazer qualquer coisa com seus arquivos yaml!

O modo como funciona é bem simples: ele faz uso de uma funcionalidade do Kubernetes chamado MutatingWebhook que consiste em Kubernetes notificando o Istio sempre que um novo pod está prestes a ser criado e dando ao Istio a chance de modificar a especificação do pod durante seu uso, pouco antes de realmente criar esse pod. Assim, o Istio injeta o sidecar istio-proxy usando o template encontrado no ConfigMap que vimos acima.

Soa bem, certo?

Você pode estar se perguntando: Ei, isso é muito intrusivo! Sim, tudo depende das suas necessidades. Esta injeção automática é muito flexível:

  • No istio-sidecar-injector ConfigMap, há um sinalizador booleano indicando se esta injeção automática está habilitada ou não.
  • Somente os namespaces rotulados adequadamente receberão a injeção automática. Você pode escolher seletivamente quais namespaces terão injeção automática.
  • Além disso, você pode ajustar esse rótulo, alterá-lo ou mesmo remover esse filtro (o que significa que a injeção ocorrerá automaticamente em todos os namespaces! Seja cuidadoso!) Editando o MutatingWebhookConfiguration: kubectl -n istio-system edit MutatingWebhookConfiguration istio-sidecar-injector. Procure o campo namespaceSelector.
  • Você pode evitar que a injeção aconteça em pods seletivos. Se um pod tiver a anotação sidecar.istio.io/inject: "false", então o Istio não irá injetar o sidecar nele.
  • Você pode inverter a lógica se você for mais conservador. Desative a injeção automática para todos e ative-a apenas para pods selecionados. Para isso, você só precisa definir a política como falsa (kubectl -n istio-system edit configmap istio-sidecar-injector) e anotar os pods que você deseja para injetar com sidecar.istio.io/inject: "true"

Eu quero mais flexibilidade!

Aqui está um caso de uso em que a flexibilidade acima não foi suficiente:

Para aqueles que não estão familiarizados com o Openshift (distribuição de Kubernetes da Red Hat), ele possui uma funcionalidade chamada source-to-image – s2i, que magicamente cria imagens de contêiner com base no código-fonte. Você fornece um repositório git (funciona com muitas linguagens de programação!) como entrada e obtém uma imagem de contêiner, sendo executada no Openshift como resultado.

É uma funcionilidade incrível! Não vou entrar em detalhes aqui, vou apenas dizer o que precisamos saber agora: para fazer isso, o Openshift cria um ou mais pods auxiliares, intermediários, para construir o código-fonte. Quando a compilação estiver concluída, os artefatos binários vão para a imagem do contêiner resultante, prontos para serem executados no Openshift, e esses conjuntos auxiliares são então descartados.

Se habilitarmos a injeção automática em um determinado namespace e usarmos a funcionalidade de s2i do Openshift, isso significa que todos os pods receberão o sidecar injetado. Mesmo aqueles pods compiladores (intermediários, auxiliares)! Pior, como eles não estão sob nosso controle (eles são criados pelo Openshift, não por nós), não podemos anotar para não obter o sidecar injetado. As compilações não se comportam bem com o sidecar injetado, então não queremos que eles sejam automaticamente injetados. O que fazer agora?

Bem, uma solução possível é seguir a abordagem conservadora explicada no último item acima: Desativar a injeção automática para todos e ativá-la apenas para os pods selecionados. Isso funciona, mas exige que você anote ativamente os pods que você deseja que a injeção automática aconteça.

Ou… Há uma nova solução para esse problema:

A nova solução

A partir da versão 1.1.0, a injeção automática do Istio tem uma maneira de adicionar exceções com base em rótulos, ou seja: não injetar o sidecar em pods que correspondam a esses rótulos, mesmo se a política for true e esse namespace for marcado para ter injeção automática. Você pode adicionar essas exceções no istio-sidecar-injector ConfigMap:

$ kubectl -n istio-system describe configmap istio-sidecar-injector
apiVersion: v1
kind: ConfigMap
metadata:
  name: istio-sidecar-injector
data:
  config: |-
    policy: enabled
    neverInjectSelector:
      - matchExpressions:
        - {key: openshift.io/build.name, operator: Exists}
      - matchExpressions:
        - {key: openshift.io/deployer-pod-for.name, operator: Exists}
    template: |-
      initContainers:
...

Você pode ver acima um campo neverInjectSelector. É uma matriz de seletores de rótulos de Kubernetes. Eles são comparados com OR, parando no primeiro jogo. A instrução acima significa: Nunca injetar em pods que tenham o rótulo openshift.io/build.name ou openshift.io/deployer-pod-for.name – os valores dos rótulos não importam, estamos apenas verificando se as chaves existem. Com essa regra adicionada, nosso caso de uso de s2i do Openshift está coberto, o que significa que os pods auxiliares não terão sidecars injetados (porque os pods auxiliares de s2i contêm esses rótulos).

Para completar, você também pode usar um campo chamado alwaysInjectSelector, que sempre injetará o sidecar em pods que correspondam ao seletor de rótulo, apesar da política global.

A abordagem do seletor de rótulos oferece muita flexibilidade sobre como expressar essas exceções. Dê uma olhada em sua documentação para ver o que você pode fazer com eles!

Vale a pena observar que as anotações nos pods ainda têm a preferência. Se um pod é anotado com sidecar.istio.io/inject: "true/false", então ele será honrado. Então, a ordem de avaliação é:

Pod Annotations → NeverInjectSelector → AlwaysInjectSelector → Namespace Policy

Como este {Never,Always}InjectSelector é uma adição recente, eu ainda tenho que atualizar as documentações para mencioná-lo, mas para todas as outras coisas, para mais informações e exemplos confira a documentação oficial.

Por que meu pod [não] está sendo intejado?

Essa é uma pergunta muito comum. Você seguiu todas as instruções (como rotular o namespace com istio-injection=enabled) e seus pods não estão recebendo a injeção automática.

Ou bem pelo contrário, você anotou seu pod com sidecar.istio.io/inject: "false" e ele está sendo injetado. Por que?

Uma coisa que você pode fazer para descobrir o que está acontecendo é olhar nos logs do pod sidecar-injector:

$ pod=$(kubectl -n istio-system get pods -l istio=sidecar-injector -o jsonpath='{.items[0].metadata.name}')
$ kubectl -n istio-system logs -f $pod

Em seguida, você pode criar seus pods e monitorar aquele log por qualquer saída. Para uma saída de log mais detalhada – confie em mim, é realmente útil – devemos editar o deployment do sidecar-injector e anexar o argumento --log_output_level=default:debug ao executável de contêiner sidecar-injector:

$ kubectl -n istio-system edit deployment istio-sidecar-injector
...
      containers:
      - args:
        - --caCertFile=/etc/istio/certs/root-cert.pem
        - --tlsCertFile=/etc/istio/certs/cert-chain.pem
        - --tlsKeyFile=/etc/istio/certs/key.pem
        - --injectConfig=/etc/istio/inject/config
        - --meshConfig=/etc/istio/config/mesh
        - --healthCheckInterval=2s
        - --healthCheckFile=/health
        - --log_output_level=default:debug
        image: docker.io/istio/sidecar_injector:1.0.2
        imagePullPolicy: IfNotPresent
...

Salve e saia do seu editor. Agora o Kubernetes está fazendo deployment do pod sidecar-injector e o sinalizador depuração deve estar em vigor. Aguarde alguns segundos para o pod estar ativo, então você pode monitorar os logs novamente:

$ pod=$(kubectl -n istio-system get pods -l istio=sidecar-injector -o jsonpath='{.items[0].metadata.name}')
$ kubectl -n istio-system logs -f $pod
Dica

Se mesmo com a saída de depuração ativada você não viu nada relevante em seus logs, isso significa que o pod sidecar-injector não está sendo notificado sobre a criação do pod. Não está sendo invocado para fazer a injeção automática. Isso pode ser devido a uma configuração incorreta em relação ao rótulo do namespace. Verifique se o namespace está rotulado de acordo com o que está no MutatingWebhookConfiguration. Por padrão, o namespace deve ter o rótulo istio-injection=enabled. Verifique se isto foi alterado executando o kubectl -n istio-system edit MutatingWebhookConfiguration istio-sidecar-injector e verifique o campo namespaceSelector.

Quando terminar a sessão de depuração, você poderá editar o deployment novamente e remover esse argumento de depuração.

É isso. Espero que ajude. Sinta-se à vontade para perguntar ou comentar qualquer coisa, aqui ou no twitter! Nos vemos em breve!