Aller au contenu principal

Les templates ytt

· 8 minutes de lecture
Stéphane ROBERT
Consultant DevOps

Ytt fait aussi partie de la suite carvel. Ytt est un outil de templating et d'applications de patch pour fichiers Yaml. Il n'est pas essentiellement utilisé pour écrire des manifests kubernetes mais peut être aussi utilisé avec d'autres outils dont les fichiers de configurations sont écrit en Yaml.

Installation de ytt

Comme pour les autres outils je vous montre comment installer toute la suite Carvel:

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

Fonctionnement de ytt

Ytt charge tous les documents, les analyse pour identifier leurs types et calculer la sortie. Il existe différentes types de documents: les fichiers plein, les templates et ses sous-types et les overlays. Pour différencier les types de documents, il recherche tout simplement la présence de lignes contenant des instructions ytt qui débute par #@.

En noir le pipeline.

Les différents types de documents manipulés par ytt

Comme dis dans le descriptif du document ytt utilise différents types de documents :

  • Les Data Values Schema Document qui décrivent les variables ytt avec leur type et valeur par défaut :

    #@data/values-schema
    ---
    instances: 1
    
  • Les Data Values Document qui fixent les valeurs des variables :

    #@data/values
    ---
    instances: 8
    
  • Les Templated Documents :

    #@ load("@ytt:data", "data")
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-app
    spec:
      replicas: #@ data.values.instances
    ...
    
  • Les Overlay Documents qui sont appliqués tout à la fin du processus d'évaluation.

    #@ load("@ytt:overlay", "overlay")
    #@overlay/match by=overlay.subset({"kind": "Deployment", "metadata": {"name": "nginx-deployment"}})
    ---
    spec:
      #! change spec.replicas to 3
      replicas: 3
    

Le langage ytt

remarque

Première ébauche, sera complété au fil de mes progrès

Le langage ytt est dérivé de celui de starlark, qui est un dialecte de Python destiné à être utilisé comme langage de configuration.

Les commentaires

Les commentaires débute par #! :

#! Ceci est un commentaire

Les types manipulés par ytt

  • NoneType: None (equivalent to null in other languages)
  • Bool: True or False
  • Integer: 1
  • Float: 1.1
  • String: "string"
  • List: [1, 2, {"a":3}]
  • Tuple: (1, 2, "a")
  • Dictionary: {"a": 1, "b": "b"}
  • Struct: struct.make(field1=123, field2="val2")
  • Annotation: @name arg1,arg2,keyword_arg3=123

Instructions de contrôles

Les conditions

On retrouve bien une syntaxe proche de python avec if else elif en ajoutant le @ end :

#@ test = 123
#@ if test > 100 and test < 200:
test1: 123
#@ elif test == 100 or test == 200:
test2: 124
#@ else:
test3: 125
#@ end

Les tests peuvent être écrits sur une seule ligne :

test1: #@ passwd if passwd else assert.fail("password must be set")

ou encore :

#@ if/end True:
key3:
  nested_key:
    further_nesting: value1

Les boucles

On retrouve la boucle for avec les instructions continue/break. Sa définition se termine avec @ end :

array_odd:
#@ for j in range(0,10):
#@   if j % 2 == 0:
#@     continue
#@   end
#@   if j > 5:
#@     break
#@   end
- #@ j
#@ end

Une boucle peut s'écrire sur une ligne :

#@ for/end val in [1,5,{"key":"val"}]:
- item: #@ val

Les fonctions

On retrouve les instructions def et return. Ici une fonction créant un deployement et utilisant des variables #@ <variable-name>. Sa définition se termine par @ end :

#@ def my_deployment(name, replicas=1, labels={}):
kind: Deployment
metadata:
  name: #@ name
  labels: #@ labels
spec:
  replicas: #@ replicas
#@ end

---
kind: List
items:
- #@ my_deployment("dep1", replicas=3)

Vous avez remarqué nous retrouvons les paramètres optionnels de python qui doivent être nommé leur définition :

Load

La fonction load permet de faire appel aux fonctions provenant :

  • de modules démarrant par @yt: :
  • de documents écrits sous la forme yaml, text ou starlark :
#@ load("@ytt:overlay", "overlay")                # load overlay module from builtin ytt library
#@ load("@ytt:overlay", ov="overlay")             # load overlay symbol under a different alias
#@ load("helpers.star", "func1", "func2")         # load func1, func2 from Starlark file
#@ load("helpers.lib.yml", "func1", "func2")      # load func1, func2 from YAML file
#@ load("helpers.lib.txt", "func1", "func2")      # load func1, func2 from text file
#@ load("/dir/helpers.lib.yml", "func1")          # load func1 from file relative to root of library
#@ load("sub-dir/helpers.lib.txt", "func1")       # load func1 from a sub-directory
#@ load("@project:dir/helpers.lib.txt", "func1")  # load func1 from a project located under _ytt_lib

Les fichiers .star sont des fichiers écrits en starlark :

# this file can only contain Starlark code, not YAML content
# this restriction is based on the file extension

def func1():
  return [1,2,{"key": "value"}]
end

Écrire ses premiers templates Ytt

Maintenant que nous avons vu les bases de ce langage, passons à l'écriture d'un premier template (tiré de la documentation).

Nous allons déclarer nos variables dans le ficher de schema schema.yml contenant deux définitions echos et service :

#@data/values-schema
---
echos:
- name: ""
  port: 8080
  text: "Hello #ytt World on 8080!"
service:
  enabled: true

Créons un fichier de data default.yml qui vient écraser les valeurs par défaut :

#@data/values
---
echos:
- name: first
- name: second
  port: 8081
  text: "Hello #ytt World on 8081!"

Créons le template lui-meme demo.yml. Le code est expliqué par ses commentaires.

#! On charge les valeurs depuis le fichier
#@ load("@ytt:data", "data")

#! Une fonction retournant deux clés/valeurs
#@ def labels():
app: echo
org: test
#@ end

#! Une autre fonction name retournant une string
#@ def name(echo):
#@   return "echo-"+echo.name
#@ end

kind: Pod
apiVersion: v1
metadata:
  name: echo-app
  #! Appel de la fonction labels
  labels: #@ labels()
spec:
  containers:
  #! Une boucle écrite sur une ligne utilisant la variable echos définit dans le fichier de data
  #@ for/end echo in data.values.echos:
  - name: #@ name(echo)
    image: hashicorp/http-echo
    args:
    - #@ "-listen=:" + str(echo.port)
    - #@ "-text=" + echo.text

#@ if/end data.values.service.enabled:
---
kind: Service
apiVersion: v1
metadata:
  name: echo-service
spec:
  selector: #@ labels()
  ports:
  #@ for/end echo in data.values.echos:
  - name: #@ name(echo)
    port: #@ echo.port

Pour lancer l'évaluation de ce template il suffit de lancer ytt avec tous ces fichiers de définition (peu importe l'ordre) :

ytt -f default.yml -f schema.yml -f demo.yml
# ou plus simplement
ytt -f ./

Ce qui nous donne :

kind: Pod
apiVersion: v1
metadata:
  name: echo-app
  labels:
    app: echo
    org: test
spec:
  containers:
  - name: echo-first
    image: hashicorp/http-echo
    args:
    - -listen=:8080
    - '-text=Hello #ytt World on 8080!'
  - name: echo-second
    image: hashicorp/http-echo
    args:
    - -listen=:8081
    - '-text=Hello #ytt World on 8081!'
---
kind: Service
apiVersion: v1
metadata:
  name: echo-service
spec:
  selector:
    app: echo
    org: test
  ports:
  - name: echo-first
    port: 8080
  - name: echo-second
    port: 8081

Pour gérer plusieurs environnements, on pourrait créer plusieurs jeux de valeurs default-dev.yml default-prod.yml.

Regardez cet exemple : vmware-tanzu/carvel-ytt-starter-for-kubernetes

Patcher des manifests kubernetes avec Ytt

L'utilisation la plus simple d'ytt est de l'utiliser pour patcher des fichiers yaml. Nous allons voir comment l'utiliser pour patcher un manifest Kubernetes récupérer avec vendir.

Commençons par écrire la configuration vendir qui se charge de récupérer le manifest du deployment de pod nginx :

apiVersion: vendir.k14s.io/v1alpha1
kind: Config
directories:
  - path: deploy/definitions
    contents:
      - path: nginx
        git:
          url: https://github.com/kubernetes/website
          ref: main
        includePaths:
          - content/en/examples/application/deployment.yaml
        newRootPath: content/en/examples/application/

Le paramètre newRootPath permet de changer le chemin root par défaut. Le résultat, c'est le que le fichier qui se trouvait dans le répertoire content/en/examples/application/ sera déposé dans le path défini deploy/definitions.

Lançons la synchronisation :

vendor sync

Rappel : vendir créé un lockfile pointant sur le commit qui a été utilisé. Si vous voulez ré la même version il suffit de lancer la commande vendir sync --locked

Maintenant créons le fichier de patch que nous allons déposer dans le répertoire deploy/patchs

mkdir -p deploy/patchs/nginx
vi deploy/patchs/nginx/nginx-deployment-replica-count.yaml

C'est donc un fichier formaté ytt :

#! Ceci est un commentaire ytt, Les instruction ytt sont de la forme #@
#@ load("@ytt:overlay", "overlay")
#@overlay/match by=overlay.subset({"kind": "Deployment", "metadata": {"name": "nginx-deployment"}})
---
spec:
  #! change spec.replicas to 3
  replicas: 3

Ici on charge le module ytt overlay et on lui indique ce qui doit être patcher le deployement avec le match sur la définition {"kind": "Deployment", "metadata": {"name": "nginx-deployment"}}

Créons le manifest kubernetes patché :

ytt -f deploy/definitions/nginx/ -f deploy/patchs/nginx

En sortie nous obtenons bien replicas : 3 :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

Conclusion

Ytt est une belle alternative à Helm. Mais je n'ai pas trouvé trace de repository où on y retrouverait comme pour helm tous les templates des applications les plus courantes. Il va falloir donc tout écrire.