Tenir le Cap avec les Meilleures Pratiques pour les Charts Helm
Kubernetes, le célèbre outil d’orchestration pour les applications de conteneurs, tire son nom du mot grec pour « pilote », ou celui qui gouverne le navire. Mais comme dans tout voyage, le succès du navigateur dépend de la carte dont il dispose.
Le chart Helm d’une application est cette carte. Une collection de fichiers qui peuvent être déployés à partir d’un dépôt de charts Helm qui décrivent un ensemble connexe de ressources K8s. La confection la plus efficace possible de vos charts Helm permet à Kubernetes de naviguer entre les écueils lors du déploiement des conteneurs dans votre environnement de production.
Mais il existe d'autres façons de partir à la dérive, comme je l'ai constaté en développant des charts K8s accessibles au public pour déployer des produits. Avec chaque demande d'extraction, les commentaires de la communauté Helm m'ont aidé à m'orienter vers les meilleures pratiques des charts Helm qui offraient les meilleurs résultats pour l'exploitation et la mise à jour des conteneurs.
Voici quelques éléments à prendre en compte lors de l'écriture de charts K8s qui seront utilisés par la communauté ou les clients en production. Les questions à se poser sont notamment les suivantes :
- Quelles dépendances devez-vous définir ?
- Votre application aura-t-elle besoin d’un état persistant pour fonctionner ?
- Comment allez-vous gérer la sécurité via les secrets et les autorisations ?
- Comment allez-vous contrôler l’exécution des conteneurs kubelet ?
- Comment vous assurer que vos applications sont en cours d’exécution et en mesure de recevoir des appels ?
- Comment allez-vous présenter au monde les services de l’application ?
- Comment allez-vous tester votre chart ?
Ce guide propose quelques bonnes pratiques pour structurer et spécifier vos charts Helm qui aideront K8s à faire accoster délicatement vos applications conteneurisées.
Démarrer
Avant de commencer, assurez-vous que vous êtes familiarisé avec les procédures essentielles pour le développement de charts Helm.
Dans ce guide, nous allons créer un chart Helm selon les meilleures pratiques que nous recommandons pour déployer une application de création, lecture, mise à jour et suppression (CRUD) à deux niveaux pour la base de données Mongo à l’aide d’Express.js.
Vous trouverez le code source de notre exemple d’application dans express-crud dans GitHub.
Création et remplissage du chart Helm
Créons notre modèle de chart Helm à l’aide de la commande de créationdu client Helm :
$ helm create express-crud
Nous créerons ainsi une structure de répertoires pour un chart Helm express-crud.
Pour commencer, mettez à jour les métadonnées du chart dans le fichier Chart.yaml qui vient d’être créé. Veillez à ajouter des informations appropriées pour l'appVersion (la version de l’application à utiliser comme balise d'image docker), la description, la version (une chaîne de version SemVer 2), les sources, les responsables et l'icône.
apiVersion: v1
appVersion: "1.0.0"
description: Un chart Helm pour une application express-crud
name: express-crud
version: 0.1.0
sources:
- https://github.com/jainishshah17/express-mongo-crud
maintainers:
- name: myaccount
email: myacount@mycompany.com
icon: https://github.com/mycompany17/mycompany.com/blob/master/app/public/images/logo.jpg
home: https://mycompany.com/
Définition des Dépendances
Si votre application a des dépendances, vous devez créer un fichier requirements.yaml dans la structure de répertoires du chart Helm qui les spécifie. Étant donné que notre application a besoin de la base de données mongodb, nous devons le spécifier dans la liste des dépendances du fichier requirements.yaml que nous créons.
Un fichier requirements.yaml pour cet exemple contient :
dependencies:
- name: mongodb
version: 3.0.4
repository: https://kubernetes-charts.storage.googleapis.com/
condition: mongodb.enabled
Une fois qu’un fichier requirements.yaml est créé, vous devez exécuter la commande de mise à jour des dépendances dans le client Helm :
$ helm dep update
Création de fichiers de déploiement
Les fichiers de déploiement de votre chart Helm résident dans le sous-répertoire \templates et spécifient la façon dont K8s déploiera l’application conteneur.
Le développement de vos fichiers de déploiement implique la prise de certaines décisions clés.
Objet de Déploiement ou Objet StatefulSet
Le fichier de déploiement que vous créez dépendra du fait que l'application nécessite que K8s la gère comme un Objet de Déploiement ou un Objet StatefulSet.
Un Objet de Déploiement est une application sans état qui est déclarée dans le fichier deployment.yaml et spécifie le paramètre kind comme deployment.
Un Objet Stateful est destiné aux applications avec état et utilisées avec des systèmes distribués. Ils sont déclarés dans le fichier stateless.yaml et spécifient le paramètre kind comme stateful.
Deployment | StatefulSet |
Les Deployments sont destinés à une utilisation sans état et sont plutôt légers. | Les StatefulSets sont utilisés lorsque l'état doit être rendu persistant. Par conséquent, il utilise volumeClaimTemplates sur les volumes persistants pour s’assurer qu’ils peuvent conserver l’état entre les redémarrages des composants. |
Si votre application est sans état ou si l’état peut être créé à partir de systèmes backend au démarrage, utilisez Deployments. | Si votre application est avec état ou si vous souhaitez déployer un stockage avec état en plus de Kubernetes, utilisez un StatefulSet. |
Cette application n’ayant pas besoin d’état pour être rendue persistante, j’utilise un objet de déploiement.
Le fichier deployment.yaml a déjà été créé par la commande helm create.
Nous utiliserons AppVersion comme balise d’image Docker pour notre application. Cela nous permet de mettre à niveau le chart Helm avec la nouvelle version de l’application en changeant simplement la valeur dans Chart.yaml
image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}"
Secret Ou ConfigMap
Vous devrez déterminer quels sont les identifiants ou données de configuration qu'il convient de stocker en tant que secrets et celles qui peuvent se trouver dans une ConfigMap.
Les secrets sont destinés aux informations sensibles telles que les mots de passe que K8s stockera dans un format crypté.
Une ConfigMap est un fichier qui contient des informations de configuration qui peuvent être partagées par les applications. Les informations contenues dans une ConfigMap ne sont pas chiffrées, elle ne doit donc pas contenir d’informations sensibles.
Secret | ConfigMap |
Le fait de placer ces informations dans un secret est plus sûr et plus souple que de les placer textuellement dans une définition de pod ou dans une image docker ; | Une ConfigMap vous permet de découpler les artefacts de configuration du contenu de l'image afin de conserver les applications conteneurisées portables |
Utilisation pour les données confidentielles | Utilisation pour les données non confidentielles |
Exemples d'utilisation : Clés API, mot de passe, jetons et clés ssh | Exemples d'utilisation : Rotateurs de journaux, Configuration sans données confidentielles |
Dans cet exemple, nous allons autoriser Helm à extraire des images docker à partir de registres docker privés à l’aide de secrets d’extraction d’images.
Cette procédure s'appuie sur le fait que le cluster Kubernetes dispose d'un secret qui spécifie les informations d'identification du gestionnaire de dépôts. Ce secret peut être créé par une ligne de commande kubectl telle que :
$ kubectl create secret docker-registry regsecret --docker-server=$DOCKER_REGISTRY_RUL --docker-username=$USERNAME --docker-password=$PASSWORD --docker-email=$EMAIL
Dans le fichier values.yaml de votre chart Helm, vous pouvez ensuite passer le nom du secret à une valeur :
imagePullSecrets: regsecret
Vous pouvez ensuite utiliser le secret pour permettre à Helm d’accéder au registre docker via ces lignes dans deployment.yaml :
{{- if .Values.imagePullSecrets }}
imagePullSecrets:
- name: {{ .Values.imagePullSecrets }}
{{- end }}
Pour les secrets disponibles pour l’application, vous devez ajouter ces informations directement à values.yaml.
Par exemple, pour configurer notre application pour accéder à mongodb avec un utilisateur et une base de données pré-créés, ajoutez ces informations dans values.yaml
mongodb:
enabled: true
mongodbRootPassword:
mongodbUsername: admin
mongodbPassword:
mongodbDatabase: test
Notez qu’ici, nous ne codons pas en dur les identifiants par défaut dans notre chart Helm. Au lieu de cela, nous utilisons une logique pour générer aléatoirement le mot de passe quand il n'est pas fourni via –set flag ou values.yaml
Nous utiliserons un secret pour transmettre les identifiants mongodb à notre application, via ces lignes dans deployment.yaml.
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-mongodb
key: mongodb-password
Vous pouvez contrôler l’exécution des conteneurs kubelet via des Init Containers spécialisés ou des Container Lifecycle Hooks.
InitContainers | Conteneur Lifecycle Hooks |
Les InitContainers sont des conteneurs spécialisés qui s’exécutent avant les conteneurs d’application et peuvent contenir des utilitaires ou des scripts de configuration qui ne sont pas présents dans une image d’application. | Les conteneurs peuvent utiliser le framework Container lifecycle hook pour exécuter du code déclenché par des événements pendant leur cycle de vie de gestion. |
Un Pod peut avoir un ou plusieurs Init Containers, qui sont exécutés avant le démarrage des conteneurs d'applications.
|
Un Pod ne peut avoir qu’un seul hook PostStart ou PreStop |
Le hook PostStart s’exécute immédiatement après la création d’un conteneur. Toutefois, il n’y a aucune garantie que le hook s’exécutera avant l'ENTRYPOINT du conteneur. Aucun paramètre n'est passé au handler. par ex., déplacement de fichiers montés à l’aide de ConfigMap/Secrets vers un emplacement différent. |
|
Le hook PreStop est appelé immédiatement avant qu'un conteneur se termine. Il est bloquant, ce qui veut dire qu'il est synchrone, et doit donc se terminer avant que l'appel pour supprimer le conteneur soit envoyé.
ex., Arrêt progressif de l'application |
|
Vous pouvez utiliser des initContainers pour ajouter des attentes, afin de vérifier que les microservices dépendants sont fonctionnels avant de continuer. | Vous pouvez utiliser le hook PostStart pour mettre à jour le fichier dans le même pod, par exemple pour mettre à jour les fichiers de configuration avec l'IP Service |
Dans notre exemple, ajoutez ces spécifications initContainers au fichier deployments.yaml pour suspendre le démarrage de notre application jusqu’à ce que la base de données soit opérationnelle.
initContainers:
- name: wait-for-db
image: "{{ .Values.initContainerImage }}"
command:
- 'sh'
- '-c'
- >
until nc -z -w 2 {{ .Release.Name }}-mongodb 27017 && echo mongodb ok;
do sleep 2;
done
Ajout de sondes de disponibilité et d'activité (Readiness et Liveness)
Il est souvent utile d'ajouter une sonde de disponibilité et une sonde d'activité pour vérifier la santé de l'application. Si vous ne le faites pas, l'application peut échouer de telle sorte qu'elle semble fonctionner, mais ne répond pas aux appels ou aux requêtes.
Ces lignes dans le fichier deployment.yaml ajouteront ces sondes pour effectuer des vérifications périodiques :
livenessProbe:
httpGet:
path: '/health'
port: http
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 10
readinessProbe:
httpGet:
path: '/health'
port: http
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 10
Ajout de la prise en charge d'un RBAC
Ces procédures ajouteront la prise en charge d'un contrôle d’accès basé sur les rôles (RBAC) à notre chart, lorsqu’une application l’exige.
Étape 1 : Créez un Rôle en ajoutant le contenu suivant dans un fichier role.yaml :
Un rôle peut uniquement être utilisé pour accorder l’accès aux ressources dans un espace de noms unique.
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.fullname" . }}
rules:
{{ toYaml .Values.rbac.role.rules }}
{{- end }}
Étape 2 : Créez un RoleBinding en ajoutant le contenu suivant dans un fichier rolebinding.yaml :
Un ClusterRole peut être utilisé pour accorder les mêmes autorisations qu'un Rôle, mais étant à l'échelle du cluster, ils peuvent également être utilisés pour accorder l'accès à :
- ressources à l'échelle du cluster (comme des nœuds)
- points de terminaison hors ressources (comme "/healthz")
- ressources avec espace de noms (comme des pods) dans tous les espaces de noms
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ template "express-crud.serviceAccountName" . }}
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: {{ template "express-crud.fullname" . }}
{{- end }}
Étape 3 : Créez un ServiceAccount en ajoutant le contenu suivant dans un fichier serviceaccount.yaml :
Un compte de service fournit une identité pour les processus qui s'exécutent dans un Pod.
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.serviceAccountName" . }}
{{- end }}
Étape 4 : Utilisez le modèle d’assistance pour définir le nom du ServiceAccount.
Pour ce faire, ajoutez le contenu suivant dans le fichier _helpers.tpl
{{/*
Créez le nom du compte de service à utiliser
*/}}
{{- define "express-crud.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "express-crud.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}
Ajout d’un service
Il est maintenant temps de présenter notre application au monde à travers un service.
Un service permet à votre application de recevoir du trafic via une adresse IP. Les services peuvent être présentés de différentes manières en spécifiant un type :
ClusterIP | Le service est uniquement accessible par une adresse IP interne depuis le cluster. |
NodePort | Le service est accessible depuis l’extérieur du cluster via NodeIP et NodePort. |
LoadBalancer | Le service est accessible depuis l’extérieur du cluster via un équilibreur de charge externe. Peut Entrer dans l’application.. |
Pour ce faire, ajouter le contenu suivant à service.yaml :
apiVersion: v1
kind: Service
metadata:
name: {{ template "express-crud.fullname" . }}
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.externalPort }}
targetPort: http
protocol: TCP
name: http
selector:
app: {{ template "express-crud.name" . }}
release: {{ .Release.Name }}
Il convient de noter que dans ce qui précède, pour notre type de service nous faisons référence à un paramètre dans notre values.yaml:
service:
type: LoadBalancer
internalPort: 3000
externalPort: 80
Résumé Values.yaml
La définition d'un grand nombre de nos paramètres dans un fichier values.yaml est une bonne pratique pour aider à maintenir vos charts Helm.
Voici comment le fichier values.yaml de notre exemple s'affiche, montrant la variété des paramètres que nous définissons pour de nombreuses fonctionnalités décrites ci-dessus :
# Valeurs par défaut pour express-mongo-crud.
# Il s’agit d’un fichier au format YAML.
# Déclarez les variables à passer dans vos modèles.
## Contrôle d’accès basé sur un rôle
## Réf : https://kubernetes.io/docs/admin/authorization/rbac/
rbac:
create: true
role:
## Règles à créer. Selon la spécification de rôle
rules:
- apiGroups:
- ''
resources:
- services
- endpoints
- pods
verbs:
- get
- watch
- list
## Compte de service
## Réf : https://kubernetes.io/docs/admin/service-accounts-admin/
##
serviceAccount:
create: true
## Nom du ServiceAccount à utiliser.
## Si non défini et que la valeur de create est true, un nom est généré à l’aide du modèle fullname
name:
## Valeurs de configuration pour la dépendance mongodb
## réf. : https://github.com/kubernetes/charts/blob/master/stable/mongodb/README.md
##
mongodb:
enabled: true
image:
tag: 3.6.3
pullPolicy: IfNotPresent
persistence:
size: 50Gi
# resources:
# requests:
# memory: "12Gi"
# cpu: "200m"
# limits:
# memory: "12Gi"
# cpu: "2"
## Assurez-vous que la valeur de --wiredTigerCacheSizeGB n’est pas supérieure à la moitié de la limite de mémoire !
## C’est essentiel pour se protéger contre OOMKill par Kubernetes !
mongodbExtraFlags:
- "--wiredTigerCacheSizeGB=1"
mongodbRootPassword:
mongodbUsername: admin
mongodbPassword:
mongodbDatabase: test
# livenessProbe:
# initialDelaySeconds: 60
# periodSeconds: 10
# readinessProbe:
# initialDelaySeconds: 30
# periodSeconds: 30
ingress:
enabled: faux
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- chart-example.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
initContainerImage: "alpine:3.6"
imagePullSecrets:
replicaCount: 1
image:
repository: jainishshah17/express-mongo-crud
# tag: 1.0.1
pullPolicy: IfNotPresent
service:
type: LoadBalancer
internalPort: 3000
externalPort: 80
resources: {}
# Nous recommandons généralement de ne pas spécifier les ressources par défaut et de laisser cela comme un
# choix conscient de l’utilisateur. Cela augmente également les chances que les charts s’exécutent dans des environnements avec peu
# de ressources, par ex. Minikube. Si vous souhaitez spécifier des ressources, décommentez les
# lignes suivantes, modifiez-les si nécessaire, et supprimez les accolades après 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
Tests et installations du chart Helm
Il est essentiel de tester notre chart Helm, ce que nous ferons avec la commande helm lint.
$ helm lint ./ ## Output ==> Linting ./ Lint OK 1 chart testé, pas d’échecs
Utilisez la commande helm install pour déployer notre application à l'aide d'un chart helm sur Kubernetes.
$ helm install --name test1 ./ ## Output NAME: test1 LAST DEPLOYED: Sat Sep 15 09:36:23 2018 NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1beta1/Deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE test1-mongodb 1 1 1 0 0s ==> v1beta2/Deployment test1-express-crud 1 1 1 0 0s ==> v1/Secret NAME TYPE DATA AGE test1-mongodb Opaque 2 0s ==> v1/PersistentVolumeClaim NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE test1-mongodb Pending standard 0s ==> v1/ServiceAccount NAME SECRETS AGE test1-express-crud 1 0s ==> v1/Service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE test1-mongodb ClusterIP 10.19.248.205 27017/TCP 0s test1-express-crud LoadBalancer 10.19.254.169 80:31994/TCP 0s ==> v1/Role NAME AGE test1-express-crud 0s ==> v1/RoleBinding NAME AGE test1-express-crud 0s ==> v1/Pod(related) NAME READY STATUS RESTARTS AGE test1-mongodb-67b6697449-tppk5 0/1 Pending 0 0s test1-express-crud-dfdbd55dc-rdk2c 0/1 Init:0/1 0 0s NOTES: 1. Get the application URL by running these commands: NOTE: It may take a few minutes for the LoadBalancer IP to be available. You can watch the status of by running 'kubectl get svc -w test1-express-crud' export SERVICE_IP=$(kubectl get svc --namespace default test1-express-crud -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo https://$SERVICE_IP:80
L’exécution de la commande helm install ci-dessus produira une IP_Externe pour l’équilibreur de charge. Vous pouvez utiliser cette adresse IP pour exécuter l’application.
Voici comment notre application apparaît lors de l’exécution :
Conclusion
Comme vous pouvez le voir dans cet exemple, Helm est un système extrêmement polyvalent qui vous offre une grande flexibilité dans la manière de structurer et de développer un chart. En procédant conformément aux conventions de la communauté Helm vous pourrez soumettre plus facilement vos charts Helm pour une utilisation publique, et vous pourrez les gérer beaucoup plus facilement lors de la mise à jour de votre application.
Les charts Helm achevés pour cet exemple de projet se trouvent dans le dépôt express-crud sur GitHub, et vous pouvez examiner ces fichiers fonctionnels pour vous aider à mieux comprendre leur structure.
Pour explorer d’autres exemples, vous pouvez consulter mon dépôt d'exemples de charts Helm pour le développement de produits sur Kubernetes.