Nota perev. : Con este artículo, escrito por Scott Rahner, ingeniero de Dow Jones, continuamos la serie de numerosos materiales que explican cómo funciona Kubernetes, cómo funcionan sus componentes básicos, están interconectados y utilizados. Esta vez, esta es una nota práctica con código de muestra para crear un gancho en Kubernetes, demostrada por el autor "con el pretexto" de crear automáticamente contenedores de sidecar.
(Foto de Gordon A. Maxwell, encontrada en Internet).Cuando comencé a aprender los contenedores de sidecar y la malla de servicio, tenía que descubrir cómo funciona el mecanismo clave, la inserción automática de contenedores de sidecar. De hecho, en el caso de utilizar sistemas como Istio o Consul, cuando se despliega el contenedor con la aplicación, el contenedor Envoy ya configurado aparece repentinamente en su pod
(ocurre una situación similar con Conduit, sobre la que escribimos a principios de año - aprox. Transl.) . Que? Como? Entonces mi investigación comenzó ...
Para aquellos que no lo saben, un contenedor de sidecar es un contenedor que se implementa junto a los contenedores de la aplicación para "ayudar" a esta aplicación de alguna manera. Un ejemplo de tal uso es un proxy para administrar el tráfico y finalizar las sesiones TLS, un contenedor para la transmisión de registros y métricas, un contenedor para escanear problemas de seguridad ... La idea es aislar varios aspectos de toda la aplicación de la lógica empresarial mediante el uso de contenedores separados para cada funciones
Antes de continuar, describiré mis expectativas. El propósito de este artículo no es explicar las complejidades y los escenarios de uso de Docker, Kubernetes, la malla de servicios, etc., sino demostrar un enfoque poderoso para expandir las capacidades de estas tecnologías. El artículo es para aquellos que ya están familiarizados con el uso de estas tecnologías o, al menos, han leído mucho sobre ellas. Para probar la parte práctica en acción, necesitará una máquina con Docker y Kubernetes ya configurados. La forma más fácil de hacer esto es
https://docs.docker.com/docker-for-windows/kubernetes/ (un manual de Windows que funciona con Docker para Mac).
(Nota perev .: Como alternativa a los usuarios de Linux y * nix-systems podemos ofrecer Minikube ).Cuadro general
Para comenzar, echemos un vistazo a Kubernetes un poco:
Kube Arch bajo licencia CC BY 4.0Cuando intente implementar algo en Kubernetes, debe enviar el objeto a kube-apiserver. Esto se hace con mayor frecuencia pasando argumentos o un archivo YAML a kubectl. En este caso, el servidor API pasa por varias etapas antes de colocar directamente los datos en etcd y programar las tareas correspondientes:

