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.0Quando 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:
- Aprenda e entenda o JSON Patch .
- 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();
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: