Aller au contenu

Mon second déploiement avec Kompose

Nous devons recréer un cluster k3d d'une maniere que le port interne 80 (ou le contrôleur ingress traefik écoute) soit exposé au système (en port 8180). Nous allons aussi configurer le cluster avec deux noeuds "worker" virtuels.

k3d cluster delete mycluster 
k3d cluster create mycluster-lb -p "8180:80@loadbalancer" --agents 2

Puis changer de contexte et de namespace comme au début du tutoriel :

kubectx k3d-mycluster-lb &&\
k create ns tuto &&\
kubens tuto

De Docker à k8s avec Kompose

Maintenant que nous avons vu toutes les étapes qui mènent au déploiement complet d'une application Jupyter, nous reprenons les 3 fichiers générés avec Kompose dans la partie du tutoriel sur Docker qui sont un déploiement, un service de type ClusterIP et un PersistentVolumeClaim. Nous les plaçons dans un répertoire à part afin de les appliquer ensemble :

jupyter-service.yaml
jupyter-deployment.yaml
jupyter-claim0-persistentvolumeclaim.yaml

Pour appliquer tous au cluster k8s :

k apply -f . && k get all

Et vous devez obtenir :

NAME                           READY   STATUS         RESTARTS   AGE
pod/jupyter-7b8bbbf8b7-brpjq   0/1     ErrImagePull   0          14s

NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/jupyter   ClusterIP   10.43.169.51   <none>        8888/TCP   14s

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/jupyter   0/1     1            0           14s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/jupyter-7b8bbbf8b7   1         1         0       14s

Il y a une erreur de type ErrImagePull dans le pod cela est due au nom de l'image qu'il faut changer dans le yaml (nous utilisons l'image de base de Jupyter).

...
    spec:
      containers:
        - image: jupyter/base-notebook # <-- modifié
          name: jupyter
          env:
            - name: JUPYTER_PORT
              value: "8888"

...

!!! info Variable d'environnement Il faut ajouter la variable d'environnement JUPYTER_PORT pour que le conteneur fonctionne. L'initialisation standard y mettre mauvais valeur dans un cluster multi-node.

Ensuite réappliquer les manifests.

k apply -f .

Comme lors du premier déploiement, il y a des ReplicaSet associé au déploiement. Il est possible de voir l'historique des updates de pod via :

k rollout history deployment jupyter
deployment.apps/jupyter
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

Volumes

Nous avons utilisé des nouveaux objets que sont les volumes/persistentVolumeClaim pour garder les données entre redémarrages de pod. Il est possible de les afficher avec :

k get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
pvc-8bb4749f-5b6c-45ab-b1a5-ef850ea39353   100Mi      RWO            Delete           Bound    tuto/jupyter-claim0   local-path     <unset>                          10s

Ce volume est créé à partir d'un PersistentVolumeClaim qui déclare des caractéristiques d'un Volume (il est possible de créer le volume manuellement, mais c'est peu pratique et n'est pas souvent utilisé)

k get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
jupyter-claim0   Bound    pvc-901b9698-5ee0-47e6-b465-4bf5dabb448a   100Mi      RWO            local-path     <unset>                 16m
cat jupyter-claim0-persistentvolumeclaim.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  labels:
    io.kompose.service: jupyter-claim0
  name: jupyter-claim0
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi

L'option clé de PVC est StorageClass (on utilise StorageClass par défaut si manqué)

k get sc
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  177m

Ici le seul SC fournie avec k3d est de type local-path. Les données de ces volumes sont stockés dans un dossier local de noeud. On peut avoir les SCs différents en fonction d'installation particulaire de k8s.

Autre option important est AccessMode. Avec ReadWriteOnce (RWO) le volume peut être attache à un seul noeud en mode read-write, ReadOnlyMany (ROX) permet d'attacher le volume aux quelques noeuds en mode readonly et ReadWriteMany (RWX) est utilisé si on a besoin d'écrire sur le volume à partir des différents noeuds en façon concourante.

Exposer l'application au port web

Avant, nous avons utilisé soit le service du type NodePort soit port-forwarding pour accéder l'application. Pour exposer l'application web (protocole HTTP/HTTPS) nous allons créer l'autre ressource k8s : Ingress.

Créer le manifest :

myjupyter-lb.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jupyter 
  annotations:
    ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: jupyter 
            port:
              number: 8888

Et l'appliquer :

k apply -f myjupyter-lb.yaml

Nous pouvons accéder à la définition d'ingress :

k describe ingress jupyter
Name:             jupyter
Labels:           <none>
Namespace:        tuto
Address:          172.19.0.2,172.19.0.3,172.19.0.4
Ingress Class:    traefik
Default backend:  <default>
Rules:
  Host        Path  Backends
  ----        ----  --------
  *           
              /   jupyter:8888 (10.42.2.6:8888)
Annotations:  ingress.kubernetes.io/ssl-redirect: false
Events:       <none>

L'application Jupyter est maintenant accessible :

open http://localhost:8180

Configuration d'application

apiVersion: v1
kind: ConfigMap
metadata:
  name: initscript
data:
  initpwd.py: |-
    #!/usr/bin/env python

    from jupyter_server.auth import passwd
    import os
    import json

    if os.getenv('JUPYTER_PWD') is not None:
        pwd = passwd(os.getenv('JUPYTER_PWD'))


        cfg_dir = os.path.join(os.getenv('HOME'), '.jupyter')
        os.makedirs(cfg_dir, exist_ok=True)
        cfg_json = os.path.join(cfg_dir, 'jupyter_server_config.json')

        with open(cfg_json, 'w') as fd:
            json.dump({"IdentityProvider": {"hashed_password": pwd}}, fd)
apiVersion: v1
kind: Secret
metadata:
  name: jupyter-pwd
type: Opaque
data:
  password: dmVyeV9zZWN1cmVfanVweXRlcl9wd2Q=
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose --file compose.yaml convert
    kompose.version: 1.35.0 (9532ceef3)
  labels:
    io.kompose.service: jupyter
  name: jupyter
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: jupyter
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose --file compose.yaml convert
        kompose.version: 1.35.0 (9532ceef3)
      labels:
        io.kompose.service: jupyter
    spec:
      containers:
        - image: jupyter/base-notebook 
          name: jupyter
          ports:
            - containerPort: 8888
              protocol: TCP
          env:
            - name: JUPYTER_PORT
              value: "8888"
          volumeMounts:
            - mountPath: /home/jovyan/work/local
              name: jupyter-claim0
            - name: jupyter-confdir
              mountPath: /home/jovyan/.jupyter
      initContainers:
        - image: jupyter/base-notebook
          name: pwd-init
          command: 
            - python 
            - /initpwd.py
          volumeMounts:
            - name: initscript
              mountPath: /initpwd.py
              subPath: initpwd.py
            - name: jupyter-confdir
              mountPath: /home/jovyan/.jupyter
          env:
            - name: JUPYTER_PWD
              valueFrom:
                secretKeyRef:
                  name: jupyter-pwd
                  key: password
      restartPolicy: Always
      volumes:
        - name: jupyter-claim0
          persistentVolumeClaim:
            claimName: jupyter-claim0
        - name: initscript
          configMap:
            name: initscript
        - name: jupyter-confdir
          emptyDir: {}
Authors: Cécile Cavet (26.9%), SAVCHENKO Denys (73.1%)