Utiliser Kubernetes dans le Cloud AWS : déployer avec Helm

Dans la série de mes articles Kubernetes, je vais introduire aujourd’hui un composant qui fait corps avec Kubernetes, il s’agit d’Helm.
Nous avons utilisé Kubernetes pour surcoucher Docker et lui donner des possibilités de gestion de la haute disponibilité, de l’utilisation des ressources, du réseau, des déploiements, des liens vers l’extérieur… Il ne s’agit pas d’un sujet propre à Kubernetes déployé dans le Cloud AWS avec le service EKS, mais le sujet vaut la peine qu’on en parle.

Nous allons utiliser Helm pour surcoucher Kubernetes dans la gestion des déploiements.
Helm s’intéresse à la partie déploiements de Kubernetes pour notamment permettre de faciliter leur factorisation et la gestion des versions.

Prenons comme exemple le déploiement de notre application micro-service marron-vert dont il est question dans un précédent article.
Je suis confronté aux situations suivantes lorsque je tente d’aller plus loin et d’industrialiser les déploiements d’applications : je ne peux pas déployer le même environnement tel quel dans un même namespace (en l’occurrence « blog ») parce qu’il n’est pas possible d’avoir des objects k8s (service, pod, deployment, configmap) qui portent le même nom. Et Kubernetes ne m’offre pas de mécanisme de paramétrage qui permet de modifier des paramètres dans un fichier yaml afin de le réadapter.

C’est là que rentre en piste Helm.
En plus de pouvoir gérer des paramètres dans les fichiers yaml de Kubernetes afin de les adapter à une situation donnée, il propose entre autres des mécanismes de gestion de versions déployées et de packaging d’applications pour les transformer en librairies. Et surtout, Helm permet de regrouper des fichiers .yaml sous la notion de release pour traiter un ensemble d’objets k8s comme faisant corps ensemble, déployés et supprimés en une seule action.

Parmi toutes les possibilités, nous allons nous intéresser à ce que peut nous apporter Helm à travers une problématique qui est la suivante.
Imaginons que nous avons plusieurs clients qui veulent disposer d’une même application (marron-vert) que nous allons déployer en plusieurs exemplaires (quelques milliers ?).
Nous avons la possibilité de déployer chacun de ces clients dans un namespace dédié afin de ne pas modifier les noms des objets (service, deployment, configmap …). Et si je veux adapter l’un ou l’autre des paramètres, je suis obligé de modifier les fichiers yaml ou de gérer de très nombreux fichiers de configuration si j’utilise le composant configmap.

Le but de la manip qui va suivre va être une proposition d’industrialisation de notre application microservice marron-vert afin de la déployer de nombreuses fois.

 

Principe d’Helm

Helm utilise deux entités : les charts et les releases. Le fichier de paramètre values.conf est l’élément qui permettra d’adapter les fichiers .yaml de définitions des objets Kubernetes.
Ce fichier est par défaut dans le répertoire de la chart utilisée, soit passé en paramètre par la commande :

# helm install --name mv1 -f myvalues.yaml marronvert-helm

 

1) Les charts

Les charts sont les templates de déploiement (contenu dans un répertoire racine).
Ils sont créés avec la commande :

# helm create marronvert-helm

Le répertoire créé contient une structure de répertoires et de fichiers. Les plus importants sont le fichier « values.yaml » qui contient les paramètres utilisées pour faire varier les versions de releases, et le répertoire « templates » qui va contenir les fichiers « .yaml » de configuration.

Le mieux est de supprimer complétement le contenu du répertoire « templates », qui contient par défaut des fichiers de démo sans importance. Il contiendra par la suite nos fichiers de configuration .yaml propres à notre application à déployer.

Helm va nous permettre de configurer les fichiers de définition d’objets .yaml de Kubernetes par utilisation de paramètres {{ xxx }} contenus dans le fichier values.conf, comme dans l’extrait d’un fichier de paramétrage de deploiement ci-dessous :

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: dep-vert-{{ .Release.Name }}
namespace: {{ .Values.nameSpace }}
spec:
replicas: {{ .Values.apache1.container.replicaCount }}
selector:
matchLabels:
app: app-marron-{{ .Release.Name }}
template:

 

2)Les releases

Les releases sont les instanciations des charts, à partir du fichier values.yaml qui donnera sa pleine valeur à l’utilisation d’Helm en créant des versions différentes de releases à partir d’une même chart.

Ci-dessous le listage des releases déployés sur le cluster Kubernetes :

# helm ls
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
mv1 1 Tue Jul 30 22:17:18 2019 DEPLOYED marronvert-0.1.0 1.0 default
mv2 1 Tue Jul 30 22:17:15 2019 DEPLOYED marronvert-0.1.0 1.0 default
mv4 1 Wed Jul 31 11:44:12 2019 DEPLOYED marronvert-0.1.0 1.0 default

 

Installation d’Helm

1) Télécharger l’outil sur le site helm.sh, la dernière version actuelle est celle-ci :

# wget https://get.helm.sh/helm-v2.14.3-linux-amd64.tar.gz

2) Dézipper l’outil :

# tar -zxvf helm-v2.14.3-linux-amd64.tar.gz

3) Copier l’outil dans le répertoire /usr/local/bin/helm :

# cp linux-amd64/helm /usr/local/bin/helm

4) La commande suivante installe un pod « tiller-deploy » dans le namespace kube-system :

# helm init --history-max 200

5) Donner les droits à Tiller (partie serveur de Helm, qui se trouve dans un pod). Les droits admin sont donnés, nous sommes dans un cluster de test :

# kubectl create serviceaccount --namespace kube-system tiller
# kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
#  kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

A partir de maintenant nous pouvons utiliser Helm.

 

Installation et déploiement de notre application microservice marron vert

1)Récupérer les fichiers de yaml de déploiement de l’application avec la commande suivante. Dans ce cas il n’y a pas besoin d’utiliser la commande « helm create », le répertoire de la chart est créé par la commande :

# git clone https://github.com/jean-pierre-carret/marronvert-helm

2) Créer, s’il n’existe pas, un namespace « blog » :

# kubectl create namespace blog

3) Lancement d’un release helm sur Kubernetes :

# helm install --name mv1 marronvert-helm


NAME: mv1
LAST DEPLOYED: Wed Jul 31 11:44:12 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
cm-mv1 1 0s

==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
dep-marron-mv1-84b6674cc5-9r4cs 0/1 ContainerCreating 0 0s
dep-marron-mv1-84b6674cc5-d5z9s 0/1 ContainerCreating 0 0s
dep-marron-mv1-84b6674cc5-sdt9f 0/1 ContainerCreating 0 0s
dep-nginx-mv1-6b86dbf868-qnd64 0/1 ContainerCreating 0 0s
dep-nginx-mv1-6b86dbf868-tdtz4 0/1 ContainerCreating 0 0s
dep-nginx-mv1-6b86dbf868-wnfzw 0/1 ContainerCreating 0 0s
dep-vert-mv1-7d95c8b459-h4f2t 0/1 ContainerCreating 0 0s
dep-vert-mv1-7d95c8b459-mm254 0/1 ContainerCreating 0 0s
dep-vert-mv1-7d95c8b459-vd9wf 0/1 ContainerCreating 0 0s

==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
app-marron-mv1 ClusterIP None <none> 80/TCP 0s
app-marronvert-mv1 LoadBalancer 172.20.156.40 <pending> 80:32448/TCP 0s
app-vert-mv1 ClusterIP None <none> 80/TCP 0s

==> v1beta1/Deployment
NAME READY UP-TO-DATE AVAILABLE AGE
dep-marron-mv1 0/3 3 0 0s
dep-nginx-mv1 0/3 3 0 0s
dep-vert-mv1 0/3 3 0 0s

4) La commande ci-dessous permet de connaitre les releases s’exécutant sur Kubernetes.
La commande « helm install –name xxx marronvert-helm » a lancé plusieurs des releases à partir de la même chart :

# helm ls
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
mv1 1 Tue Jul 30 22:17:18 2019 DEPLOYED marronvert-0.1.0 1.0 default
mv2 1 Tue Jul 30 22:17:15 2019 DEPLOYED marronvert-0.1.0 1.0 default
mv4 1 Wed Jul 31 11:44:12 2019 DEPLOYED marronvert-0.1.0 1.0 default

5) Du point de vue Kubernetes, nous pouvons voir que les déploiements portent chacun un nom différent préfixé par dep-xxx- :

# kubectl -n blog get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
dep-marron-mv1 3/3 3 3 15h
dep-marron-mv2 3/3 3 3 15h
dep-marron-mv4 3/3 3 3 133m
dep-nginx-mv1 3/3 3 3 15h
dep-nginx-mv2 3/3 3 3 15h
dep-nginx-mv4 3/3 3 3 133m
dep-vert-mv1 3/3 3 3 15h
dep-vert-mv2 3/3 3 3 15h
dep-vert-mv4 3/3 3 3 133m

Pour tester la ou les applications, reportez-vous à l’article de blog « Utiliser Kubernetes dans le Cloud AWS : Partie 3 – Application micro-services » publié précédemment.

Vous pouvez aussi utiliser la commande suivante pour connaitre les load balancers AWS rattachés à chaque applications :

# kubectl -n blog get services|grep LoadBalancer
app-marronvert-mv1 LoadBalancer 172.20.141.218 a07aa5b16b30711e9b6610ada0f83cb7-2099517485.us-west-2.elb.amazonaws.com 80:32091/TCP 16h
app-marronvert-mv2 LoadBalancer 172.20.47.168 a058f3e48b30711e9b6610ada0f83cb7-942146908.us-west-2.elb.amazonaws.com 80:31186/TCP 16h
app-marronvert-mv4 LoadBalancer 172.20.156.40 ac0f2e647b37711e9b6610ada0f83cb7-869861732.us-west-2.elb.amazonaws.com 80:32448/TCP 3h1m
marron-vert LoadBalancer 172.20.187.89 ad6f460d9b38d11e9b6610ada0f83cb7-1068346903.us-west-2.elb.amazonaws.com 80:31317/TCP 23m

 

Particularité de notre chart marron-vert-helm

J’attire l’attention sur un point particulier qui est celui, dans notre cas, du fichier de configuration nginx.conf.
Le problème résolu avec Helm et l’utilisation d’un objet configmap de Kubernetes est de pouvoir adapter à chaque release le contenu du fichier nginx.conf. En effet, les lignes « proxy_pass » de ce fichier permettent de rediriger vers un des serveurs Apache la demande de résolution d’URL. L’inconvénient est que le nom du service rattaché aux serveurs Apache, vers lequel doit être redirigé l’URL, change à chaque création d’un release.

Pour résoudre cela et générer un fichier nginx.conf adapté à chaque nouveau release, le fichier nginx.conf contient des paramètres {{ xxx }} qui font varier le nom de domaine de l’URL à chaque release.

location /marron {
# trois cas : url sans fichier, url sans fichier avec /, url avec fichier
rewrite /marron/(.*) /$1 break;
rewrite /marron[/]* /index.html break;
proxy_pass http://app-marron-{{ .Release.Name }}:80;
}

location /vert {
rewrite /vert/(.*) /$1 break;
rewrite /vert[/]* /index.html break;
proxy_pass http://app-vert-{{ .Release.Name }}:80;
}

Regardez le contenu du fichier templates/nginx-conf.yaml ci-dessous, et vous comprendrez que l’utilisation de la fonction « tpl » permet de transformer les paramètres du fichier nginx.conf :

apiVersion: v1
kind: ConfigMap
metadata:
name: cm-{{ .Release.Name }}
namespace: {{ .Values.nameSpace }}
data:
{{ tpl (.Files.Glob "files/nginx.conf").AsConfig . | indent 4 }}
#!!! l'indent de la ligne ci-dessus doit etre a la hauteur de "data"

afin de pouvoir utiliser un fichier nginx.conf différent lors du lancement du conteneur défini dans le pod nginx.
Ci-dessous le fichier templates/nginx-deploy.yaml qui utilise le configmap :

kind: Deployment
metadata:
name: dep-nginx-{{ .Release.Name }}
namespace: {{ .Values.nameSpace }}
spec:
replicas: {{ .Values.nginx.container.replicaCount }}
selector:
matchLabels:
app: app-marronvert-{{ .Release.Name }}
template:
metadata:
labels:
app: app-marronvert-{{ .Release.Name }}
spec:
containers:
- name: {{ .Values.nginx.container.name }}
image: {{ .Values.nginx.container.image }}:{{ .Values.nginx.container.tag }}
ports:
- containerPort: {{ .Values.nginx.container.port }}
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: nginx-config
configMap:
name: cm-{{ .Release.Name }}

 

Voilà un sujet très intéressant qui devrait vous occuper un certain temps, car il en faut pour Kubernetes, tellement le sujet est vaste.
Et à bientôt pour un prochain article sur le sujet EKS.

 

Et si vous souhaitez vous former sur Amazon Web Services, découvrez notre offre de formations AWS.