Como buildar imagens customizadas do control plane do Kubernetes (e testá-las com Kind)

A postagem original desse texto pode ser encontrada nesse link: How to build custom Kubernetes control plane images (and test them with Kind) escrito por Mike Dame, Software Engineer na Google e contribuidor de vários projetos no ecosistema Kubernetes. Resolvi traduzir pois é um passo inicial importante para fazer contribuições funcionais para o Kubernetes, e também pois já me perguntaram por material desse tipo em português.

Se você está trabalhando em mudanças para um componente Kubernetes core, como por exemplo, o kube-scheduler, controller-manager ou ainda api-server, você pode estar se perguntando como testar suas mudanças localmente, ter imagens para usar, e como deployar e ver funcionando. Esse blog vai explicar exatamente isso: Como buildar e testar mudanças em componentes do control plane do Kubernetes. Podem existir formas mais eficientes de fazer a mesma coisa, mas essa abordagem possibilita mais flexibilidade e controle de cada passo.

Geralmente meu time e eu estamos desenvolvendo schedulers customizados, e por conta disso, será o foco dos passos mostrados aqui (mas., como irá perceber, tudo aqui é reproduzível com outros componentes Kubernetes).

Buildando imagens customizadas de componentes

O primeiro passo (depois que tiver feito alguma alteração de código - tente adicionar algum log novo pra ver) é rodar o seguinte comando make quick-release-images:

~/go/src/k8s.io/kubernetes$ make quick-release-images 
+++ [1103 14:32:11] Verifying Prerequisites....
+++ [1103 14:32:11] Using Docker for MacOS
+++ [1103 14:32:12] Building Docker image kube-build:build-841902342a-5-v1.15.2-1
+++ [1103 14:32:18] Syncing sources to container
+++ [1103 14:32:21] Running build command...
+++ [1103 14:33:25] Building go targets for linux/amd64:
    cmd/kube-apiserver
    cmd/kube-controller-manager
    cmd/kube-scheduler
    cmd/kube-proxy
    vendor/github.com/onsi/ginkgo/ginkgo
    test/e2e/e2e.test
    cluster/images/conformance/go-runner
    cmd/kubectl
+++ [1103 14:34:31] Syncing out of container
+++ [1103 14:34:40] Building images: linux-amd64
+++ [1103 14:34:40] Starting docker build for image: kube-apiserver-amd64
+++ [1103 14:34:40] Starting docker build for image: kube-controller-manager-amd64
+++ [1103 14:34:40] Starting docker build for image: kube-scheduler-amd64
+++ [1103 14:34:40] Starting docker build for image: kube-proxy-amd64
+++ [1103 14:34:40] Building conformance image for arch: amd64
+++ [1103 14:35:05] Deleting docker image k8s.gcr.io/kube-scheduler-amd64:v1.20.0-beta.1.5_c82d5ee0486191-dirty
+++ [1103 14:35:08] Deleting docker image k8s.gcr.io/kube-proxy-amd64:v1.20.0-beta.1.5_c82d5ee0486191-dirty
+++ [1103 14:35:15] Deleting docker image k8s.gcr.io/kube-controller-manager-amd64:v1.20.0-beta.1.5_c82d5ee0486191-dirty
+++ [1103 14:35:15] Deleting docker image k8s.gcr.io/kube-apiserver-amd64:v1.20.0-beta.1.5_c82d5ee0486191-dirty
+++ [1103 14:35:24] Deleting conformance image k8s.gcr.io/conformance-amd64:v1.20.0-beta.1.5_c82d5ee0486191-dirty
+++ [1103 14:35:24] Docker builds done

Esse comando coloca as imagens no seguinte caminho: ./_output/release-images/amd64.

$ ls _output/release-images/amd64/
total 817M
drwxr-xr-x 7 mdame staff  224 Nov  3 14:35 .
drwxr-xr-x 3 mdame staff   96 Nov  3 14:34 ..
-rw-r--r-- 1 mdame staff 288M Nov  3 14:35 conformance-amd64.tar
-rw------- 2 mdame staff 159M Nov  3 14:35 kube-apiserver.tar
-rw------- 2 mdame staff 150M Nov  3 14:35 kube-controller-manager.tar
-rw------- 2 mdame staff 130M Nov  3 14:35 kube-proxy.tar
-rw------- 2 mdame staff  63M Nov  3 14:35 kube-scheduler.tar

Para carregar essas imagens no docker ou podman, basta rodar:

podman load -i _output/release-images/amd64/kube-scheduler.tar

ou

docker load -i _output/release-images/amd64/kube-scheduler.tar


E em seguida basta taggear e pushar a imagem para o registry de sua escolha:

docker tag k8s.gcr.io/kube-scheduler-amd64:v1.24-XXXXXXXXXXXXXXXX quay.io/rh_ee_lseveroa/kube-scheduler:blog-test

e o push:

docker push  quay.io/rh_ee_lseveroa/kube-scheduler:blog-test

Deployando sua imagem customizada em um cluster Kind

Obs:

Na verdade o Kind disponibiliza maneiras para que você builde e carregue mudanças de código para um cluster. Veja Loading an Image Into Your Cluster e Building Images.

Então esse jeito pode ser um pouco overkill, mas te deixa fazer mudanças em um cluster já rodando, ou ainda em imagens que você ja buildou.

Comece com um cluster kind padrão:

$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.24.0) 🖼 
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂

Ache o container onde o cluster está rodando:

$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                       NAMES
b4f712eff358        kindest/node:v1.19.1   "/usr/local/bin/entr…"   8 minutes ago       Up 8 minutes        127.0.0.1:58206->6443/tcp   kind-control-plane

Dê um cat no manifesto estático do componente que você quer rodar de maneira customizada:

$ docker exec -it b4f712eff358 cat /etc/kubernetes/manifests/kube-scheduler.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    component: kube-scheduler
    tier: control-plane
  name: kube-scheduler
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-scheduler
    - --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
    - --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
    - --bind-address=127.0.0.1
    - --kubeconfig=/etc/kubernetes/scheduler.conf
    - --leader-elect=true
    - --port=0
    image: k8s.gcr.io/kube-scheduler:v1.19.1
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 10259
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    name: kube-scheduler
    resources:
      requests:
        cpu: 100m
    startupProbe:
      failureThreshold: 24
      httpGet:
        host: 127.0.0.1
        path: /healthz
        port: 10259
        scheme: HTTPS
      initialDelaySeconds: 10
      periodSeconds: 10
      timeoutSeconds: 15
    volumeMounts:
    - mountPath: /etc/kubernetes/scheduler.conf
      name: kubeconfig
      readOnly: true
  hostNetwork: true
  priorityClassName: system-node-critical
  volumes:
  - hostPath:
      path: /etc/kubernetes/scheduler.conf
      type: FileOrCreate
    name: kubeconfig
status: {}

Copie e modifique a parte onde fica a imagem para que use a imagem que você buildou antes:

[...]
    - --port=0
    image: quay.io/rh_ee_lseveroa/kube-scheduler:blog-test
    imagePullPolicy: Always
[...]

Copie o arquivo diretamente para onde está o manifesto do pod estático (troque <cluster-container-ID> pelo id que pegou antes do docker ps):

$ docker cp kube-scheduler.yaml <cluster-container-ID>:/etc/kubernetes/manifests/kube-scheduler.yaml

Deployando novas mudanças depois do primeiro carregamento

Já que esses pods de componentes core são pods estáticos, deletar o pod após pushar imagens não vai ser suficiente para que uma nova imagem seja baixada e carregada (mesmo com imagePullPolicy: Always). Então para carregar novas imagens você vai precisar entrar no shell do docker rodando o cluster e parar o container do componente (assumindo docker no seu host, e cri-o no container do kind):

$ docker exec -it <container-ID> crictl ps
$ docker exec -it <container-ID> crictl stop cfcb6d257aa59

Em seguida você vai poder ver um novo pod começando com a nova imagem sendo baixada (como mencionado anteriormente, você também pode carregar imagens diretamente no registry do kind e carregar no pod diretamente).