Kubernetes - Le gestionnaire de déploiement kapp-controller
Publié le : 28 janvier 2022 | Mis à jour le : 22 janvier 2023kapp-controller
est une extension de
kapp qui
permet d’installer et de mettre à jour vos applications et packages sur vos
clusters Kubernetes. Il fait partie de la suite Carvel.
kapp-controller
vous laisse le choix de vos outils de configuration, et de vos
sources de configuration : configurations YAML simples, graphiques Helm, modèles
ytt, modèles jsonnet, … chargé depuis un référentiel Git, une archive sur
HTTP, un référentiel Helm, etc
Fonctionnement de kapp-controller
On retrouve le même principe de fonctionnement que les systèmes de packages
Linux (apt, yum, …). En effet, kapp-controller
décompose l’installation d’un
package d’une application en trois étapes :
- Fetch : récupération des définitions des sources de l’application depuis le package repository.
- Template: applications des valeurs fournies par l’utilisateur pour personnaliser le logiciel en utilisant des templates ytt, helm, …
- Deploy : création ou mise à jour des ressources sur le cluster
kapp-controller utilise des images crées avec l’outil de la suite Carvel
répondant au nom de imgpkg
. Nous en verrons l’utilisation plus bas dans le
tutoriel.
Installation de kapp-controller sur votre cluster
Pour notre tutoriel je vais avoir besoin d’une registry docker. Je la crée en locale :
docker run -d -p 5000:5000 --restart=always --name registry registry:2
export REPO_HOST="`ifconfig | grep -A1 docker | grep inet | cut -f10 -d' '`:5000"
echo $REPO_HOST
172.17.0.1:5000
L’installation de kapp-controller
se fait simplement avec kapp. Pour ce
tutoriel j’ai utilisé un cluster créé avec
kind.
Attention il est nécessaire que la docker registry soit accessible depuis votre cluster.
kapp deploy -a kc -f https://github.com/vmware-tanzu/carvel-kapp-controller/releases/latest/download/release.yml
Target cluster 'https://127.0.0.1:38717' (nodes: master1, 1+)
Apps in all namespaces
Namespace Name Namespaces Lcs Lca
default kc (cluster),kapp-controller,kube-system true 15m
Lcs: Last Change Successful
Lca: Last Change Age
1 apps
Un pti coup d’inspect :
kapp inspect -a kc --tree
Target cluster 'https://127.0.0.1:38717' (nodes: master1, 1+)
Resources in app 'kc'
Namespace Name Kind Owner Conds. Rs Ri Age
(cluster) pkg-apiserver:system:auth-delegator ClusterRoleBinding kapp - ok - 17m
(cluster) kapp-controller-cluster-role ClusterRole kapp - ok - 17m
(cluster) kapp-controller-cluster-role-binding ClusterRoleBinding kapp - ok - 17m
(cluster) internalpackagemetadatas.internal.packaging.carvel.dev CustomResourceDefinition kapp 2/2 t ok - 17m
kube-system pkgserver-auth-reader RoleBinding kapp - ok - 17m
kapp-controller packaging-api Service kapp - ok - 17m
kapp-controller L packaging-api Endpoints cluster - ok - 17m
kapp-controller L packaging-api-rmftl EndpointSlice cluster - ok - 17m
(cluster) internalpackages.internal.packaging.carvel.dev CustomResourceDefinition kapp 2/2 t ok - 17m
(cluster) packageinstalls.packaging.carvel.dev CustomResourceDefinition kapp 2/2 t ok - 17m
(cluster) v1alpha1.data.packaging.carvel.dev APIService kapp 1/1 t ok - 17m
(cluster) packagerepositories.packaging.carvel.dev CustomResourceDefinition kapp 2/2 t ok - 17m
(cluster) kapp-controller-packaging-global Namespace kapp - ok - 17m
kapp-controller kapp-controller-sa ServiceAccount kapp - ok - 17m
(cluster) apps.kappctrl.k14s.io CustomResourceDefinition kapp 2/2 t ok - 17m
(cluster) kapp-controller Namespace kapp - ok - 17m
kapp-controller kapp-controller Deployment kapp 2/2 t ok - 17m
kapp-controller L kapp-controller-79bd7d495 ReplicaSet cluster - ok - 17m
kapp-controller L.. kapp-controller-79bd7d495-c4jbh Pod cluster 4/4 t ok - 17m
kapp-controller L kapp-controller-79bd7d495-c4jbh PodMetrics cluster - ok - 0s
Rs: Reconcile state
Ri: Reconcile information
20 resources
On voit tout ce qui a été déployé pour kapp-controller
:
- des CRD : internalpackagemetadatas.internal.packaging.carvel.dev, internalpackages.internal.packaging.carvel.dev, packageinstalls.packaging.carvel.dev, apps.kappctrl.k14s.io et packagerepositories.packaging.carvel.dev
- une API : v1alpha1.data.packaging.carvel.dev
- un namespace : kapp-controller
- un Service Account : kapp-controller-sa
- un deployment : kapp-controller
Déployer une application avec kapp-controller
Je reprends les mêmes exemples fournis sur le projet de kapp.
cd /tmp
git clone https://github.com/vmware-tanzu/carvel-simple-app-on-kubernetes
cd config-step-2-template
Notre exemple se trouve dans le répertoire config-step-2-template où on y
retrouve deux fichiers au format ytt
, c’est-à-dire un template :
- config.yaml : qui définit un service et un deployment
- values.yml : qui contient les valeurs qui seront appliquées par ytt
Pour ceux qui ne connaissent pas le langage de template ytt
je vous renvoie à
mon billet.
Création du package de l’application avec imgpkg
Nous allons utiliser imgpgk
pour construire notre package d’application et
pour le pousser dans la registry locale.
Nous allons créer la structure qui contiendra notre package d’application et y déposer les fichiers nécessaires :
mkdir -p package-contents/config/
cp config.yml package-contents/config/config.yml
cp values.yml package-contents/config/values.yml
mkdir -p package-contents/.imgpkg
imgpkg
utilise d’un répertoire .imgpkg
pour y déposer ses définitions. On
peut y retrouver deux fichiers : bundle.yml
qui contient des métadatas
(optionnel) et images.yml
qui référence les images utilisées dans le package.
Pour construire ces images, nous allons utiliser kbld
qui offre cette
fonctionnalité. Pour rappel,
kbld scrute les metadatas d’une
ressource kubernetes pour y retrouver les images utilisées par les pods pour les
rendre immutable en lui accolant un SHA. Ici ce SHA existe déjà, regardez le
contenu du fichier config.yml. Supprimez le avant et vous verrez il l’ajoutera.
kbld -f package-contents/config/ --imgpkg-lock-output package-contents/.imgpkg/images.yml
resolve | final: docker.io/dkalinin/k8s-simple-app -> index.docker.io/dkalinin/k8s-simple-app@sha256:4c8b96d4fffdfae29258d94a22ae4ad1fe36139d47288b8960d9958d1e63a9d0
---
simple-app: ""
---
apiVersion: v1
kind: Service
metadata:
name: simple-app
namespace: default
spec:
ports:
- port: null
targetPort: null
selector: null
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kbld.k14s.io/images: |
- origins:
- resolved:
tag: latest
url: docker.io/dkalinin/k8s-simple-app
url: index.docker.io/dkalinin/k8s-simple-app@sha256:4c8b96d4fffdfae29258d94a22ae4ad1fe36139d47288b8960d9958d1e63a9d0
name: simple-app
namespace: default
spec:
selector:
matchLabels: null
template:
metadata:
labels: null
spec:
containers:
- env:
- name: HELLO_MSG
value: null
image: index.docker.io/dkalinin/k8s-simple-app@sha256:4c8b96d4fffdfae29258d94a22ae4ad1fe36139d47288b8960d9958d1e63a9d0
name: simple-app
---
app_port: 80
hello_msg: stranger
svc_port: 80
Que contient ce fichier images.yml :
---
apiVersion: imgpkg.carvel.dev/v1alpha1
images:
- annotations:
kbld.carvel.dev/id: docker.io/dkalinin/k8s-simple-app
kbld.carvel.dev/origins: |
- resolved:
tag: latest
url: docker.io/dkalinin/k8s-simple-app
image: index.docker.io/dkalinin/k8s-simple-app@sha256:4c8b96d4fffdfae29258d94a22ae4ad1fe36139d47288b8960d9958d1e63a9d0
kind: ImagesLock
Maintenant poussons l’image dans la registry avec imgpkg
:
imgpkg push -b ${REPO_HOST}/packages/simple-app:1.0.0 -f package-contents/
dir: .
dir: .imgpkg
file: .imgpkg/images.yml
dir: config
file: config/config.yml
file: config/values.yml
Pushed '172.17.0.1:5000/packages/simple-app@sha256:fe88086f7737e1bf834786856edb2bab5e153dc470479b114e58174763472145'
Succeeded
Vérifions ce que contient notre registry locale :
curl ${REPO_HOST}/v2/_catalog
{"repositories":["packages/simple-app"]}
On a bien notre image.
Création des définitions de notre application
Pour déployer notre application avec kapp-controller
nous devons définir deux
objets : un Package et un PackageMetadata.
Commençons par le PackageMetadata :
cat > metadata.yml << EOF
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: PackageMetadata
metadata:
# This will be the name of our package
name: simple-app.corp.com
spec:
displayName: "Simple App"
longDescription: "Simple app consisting of a k8s deployment and service"
shortDescription: "Simple app for demoing"
categories:
- demo
EOF
Il ne contient comme attendu que des metadatas qui sera utilisé par
kapp-controller
lors du fetch.
Ensuite créons la déclaration du Package. Nous allons utiliser ytt comme système de template, et donc nous allons devoir créer une ressource de type schema au format openAPI :
ytt -f values.yml --data-values-schema-inspect -o openapi-v3 > schema-openapi.yml
Vérifions ce qu’il contient :
openapi: 3.0.0
info:
version: 0.1.0
title: Schema for data values, generated by ytt
paths: {}
components:
schemas:
dataValues:
nullable: true
default: null
Créons maintenant le package :
cat > package-template.yml << EOF
#@ load("@ytt:data", "data") # for reading data values (generated via ytt's data-values-schema-inspect mode).
#@ load("@ytt:yaml", "yaml") # for dynamically decoding the output of ytt's data-values-schema-inspect
---
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: Package
metadata:
name: #@ "simple-app.corp.com." + data.values.version
spec:
refName: simple-app.corp.com
version: #@ data.values.version
releaseNotes: |
Initial release of the simple app package
valuesSchema:
openAPIv3: #@ yaml.decode(data.values.openapi)["components"]["schemas"]["dataValues"]
template:
spec:
fetch:
- imgpkgBundle:
image: #@ "${REPO_HOST}/packages/simple-app:" + data.values.version
template:
- ytt:
paths:
- "config/"
- kbld:
paths:
- "-"
- ".imgpkg/images.yml"
deploy:
- kapp: {}
EOF
Vous allez me dire “outch” bien difficile à comprendre. Attendez ça va s’éclaircir par la suite. Ça me rappelle la première fois ou j’ai du créer un package yum.
Création du Package Repository
Pour rappel, Un Package Repository est l’endroit où nous allons déposer nos applications et ses metadatas et dans lequel kapp-controller vient puiser pour les déployer.
Commençons par créer les répertoires nécessaires :
ll
total 16K
-rw-r--r--. 1 vagrant vagrant 639 Jan 28 08:45 config.yml
-rw-r--r--. 1 vagrant vagrant 325 Jan 28 09:09 metadata.yml
-rw-r--r--. 1 vagrant vagrant 178 Jan 28 09:03 schema-openapi.yml
-rw-r--r--. 1 vagrant vagrant 64 Jan 27 06:48 values.yml
mkdir -p my-pkg-repo/.imgpkg my-pkg-repo/packages/simple-app.corp.com
Lançons la création du package avec ytt en indiquant la version que nous voulons créer :
ytt -f package-template.yml --data-value-file openapi=schema-openapi.yml -v version="1.0.0" > my-pkg-repo/packages/simple-app.corp.com/1.0.0.yml
cat my-pkg-repo/packages/simple-app.corp.com/1.0.0.yml
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: Package
metadata:
name: simple-app.corp.com.1.0.0
spec:
refName: simple-app.corp.com
version: 1.0.0
releaseNotes: |
Initial release of the simple app package
valuesSchema:
openAPIv3:
nullable: true
default: null
template:
spec:
fetch:
- imgpkgBundle:
image: 172.17.0.1:5000/packages/simple-app:1.0.0
template:
- ytt:
paths:
- config/
- kbld:
paths:
- '-'
- .imgpkg/images.yml
deploy:
- kapp: {}
Un petit coup de kbld
pour créer notre image de repo :
kbld -f my-pkg-repo/packages/ --imgpkg-lock-output my-pkg-repo/.imgpkg/images.yml
resolve | final: 172.17.0.1:5000/packages/simple-app:1.0.0 -> 172.17.0.1:5000/packages/simple-app@sha256:fe88086f7737e1bf834786856edb2bab5e153dc470479b114e58174763472145
---
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: Package
metadata:
annotations:
kbld.k14s.io/images: |
- origins:
- resolved:
tag: 1.0.0
url: 172.17.0.1:5000/packages/simple-app:1.0.0
url: 172.17.0.1:5000/packages/simple-app@sha256:fe88086f7737e1bf834786856edb2bab5e153dc470479b114e58174763472145
name: simple-app.corp.com.1.0.0
spec:
refName: simple-app.corp.com
releaseNotes: |
Initial release of the simple app package
template:
spec:
deploy:
- kapp: {}
fetch:
- imgpkgBundle:
image: 172.17.0.1:5000/packages/simple-app@sha256:fe88086f7737e1bf834786856edb2bab5e153dc470479b114e58174763472145
template:
- ytt:
paths:
- config/
- kbld:
paths:
- '-'
- .imgpkg/images.yml
valuesSchema:
openAPIv3:
default: null
nullable: true
version: 1.0.0
---
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: PackageMetadata
metadata:
name: simple-app.corp.com
spec:
categories:
- demo
displayName: Simple App
longDescription: Simple app consisting of a k8s deployment and service
shortDescription: Simple app for demoing
Succeeded
Normalement on doit retrouver le fichier images.yml dans le répertoire .imgpkg
cat my-pkg-repo/.imgpkg/images.yml
---
apiVersion: imgpkg.carvel.dev/v1alpha1
images:
- annotations:
kbld.carvel.dev/id: 172.17.0.1:5000/packages/simple-app:1.0.0
kbld.carvel.dev/origins: |
- resolved:
tag: 1.0.0
url: 172.17.0.1:5000/packages/simple-app:1.0.0
image: 172.17.0.1:5000/packages/simple-app@sha256:fe88086f7737e1bf834786856edb2bab5e153dc470479b114e58174763472145
kind: ImagesLock
Poussons notre repository dans la registry :
imgpkg push -b ${REPO_HOST}/packages/my-pkg-repo:1.0.0 -f my-pkg-repo
dir: .
dir: .imgpkg
file: .imgpkg/images.yml
dir: packages
dir: packages/simple-app.corp.com
file: packages/simple-app.corp.com/1.0.0.yml
file: packages/simple-app.corp.com/metadata.yml
Pushed '172.17.0.1:5000/packages/my-pkg-repo@sha256:55104b57562a354cb9b5c5ed537fdc221a2c94f575b18dea6b60fa7951b2dee0'
Succeeded
curl ${REPO_HOST}/v2/_catalog
{"repositories":["packages/my-pkg-repo","packages/simple-app"]}
Déploiement du repository sur le cluster
Pour le moment nous avons préparé le terrain en créant toutes les images : celle
du package de l’application et celle du repository. Maintenant il faut créer le
repository physiquement au sein du cluster. Pour cela il faut utiliser la CRD
PackageRepository
:
cat > repo.yml << EOF
---
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageRepository
metadata:
name: simple-package-repository
spec:
fetch:
imgpkgBundle:
image: ${REPO_HOST}/packages/my-pkg-repo:1.0.0
EOF
Pour le déployer nous allons utiliser kapp
qui va nous permettre de voir ce qui
se passe :
kapp deploy -a repo -f repo.yml -y
Target cluster 'https://127.0.0.1:38717' (nodes: kind-control-plane)
Changes
Namespace Name Kind Conds. Age Op Op st. Wait to Rs Ri
default simple-package-repository PackageRepository - - create - reconcile - -
Op: 1 create, 0 delete, 0 update, 0 noop, 0 exists
Wait to: 1 reconcile, 0 delete, 0 noop
9:38:20AM: ---- applying 1 changes [0/1 done] ----
9:38:20AM: create packagerepository/simple-package-repository (packaging.carvel.dev/v1alpha1) namespace: default
9:38:20AM: ---- waiting on 1 changes [0/1 done] ----
9:38:20AM: ongoing: reconcile packagerepository/simple-package-repository (packaging.carvel.dev/v1alpha1) namespace: default
9:38:20AM: ^ Waiting for generation 1 to be observed
9:38:21AM: ongoing: reconcile packagerepository/simple-package-repository (packaging.carvel.dev/v1alpha1) namespace: default
9:38:21AM: ^ Reconciling
9:38:22AM: ok: reconcile packagerepository/simple-package-repository (packaging.carvel.dev/v1alpha1) namespace: default
9:38:22AM: ---- applying complete [1/1 done] ----
9:38:22AM: ---- waiting complete [1/1 done] ----
Succeeded
On en sort avec succès. Mais que se passe-t-il ? Nous application est-elle déjà déployée ? Non, car nous avons juste créé le repository.
Utilisons la commande kubectl
pour voir ce que nous affiche la ressource
PackageRepository que nous venons de créer :
kubectl get packagerepository
NAME AGE DESCRIPTION
simple-package-repository 6m17s Reconcile succeeded
Maintenant vérifions ce que contient notre repository :
kubectl get packagemetadatas
NAME DISPLAY NAME CATEGORIES SHORT DESCRIPTION AGE
simple-app.corp.com Simple App demo Simple app for demoing 9m41s
On y trouve notre application. Maintenant quelle version de l’application disposons-nous ?
kubectl get packages --field-selector spec.refName=simple-app.corp.com
NAME PACKAGEMETADATA NAME VERSION AGE
simple-app.corp.com.1.0.0 simple-app.corp.com 1.0.0 11m17s
Notre version 1.0.0. Mais que contient-elle ?
kubectl get package simple-app.corp.com.1.0.0 -o yaml
apiVersion: data.packaging.carvel.dev/v1alpha1
kind: Package
metadata:
annotations:
kapp.k14s.io/disable-original: ""
kapp.k14s.io/disable-wait: ""
kapp.k14s.io/identity: v1;default/data.packaging.carvel.dev/Package/simple-app.corp.com.1.0.0;data.packaging.carvel.dev/v1alpha1
kbld.k14s.io/images: |
- origins:
- resolved:
tag: 1.0.0
url: 172.17.0.1:5000/packages/simple-app:1.0.0
- preresolved:
url: 172.17.0.1:5000/packages/simple-app@sha256:fe88086f7737e1bf834786856edb2bab5e153dc470479b114e58174763472145
url: 172.17.0.1:5000/packages/simple-app@sha256:fe88086f7737e1bf834786856edb2bab5e153dc470479b114e58174763472145
creationTimestamp: "2022-01-28T10:41:22Z"
generation: 1
labels:
kapp.k14s.io/app: "1643366480557105100"
kapp.k14s.io/association: v1.fc22fec9fc12e7d2a6c2562942b75674
name: simple-app.corp.com.1.0.0
namespace: default
resourceVersion: "7383"
uid: 3375ab8b-adb5-45b3-aec2-f9be67c1a7cc
spec:
refName: simple-app.corp.com
releaseNotes: |
Initial release of the simple app package
releasedAt: null
template:
spec:
deploy:
- kapp: {}
fetch:
- imgpkgBundle:
image: 172.17.0.1:5000/packages/simple-app@sha256:fe88086f7737e1bf834786856edb2bab5e153dc470479b114e58174763472145
template:
- ytt:
paths:
- config/
- kbld:
paths:
- '-'
- .imgpkg/images.yml
valuesSchema:
openAPIv3:
default: null
nullable: true
version: 1.0.0
Tout simplement toutes les informations nécessaires à son déploiement : les phases : fetch, template et deploy.
Déploiement de l’application sur le cluster
Maintenant que nous avons notre repository d’applications contenant notre application, voyons comment la déployer. Comme pour les autres phases un yaml est nécessaire (IAC bien) :
cat > pkginstall.yml << EOF
---
apiVersion: packaging.carvel.dev/v1alpha1
kind: PackageInstall
metadata:
name: pkg-demo
spec:
serviceAccountName: default-ns-sa
packageRef:
refName: simple-app.corp.com
versionSelection:
constraints: 1.0.0
values:
- secretRef:
name: pkg-demo-values
---
apiVersion: v1
kind: Secret
metadata:
name: pkg-demo-values
stringData:
values.yml: |
---
hello_msg: "to all my katacoda friends"
EOF
On y retrouve l’appel à la référence CRD packageinstalls qui utilise une spec de type packageRef qui indique le nom du package (refname) et sa version à utiliser :
packageRef:
refName: simple-app.corp.com
versionSelection:
constraints: 1.0.0
Vous avez certainement remarqué la création d’un secret qui vient définir le fichier “values.yml” utilisé par le package et d’un service account qu’il faut déployer avant.
kapp deploy -a default-ns-rbac -f https://raw.githubusercontent.com/vmware-tanzu/carvel-kapp-controller/develop/examples/rbac/default-ns.yml -y
Déployons maintenant notre application :
kapp deploy -a pkg-demo -f pkginstall.yml -y
Si comme moi la première fois vous avez des erreurs la commande kapp inspect
avec l’option --status
vous indiquera la cause :
kapp inspect -a pkg-demo --status
Target cluster 'https://127.0.0.1:38717' (nodes: kind-control-plane)
Resources in app 'pkg-demo'
Namespace default
Name pkg-demo
Kind PackageInstall
Status conditions:
- message: Error (see .status.usefulErrorMessage for details)
status: "True"
type: ReconcileFailed
friendlyDescription: 'Reconcile failed: Error (see .status.usefulErrorMessage for
details)'
lastAttemptedVersion: 1.0.0
observedGeneration: 2
usefulErrorMessage: |
ytt: Error: Checking file '/etc/kappctrl-mem-tmp/kapp-controller-fetch-template-deploy3698766503/0/config': lstat /etc/kappctrl-mem-tmp/kapp-controller-fetch-template-deploy3698766503/0/config: no such file or directory
version: 1.0.0
Namespace default
Name pkg-demo-values
Kind Secret
Status -
2 resources
Succeeded
Une fois corrigé, dans mon cas, j’avais oublié le fichier de config dans le package de l’application on peut vérifier que notre application est bien déployé.
Target cluster 'https://127.0.0.1:38717' (nodes: kind-control-plane)
Resources in app 'pkg-demo'
Namespace Name Kind Owner Conds. Rs Ri Age
default pkg-demo PackageInstall kapp 1/1 t ok - 6m
^ pkg-demo-values Secret kapp - ok - 6m
Rs: Reconcile state
Ri: Reconcile information
2 resources
Succeeded
kubectl get deployments.apps simple-app
NAME READY UP-TO-DATE AVAILABLE AGE
simple-app 1/1 1 1 7m31s
kubectl get pod
default simple-app-58fb574f5c-55nl4 1/1 Running 0 7m3s
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 75m
simple-app ClusterIP 10.96.0.46 <none> 80/TCP 8m32s
docker exec -it kind-control-plane bash
root@kind-control-plane:/# curl http://10.96.0.46
<h1>Hello to all my katacoda friends!</h1>
Notre application est bien déployée et fonctionne.
Conclusion
Franchement j’en sors content, car j’ai bien compris le principe. Mais je me pose quelques questions comme :
- Est-il possible d’utiliser des repos externes ? Car tout mettre dans le même cluster n’est pas dans mes principes.
- Le debug de la partie déploiement est-il facile ? Là, je m’en suis sorti, mais dans des cas plus complexes ???
- Comment industrialiser tout ça ?
- Comment l’utiliser avec une démarche GitOps ? Car pour le moment je n’ai pas vu trace d’utilisation de repository git.
Voilà ça demande encore un peu de travail pour l’adopter comme gestionnaire d’applications. Et vous donnez moi en commentaires vos réflexions.
Dans le repo git de la version community edition de Tangzu on retrouve une vingtaine d’applications utilisant kapp-controller.