Como esse container lateral chegou aqui [no Kubernetes]?

Nota perev. : Com este artigo, escrito por Scott Rahner, engenheiro da Dow Jones, continuamos a série de vários materiais que explicam como o Kubernetes funciona, como seus componentes básicos funcionam, são interconectados e usados. Desta vez, esta é uma nota prática com um código de amostra para criar um gancho no Kubernetes, demonstrado pelo autor "a pretexto" de criar automaticamente contêineres laterais.


(Foto de Gordon A. Maxwell, encontrada na Internet.)

Quando comecei a estudar os contêineres laterais e a malha de serviço, eu precisava entender como o mecanismo principal funciona - inserção automática de um contêiner lateral. De fato, no caso de usar sistemas como Istio ou Consul, quando o contêiner com o aplicativo é implantado, o contêiner Envoy já configurado aparece de repente em seu pod (uma situação semelhante ocorre com o Conduit, sobre o qual escrevemos no início do ano - aprox. Transl.) . O que? Como Então minha pesquisa começou ...

Para quem não sabe, um contêiner lateral é implantado próximo aos contêineres do aplicativo para "ajudar" esse aplicativo de alguma forma. Um exemplo desse uso é um proxy para gerenciar o tráfego e encerrar sessões TLS, um contêiner para transmitir logs e métricas, um contêiner para verificar problemas de segurança ... A idéia é isolar vários aspectos de todo o aplicativo da lógica de negócios usando contêineres separados para cada funções

Antes de prosseguir, descreverei minhas expectativas. O objetivo deste artigo não é explicar os meandros e os cenários de uso do Docker, Kubernetes, malha de serviço etc., mas demonstrar uma abordagem poderosa para expandir os recursos dessas tecnologias. O artigo é para aqueles que já estão familiarizados com o uso dessas tecnologias ou, pelo menos, já leram muito sobre elas. Para experimentar a parte prática em ação, você precisará de uma máquina com o Docker e o Kubernetes já configurados. A maneira mais fácil de fazer isso é https://docs.docker.com/docker-for-windows/kubernetes/ (um manual do Windows que funciona com o Docker para Mac). (Nota perev.: Como alternativa aos usuários de sistemas Linux e * nix, podemos oferecer o Minikube .)

Quadro geral


Para começar, vamos dar uma olhada no Kubernetes um pouco:


Kube Arch licenciado sob CC BY 4.0

Quando você pretende implantar algo no Kubernetes, deve enviar o objeto ao kube-apiserver. Isso geralmente é feito passando argumentos ou um arquivo YAML para o kubectl. Nesse caso, o servidor da API passa por vários estágios antes de colocar diretamente os dados no etcd e agendar as tarefas correspondentes:



Essa sequência é importante para entender como funciona a inserção de contêiner lateral. Em particular, você precisa prestar atenção ao Controle de Admissão , no qual o Kubernetes valida e, se necessário, modifica objetos antes de armazená-los (para obter mais detalhes sobre esta etapa, consulte o capítulo "Controle de acesso" neste artigo - aprox. Transl.) . O Kubernetes também permite registrar webhooks que podem executar validações e mutações definidas pelo usuário.

No entanto, o processo de criação e registro de seus ganchos não é tão simples e bem documentado. Eu tive que passar vários dias lendo e relendo a documentação, além de analisar o código Istio e Consul. E quando se tratava do código de algumas das respostas da API, passei pelo menos meio dia tentando e testando aleatoriamente.

Depois que o resultado for alcançado, acho injusto não compartilhá-lo com todos vocês. É simples e ao mesmo tempo eficaz.

Código


O nome webhook fala por si - é um ponto de extremidade HTTP que implementa a API definida no Kubernetes. Você está criando um servidor de API que o Kubernetes pode chamar antes de lidar com as implantações. Eu tive que lidar com dificuldades aqui, pois apenas alguns exemplos estão disponíveis, alguns dos quais são apenas testes de unidade do Kubernetes, outros estão ocultos no meio de uma enorme base de código ... e todos estão escritos em Go. Mas eu escolhi uma opção mais acessível - Node.js:

const app = express(); app.use(bodyParser.json()); app.post('/mutate', (req, res) => { console.log(req.body) console.log(req.body.request.object) let adminResp = {response:{ allowed: true, patch: Buffer.from("[{ \"op\": \"add\", \"path\": \"/metadata/labels/foo\", \"value\": \"bar\" }]").toString('base64'), patchType: "JSONPatch", }} console.log(adminResp) res.send(adminResp) }) const server = https.createServer(options, app); 

( index.js )

O caminho para a API - neste caso /mutate - pode ser arbitrário (deve corresponder apenas ao YAML passado para o Kubernetes no futuro). É importante para ele ver e entender o JSON recebido do servidor da API. Nesse caso, não extraímos nada do JSON, mas pode ser útil em outros cenários. No código acima, atualizamos o JSON. São necessárias duas coisas para isso:

  1. Aprenda e entenda o JSON Patch .
  2. Converta corretamente uma expressão de JSON Patch em uma matriz de bytes codificados com base64.

Feito isso, basta passar a resposta ao servidor da API com um objeto muito simples. Nesse caso, adicionamos o rótulo foo=bar qualquer pod que chega até nós.

Implantação


Bem, temos um código que aceita solicitações do servidor da API do Kubernetes e responde a elas, mas como implantá-lo? E como fazer com que o Kubernetes nos redirecione essas solicitações? Você pode implantar esse terminal em qualquer lugar em que possa acessar o servidor da API Kubernetes. A maneira mais simples é implantar o código no próprio cluster Kubernetes, o que faremos neste exemplo. Tentei tornar o exemplo o mais simples possível, portanto, para todas as ações, uso apenas o Docker e o kubectl. Vamos começar criando um contêiner no qual o código será executado:

 FROM node:8 USER node WORKDIR /home/node COPY index.js . COPY package.json . RUN npm install #       TLS CMD node index.js 

( Dockerfile )

Aparentemente, tudo é muito simples aqui. Pegue a imagem da comunidade do nó e solte o código nele. Agora você pode executar uma montagem simples:

 docker build . -t localserver 

A próxima etapa é criar uma implantação no Kubernetes:

 apiVersion: apps/v1 kind: Deployment metadata: name: webhook-server spec: replicas: 1 selector: matchLabels: component: webhook-server template: metadata: labels: component: webhook-server spec: containers: - name: webhook-server imagePullPolicy: Never image: localserver 

( deployment.yaml )

Observe como aludimos à imagem que acabamos de criar? Também poderia ser um pod e outra coisa à qual podemos conectar um serviço no Kubernetes. Agora defina este serviço:

 apiVersion: v1 kind: Service metadata: name: webhook-service spec: ports: - port: 443 targetPort: 8443 selector: component: webhook-server 

Portanto, no Kubernetes, um terminal aparece com um nome interno que aponta para o contêiner. A etapa final é dizer ao Kubernetes que queremos que o servidor da API chame esse serviço quando estiver pronto para fazer mutações :

 apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: name: webhook webhooks: - name: webhook-service.default.svc failurePolicy: Fail clientConfig: service: name: webhook-service namespace: default path: "/mutate" #    base64-  rootCA.crt #    `cat rootCA.crt | base64 | tr -d '\n'` #    .  caBundle: "==" rules: - operations: [ "CREATE" ] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] 
( hook.yaml )

O nome e o caminho aqui podem ser qualquer um, mas tentei torná-los o mais significativos possíveis. Alterar o caminho significará a necessidade de modificar o código correspondente em JavaScript. A falha failurePolicy política da failurePolicy também é failurePolicy - determina se o objeto deve ser salvo se o gancho retornar um erro ou falhar. Nesse caso, estamos dizendo ao Kubernetes para não continuar o processamento. Finalmente, as regras: elas serão alteradas dependendo da API que você espera que ações do Kubernetes. Nesse caso, como estamos tentando emular a inserção de um contêiner lateral, precisamos interceptar solicitações para criar um pod.