Esta secuencia es importante para comprender cómo funciona la inserción de contenedores sidecar. En particular, debe prestar atención al
Control de Admisión , en el cual Kubernetes valida y, si es necesario, modifica los objetos antes de almacenarlos
(para obtener más detalles sobre este paso, consulte el capítulo "Control de acceso" en este artículo - aprox. Transl.) . Kubernetes también le permite registrar
webhooks que pueden realizar validaciones y
mutaciones definidas por el usuario.
Sin embargo, el proceso de crear y registrar sus ganchos no es tan simple y está bien documentado. Tuve que pasar varios días leyendo y releyendo la documentación, así como analizando el código Istio y Consul. Y cuando se trataba del código para algunas de las respuestas API, pasé al menos medio día haciendo pruebas y errores aleatorios.
Una vez logrado el resultado, creo que será injusto no compartirlo con todos ustedes. Es simple y al mismo tiempo efectivo.
Código
El nombre webhook habla por sí mismo: es un punto final HTTP que implementa la API definida en Kubernetes. Está creando un servidor API al que Kubernetes puede llamar antes de ocuparse de las implementaciones. Tuve que lidiar con dificultades aquí, ya que solo hay algunos ejemplos disponibles, algunos de los cuales son solo pruebas unitarias de Kubernetes, otros están ocultos en medio de una gran base de código ... y todos están escritos en Go. Pero elegí una opción más asequible: 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 )La ruta a la API, en este caso
/mutate
, puede ser arbitraria (solo debería corresponder al YAML pasado a Kubernetes en el futuro). Es importante para él ver y comprender el JSON recibido del servidor API. En este caso, no estamos sacando nada de JSON, pero podría ser útil en otros escenarios. En el código anterior, actualizamos JSON. Se necesitan dos cosas para esto:
- Aprende y entiende el parche JSON .
- Convierta correctamente una expresión de parche JSON en una matriz de bytes codificados con base64.
Una vez hecho esto, todo lo que tiene que hacer es pasar la respuesta al servidor API con un objeto muy simple. En este caso, agregamos la etiqueta
foo=bar
cualquier pod que nos llegue.
Despliegue
Bueno, tenemos un código que acepta solicitudes del servidor API de Kubernetes y responde a ellas, pero ¿cómo implementarlo? ¿Y cómo conseguir que Kubernetes nos redirija estas solicitudes? Puede implementar dicho punto final en cualquier lugar donde pueda llegar al servidor API de Kubernetes. La forma más sencilla es implementar el código en el clúster de Kubernetes, lo que haremos en este ejemplo. Intenté hacer que el ejemplo sea lo más simple posible, por lo que para todas las acciones solo uso Docker y kubectl. Comencemos creando un contenedor en el que se ejecutará el código:
FROM node:8 USER node WORKDIR /home/node COPY index.js . COPY package.json . RUN npm install # TLS CMD node index.js
( Dockerfile )Aparentemente, todo es muy simple aquí. Tome la imagen de la comunidad del nodo y coloque el código en ella. Ahora puede realizar un ensamblaje simple:
docker build . -t localserver
El siguiente paso es crear una implementación en 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
( despliegue.yaml )¿Te das cuenta de cómo aludimos a la imagen que acabamos de crear? También podría ser un pod, y algo más a lo que podamos conectar un servicio en Kubernetes. Ahora defina este servicio:
apiVersion: v1 kind: Service metadata: name: webhook-service spec: ports: - port: 443 targetPort: 8443 selector: component: webhook-server
Entonces, en Kubernetes, aparece un punto final con un nombre interno que apunta a nuestro contenedor. El último paso es decirle a Kubernetes que queremos que el servidor API llame a este servicio cuando esté listo para hacer
mutaciones :
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 )El nombre y la ruta aquí pueden ser cualquiera, pero traté de hacerlos lo más significativos posible. Cambiar la ruta significará la necesidad de modificar el código correspondiente en JavaScript. Webhook
failurePolicy
también es
failurePolicy
: determina si el objeto debe guardarse si el enlace devuelve un error o falla. En este caso, le estamos diciendo a Kubernetes que no continúe procesando. Finalmente, las reglas: cambiarán según las llamadas de API que espere acciones de Kubernetes. En este caso, dado que estamos tratando de emular la inserción de un contenedor de sidecar, debemos interceptar las solicitudes para crear un pod.
Eso es todo! Tan simple ... pero ¿qué pasa con la seguridad? RBAC es un aspecto que no está cubierto en el artículo. Supongo que está ejecutando el ejemplo en Minikube o en Kubernetes, que viene con Docker para Windows / Mac. Sin embargo, te contaré sobre otro elemento necesario. El servidor API de Kubernetes solo accede a puntos finales con HTTPS, por lo que la aplicación requiere certificados SSL. También deberá informar a Kubernetes quién es la autoridad de certificación del certificado raíz.
TLS
Solo con fines de demostración (!!!), agregué un código al
Dockerfile
para crear una CA raíz y usarlo para firmar el 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 )Tenga en cuenta: el último paso es mostrar una sola línea con CA raíz codificada en base64. Esto es exactamente lo que se requiere para la configuración del
caBundle
, por lo tanto, en sus pruebas posteriores, asegúrese de copiar esta línea en el campo
caBundle
archivo
caBundle
.
Dockerfile
lanza certificados directamente en
WORKDIR
, por lo que JavaScript simplemente los toma de allí y los usa para el servidor:
const privateKey = fs.readFileSync('webhook.key').toString(); const certificate = fs.readFileSync('webhook.crt').toString();
Ahora el código admite el lanzamiento de HTTPS y también le dijo a Kubernetes dónde encontrarnos y en qué centro de confianza confiar. Solo queda incrustarlo todo en un clúster:
kubectl create -f deployment.yaml kubectl create -f service.yaml kubectl create -f hook.yaml
Resumir
Deployment.yaml
lanza un contenedor que sirve la API de enlace sobre HTTPS y devuelve un parche JSON para modificar el objeto.Service.yaml
proporciona un punto final para el contenedor: webhook-service.default.svc
.Hook.yaml
le dice al servidor API dónde encontrarnos: https://webhook-service.default.svc/mutate
.
¡Intentémoslo en los negocios!
Todo se implementa en un clúster: es hora de probar el código en acción, lo que haremos al agregar un nuevo pod / Implementación. Si todo funciona correctamente, el gancho tendrá que agregar una etiqueta adicional para:
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 la
deployment.apps test created
... pero ¿funcionó?
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
Genial Aunque
test.yaml
recibió una sola etiqueta (
component
), la cápsula resultante recibió dos:
component
y
foo
.
Tarea
Pero espera! ¿Vamos a usar este código para crear un contenedor de sidecar? Advertí que mostraría
cómo agregar un sidecar ... Y ahora, con el conocimiento y el código:
https://github.com/dowjones/k8s-webhook - experimente con valentía y descubra cómo hacer que su sidecar se inserte automáticamente. Es bastante simple: solo necesita preparar el parche JSON correcto, que agregará un contenedor adicional en la implementación de prueba. ¡Feliz orquestación!
PD del traductor
Lea también en nuestro blog: