Aller au contenu principal

klbd tag correctement les images de conteneurs

· 6 minutes de lecture
Stéphane ROBERT
Consultant DevOps

Nous le savons tous, l'un des principaux avantages de l'utilisation des images de conteneurs, c'est qu'elles sont immutables. Mais déclarons-nous les correctement dans nos manifests Kubernetes pour en tirer tout le bénéfice ?

Dans nos déclarations de déploiement kubernetes nous indiquons le plus souvent un tag pour identifier la version de l'image à utiliser. Mais est-ce suffisant pour garantir que cette version de l'image est bien celle que nous attendons ?

Si vous connaissez le principe de construction d'une image vous devriez répondre non. En effet, cette étiquette est apposée après la construction de l'image par son créateur (ou une chaine de CI). Donc rien n'empêche de taguer une image qui n'a rien à voir avec la précédente et de la pousser dans la registry. N'est-ce pas ?

Pourquoi garantir l'Immutabilité de vos images de containers ?

Une petite mise en scène :

Imaginons que dans un manifest de deployment kubernetes vous avez :

  • Bien indiqué un tag versionné dans l'image à utiliser.
  • Respecté la bonne pratique d'utiliser dans vos specs de containeurs IfNotPresent pour imagePullPolicy

Mais voila quelqu'un a poussé dans la registry une image avec le même tag avec un contenu ou juste une version d'un composant a changé.

Que se passe-t-il si lors d'un autoscale ou même lorsqu'un pod meurt suite à un bug et que le controller de deployment de Kubernetes décide de lancer le pod sur un node ou l'image n'est pas encore présente ? Bim votre système devient incohérent ! Cela peut conduire à des incidents ou l'analyse va être plus compliquée à mener.

Que faire pour garantir cette immutabilité ?

Nous pourrions utiliser lors du passage dans le pipeline l'ID SHA du commit git pour la rendre immutable.

Mais il y a plus simple pour rendre unique le tag d'une référence d'image dans un manifest. Il suffit d'utiliser une simple information présente dans toutes les images et qui est unique ! Tout simplement son ID. Pour le retrouver il suffit de lancer la commande docker image inspect :

docker image inspect alpine:3.12
[
    {
        "Id": "sha256:b0925e0819214cd29937af66dbaf0e6fe239997faea60922cc890f9984512507",
        "RepoTags": [
            "alpine:3.12"
        ],
        "RepoDigests": [
            "alpine@sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280"
        ],
        "Created": "2021-11-12T17:20:08.442217528Z",
...
    }
]

Si vous vous rappelez cet ID est utilisé par le système de fichier de docker pour identifier un layer.

kbld tag les images avec l'id

kbld est un outil écrit par vmware, pour sa plateforme Tanzu, qui fait partie de la suite qui répond au nom de Carvel. Nous verrons d'autres outils de cette suite.

kbld, prononcé [kei·bild], qui prend en charge toutes les phases :

  • de construction des images via docker, pack, ...
  • de pose des tags aux images
  • d'envoi dans votre registry d'images de conteneurs
  • d'appliquer ces id dans les manifests kubernetes ou tout autre outil comme Helm

Le mieux est de vous montrer son fonctionnement.

Installation de kbld

Comme pour beaucoup d'outils un wget est suffisant :

wget -O- https://carvel.dev/install.sh > install.sh
sudo bash install.sh
kbld version

kbld version 0.32.0

Succeeded

Utilisation de kbld

kbld utilise en entrée les manifests kubernetes pour y ajouter les ID des images de conteneur au spec de conteneur. Prenons ce simple fichier d'un déploiement que je nommerai deployment1.yml :

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kbld-test1
spec:
  selector:
    matchLabels:
      app: kbld-test1
  template:
    metadata:
      labels:
        app: kbld-test1
    spec:
      containers:
      - name: my-app
        image: nginx:1.14.2
        #!      ^-- image reference in its tag form

Lançons kbld dessus :

kbld -f deployment1.yml
resolve | final: nginx:1.14.2 -> index.docker.io/library/nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kbld.k14s.io/images: |
      - origins:
        - resolved:
            tag: 1.14.2
            url: nginx:1.14.2
        url: index.docker.io/library/nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d
  name: kbld-test1
spec:
  selector:
    matchLabels:
      app: kbld-test1
  template:
    metadata:
      labels:
        app: kbld-test1
    spec:
      containers:
      - image: index.docker.io/library/nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d
        name: my-app

Succeeded

On voit qu'en sortie nous avons bien un manifest kubernetes dont l'image, qui au départ utilisait un tag de version, est devenu un id pioché dans la registry.

Whaouuu ! Mon code de CI va se simplifier.

Mais voyons plus loin en utilisant une image construite à partir d'un Dockerfile.

Il suffit d'ajouter quelques lignes au manifest de départ :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app1-deployment
  labels:
    app: app1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: app1
  template:
    metadata:
      labels:
        app: app1
    spec:
      containers:
      - name: app1
        image: app1
        ports:
        - containerPort: 80
---
apiVersion: kbld.k14s.io/v1alpha1
kind: Config
sources:
- image: app1
  path: app1
---
apiVersion: kbld.k14s.io/v1alpha1
kind: Config
destinations:
- image: app1
  newImage: registry.gitlab.com/bob74/docker-images

On ajoute un objet de type Config au manifest qui va indiquer la source de l'image (le dossier ou se trouve le Dockerfile) et la registry dans laquelle elle devra être poussée à la fin du build.

Créons un simple Dockerfile dans le dossier app1 :

FROM alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 as builder
ARG VERSION=5.2.0
RUN apk add --no-cache bc cargo gcc libffi-dev musl-dev openssl-dev py3-pip python3 python3-dev rust \
    && python3 -m venv /opt/venv \
    && source /opt/venv/bin/activate \
    && pip3 install --no-cache-dir --no-compile ansible-lint==$VERSION yamllint \
    && pip3 install ansible

FROM alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 as default

RUN apk add --no-cache git python3

COPY --from=builder /opt/venv /opt/venv

# Make sure we use the virtualenv:
ENV PATH="/opt/venv/bin:$PATH"

WORKDIR /src
ENTRYPOINT ["ansible-lint", "-p", "-v"]
CMD ["--version"]

Au passage vous avez vu j'utilise l'ID de l'image et nous la version comme image de base.

Si vous lancez la même commande qu'auparavant kbld construit l'image, la tag, la pousse dans la registry (il faudra s'identifier auparavant) et produit le manifest kubernetes. Il suffit soit :

  • de rediriger la sortie pour produire le manifest dans un fichier
  • de l'appliquer directement avec un simple kubectl apply
kbld -f deployment1.yml > deployment.yml
kbld -f deployment2.yml | kubectl apply -f

Comment gérer le multi-environnement ? Il suffit de traiter la sortie d'un helm, d'un kustomize, ou d'un ytt (une autre pépite de la suite carvel que nous verrons une autre fois).

helm template my-chart --values my-vals.yml | kbld -f - | kubectl apply -f -
kustomize build ./some-app | kbld -f - | kubectl apply -f -
ytt -f ./some-app | kbld -f - | kapp -y deploy -a some-app -f -

Cet outil possède toute une série de fonctionnalités qui poussent le concept encore plus loin. Tout est documenté ici

Conclusion

Je qualifie cet outil de pépite, car elle résout une problématique tout en simplifiant mes scripts de pipeline CI-CD.

Je remercie David Delassus qui m'a fait découvrir la suite Carvel dans un commentaire d'un de mes posts sur Linkedin. D'ailleurs nous parlerons bientôt de son produit qui répond au nom de Kubirds que je présenterai dans les prochains jours.