Isso é tudo! Tão simples ... mas e a segurança? O RBAC é um aspecto que não é coberto no artigo. Suponho que você esteja executando o exemplo no Minikube ou no Kubernetes, que acompanha o Docker para Windows / Mac. No entanto, vou falar sobre outro elemento necessário. O servidor da API Kubernetes acessa apenas pontos de extremidade com HTTPS, portanto, o aplicativo requer certificados SSL. Você também precisará informar ao Kubernetes quem é a autoridade de certificação do certificado raiz.

TLS


Apenas para fins de demonstração (!!!), adicionei algum código ao Dockerfile para criar uma CA raiz e usá-lo para assinar o certificado:

 RUN openssl genrsa -out rootCA.key 4096 RUN openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt \ -subj "/C=US/ST=New Jersey/L=Princeton /O=Dow Jones/OU=PIB/CN=*.default.svc/emailAddress=scott.rahner@dowjones.com" RUN openssl genrsa -out webhook.key 4096 RUN openssl req -new -key webhook.key -out webhook.csr \ -subj "/C=US/ST=New Jersey/L=Princeton /O=Dow Jones/OU=PIB/CN=webhook-service.default.svc/emailAddress=scott.rahner@dowjones.com" RUN openssl x509 -req -in webhook.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out webhook.crt -days 1024 -sha256 RUN cat rootCA.crt | base64 | tr -d '\n' 

( Dockerfile )

Observe: o último passo é exibir uma única linha com a CA raiz codificada em base64. É exatamente isso que é necessário para a configuração do gancho; portanto, em seus testes adicionais, copie esta linha no campo caBundle arquivo caBundle . Dockerfile lança certificados diretamente no WORKDIR ; portanto, o JavaScript apenas os pega de lá e o usa no servidor:

 const privateKey = fs.readFileSync('webhook.key').toString(); const certificate = fs.readFileSync('webhook.crt').toString(); //… const options = {key: privateKey, cert: certificate}; const server = https.createServer(options, app); 

Agora, o código suporta o lançamento do HTTPS e também disse ao Kubernetes onde nos encontrar e em qual centro de confiança confiar. Resta apenas incorporar tudo em um cluster:

 kubectl create -f deployment.yaml kubectl create -f service.yaml kubectl create -f hook.yaml 

Resumir


  • Deployment.yaml inicia um contêiner que serve a API de gancho por HTTPS e retorna um patch JSON para modificar o objeto.
  • Service.yaml fornece um terminal para o contêiner - webhook-service.default.svc .
  • Hook.yaml informa ao servidor da API onde nos encontrar: https://webhook-service.default.svc/mutate .

Vamos tentar nos negócios!


Tudo é implantado em um cluster - é hora de experimentar o código em ação, o que faremos adicionando um novo pod / implantação. Se tudo funcionar corretamente, o gancho terá que adicionar uma etiqueta adicional foo :

 apiVersion: apps/v1 kind: Deployment metadata: name: test spec: replicas: 1 selector: matchLabels: component: test template: metadata: labels: component: test spec: containers: - name: test image: node:8 command: [ "/bin/sh", "-c", "--" ] args: [ "while true; do sleep 30; done;" ] 

( test.yaml )

 kubectl create -f test.yaml 

Ok, vimos o deployment.apps test created ... mas funcionou?

 kubectl describe pods test Name: test-6f79f9f8bd-r7tbd Namespace: default Node: docker-for-desktop/192.168.65.3 Start Time: Sat, 10 Nov 2018 16:08:47 -0500 Labels: component=test foo=bar 

Ótimo! Embora test.yaml receba um rótulo único ( component ), o pod resultante recebeu dois: component e foo .

Lição de casa


Mas espera! Vamos usar esse código para criar um contêiner lateral? Eu avisei que mostrarei como adicionar sidecar ... E agora, com o conhecimento e o código: https://github.com/dowjones/k8s-webhook - fique à vontade para experimentar e descobrir como fazer seu sidecar inserido automaticamente. É bem simples: você só precisa preparar o patch JSON correto, que adicionará um contêiner adicional na implantação de teste. Orquestração feliz!

PS do tradutor


Leia também em nosso blog:

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


All Articles