LABS Kubernetes


1: LABS Kubernetes


2: Création d’un cluster Kubernetes

Installation avec Kubeadm

verification de la version


kubeadm version

kubeadm version: &version.Info{Major:"1", Minor:"27", GitVersion:"v1.27.9", GitCommit:"d15213f69952c79b317e635abff6ff4ec81475f8", GitTreeState:"clean", BuildDate:"2023-12-19T13:39:19Z", GoVersion:"go1.20.12", Compiler:"gc", Platform:"linux/amd64"}

Machines : master, worker-0, worker-1

Mettre à jour le fichier /etc/hosts en renseignant les IP des trois VM. Prenez soin de remplacer les adresses IP par celles de vos VM.

exemple :

# 2: /etc/hosts file
10.10.4.80 master
10.10.4.81 worker-0
10.10.4.82 worker-1
  1. Nous allons utiliser Kubeadm pour initialiser un cluster Kubernetes avec le noeud master.

1.1 (Préparation de l’environnement) Installation de la completion pour kubectl


echo 'source <(kubectl completion bash)' >>~/.bashrc
echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -o default -F __start_kubectl k' >>~/.bashrc

source ~/.bashrc

# 2: test 
k version

1.2 Installation du cluster kubernetes

Machine : master

sudo kubeadm init 
...
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:
...

Un token sera généré à l’issue du processus d’initialisation. Il est important de le sauvegarder car il servira à connecter les worker nodes au cluster

Notez la commande de join :

exemple :

 kubeadm join 10.10.3.243:6443 --token m03nzv.vtfeaij5yu876u7z \
    --discovery-token-ca-cert-hash sha256:2da9df40f55f901d221d30cf0574264bcd4c62b7c38200498e99e2797a55753f
  1. Nous avons donc installé un premier noeud master Kubernetes. Nous allons maintenant configurer la CLI kubectl pour pouvoir l’utiliser depuis le master:
mkdir -p $HOME/.kube

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

sudo chown $(id -u):$(id -g) $HOME/.kube/config
  1. Nous allons maintenant installer un add-on réseaux pour nos pods sur le master. Il existe plusieurs plugins répondant à ce besoin : Calico, Canal, Weave, Flannel etc. Pour cette exercice, nous allons installer le plugin weave, de la façon suivante :
kubectl apply -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml

Vérification :

ubuntu@master:~$ k get nodes
NAME     STATUS     ROLES           AGE     VERSION
master   NotReady   control-plane   3m34s   v1.27.9
ubuntu@master:~$ k get nodes
NAME     STATUS   ROLES           AGE     VERSION
master   Ready    control-plane   4m18s   v1.27.9

Note : Si on souhaite utiliser les network policies (que nous explorerons plus tard), il faut utiliser un plugin supportant cette fonctionnalité. (Il faut éviter flannel notamment)

Machines : worker-0, worker-1 4. Nous allons maintenant ajouter les deux noeuds worker à notre cluster. Pour ce faire, nous allons utiliser la commande suivante sur les noeuds worker worker-0 et worker-1:

kubeadm join INTERNAL_MASTER_IP:6443 --token TOKEN --discovery-token-ca-cert-hash DISC_TOKEN

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
  1. Nous pouvons lister les noeuds de la façon suivante afin de nous assurer que les noeuds worker ont bien rejoint le cluster (les noeuds worker sont NotReady pour quelques secondes) :
kubectl get nodes
NAME     STATUS   ROLES    AGE     VERSION
master   Ready    master   25m     v1.19.3
worker-0   Ready    <none>   2m24s   v1.19.3
worker-1   Ready    <none>   1m24s   v1.19.3
  1. Nous allons déployer un pod redis pour tester l’installation de notre cluster :
kubectl run --image redis test-pod
pod/test-pod created
  1. Une petite liste des pods en cours d’exécution pour s’assurer que tout fonctionne bien :
kubectl get pods
NAME       READY   STATUS    RESTARTS   AGE
test-pod   1/1     Running   0          34s
  1. Supprimons maintenant notre pod :
kubectl delete pod test-pod

3: Les Bases


Machine : master


Namespace

Aide à résoudre la complexité de l’organisation des objets au sein d’un cluster. Les namespaces permettent de regrouper des objets afin que vous puissiez les filtrer et les contrôler comme une unité. Qu’il s’agisse d’appliquer des politiques de contrôle d’accès personnalisées ou de séparer tous les composants d’un environnement de test, les namespaces sont un concept puissant et flexible pour gérer les objets en tant que groupe.

En ligne de commande

mkdir basics
cd basics
kubectl create namespace basics

Autre méthode

  1. Créons un manifeste de namespace avec le contenu yaml suivant :
apiVersion: v1
kind: Namespace
metadata:
  name: lab
  1. Appliquons le fichier pour créer le namespace :
kubectl apply -f lab-ns.yaml
  1. Vérifions que le namespace lab a bien été créé :
kubectl get namespace

Lancement du premier pod

Pod

Unité d’exécution de base d’une application Kubernetes. Il constitue la plus petite et la plus simple unité dans le modèle d’objets de Kubernetes pouvant être créer ou déployer. Un Pod représente des process en cours d’exécution dans un cluster.

Pod Unité d’exécution de base d’une application Kubernetes. Il constitue la plus petite et la plus simple unité dans le modèle d’objets de Kubernetes pouvant être créer ou déployer. Un Pod représente des process en cours d’exécution dans un cluster.

  1. Créons un manifeste d’un pod avec le contenu yaml suivant :
apiVersion: v1
kind: Pod
metadata:
  name: lab-pod
  namespace: lab  
  labels:
    app: web
spec:
  containers:
  - image: nginx
    name: nginx
  1. Appliquons le fichier pour créer le pod :
kubectl apply -f lab-pod.yaml
  1. Vérifions que le pod lab-pod a bien été créé :
kubectl -n lab get pods

Deployment

Un déploiement Kubernetes est un objet Kubernetes qui fournit des mises à jour déclaratives aux applications. Un déploiement permet de décrire le cycle de vie d’une application, comme les images à utiliser, le nombre de pods qu’il devrait y avoir et la manière dont ils doivent être mis à jour.

  1. Créons un déploiement avec le contenu yaml suivant :
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lab-deployment
  namespace: lab
  labels:
    app: httpd
spec:
  replicas: 2
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      containers:
      - name: httpd
        image: httpd:2.4.43
        ports:
        - containerPort: 80
  1. Appliquons le fichier pour créer le déploiement :
kubectl apply -f lab-deployment.yaml
  1. Vérifions que le déploiement lab-deployment a bien été créé :
kubectl -n lab get deployment

Autres manières de déployer les applications :

A part le type Deployment, il existe aussi les Statefulsets et les Daemonsets.

Les objets StatefulSet sont conçus pour déployer des applications avec état et des applications en cluster qui enregistrent des données sur un espace de stockage persistant .

Les DaemonSets sont utilisés pour garantir que tous vos nœuds exécutent une copie d’un pod, ce qui vous permet d’exécuter l’application sur chaque nœud. Lorsque vous ajoutez un nouveau nœud au cluster, un pod est créé automatiquement sur le nœud.

Service

Dans Kubernetes, un service est une abstraction qui définit un ensemble logique de pods et une politique permettant d’y accéder (parfois ce modèle est appelé un micro-service). L’ensemble des pods ciblés par un service est généralement déterminé par un “Selector”.

  1. Créons un manifeste d’un pod avec le contenu yaml suivant :
apiVersion: v1
kind: Service
metadata:
  name: app-service
  namespace: lab
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  1. Appliquons le fichier pour créer le service :
kubectl apply -f lab-svc.yaml
  1. Vérifions que le service app-service a bien été créé :
kubectl -n lab get svc

4: Stockage


Machine : master
mkdir storage
cd storage
kubectl create namespace storage

PersistentVolume et PersistentVolumeClaim

  1. Commençons par définir un persistantvolume :
touch postgres-pv.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-pv
  namespace: storage
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"
  1. Nous allons donc créer ce pv :
kubectl apply -f postgres-pv.yaml

persistentvolume/postgres-pv created

  1. Nous pouvons récupérer des informations sur ce pv de la façon suivante :
kubectl describe pv -n storage postgres-pv
Name:            postgres-pv
Labels:          type=local
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    manual
Status:          Available
Claim:           
Reclaim Policy:  Retain
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        10Gi
Node Affinity:   <none>
Message:         
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /mnt/data
    HostPathType:  
Events:            <none>
  1. Nous allons maintenant définir un persistantvolumeclaim :
touch postgres-pvc.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-pvc
  namespace: storage
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
  1. Nous allons créer ce pvc :
kubectl apply -f postgres-pvc.yaml

persistentvolumeclaim/postgres-pvc created 6. Nous pouvons maintenant inspecter ce pvc :

kubectl get pvc -n storage postgres-pvc
NAME           STATUS   VOLUME        CAPACITY   ACCESS MODES   STORAGECLASS   AGE
postgres-pvc   Bound    postgres-pv   10Gi       RWO            manual         14s

Notre pvc est maintenant bound a notre pv.

  1. Nous allons maintenant définir un pod utilisant ce pvc :
touch postgres-with-pvc-pod.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: postgres-with-pvc-pod
  namespace: storage
spec:
  volumes:
    - name: postgres-volume
      persistentVolumeClaim:
        claimName: postgres-pvc
  containers:
    - name: postgres-with-pvc
      image: postgres
      env:
      - name: POSTGRES_PASSWORD
        value: password
      volumeMounts:
        - mountPath: "/var/lib/postgresql/data"
          name: postgres-volume
          subPath: pgdata
  1. Créons donc ce pod :
kubectl apply -f postgres-with-pvc-pod.yaml

pod/postgres-with-pvc-pod created

  1. Inspectons ce pod, nous devrions voir qu’il utilise bien notre pvc :
kubectl describe pods -n storage postgres-with-pvc-pod
...
Volumes:
  postgres-volume:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  postgres-pvc
    ReadOnly:   false
...

Longhorn (rancher)


  1. Nous allons commencer par installer longhorn :
helm repo add longhorn https://charts.longhorn.io
helm repo update
helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace
NAME: longhorn
LAST DEPLOYED: Fri Jul  1 11:45:19 2022
NAMESPACE: longhorn-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Longhorn is now installed on the cluster!

Please wait a few minutes for other Longhorn components such as CSI deployments, Engine Images, and Instance Managers to be initialized.

Visit our documentation at https://longhorn.io/docs/

...
  1. Par défaut, lonhorn une classe de stockage (storageclasses) , que nous pouvons voir de la façon suivante :
kubectl describe storageclass longhorn
Name:            longhorn
IsDefaultClass:  Yes
Annotations:     longhorn.io/last-applied-configmap=kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhorn
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: "Delete"
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: "3"
  staleReplicaTimeout: "30"
  fromBackup: ""
  fsType: "ext4"
  dataLocality: "disabled"
,storageclass.kubernetes.io/is-default-class=true
Provisioner:           driver.longhorn.io
Parameters:            dataLocality=disabled,fromBackup=,fsType=ext4,numberOfReplicas=3,staleReplicaTimeout=30
AllowVolumeExpansion:  True
MountOptions:          <none>
ReclaimPolicy:         Delete
VolumeBindingMode:     Immediate
Events:                <none>
  1. Nous allons maintenant définir un pvc utilisant la storageclass longhorn :
touch postgres-longhorn-pvc.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-longhorn-pvc
  namespace: storage
spec:
  storageClassName: longhorn
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
  1. Créons donc ce pvc :
kubectl apply -f postgres-longhorn-pvc.yaml

persistentvolumeclaim/postgres-openebs-pvc created

  1. Que nous pouvons inspecter de la façon suivante :
kubectl get pvc -n storage postgres-longhorn-pvc
NAME                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS        AGE
postgres-longhorn-pvc   Bound    pvc-69b06a24-90e3-4ad9-8a25-5d7f4d216616   3Gi        RWO            longhorn       32s
  1. Nous pouvons également voir qu’un pv a été généré de façon automatique :
kubectl get pv -n storage
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                          STORAGECLASS          REASON   AGE
pvc-69b06a24-90e3-4ad9-8a25-5d7f4d216616   3Gi        RWO            Delete           Bound    storage/postgres-longhorn-pvc   longhorn                73s
  1. Utilisation de ce pvc
touch postgres-with-longhorn-pvc-pod.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: postgres-with-longhorn-pvc-pod
  namespace: storage
spec:
  volumes:
    - name: postgres-volume
      persistentVolumeClaim:
        claimName: postgres-longhorn-pvc
  containers:
    - name: postgres-with-pvc
      image: postgres
      env:
      - name: POSTGRES_PASSWORD
        value: password
      volumeMounts:
        - mountPath: "/var/lib/postgresql/data"
          name: postgres-volume
          subPath: pgdata
  1. Créons donc ce pod :
kubectl apply -f postgres-with-longhorn-pvc-pod.yaml

pod/postgres-with-longhorn-pvc-pod created

  1. Inspectons ce pod, nous devrions voir qu’il utilise bien notre pvc :
kubectl describe pods -n storage postgres-with-longhorn-pvc-pod

Clean Up


Nous pouvons supprimer les objets générés par cet exercice de la façon suivante :

kubectl delete -f postgres-longhorn-pvc.yaml -f postgres-pv.yaml -f postgres-pvc.yaml -f postgres-with-pvc-pod.yaml -f postgres-with-longhorn-pvc-pod.yaml

persistentvolumeclaim "postgres-openebs-pvc" deleted
persistentvolume "postgres-pv" deleted
persistentvolumeclaim "postgres-pvc" deleted
pod "postgres-with-pvc-pod" deleted
pod "postgres-with-longhorn-pvc-pod" deleted

5: ConfigMaps


Machine : master


All in One

  1. Commençons par définir un manifest :
touch cm.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-config
data:
    redis-config: |
      maxmemory 2mb
      maxmemory-policy allkeys-lru

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-env
data:
   redis_host: "redis-svc"
   redis_port: "6349"

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: env-config
data:
   log_level: "NOTICE"
---

apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      env:
        - name: REDIS_HOST
          valueFrom:
            configMapKeyRef:
              name: redis-env
              key: redis_host
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: env-config
              key: log_level
  restartPolicy: Never


---

apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod-v
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "cat /etc/config/redis-config" ]
      volumeMounts:
      - name: redis-conf-volume
        mountPath: /etc/config
  volumes:
    - name: redis-conf-volume
      configMap:
        # Provide the name of the ConfigMap containing the files you want
        # to add to the container
        name: redis-config
  restartPolicy: Never
  1. Appliquons ce manifest
kubectl apply -f cm.yaml
  1. Vérifions que les variables d’envronnement et fichier ont bien été affectés
kubectl logs dapi-test-pod
kubectl logs dapi-test-pod-v

Configmap à partir d’un fichier

  1. Créer un fichier valeurs.txt avec les valeurs suivantes :
cle1: valeur1
cle2: valeur2
cleN: valeurN
  1. Créer un fichier valeurs.json avec les valeurs suivantes :
{cle1: valeur1, cle2: valeur2, cleN: valeur2}.
  1. Créer les configmaps
kubectl create configmap cmjson --from-file=valeurs.json
kubectl create configmap cmtxt --from-file=valeurs.txt
  1. Vérifier le contenu des cm
kubectl get cm -o yaml cmjson
kubectl get cm -o yaml cmtxt
  1. Utilisation dans un pod
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh","-c","cat /etc/config/keys" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: cmjson
        items:
        - key: valeurs.json
          path: keys
  restartPolicy: Never

6: Secrets


Machine : master


mkdir secrets
cd secrets
kubectl create namespace secrets

Postgres’s Password in a secret (As environment variable)

  1. Commençons par créer un secret contenant notre mot de passe postgres dans une clé postgres_password :
kubectl create secret generic dev-db-secret -n secrets --from-literal postgres_password=password

secret/dev-db-secret created
  1. Nous pouvons inspecter notre secret de la façon suivante :
kubectl describe secret -n secrets dev-db-secret

Name:         dev-db-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
postgres_password:  8 bytes
  1. Définissons maintenant un pod postgres, utilisant le secret que nous avons créé ci dessus pour le mot de passe de la base de données :
touch pod-with-secret.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-secret
  namespace: secrets
spec:
  containers:
  - name: pod-with-secret
    image: postgres
    env:
      - name: POSTGRES_PASSWORD
        valueFrom:
          secretKeyRef:
            name: dev-db-secret
            key: postgres_password
  1. Exécutons ce pod :
kubectl apply -f pod-with-secret.yaml

pod/pod-with-secret created

  1. Nous pouvons voir que notre pod utilise bien notre secret via un describe :
kubectl describe pods -n secrets pod-with-secret
...
Ready:          True
Restart Count:  0
Environment:
  POSTGRES_PASSWORD:  <set to the key 'postgres_password' in secret 'dev-db-secret'>  Optional: false
Mounts:
  /var/run/secrets/kubernetes.io/serviceaccount from default-token-4xjhx (ro)
...
  1. Vérifions que le secret est bien utilisé dans notre pod :
kubectl exec -it -n secrets pod-with-secret -- printenv
...
HOSTNAME=pod-with-secret
TERM=xterm
POSTGRES_PASSWORD=password
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
...

Notre secret se trouve bien en tant que variable d’environnement dans notre conteneur.


Secret file as a mount

  1. Commençons par créer un secret à partir d’un fichier :
echo "Iamasecret" > secret.txt
  1. On va donc créer un secret à partir de ce fichier :
kubectl create secret generic -n secrets secret-file --from-file=secret.txt

secret/secret-file created

  1. Définissons un pod, qui va monter ce secret en tant que volume :
touch pod-with-volume-secret.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-volume-secret
  namespace: secrets
spec:
  containers:
  - name: pod-with-volume-secret
    image: redis
    volumeMounts:
    - name: secret-mount
      mountPath: "/tmp"
      readOnly: true
  volumes:
  - name: secret-mount
    secret:
      secretName: secret-file
  1. Création du pod :
kubectl apply -f pod-with-volume-secret.yaml

pod/pod-with-volume-secret created

  1. Nous pouvons faire un describe sur le pod pour voir qu’il utilise bien notre secret en tant que mount :
kubectl describe pods -n secrets pod-with-volume-secret
...
Environment:    <none>
Mounts:
  /tmp from secret-mount (ro)
  /var/run/secrets/kubernetes.io/serviceaccount from default-token-4xjhx (ro)
...
  1. Vérifions que le fichier a bien été monte sur le conteneur du pod :
kubectl exec -it -n secrets pod-with-volume-secret -- cat /tmp/secret.txt

Iamasecret


Kubeseal

  1. Commençons par installer Kubeseal :
curl -Lo kubeseal https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.16.0/kubeseal-linux-amd64
chmod +x kubeseal
sudo mv kubeseal /usr/local/bin/
  1. Vérifions son installation :
kubeseal --version

kubeseal version: v0.16.0
  1. Nous allons également installer l’opérateur via helm :
helm repo add stable https://charts.helm.sh/stable
helm repo update
helm install --namespace kube-system sealed-secrets stable/sealed-secrets

NAME: sealed-secrets
LAST DEPLOYED: Sun Nov  1 10:18:33 2020
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
You should now be able to create sealed secrets
  1. Nous allons maintenant créer un secret :
kubectl create secret generic -n secrets secret-example --dry-run --from-literal=secret=value -o yaml > secret-example.yaml

cat secret-example.yaml
apiVersion: v1
data:
  secret: dmFsdWU=
kind: Secret
metadata:
  creationTimestamp: null
  name: sealed-secret-example
  1. Et un SealedSecret à partir de ce secret :
kubeseal --controller-name=sealed-secrets --controller-namespace=kube-system --format yaml <secret-example.yaml > sealed-secret-example.yaml

cat sealed-secret-example.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: secret-example
  namespace: secrets
spec:
  encryptedData:
    secret: AgBy3DUDSGCwPLFOJ+jYp1wm1Wqf9PlCFLvIdUDPMdSr0tBIniBLNBpQbdZ+bqP6Tq7zBhDuJz4hNq5qchgfHXyKb6qxhSP30BuquSBHboO+19NHMEG6GOYT1TatHJwUVFlzGtqHcIRFwwEOZpJs9FRByYMf4jSbfu1Lb9u1E1Q49I3Ycw+LprqSZG4rZXtnBL+d6R1iO9OKsx6uQ3fklSYRyYuNWCrqGPYINcX9pcShvJHa8N30H6xZT8jrTpp+UPNXQTI3iaBHxHMTcc5jQCcduOp5Wgbm4G8OEr1Pd4fiNCb7QBAuiGLQa81RhdN887cifdv6mweDLnsRJk09fWGIyTXTezgCYnpsBQv0RFk/EEFiL7pm7w6zMHjp+ldy8NwonoJ8DL6mXFM2otdstGiDayoELrr47MEMp+Y4VVvbQai2YufUKdbF0/unBeB0BRMCMHYgqkCoKG5UPaekIVaYSPjUvT69WjY6DJnFoMz8uVtTqIaCpFAZ8Lm0G3cpfko3rwUGDefmVi4E8eLmcLn3t8KSdzkY5TLP+s58LFjFeDPz+OWvxnJ+1NmOig4OgzhItC0ngtulwhY2lXbuLgNhkjTXHTqRlCF4PXu/vcYHFhq4sBp+bTCvVsJYJTBpkNNCefT51KMTIg+xqOWC73/FqFwujJ4JAue4N99Fvh+7qbEYEw5sPPv6CmwuO0oVzNv52bjBRQ==
  template:
    metadata:
      creationTimestamp: null
      name: secret-example
      namespace: secrets
  1. Nous pouvons créer notre secret à partir de notre Sealed Secret :
kubectl apply -f sealed-secret-example.yaml

sealedsecret.bitnami.com/secret-example created

  1. Nous pouvons voir qu’un Sealed Secret a été créé :
kubectl get sealedsecrets -n secrets
NAME             AGE
secret-example   25s
  1. Ainsi qu’un Secret à partir de notre Sealed Secret :
kubectl get secrets -n secrets secret-example
NAME                  TYPE                                  DATA   AGE
secret-example        Opaque                                1      2m13s

Clean up

Supprimons les différents objets créés par ces exercices :

kubectl delete -f .
kubectl delete secret secret-file -n secrets
kubectl delete secret dev-db-secret -n secrets

7: Resources Handling

Machine : master

mkdir resources
cd resources
kubectl create namespace resources

Limits/Requests for a pod


  1. Nous allons commencer par créer un pod qui réclame des ressources et qui a une limite de ressources également :
touch test-resources.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: test-resources
  namespace: resources
spec:
  containers:
  - name: app
    image: redis
    resources:
      requests:
        memory: "128Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  1. Créons donc ce pod :
kubectl apply -f test-resources.yaml
pod/test-resources created
  1. Nous pouvons faire un describe sur notre pod pour voir que notre les requests/limits de ressources ont bien été prises en comptes :
kubectl describe pods -n resources test-resources
...
Host Port:      <none>
State:          Running
  Started:      Wed, 28 Oct 2020 13:18:59 +0000
Ready:          True
Restart Count:  0
Limits:
  cpu:     500m
  memory:  128Mi
Requests:
  cpu:        250m
  memory:     128Mi
Environment:  <none>
Mounts:
  /var/run/secrets/kubernetes.io/serviceaccount from default-token-587zl (ro)
...

Limit Ranges


  1. Nous allons donc créer une limit range définissant une limite et une request par défaut pour nos pods :
touch mem-limit-range.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
  namespace: resources
spec:
  limits:
  - default:
      memory: 768Mi
    defaultRequest:
      memory: 256Mi
    type: Container
  1. Créons donc cette limitRange :
kubectl apply -f mem-limit-range.yaml
limitrange/mem-limit-range created
  1. Nous pouvons consulter notre limitRange de la façon suivante :
kubectl describe -n resources limitrange mem-limit-range
Name:       mem-limit-range
Namespace:  resources
Type        Resource  Min  Max  Default Request  Default Limit  Max Limit/Request Ratio
----        --------  ---  ---  ---------------  -------------  -----------------------
Container   memory    -    -    256Mi            768Mi          -
  1. Nous allons maintenant créer un pod, sans définir une request/limit de ressources :
touch test2-resources.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: test2-resources
  namespace: resources
spec:
  containers:
  - name: app
    image: redis
  1. Créons donc ce pod :
kubectl apply -f test2-resources.yaml
pod/test2-resources created
  1. Faisons maintenant un describe sur ce pod, nous voyons bien que la limite de RAM est a 768Mi et la request est de 256Mi :
kubectl describe pods -n resources test2-resources
...
Host Port:      <none>
State:          Running
  Started:      Wed, 28 Oct 2020 13:34:12 +0000
Ready:          True
Restart Count:  0
Limits:
  memory:  768Mi
Requests:
  memory:     256Mi
Environment:  <none>
Mounts:
  /var/run/secrets/kubernetes.io/serviceaccount from default-token-587zl (ro)
...

Resource Quota


  1. Commençons par créer une resource-quota sur note namespace de 1Gi en requests et 2Gi en limits :
touch resource-quota.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: ResourceQuota
metadata:
  name: resource-quota
  namespace: resources
spec:
  hard:
    requests.memory: 1Gi
    limits.memory: 2Gi
  1. Créons cette resource-quota :
kubectl apply -f resource-quota.yaml

resourcequota/resource-quota created
  1. Nous pouvons consulter notre Resource Quota de la façon suivante :
kubectl describe -n resources resourcequota
Name:            resource-quota
Namespace:       resources
Resource         Used   Hard
--------         ----   ----
limits.memory    896Mi  2Gi
requests.memory  384Mi  1Gi
  1. Nous allons maintenant définir un pod redis avec une request de 768Mi (Sachant que nous avons déjà un pod avec 128Mi de request et un autre avec 256Mi) :
touch test3-resources.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: test3-resources
  namespace: resources
spec:
  containers:
  - name: app
    image: redis
    resources:
      requests:
        memory: "768Mi"
        cpu: "250m"
  1. Essayons de créer ce pod :
kubectl apply -f test3-resources.yaml

Error from server (Forbidden): error when creating "test3-resources.yaml": pods "test3-resources" is forbidden: exceeded quota: resource-quota, requested: requests.memory=768Mi, used: requests.memory=384Mi, limited: requests.memory=1Gi

La création échoue puisque la request demandée, s’ajoutant aux requests des deux pods existants, est supérieur à celle définie par la ressource quota qui est de 1Gi.

Clean up

Nous pouvons maintenant supprimer les ressources que nous avons crées dans ces exercices :

kubectl delete -f .
limitrange "mem-limit-range" deleted
resourcequota "resource-quota" deleted
pod "test-resources" deleted
pod "test2-resources" deleted
Error from server (NotFound): error when deleting "test3-resources.yaml": pods "test3-resources" not found

8: Liveness and Readiness probe


Machine : master


mkdir healthchecking
cd healthchecking
kubectl create namespace healthchecking

Liveness probe, avec un fichier

  1. Commençons par créer un ficher yaml décrivant un pod avec une liveness probe.
touch file-liveness.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: file-liveness
  namespace: healthchecking
spec:
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5
  1. Nous allons donc créer ce pod de la façon suivante :
kubectl apply -f file-liveness.yaml

pod/file-liveness created

  1. Au bout de quelques secondes, nous pouvons faire un describe sur le pod et observer le résultat suivant :
kubectl describe pods -n healthchecking file-liveness
...
Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  29s               default-scheduler  Successfully assigned default/liveness-exec to worker
  Normal   Pulling    29s               kubelet            Pulling image "busybox"
  Normal   Pulled     27s               kubelet            Successfully pulled image "busybox" in 1.59651835s
  Normal   Created    27s               kubelet            Created container liveness
  Normal   Started    27s               kubelet            Started container liveness
  Warning  Unhealthy  5s (x3 over 15s)  kubelet            Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Killing    5s                kubelet            Container liveness failed liveness probe, will be restarted

La liveness probe fini donc par échouer comme prévu, étant donne que le fichier /tmp/healthy n’existe plus. On remarque également que Kubernetes kill le conteneur a l’intérieur du pod et le recrée.

Liveness probe, avec une requête http

Nous allons cette fois mettre en place une liveness probe mais avec une requête http exécutée périodiquement.

  1. Commençons par créer un fichier http-liveness.yaml :
touch http-liveness.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: http-liveness
  namespace: healthchecking
spec:
  containers:
  - name: liveness
    image: nginx
    livenessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 3
      periodSeconds: 3

Cette fois ci, la liveness probe utilise une requête http avec la méthode GET sur la racine toute les 3 secondes. La liveness probe échouera selon le code d’erreur de la requête http.

  1. Créons donc ce pod :
kubectl apply -f http-liveness.yaml

pod/http-liveness created

  1. Si nous faisons un describe sur le pod, nous devrions voir que tout se passe bien pour l’instant :
kubectl describe pods -n healthchecking http-liveness
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  118s  default-scheduler  Successfully assigned healthchecking/http-liveness to worker
  Normal  Pulling    118s  kubelet            Pulling image "nginx"
  Normal  Pulled     114s  kubelet            Successfully pulled image "nginx" in 3.862745132s
  Normal  Created    114s  kubelet            Created container liveness
  Normal  Started    113s  kubelet            Started container liveness
  1. Nous allons supprimer la page d’accueil de nginx dans le conteneur, ce qui entraînera un code d’erreur 400 pour la requête http de la liveness probe :
kubectl exec -n healthchecking http-liveness -- rm /usr/share/nginx/html/index.html
  1. Au bout de quelques secondes, on devrait voir que la liveness probe échoue et le conteneur est recréé :
kubectl describe pods -n healthchecking http-liveness

Type     Reason     Age                From               Message
----     ------     ----               ----               -------
Normal   Scheduled  59s                default-scheduler  Successfully assigned healthchecking/http-liveness to worker
Normal   Pulled     57s                kubelet            Successfully pulled image "nginx" in 1.609742987s
Normal   Pulling    34s (x2 over 58s)  kubelet            Pulling image "nginx"
Warning  Unhealthy  34s (x3 over 40s)  kubelet            Liveness probe failed: HTTP probe failed with statuscode: 403
Normal   Killing    34s                kubelet            Container liveness failed liveness probe, will be restarted
Normal   Created    32s (x2 over 57s)  kubelet            Created container liveness
Normal   Started    32s (x2 over 57s)  kubelet            Started container liveness
Normal   Pulled     32s                kubelet            Successfully pulled image "nginx" in 2.031773864s

On voit que le conteneur a été tué par Kubernetes étant donné que la liveness probe a échoué.


Readiness Probe


Nous allons maintenant voir une autre façon de faire du healthchecking sur un pod : la readiness probe. Elle permet à Kubernetes de savoir lorsque l’application se trouvant dans un pod a bel et bien démarré. Comme la liveness probe, elle fait ça a l’aide de commandes, de requêtes http/tcp, etc.

  1. Commençons par créer un fichier file-readiness.yaml :
touch file-readiness.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: file-readiness
  namespace: healthchecking
spec:
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - sleep 60; touch /tmp/healthy; sleep 600
    readinessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

Ce pod est un peu similaire à celui de file-liveness dans l’exercice 1. Cette fois ci, le pod attend 30 secondes au démarrage avant de créer un fichier /tmp/healthy. Ce pod contient également une readiness probe vérifiant l’existence de ce fichier /tmp/healthy.

  1. Créons donc ce pod :
kubectl apply -f file-readiness.yaml

pod/file-readiness created

  1. Si on fait on describe tout de suite après la création du pod, on devrait voir le pod n’est pas encore prêt :
kubectl describe pods -n healthchecking file-readiness  
Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  39s               default-scheduler  Successfully assigned healthchecking/file-readiness to worker
  Normal   Pulling    38s               kubelet            Pulling image "busybox"
  Normal   Pulled     37s               kubelet            Successfully pulled image "busybox" in 1.64435698s
  Normal   Created    37s               kubelet            Created container liveness
  Normal   Started    36s               kubelet            Started container liveness
  Warning  Unhealthy  1s (x7 over 31s)  kubelet            Readiness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  1. Au bout d’environ une minute, on devrait voir le pod entrant dans l’état ready (1/1) :
kubectl get pods -n healthchecking

NAME             READY   STATUS             RESTARTS   AGE
file-liveness    0/1     CrashLoopBackOff   7          14m
file-readiness   1/1     Running            0          105s
http-liveness    1/1     Running            1          6m3s  

Clean up

Nous allons supprimer les ressources créées par cet exercice de la façon suivante :

kubectl delete -f .

9: Scheduling


Machine : master


mkdir scheduling
cd scheduling
kubectl create namespace scheduling

Taints and Tolerations

  1. Nous allons commencer par mettre un taint sur les noeuds worker-0 et worker-1:
kubectl taint nodes worker-0 dedicated=experimental:NoSchedule

node/worker-0 tainted

kubectl taint nodes worker-1 dedicated=experimental:NoSchedule

node/worker-1 tainted

  1. Nous pouvons faire un describe sur le noeud pour voir que notre taint a bien été prise en compte :
kubectl describe node worker-0

CreationTimestamp:  Sun, 01 Nov 2020 09:49:52 +0000
Taints:             dedicated=experimental:NoSchedule
Unschedulable:      false
  1. Essayons de déployer un pod sans toleration :
touch pod-without-toleration.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod-without-toleration
  namespace: scheduling
spec:
  containers:
  - name: nginx
    image: nginx
  1. Créons donc ce pod :
kubectl apply -f pod-without-toleration.yaml

pod/pod-without-toleration created

  1. Voyons voir sur quel noeud notre pod a été schedulé :
kubectl get pods -n scheduling pod-without-toleration -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED NODE   READINESS GATES
pod-without-toleration   0/1     Pending   0          11m   <none>   <none>   <none>           <none>

Notre pod n’ayant pas de toleration pour la taint que nous avons mis sur les noeuds worker-0 et worker-1, il n’a pu être déployé.

  1. Définissons maintenant un pod avec un toleration avec la taint définie plus haut :
touch pod-toleration.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
  namespace: scheduling
spec:
  containers:
  - name: nginx
    image: nginx
  tolerations:
  - key: "dedicated"
    value: "experimental"
    operator: "Equal"
    effect: "NoSchedule"
  1. Créons ce pod :
kubectl apply -f pod-toleration.yaml

pod/pod-toleration created

  1. Nous pouvons voir sur quel noeud notre pod a été schedulé :
kubectl get pods -n scheduling pod-toleration -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
pod-toleration   1/1     Running   0          49s   10.44.0.1   worker-0   <none>           <none>

Le pod peut maintenant être schedulé sur le noeud worker-0

  1. Supprimons les objets créés dans cet exercice :
kubectl delete -f pod-toleration.yaml -f pod-without-toleration.yaml

pod “pod-toleration” deleted

pod “pod-without-toleration” deleted

kubectl taint nodes worker-0 dedicated:NoSchedule-

node/worker-0 untainted

kubectl taint nodes worker-1 dedicated:NoSchedule-

node/worker-1 untainted


NodeSelector

  1. Nous allons enlever la taint sur le master pour pouvoir scheduler des pods dessus :
kubectl taint nodes master node-role.kubernetes.io/master:NoSchedule-

node/master untainted

  1. Nous allons commencer par mettre un label sur le noeud worker-1 “disk=ssd” :
kubectl label nodes worker-1 disk=ssd

node/worker-1 labeled

  1. Nous pouvons faire un describe sur le noeud worker-1 pour voir que notre label a bien été pris en compte :
kubectl describe nodes worker-1
Name:               worker-1
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    disk=ssd
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=worker
                    kubernetes.io/os=linux
  1. Définissons un pod que l’on va scheduler sur le noeud worker-1 à l’aide du label défini ci-dessus :
touch pod-nodeselector.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
  namespace: scheduling
spec:
  containers:
  - name: nginx
    image: nginx
  nodeSelector:
    disk: ssd
  1. Créons donc ce pod :
kubectl apply -f pod-nodeselector.yaml

pod/pod-nodeselector created

  1. Voyons voir dans quel noeud notre pod a été mis :
kubectl get pods -n scheduling pod-nodeselector -o wide
NAME               READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
pod-nodeselector   1/1     Running   0          17s   10.44.0.1   worker-1   <none>           <none>

Sans surprise, sur le noeud worker-1.

  1. Supprimons le pod créé dans cet exercice :
kubectl delete -f pod-nodeselector.yaml

pod “pod-nodeselector” deleted


Node Affinity/AntiAffinity


  1. Définissons un pod, avec une nodeAffinity lui imposant d’aller dans un noeud ayant comme label “disk=ssd”, autrement dit le noeud worker-1 :
touch pod-nodeaffinity.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity
  namespace: scheduling
  labels:
    pod: alone
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disk
            operator: In
            values:
            - ssd
  containers:
  - name: pod-nodeaffinity
    image: nginx
  1. Créons donc ce pod :
kubectl apply -f pod-nodeaffinity.yaml

pod/pod-nodeaffinity created

  1. Voyons voir dans quel noeud ce pod a été mis :
kubectl get pods -n scheduling pod-nodeaffinity -o wide
NAME               READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
pod-nodeaffinity   1/1     Running   0          36s   10.44.0.1        worker-1   <none>           <none>

Sans surprise, dans le noeud worker-1.


Pod Affinity/AntiAffinity


  1. Définissons un pod, avec une podAntiAffinity lui imposant d’aller dans un noeud ne comportant pas le pod pod-nodeaffinity :
touch pod-podantiaffinity.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod-podantiaffinity
  namespace: scheduling
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: pod
                operator: In
                values:
                - alone
          topologyKey: "kubernetes.io/hostname"
  containers:
  - name: pod-podantiaffinity
    image: nginx
  1. Créons donc ce pod :
kubectl apply -f pod-podantiaffinity.yaml

pod/pod-podantiaffinity created

  1. Voyons voir dans quel noeud ce pod a été mis :
kubectl get pods -n scheduling pod-podantiaffinity -o wide
NAME                  READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
pod-podantiaffinity   1/1     Running   0          14s   10.32.0.4   master   <none>           <none>

Cette fois-ci, soit sur le noeud master ou worker-0.


NodeName


  1. Définissons un pod que l’on va scheduler dans le noeud master avec la propriété nodename :
touch pod-nodename.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: scheduling
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: master
  1. Créons donc ce pod :
kubectl apply -f pod-nodename.yaml

pod/pod-nodename created

  1. Regardons dans quel noeud ce pod se trouve :
kubectl get pods -n scheduling pod-nodename -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP          NODE     NOMINATED NODE   READINESS GATES
pod-nodename   1/1     Running   0          4s    10.44.0.4       master   <none>           <none>

Sans surprise le noeud master. :)

Clean Up

Nous pouvons supprimer les ressources générées par cet exercice de la façon suivante :

kubectl delete -f .
pod "pod-nodeaffinity" deleted
pod "pod-nodename" deleted
pod "pod-podantiaffinity" deleted
Error from server (NotFound): error when deleting "pod-nodeselector.yaml": pods "pod-nodeselector" not found
Error from server (NotFound): error when deleting "pod-toleration.yaml": pods "pod-toleration" not found
Error from server (NotFound): error when deleting "pod-without-toleration.yaml": pods "pod-without-toleration" not found

10: RBAC


Machine : master


mkdir rbac
cd rbac
kubectl create namespace rbac

Service Accounts

  1. Nous allons créer 1 service accounts :
touch example-serviceaccount.yaml

Avec respectivement les contenus yaml suivants :

apiVersion: v1
kind: ServiceAccount
metadata:
  name: example-serviceaccount
  namespace: rbac
  1. Créons ce service account :
kubectl apply -f example-serviceaccount.yaml

serviceaccount/example-serviceaccount created
  1. Nous pouvons voir les services accounts avec la commande suivante :
kubectl get serviceaccounts -n rbac

NAME                          SECRETS   AGE
example-serviceaccount        1         6s
default                       1         2m5s
  1. Nous pouvons faire un describe sur le service account pour voir plusieurs informations à son sujet :
kubectl describe serviceaccounts -n rbac

Name:                default
Namespace:           rbac
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   default-token-4mpqg
Tokens:              default-token-4mpqg
Events:              <none>

Note : Le token utilisé par le service account est stocké dans un secret, que l’on peut voir ci-dessus

User

  1. Créer un utilisateur unix avec votre tri-gramme
TRIG="hel" # Remplacer avec votre trigramme par exemple
sudo useradd ${TRIG} -m -s /bin/bash
  1. Commençons par générer une clé privée un CSR pour notre utilisateur :
openssl req -new -newkey rsa:4096 -nodes -keyout ${TRIG}-kubernetes.key -out ${TRIG}-kubernetes.csr -subj "/CN=${TRIG}/O=devops"
  1. Encodons en base64 le CSR généré
base64 ${TRIG}-kubernetes.csr | tr -d '\n' > ${TRIG}.csr
  1. Mettons le csr encodé dans une variable
REQUEST=$(cat ${TRIG}.csr)
  1. Faisons une request de signature pour le csr généré au niveau du cluster
cat << EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: ${TRIG}-kubernetes-csr
spec:
  groups:
  - system:authenticated
  request: $REQUEST
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - client auth
EOF
  1. Vérifions que la request est passée
kubectl get csr
# 10: Pending
  1. Approuvons le certificat
kubectl certificate approve ${TRIG}-kubernetes-csr
  1. Vérifions que la request est signée
kubectl get csr
# 10: Approved,Issued
  1. Génération du certificat utilisateur
kubectl get csr ${TRIG}-kubernetes-csr -o jsonpath='{.status.certificate}' | base64 --decode > ${TRIG}-kubernetes-csr.crt
  1. Génération de la CA du cluster k8s
kubectl config view -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' --raw | base64 --decode - > kubernetes-ca.crt
  1. Création du kubeconfig
kubectl config set-cluster $(kubectl config view -o jsonpath='{.clusters[0].name}') --server=$(kubectl config view -o jsonpath='{.clusters[0].cluster.server}') --certificate-authority=kubernetes-ca.crt --kubeconfig=${TRIG}-kubernetes-config --embed-certs
  1. Mise à jour du context
kubectl config set-credentials ${TRIG} --client-certificate=${TRIG}-kubernetes-csr.crt --client-key=${TRIG}-kubernetes.key --embed-certs --kubeconfig=${TRIG}-kubernetes-config
  1. Optionnellement positionnement sur un namespace

kubectl config set-context ${TRIG} --cluster=$(kubectl config view -o jsonpath='{.clusters[0].name}') --namespace=rbac --user=${TRIG} --kubeconfig=${TRIG}-kubernetes-config

KUBECONFIG=hel-kubernetes-config kubectx hel
  1. Déplacements


sudo mkdir -p /home/${TRIG}/.kube
sudo cp ${TRIG}-kubernetes-config /home/${TRIG}/.kube/config
sudo chown -R ${TRIG}:${TRIG} /home/${TRIG}/.kube
  1. Testons notre kubeconfig :
sudo su - ${TRIG}

kubectl get pods

Error from server (Forbidden): pods is forbidden: User "${TRIG}" cannot list resource "pods" in API group "" in the namespace "default"


#!!!  A faire pour repasser sur ubuntu ou utiliser un autre onglet
exit

Roles/RoleBinding

  1. Commençons par créer un rôle pod-reader permettant de lire les pods sur le namespace rbac. Nous allons donc créer un fichier pod-reader.yaml
touch pod-reader.yaml

Avec le contenu yaml suivant :

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: rbac
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
  1. Nous allons maintenant créer un rôle similaire, mais contenant cette fois ci des droits de création et de mise à jour, en plus des droits de lecture :
touch pod-creator.yaml

Avec le contenu yaml suivant :

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-creator
  namespace: rbac
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list", "create", "update", "patch"]
  1. Créons donc ces rôles :
kubectl apply -f pod-reader.yaml -f pod-creator.yaml

role.rbac.authorization.k8s.io/pod-reader created
role.rbac.authorization.k8s.io/pod-creator created
  1. Nous pouvons consulter ces rôles de la façon suivante :
kubectl describe roles -n rbac pod-reader

Name:         pod-reader
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  pods       []                 []              [get watch list]

kubectl describe roles -n rbac pod-creator

Name:         pod-creator
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  pods       []                 []              [get watch list create update patch]
  1. Nous allons associer dans un premier le rôle pod reader au user ${TRIG} précédemment créé En ligne de commande
kubectl create rolebinding hel-pod-reader --role=pod-reader --user=hel -n rbac
  1. Vérifier qu’il n’y a plus d’erreur
# 10: ! En tant que ubuntu 
kubectl run --image nginx nginx -n rbac

# 10: En tant que hel
kubectl get po -n rbac

# 10: Essayer de supprimer le pod en tant que hel
kubectl delete po nginx -n rbac  #! erreur
  1. Refaire l’exercice avec le rôle pod-creator

  1. Nous allons maintenant associer ces rôles aux utilisateurs reader et creator. Nous allons donc créer des rolesbindings :
touch read-pods.yaml
touch create-pods.yaml

Avec respectivement les contenus yaml suivants :

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: rbac
subjects:
- kind: User
  name: reader
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: create-pods
  namespace: rbac
subjects:
- kind: User
  name: creator
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-creator
  apiGroup: rbac.authorization.k8s.io
  1. Créons donc ces rolesbindings :
kubectl apply -f read-pods.yaml -f create-pods.yaml

rolebinding.rbac.authorization.k8s.io/read-pods created
rolebinding.rbac.authorization.k8s.io/create-pods created
  1. Nous pouvons consulter ces rolebindings de la façon suivante :
kubectl describe rolebindings -n rbac create-pods


Name:         create-pods
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  pod-creator
Subjects:
  Kind  Name     Namespace
  ----  ----     ---------
  User  creator

kubectl describe rolebindings -n rbac read-pods

Name:         read-pods
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  pod-reader
Subjects:
  Kind  Name    Namespace
  ----  ----    ---------
  User  reader
  1. Nous allons maintenant tenter de créer un pod en tant qu’utilisateur reader :
kubectl run --image nginx test-rbac -n rbac --as reader

Error from server (Forbidden): pods is forbidden: User "reader" cannot create resource "pods" in API group "" in the namespace "rbac"
  1. Essayons maintenant en tant que creator :
kubectl run --image nginx test-rbac -n rbac --as creator

pod/nginx created
  1. Maintenant, nous allons essayer de récupérer des informations sur ces pods en tant que unauthorized :
kubectl get pods test-rbac -n rbac --as unauthorized

Error from server (Forbidden): pods "test-rbac" is forbidden: User "unauthorized" cannot get resource "pods" in API group "" in the namespace "rbac"
  1. Essayons maintenant en tant que reader :
kubectl get pods test-rbac -n rbac --as reader

NAME        READY   STATUS    RESTARTS   AGE
test-rbac   1/1     Running   0          58s

ClusterRoles/ClusteRoleBinding

  1. Commençons par créer un secret dans le namespace default :
touch secret-rbac.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Secret
metadata:
  name: secret-rbac
  namespace : default
type: Opaque
stringData:
  iam: asecret
  1. Nous allons créer un clusterrole permettant de lire les secrets, quelque soit le namespace dans lequel ils se trouvent :
touch secret-reader.yaml

Avec le contenu yaml suivant :

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: secret-reader
rules:
- resources: ["secrets"]
  verbs: ["get", "watch", "list"]
  apiGroups: [""]
  1. Nous allons également créer un clusterrole permettant de récupérer des informations sur les noeuds du cluster :
touch node-reader.yaml

Avec le contenu yaml suivant :

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
- resources: ["nodes"]
  verbs: ["get", "watch", "list"]
  apiGroups: [""]
  1. Créons maintenant ces clusterroles :
kubectl apply -f secret-reader.yaml -f node-reader.yaml -f secret-rbac.yaml

clusterrole.rbac.authorization.k8s.io/secret-reader created
clusterrole.rbac.authorization.k8s.io/node-reader created
secret/secret-rbac created
  1. Nous allons maintenant lier ces clusterroles à l’utilisateur reader. Nous allons donc créer des clusterrole binding :
touch read-secrets-global.yaml
touch read-nodes-global.yaml

Avec respectivement les contenus yaml suivants :

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: User
  name: reader
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-nodes-global
subjects:
- kind: User
  name: reader
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: node-reader
  apiGroup: rbac.authorization.k8s.io
  1. Créons donc ces clusterrole bindings :
kubectl apply -f read-secrets-global.yaml -f read-nodes-global.yaml

clusterrolebinding.rbac.authorization.k8s.io/read-secrets-global created
clusterrolebinding.rbac.authorization.k8s.io/read-nodes-global created
  1. Essayons maintenant de lire le secret se trouvant dans le namespace default en tant que unauthorized :
kubectl get secrets secret-rbac --as unauthorized

Error from server (Forbidden): secrets "secret-rbac" is forbidden: User "unauthorized" cannot get resource "secrets" in API group "" in the namespace "default"
  1. Essayons maintenant en tant que reader :
kubectl get secrets secret-rbac -n default --as reader

NAME          TYPE     DATA   AGE
secret-rbac   Opaque   1      10m
  1. De même, essayons de lister les noeuds en tant que unauthorized :
kubectl get nodes --as unauthorized

Error from server (Forbidden): nodes is forbidden: User "unauthorized" cannot list resource "nodes" in API group "" at the cluster scope
  1. Essayons maintenant en tant que reader :
kubectl get nodes --as reader

NAME     STATUS   ROLES    AGE   VERSION
master   Ready    master   25h   v1.19.3
worker   Ready    <none>   25h   v1.19.3

CleanUp

kubectl delete -f .

rolebinding.rbac.authorization.k8s.io "create-pods" deleted
serviceaccount "example-serviceaccount" deleted
clusterrole.rbac.authorization.k8s.io "node-reader" deleted
role.rbac.authorization.k8s.io "pod-creator" deleted
role.rbac.authorization.k8s.io "pod-reader" deleted
clusterrolebinding.rbac.authorization.k8s.io "read-nodes-global" deleted
rolebinding.rbac.authorization.k8s.io "read-pods" deleted
clusterrolebinding.rbac.authorization.k8s.io "read-secrets-global" deleted
secret "secret-rbac" deleted
clusterrole.rbac.authorization.k8s.io "secret-reader" deleted

11: Rolling Update

Stratégies de déploiement


Machine : master


mkdir updating
cd updating
kubectl create namespace updating

Mise a jour d’un deployment

  1. Commençons par créer un simple deployment :
touch example-update.yaml

Avec le contenu yaml suivant :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-update
  namespace: updating
  labels:
    app: httpd
spec:
  replicas: 4
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      containers:
      - name: httpd
        image: httpd:2.4.43
        ports:
        - containerPort: 80
  1. Créons donc ce deployment :
kubectl apply -f example-update.yaml --record

deployment.apps “example-update” created

  1. Nous pouvons voir le statut du rollout de la façon suivante :
kubectl rollout status deployment -n updating example-update

deployment “example-update” successfully rolled out

  1. Nous allons mettre à jour l’image httpd avec la version 2.4.46
containers:
- name: httpd
  image: httpd:2.4.46
  1. Mettons à jour notre deployment :
kubectl apply -f example-update.yaml --record

deployment.apps/example-update configured
  1. Vérifions à nouveau le statut de notre rollout :
kubectl rollout status deployment -n updating example-update
Waiting for deployment "example-update" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "example-update" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "example-update" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "example-update" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "example-update" rollout to finish: 1 old replicas are pending termination...
deployment "example-update" successfully rolled out
  1. Nous pouvons voir l’historique du rollout avec la commande suivante :
kubectl rollout history deployment -n updating example-update
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=example-update.yaml --record=true
2         kubectl apply --filename=example-update.yaml --record=true
  1. Nous pouvons voir les images de nos pods de la façon suivante :
kubectl get pods -n updating -o jsonpath='{range .items[*]}{@.spec.containers[0].image}{"\n"}'
httpd:2.4.46
httpd:2.4.46
httpd:2.4.46
httpd:2.4.46
  1. Nous pouvons faire un rollback si nous souhaitons revenir en arrière :
kubectl rollout undo deployment -n updating example-update

deployment.apps/example-update rolled back

  1. Re-vérifions le statut de notre rollout :
kubectl rollout status deployment -n updating example-update
Waiting for deployment "example-update" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "example-update" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "example-update" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "example-update" rollout to finish: 3 of 4 updated replicas are available...
deployment "example-update" successfully rolled out
  1. Nous pouvons voir les images de nos pods de la façon suivante :
kubectl get pods -n updating -o jsonpath='{range .items[*]}{@.spec.containers[0].image}{"\n"}'
httpd:2.4.43
httpd:2.4.43
httpd:2.4.43
httpd:2.4.43
  1. Si nous faisons un describe sur notre deployement, nous pouvons voir les paramètre MaxSurge et MaxUnavailable :
kubectl describe deployments -n updating example-update
...
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
...

Blue/Green

  1. Commençons par définir un pod “v1” :
touch app-v1.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: blue
  name: app-v1
  namespace: updating
spec:
  containers:
  - image: nginx
    name: nginx
  1. Que nous allons exposer avec le service suivant :
touch app-service.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Service
metadata:
  name: app-service
  namespace: updating
spec:
  type: ClusterIP
  selector:
    run: blue
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  1. Déployons donc notre application :
kubectl apply -f app-v1.yaml -f app-service.yaml

pod/app-v1 created

service/app-service created

  1. Nous pouvons faire un test de connexion pour voir que tout fonctionne bien :
kubectl get svc -n updating
NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
app-service   ClusterIP   10.106.61.45   <none>        80/TCP    23s
curl IP_SERVICE_APP
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
  1. Supposons que nous souhaitons mettre à jour notre application (dans notre cas, nous allons remplacer l’image nginx par httpd). Définissons donc un pod v2 :
touch app-v2.yaml

Avec le contenu yaml suivant :

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: green
  name: app-v2
  namespace: updating
spec:
  containers:
  - image: httpd
    name: httpd
  1. Créons donc ce pod :
kubectl apply -f app-v2.yaml

pod/app-v2 created

  1. La mise à jour de notre application consiste uniquement en la mise à jour de notre service, en changeant le selector blue par green :
selector:
  run: green
kubectl apply -f app-service.yaml

service/app-service configured

  1. Nous pouvons faire un test de connexion pour voir que notre service pointe désormais sur le httpd :
curl IP_SERVICE_APP
<html><body><h1>It works!</h1></body></html>
  1. Si nous souhaitons revenir en arrière, il nous suffit simplement remettre le label green a blue :
selector:
  run: blue
kubectl apply -f app-service.yaml

service/app-service configured

  1. Un petit test pour confirmer :
curl IP_SERVICE_APP
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Clean Up

Nous pouvons maintenant supprimer les ressources que nous avons créées dans ces exercices :

kubectl delete -f .

service "app-service" deleted
pod "app-v1" deleted
pod "app-v2" deleted
deployment.apps "example-update" deleted

12: Network Policies


Machine : master


mkdir network-policies
cd network-policies
kubectl create namespace network-policies
  1. Nous allons commencer par créer 3 pods : 2 pods “source” et un pod “dest” :
touch source1-pod.yaml
touch source2-pod.yaml
touch dest-pod.yaml

Avec respectivement ces contenus yaml :

apiVersion: v1
kind: Pod
metadata:
  name: source1-pod
  namespace: network-policies
  labels:
    role: source1
spec:
  containers:
  - name: source1
    image: nginx
apiVersion: v1
kind: Pod
metadata:
  name: source2-pod
  namespace: network-policies
  labels:
    role: source2
spec:
  containers:
  - name: source2
    image: nginx
apiVersion: v1
kind: Pod
metadata:
  name: dest-pod
  namespace: network-policies
  labels:
    role: dest
spec:
  containers:
  - name: dest
    image: nginx
  1. Déployons ces 3 pods :
kubectl apply -f source1-pod.yaml -f source2-pod.yaml -f dest-pod.yaml
pod/dest-pod created
pod/source1-pod created
pod/source2-pod created
  1. Nous allons également définir un service pour chacun de nos pods :
touch source1-service.yaml
touch source2-service.yaml
touch dest-service.yaml

Avec respectivement les contenus yaml suivants :

apiVersion: v1
kind: Service
metadata:
  name: source1-service
  namespace: network-policies
spec:
  type: ClusterIP
  selector:
    role: source1
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
apiVersion: v1
kind: Service
metadata:
  name: source2-service
  namespace: network-policies
spec:
  type: ClusterIP
  selector:
    role: source2
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
apiVersion: v1
kind: Service
metadata:
  name: dest-service
  namespace: network-policies
spec:
  type: ClusterIP
  selector:
    role: dest
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  1. Création des services :
kubectl apply -f source1-service.yaml -f source2-service.yaml -f dest-service.yaml
service/dest-service created
service/source1-service created
service/source2-service created
  1. Essayons de faire une requête depuis les pods source1 et source2 vers dest :
kubectl exec -n network-policies -it source1-pod -- curl dest-service
kubectl exec -n network-policies -it source2-pod -- curl dest-service
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Sans Network Policies, on remarque que les requêtes se déroulent bien.

  1. Nous allons maintenant créer une Network Policy autorisant source1 à faire des requêtes sur dest, mais pas source2 (ni aucun autre pod) :
touch ingress-network-policy.yaml

Avec le contenu yaml suivant :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ingress-network-policy
  namespace: network-policies
spec:
  podSelector:
    matchLabels:
      role: dest
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: source1
    ports:
    - protocol: TCP
      port: 80
  1. Créons cette Network Policy :
kubectl apply -f ingress-network-policy.yaml

networkpolicy.networking.k8s.io/ingress-network-policy created

  1. Nous pouvons inspecter la network policy de la façon suivante :
kubectl describe networkpolicies -n network-policies ingress-network-policy
Name:         ingress-network-policy
Namespace:    network-policies
Created on:   2020-11-02 09:29:07 +0000 UTC
Labels:       <none>
Annotations:  <none>
Spec:
  PodSelector:     role=dest
  Allowing ingress traffic:
    To Port: 80/TCP
    From:
      PodSelector: role=source1
  Not affecting egress traffic
  Policy Types: Ingress
  1. Maintenant, essayons le même test de connexion :
kubectl exec -n network-policies -it source1-pod -- curl dest-service
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
kubectl exec -n network-policies -it source2-pod -- curl dest-service
curl: (7) Failed to connect to dest-service port 80: Connection timed out
command terminated with exit code 7
  1. Nous allons maintenant définir une network policy mais en egress, autorisant dest à faire une requête à source1 mais pas à source 2 :
touch egress-network-policy.yaml

Avec le contenu yaml suivant :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-network-policy
  namespace: network-policies
spec:
  podSelector:
    matchLabels:
      role: dest
  policyTypes:
  - Egress
  egress: []
  1. Créons donc cette network policy :
kubectl apply -f egress-network-policy.yaml

networkpolicy.networking.k8s.io/egress-network-policy created

  1. Nous pouvons maintenant essayer de faire une requête depuis dest vers source1 ou source2 :
kubectl exec -n network-policies -it dest-pod -- curl source2-service
curl: (6) Could not resolve host: source2-service
command terminated with exit code 6
  1. Modifions le contenu yaml de l’egress network policy :
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-network-policy
  namespace: network-policies
spec:
  podSelector:
    matchLabels:
      role: dest
  policyTypes:
  - Egress
  egress:
  - to:
    ports:
    - protocol: TCP
      port: 80
    - port: 53
      protocol: UDP
    - port: 53
      protocol: TCP

12 Appliquons la modification :

kubectl apply -f egress-network-policy.yaml

networkpolicy.networking.k8s.io/egress-network-policy configured

  1. Nous pouvons réessayer le test de connexion :
kubectl exec -n network-policies -it dest-pod -- curl source2-service
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Clean up

Nous allons supprimer les ressources créées par cet exercice de la façon suivante :

kubectl delete -f .
pod "dest-pod" deleted
service "dest-service" deleted
networkpolicy.networking.k8s.io "egress-network-policy" deleted
networkpolicy.networking.k8s.io "ingress-network-policy" deleted
pod "source1-pod" deleted
service "source1-service" deleted
pod "source2-pod" deleted
service "source2-service" deleted

13: Monitoring


Machine : master


mkdir monitoring
cd monitoring
kubectl create namespace monitoring

Metric Server

  1. Nous allons essayer d’obtenir les métriques pour les noeuds de notre cluster :
kubectl top node
error: Metrics API not available

… Sans succès.

  1. De même, si nous souhaitons récupérer les métriques de nos pods, nous obtenons une erreur :
kubectl top pod
error: Metrics API not available

Nous avons besoin d’installer un metrics server.

  1. Nous allons créer un fichier metrics-server.yaml :
touch metrics-server.yaml

Avec le contenu yaml suivant :

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: system:aggregated-metrics-reader
  labels:
    rbac.authorization.k8s.io/aggregate-to-view: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
    rbac.authorization.k8s.io/aggregate-to-admin: "true"
rules:
- apiGroups: ["metrics.k8s.io"]
  resources: ["pods", "nodes"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: metrics-server:system:auth-delegator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: metrics-server-auth-reader
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.metrics.k8s.io
spec:
  service:
    name: metrics-server
    namespace: kube-system
  group: metrics.k8s.io
  version: v1beta1
  insecureSkipTLSVerify: true
  groupPriorityMinimum: 100
  versionPriority: 100
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: metrics-server
  namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metrics-server
  namespace: kube-system
  labels:
    k8s-app: metrics-server
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  template:
    metadata:
      name: metrics-server
      labels:
        k8s-app: metrics-server
    spec:
      serviceAccountName: metrics-server
      volumes:
      # mount in tmp so we can safely use from-scratch images and/or read-only containers
      - name: tmp-dir
        emptyDir: {}
      containers:
      - name: metrics-server
        image: k8s.gcr.io/metrics-server/metrics-server:v0.3.7
        imagePullPolicy: IfNotPresent
        args:
          - --cert-dir=/tmp
          - --secure-port=4443
          - --kubelet-insecure-tls
          - --kubelet-preferred-address-types=InternalIP
        ports:
        - name: main-port
          containerPort: 4443
          protocol: TCP
        securityContext:
          readOnlyRootFilesystem: true
          runAsNonRoot: true
          runAsUser: 1000
        volumeMounts:
        - name: tmp-dir
          mountPath: /tmp
      nodeSelector:
        kubernetes.io/os: linux
---
apiVersion: v1
kind: Service
metadata:
  name: metrics-server
  namespace: kube-system
  labels:
    kubernetes.io/name: "Metrics-server"
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    k8s-app: metrics-server
  ports:
  - port: 443
    protocol: TCP
    targetPort: main-port
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: system:metrics-server
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - nodes
  - nodes/stats
  - namespaces
  - configmaps
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: system:metrics-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:metrics-server
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
  1. Nous pouvons donc déployer notre metrics-server :
kubectl apply -f metrics-server.yaml
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
Warning: apiregistration.k8s.io/v1 APIService is deprecated in v1.19+, unavailable in v1.22+; use apiregistration.k8s.io/v1 APIService
apiservice.apiregistration.k8s.io/v1.metrics.k8s.io created
serviceaccount/metrics-server created
deployment.apps/metrics-server created
service/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
  1. Après environ 1 minute, nous pouvons refaire notre top node :
kubectl top node
NAME     CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
master   180m         9%     1249Mi          15%       
worker   47m          2%     818Mi           10%
  1. Nous obtenons bien les consommations CPU/RAM pour chaque noeud. Voyons voir maintenant pour les consommations de ressources par nos pods :
kubectl top pod -A
NAMESPACE     NAME                              CPU(cores)   MEMORY(bytes)   
kube-system   coredns-f9fd979d6-9kb87           4m           12Mi            
kube-system   coredns-f9fd979d6-tl95z           3m           12Mi            
kube-system   etcd-master                       20m          41Mi            
kube-system   kube-apiserver-master             48m          294Mi           
kube-system   kube-controller-manager-master    16m          47Mi            
kube-system   kube-proxy-8dvrj                  1m           15Mi            
kube-system   kube-proxy-ll8tb                  1m           15Mi            
kube-system   kube-scheduler-master             4m           21Mi            
kube-system   metrics-server-75f98fdbd5-2lp87   1m           12Mi            
kube-system   weave-net-c4b7d                   2m           58Mi            
kube-system   weave-net-zfqt6                   2m           62Mi

Parfait !


Prometheus/Grafana


Nous allons déployer une stack de monitoring basée sur Prometheus et Grafana via Helm.

  1. Commençons par créer le fichier values.yaml pour le chart :
touch kube-prometheus-stack.yaml

Avec le contenu yaml suivant :

grafana:
   adminPassword: prom-passw0rd
  1. Nous pouvons donc installer la stack prometheus via Helm :
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm upgrade --install prometheus prometheus-community/kube-prometheus-stack --values kube-prometheus-stack.yaml --namespace monitoring --create-namespace
  1. Nous pouvons voir les ressources créées de la façon suivante :
kubectl get all -n monitoring
  1. Nous allons faire un port-forward pour se connecter à notre serveur Prometheus :
kubectl --namespace monitoring port-forward --address 0.0.0.0 service/prometheus-kube-prometheus-prometheus 8080:80
Forwarding from 0.0.0.0:8080 -> 9090
  1. De même pour Grafana :
kubectl --namespace monitoring port-forward --address 0.0.0.0 service/prometheus-grafana 8081:80
Forwarding from 0.0.0.0:8081 -> 80
  1. Enjoy :)

14: Helm


Machine : master


mkdir helm
cd helm
kubectl create namespace helm

Installation d’un echo-server avec Helm

  1. Commençons par l’installation de Helm :
curl -Lo helm.tar.gz https://get.helm.sh/helm-v3.3.4-linux-amd64.tar.gz
tar xvf helm.tar.gz
sudo mv linux-amd64/helm /usr/local/bin
rm -rf helm.tar.gz linux-amd64
  1. Nous pouvons tester l’installation de la façon suivante :
helm version
version.BuildInfo{Version:"v3.3.4", GitCommit:"a61ce5633af99708171414353ed49547cf05013d", GitTreeState:"clean", GoVersion:"go1.14.9"}
  1. Les artefacts Helm sont stockes sur des repositories. Il est nécessaire d’ajouter. Nous allons ajouter le repo ealenn contenant le chart echo-server :
helm repo add ealenn https://ealenn.github.io/charts

"ealenn" has been added to your repositories
  1. Une fois notre repository ajouté, nous pouvons installer n’importe quel chart se trouvant dans ce repository. Installons donc notre echo-server :
helm install echo-server ealenn/echo-server
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.zsh}

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.zsh}
NAME: echo-server
LAST DEPLOYED: Tue Oct 27 10:21:27 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
  1. Nous pouvons lister les charts installés de la façon suivante :
helm list
NAME        NAMESPACE   REVISION    UPDATED                                 STATUS      CHART               APP VERSION
echo-server default     1           2020-10-27 10:21:27.307028704 +0000 UTC deployed    echo-server-0.3.0   0.4.0
  1. Nous pouvons voir les pods générés par l’installation :
kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
echo-server-79cc9789cb-hqmlt   1/1     Running   0          2m10s
  1. Quant à la désinstallation, elle se fait de la façon suivante :
helm uninstall echo-server
release "echo-server" uninstalled

Installation avec un fichier values.yaml

  1. Commençons par créer un fichier values.yaml :
touch values.yaml

Avec le contenu yaml suivant :

replicaCount: 3

image:
  tag: "0.4.1"
  1. Nous allons cette fois ci installer notre echo-server dans le namespace helm, configure à l’aide du fichier values.yaml ci dessus :
helm install echo-server ealenn/echo-server --values values.yaml --namespace helm
NAME: echo-server
LAST DEPLOYED: Sat Oct 31 17:57:50 2020
NAMESPACE: helm
STATUS: deployed
REVISION: 1
  1. Nous allons maintenant voir si notre chart bien génère 3 pods :
kubectl get pods -n helm
NAME                           READY   STATUS    RESTARTS   AGE
echo-server-66d9c454b5-8crn7   1/1     Running   0          32s
echo-server-66d9c454b5-wdr7p   1/1     Running   0          32s
echo-server-66d9c454b5-z6cwt   1/1     Running   0          32s
  1. Nous pouvons maintenant désinstaller notre echo-server :
helm uninstall echo-server -n helm
release "echo-server" uninstalled

15: Kustomize


Machine : master


mkdir -p kustomize/k8s/base
cd kustomize/k8s/base
kubectl create namespace kustomize

Installation de kustomize

https://kubectl.docs.kubernetes.io/installation/kustomize/

Choisissez !

Structure à Créer

.
├── base
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    └── prod
        ├── custom-env.yaml
        ├── database-secret.yaml
        ├── kustomization.yaml
        └── replica-and-rollout-strategy.yaml

Contenu de base/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sl-demo-app
spec:
  selector:
    matchLabels:
      app: sl-demo-app
  template:
    metadata:
      labels:
        app: sl-demo-app
    spec:
      containers:
      - name: app
        image: nginx:1.19.9
        ports:
        - name: http
          containerPort: 80
          protocol: TCP

Contenu de base/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: sl-demo-app
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 80
  selector:
    app: sl-demo-app

Contenu de base/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - service.yaml
  - deployment.yaml

Lancement initial

kubectl apply -k k8s/base

kubectl get  all -l app=sl-demo-app
kubectl get deploy 
NAME                              READY   STATUS    RESTARTS   AGE
pod/sl-demo-app-bb6494cc6-sd6k7   1/1     Running   0          6m42s

NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/sl-demo-app   1/1     1            1           6m42s

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/sl-demo-app-bb6494cc6   1         1         1       6m42s

Création de l’overlay de production (prod)

Contenu du fichier overlays/prod/replica-and-rollout-strategy.yaml


apiVersion: apps/v1
kind: Deployment
metadata:
  name: sl-demo-app
spec:
  replicas: 10
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate

Contenu du fichier overlays/prod/database-secret.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sl-demo-app
spec:
  template:
    spec:
      containers:
      - name: app
        env:
        - name: "DB_PASSWORD"
          valueFrom:
            secretKeyRef:
              name: sl-demo-app
              key: db-password

Contenu du fichier overlays/prod/custom-env.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sl-demo-app
spec:
  template:
    spec:
      containers:
        - name: app # (1)
          env:
            - name: CUSTOM_ENV_VARIABLE
              value: Value defined by Kustomize ❤️

Contenu du fichier overlays/prod/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
commonLabels:
    caas.fr/environment: "prod"
bases:
- ../../base

patchesStrategicMerge:
- custom-env.yaml
- replica-and-rollout-strategy.yaml
- database-secret.yaml

secretGenerator:
- literals:
  - db-password=12345
  name: sl-demo-app
  type: Opaque

images:
- name: nginx
  newName: nginx
  newTag: 1.21.0

Lancement overlay prod

kubectl delete -k k8s/base
kubectl apply -k k8s/overlays/prod

kubectl get  all -l caas.fr/environment=prod

16: Logging


Machine : master


Simple

Avec la CLI kubectl, nous pouvons d’ores et déjà récupérer plusieurs logs concernant notre cluster Kubernetes.

  1. Tout d’abord, nous pouvons récupérer des informations sur le cluster, ainsi que de certains composants de ce cluster de la façon suivante :
kubectl cluster-info
Kubernetes master is running at https://10.156.0.3:6443
KubeDNS is running at https://10.156.0.3:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://10.156.0.3:6443/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
  1. Nous pouvons également voir tout les évènements qui ont eu lieu dans le cluster. Un évènement peut désigner le rescheduling d’un pod, la mise à jour d’un deployment, la création d’un PV ou binding d’un PVC à un PV. Nous pouvons avoir toute ces infos de la façon suivante :
kubectl get events -A
LAST SEEN   TYPE      REASON                 OBJECT                                       MESSAGE
81s         Normal    ExternalProvisioning   persistentvolumeclaim/postgres-openebs-pvc   waiting for a volume to be created, either by external provisioner "openebs.io/provisioner-iscsi" or manually created by system administrator
89s         Normal    Provisioning           persistentvolumeclaim/postgres-openebs-pvc   External provisioner is provisioning volume for claim "default/postgres-openebs-pvc"
  1. Nous allons maintenant voir comment récupérer les logs générés par les conteneurs d’un pod. Commençons par créer un pod avec l’image nginx à titre d’exemple :
kubectl run --image nginx test-logs
pod/test-logs created
  1. Nous pouvons récupérer les logs de ce pod de la façon suivante :
kubectl logs test-logs
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
  1. Notez également que les pods system stockent également des logs dans les dossiers /var/log/containers et /var/log/pods, on peut donc les voir de la façon suivante :
sudo cat /var/log/containers/*
...
{"log":"I1027 12:51:51.629401       1 client.go:360] parsed scheme: \"passthrough\"\n","stream":"stderr","time":"2020-10-27T12:51:51.629623287Z"}
{"log":"I1027 12:51:51.629456       1 passthrough.go:48] ccResolverWrapper: sending update to cc: {[{https://127.0.0.1:2379  \u003cnil\u003e 0 \u003cnil\u003e}] \u003cnil\u003e \u003cnil\u003e}\n","stream":"stderr","time":"2020-10-27T12:51:51.629671282Z"}
{"log":"I1027 12:51:51.629471       1 clientconn.go:948] ClientConn switching balancer to \"pick_first\"\n","stream":"stderr","time":"2020-10-27T12:51:51.62968064Z"}
  1. Enfin une dernière façon de regarder les logs des différents conteneurs peuplant notre cluster kubernetes est d’utiliser tout simplement Docker :
docker ps -a
docker logs ID_CONTENEUR
  1. Voila, nous allons maintenant supprimer notre pod de test :
kubectl delete pod test-logs
pod "test-logs" deleted

Stack Elastic


machine : master
mkdir eck
cd eck
  1. Commençons par installer les composants essentiels de ECK, notamment elastic-operator :

# 16: CRDs
kubectl apply  -f https://download.elastic.co/downloads/eck/1.9.1/crds.yaml

# 16: Operateur
kubectl apply -f https://download.elastic.co/downloads/eck/1.9.1/operator.yaml
  1. Nous pouvons monitorer le déploiement d’elastic-operator de la façon suivante :
kubectl -n elastic-system logs -f statefulset.apps/elastic-operator
...
{"log.level":"info","@timestamp":"2020-11-01T17:01:06.426Z","log.logger":"controller-runtime.controller","message":"Starting workers","service.version":"1.2.1-b5316231","service.type":"eck","ecs.version":"1.4.0","controller":"enterprisesearch-controller","worker count":3}
{"log.level":"info","@timestamp":"2020-11-01T17:01:06.426Z","log.logger":"controller-runtime.controller","message":"Starting workers","service.version":"1.2.1-b5316231","service.type":"eck","ecs.version":"1.4.0","controller":"elasticsearch-controller","worker count":3}
  1. Nous allons maintenant déployer un elasticsearch. Avec ECK, de nouvelles CustomeDefinitions sont ajoutées au cluster. L’une parmi elles permet notamment de définir un serveur Elasticsearch via yaml. Nous allons donc créer un fichier elasticsearch.yaml :
touch elasticsearch.yaml

Avec le contenu yaml suivant :

apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: elasticsearch
spec:
  version: 7.9.3
  nodeSets:
  - name: default
    count: 1
    volumeClaimTemplates:
    - metadata:
        name: elasticsearch-data
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 5Gi
        storageClassName: longhorn
    config:
      node.master: true
      node.data: true
      node.ingest: true
      node.store.allow_mmap: false
  1. Nous pouvons donc créer notre serveur Elasticsearch :
kubectl apply -f elasticsearch.yaml
elasticsearch.elasticsearch.k8s.elastic.co/elasticsearch created
  1. Nous pouvons voir le déroulement de son déploiement de la façon suivante :
kubectl get elasticsearch elasticsearch
NAME            HEALTH   NODES   VERSION   PHASE   AGE
elasticsearch   green    1       7.9.3     Ready   106s
  1. Un service exposant notre elasticsearch est créé lors du déploiement, nous pouvons le voir de la façon suivante :
kubectl get service elasticsearch-es-http
NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
elasticsearch-es-http   ClusterIP   10.99.41.114   <none>        9200/TCP   2m24s
  1. Testons la connexion a notre elasticsearch :
PASSWORD=$(kubectl get secret elasticsearch-es-elastic-user -o go-template='{{.data.elastic | base64decode}}')
curl -u "elastic:$PASSWORD" -k "https://CLUSTER_IP_ELASTICSEARCH:9200"
{
  "name" : "elasticsearch-es-default-0",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "76FfZR4ARxO78QBQw_kBhg",
  "version" : {
    "number" : "7.9.3",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
    "build_date" : "2020-10-16T10:36:16.141335Z",
    "build_snapshot" : false,
    "lucene_version" : "8.6.2",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Parfait !

  1. Nous allons maintenant passer à l’installation de Kibana. De la même manière, nous allons définir un fichier kibana.yaml permettant de déployer notre kibana :
touch kibana.yaml

Avec le contenu yaml suivant :

apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: kibana
spec:
  version: 7.9.3
  count: 1
  elasticsearchRef:
    name: elasticsearch
  1. Nous pouvons donc créer notre serveur Kibana :
kubectl apply -f kibana.yaml
kibana.kibana.k8s.elastic.co/kibana created
  1. De la même manière qu’elasticsearch, nous pouvons voir l’état de notre Kibana de la façon suivante :
kubectl get kibana kibana
NAME     HEALTH   NODES   VERSION   AGE
kibana   green    1       7.9.3     2m23s
  1. De même, un service pour Kibana est créé, nous pouvons le voir de la façon suivante :
kubectl get service kibana-kb-http
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kibana-kb-http   ClusterIP   10.106.23.116   <none>        5601/TCP   2m45s
  1. Nous allons récupérer le mot de passe de Kibana :
kubectl get secret elasticsearch-es-elastic-user -o=jsonpath='{.data.elastic}' | base64 --decode; echo
pb809RTC51EVCd3f19i9UVW5
  1. Nous allons faire un port-forward de notre service pour pouvoir se connecter dessus via un navigateur :
kubectl port-forward --address 0.0.0.0 service/kibana-kb-http 5601

Notre Kibana est donc installé ! Vous pouvez y’accéder à l’aide de l’URL suivante : https://MASTER_EXTERNAL_IP:5601

Page d’authentification :

Page d’accueil :

  1. Nous allons maintenant collecter des logs . Nous allons installé un filebeat et récupérer les logs se trouvant dans /var/log/containers, /var/lib/docker/containers et /var/log/pods/. On va donc créer le fichier filebeat.yaml suivant :
touch filebeat.yaml

Avec le contenu yaml suivant :

apiVersion: beat.k8s.elastic.co/v1beta1
kind: Beat
metadata:
  name: filebeat
spec:
  type: filebeat
  version: 7.9.3
  elasticsearchRef:
    name: elasticsearch
  config:
    filebeat.inputs:
    - type: container
      paths:
      - /var/log/containers/*.log
  daemonSet:
    podTemplate:
      spec:
        dnsPolicy: ClusterFirstWithHostNet
        hostNetwork: true
        securityContext:
          runAsUser: 0
        containers:
        - name: filebeat
          volumeMounts:
          - name: varlogcontainers
            mountPath: /var/log/containers
          - name: varlogpods
            mountPath: /var/log/pods
          - name: varlibdockercontainers
            mountPath: /var/lib/docker/containers
        volumes:
        - name: varlogcontainers
          hostPath:
            path: /var/log/containers
        - name: varlogpods
          hostPath:
            path: /var/log/pods
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers
  1. Nous pouvons donc créer notre filebeat :
kubectl apply -f filebeat.yaml
beat.beat.k8s.elastic.co/filebeat created
  1. Nous pouvons voir l’état de notre filebeat de la façon suivante :
kubectl get beat
NAME       HEALTH   AVAILABLE   EXPECTED   TYPE       VERSION   AGE
filebeat   green    2           2          filebeat   7.9.2     94s
  1. Nous pouvons créer un index pattern en allant sur Discover -> Create index pattern -> Mettre "filebeat-*" en index pattern name -> Mettre @timestamp en time field :

Création de l’index pattern :

Nom de l’index pattern :

Time Field :

  1. Nous pouvons voir les logs ou bien sur Discover ou bien sur Logs :

Discover :

Logs :

Clean Up

Pour désinstaller notre stack ELK via ECK :

kubectl delete -f .
elasticsearch.elasticsearch.k8s.elastic.co "elasticsearch" deleted
beat.beat.k8s.elastic.co "filebeat" deleted
kibana.kibana.k8s.elastic.co "kibana" deleted

17: Mise à jour d’un cluster


Machine : master, worker-0, worker-1

Préparation de la mise à jour

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring-1.28.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring-1.28.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee -a /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update

Pour commencer, il faut mettre à jour kubeadm :

sudo apt-mark unhold kubeadm
sudo apt-get install kubeadm=1.28.8-1.1
sudo apt-mark hold kubeadm

Vérifions la version de kubeadm :

kubeadm version

Nous devons maintenant drain le noeud master afin de pouvoir faire l’upgrade dessus :


kubectl drain master --ignore-daemonsets

Nous pouvons avoir un aperçu de l’upgrade de la façon suivante :

sudo kubeadm upgrade plan


[[upgrade/config] Making sure the configuration is correct:
[upgrade/config] Reading configuration from the cluster...
[upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[preflight] Running pre-flight checks.
[upgrade] Running cluster health checks
[upgrade] Fetching available versions to upgrade to
[upgrade/versions] Cluster version: v1.27.12
[upgrade/versions] kubeadm version: v1.28.8
I0408 06:40:22.060915    4163 version.go:256] remote version is much newer: v1.29.3; falling back to: stable-1.28
[upgrade/versions] Target version: v1.28.8
[upgrade/versions] Latest version in the v1.27 series: v1.27.12

Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT   CURRENT       TARGET
kubelet     3 x v1.27.9   v1.28.8

Upgrade to the latest stable version:

COMPONENT                 CURRENT    TARGET
kube-apiserver            v1.27.12   v1.28.8
kube-controller-manager   v1.27.12   v1.28.8
kube-scheduler            v1.27.12   v1.28.8
kube-proxy                v1.27.12   v1.28.8
CoreDNS                   v1.10.1    v1.10.1
etcd                      3.5.9-0    3.5.12-0

You can now apply the upgrade by executing the following command:

    kubeadm upgrade apply v1.28.8

_____________________________________________________________________


The table below shows the current state of component configs as understood by this version of kubeadm.
Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or
resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually
upgrade to is denoted in the "PREFERRED VERSION" column.

API GROUP                 CURRENT VERSION   PREFERRED VERSION   MANUAL UPGRADE REQUIRED
kubeproxy.config.k8s.io   v1alpha1          v1alpha1            no
kubelet.config.k8s.io     v1beta1           v1beta1             no
_____________________________________________________________________

Nous pouvons maintenant upgrade les composants du cluster :


sudo kubeadm upgrade apply v1.28.8

[[upgrade/config] Making sure the configuration is correct:
[upgrade/config] Reading configuration from the cluster...
[upgrade/config] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[preflight] Running pre-flight checks.
[upgrade] Running cluster health checks
[upgrade/version] You have chosen to change the cluster version to "v1.28.8"
[upgrade/versions] Cluster version: v1.27.12
[upgrade/versions] kubeadm version: v1.28.8
[upgrade] Are you sure you want to proceed? [y/N]: y



[upgrade/prepull] Pulling images required for setting up a Kubernetes cluster
[upgrade/prepull] This might take a minute or two, depending on the speed of your internet connection
[upgrade/prepull] You can also perform this action in beforehand using 'kubeadm config images pull'
W0408 06:41:41.559443    4249 checks.go:835] detected that the sandbox image "registry.k8s.io/pause:3.6" of the container runtime is inconsistent with that used by kubeadm. It is recommended that using "registry.k8s.io/pause:3.9" as the CRI sandbox image.
[upgrade/apply] Upgrading your Static Pod-hosted control plane to version "v1.28.8" (timeout: 5m0s)...
[upgrade/etcd] Upgrading to TLS for etcd
[upgrade/staticpods] Preparing for "etcd" upgrade
[upgrade/staticpods] Renewing etcd-server certificate
[upgrade/staticpods] Renewing etcd-peer certificate
[upgrade/staticpods] Renewing etcd-healthcheck-client certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/etcd.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2024-04-08-06-41-48/etcd.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s)
[apiclient] Found 1 Pods for label selector component=etcd
[upgrade/staticpods] Component "etcd" upgraded successfully!
[upgrade/etcd] Waiting for etcd to become available
[upgrade/staticpods] Writing new Static Pod manifests to "/etc/kubernetes/tmp/kubeadm-upgraded-manifests2343628394"
[upgrade/staticpods] Preparing for "kube-apiserver" upgrade
[upgrade/staticpods] Renewing apiserver certificate
[upgrade/staticpods] Renewing apiserver-kubelet-client certificate
[upgrade/staticpods] Renewing front-proxy-client certificate
[upgrade/staticpods] Renewing apiserver-etcd-client certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/kube-apiserver.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2024-04-08-06-41-48/kube-apiserver.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s)
[apiclient] Found 1 Pods for label selector component=kube-apiserver
[upgrade/staticpods] Component "kube-apiserver" upgraded successfully!
[upgrade/staticpods] Preparing for "kube-controller-manager" upgrade
[upgrade/staticpods] Renewing controller-manager.conf certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/kube-controller-manager.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2024-04-08-06-41-48/kube-controller-manager.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s)
[apiclient] Found 1 Pods for label selector component=kube-controller-manager
[upgrade/staticpods] Component "kube-controller-manager" upgraded successfully!
[upgrade/staticpods] Preparing for "kube-scheduler" upgrade
[upgrade/staticpods] Renewing scheduler.conf certificate
[upgrade/staticpods] Moved new manifest to "/etc/kubernetes/manifests/kube-scheduler.yaml" and backed up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2024-04-08-06-41-48/kube-scheduler.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This might take a minute or longer depending on the component/version gap (timeout 5m0s)
[apiclient] Found 1 Pods for label selector component=kube-scheduler
[upgrade/staticpods] Component "kube-scheduler" upgraded successfully!
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upgrade] Backing up kubelet config file to /etc/kubernetes/tmp/kubeadm-kubelet-config1974182237/config.yaml
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.28.8". Enjoy!

[upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.

Nous pouvons remettre le noeud master en marche :

kubectl uncordon master

node/master uncordoned

Nous devons maintenant mettre à jour la kubelet et kubectl :

sudo apt-mark unhold kubectl kubelet
sudo apt-get install kubectl=1.28.8-1.1 kubelet=1.28.8-1.1
sudo apt-mark hold kubectl kubelet

Enfin nous devons redémarrer la kubelet :

sudo systemctl daemon-reload
sudo systemctl restart kubelet

Vérification de la mise à jour du master

kubectl get nodes

ubuntu@master:~$ kubectl get nodes
NAME       STATUS   ROLES           AGE   VERSION
master     Ready    control-plane   19m   v1.28.8
worker-0   Ready    <none>          14m   v1.27.9
worker-1   Ready    <none>          14m   v1.27.9

Nous devons maintenant mettre à jour les workers :

A faire sur les noeud worker-0 et worker-1

Préparation de la mise à jour

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring-1.28.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring-1.28.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee -a /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update
sudo apt-mark unhold kubeadm
sudo apt-get install kubeadm=1.28.8-1.1
sudo apt-mark hold kubeadm

Comme pour le master, nous devons drain les noeuds workers :

Répéter les actions pour le noeud 2 noeud par noeud (pas en // )

Sur le master

kubectl drain worker-0 --ignore-daemonsets

Nous devons maintenant mettre à jour la configuration de notre worker-0 :

Sur le worker-0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.zsh .numberLines} sudo kubeadm upgrade node ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[upgrade] Reading configuration from the cluster...
[upgrade] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[preflight] Running pre-flight checks
[preflight] Skipping prepull. Not a control plane node.
[upgrade] Skipping phase. Not a control plane node.
[upgrade] Backing up kubelet config file to /etc/kubernetes/tmp/kubeadm-kubelet-config2717758596/config.yaml
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[upgrade] The configuration for this node was successfully updated!
[upgrade] Now you should go ahead and upgrade the kubelet package using your package manager.

Enfin, comme pour le master nous devons mettre a jour la kubelet et kubectl :

sudo apt-mark unhold kubectl kubelet
sudo apt-get install kubectl=1.28.8-1.1 kubelet=1.28.8-1.1
sudo apt-mark hold kubectl kubelet

En prenant soin de redémarrer la kubelet :

sudo systemctl daemon-reload
sudo systemctl restart kubelet

Sans oublier de remettre le noeud en marche :

Sur le master ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.zsh .numberLines} kubectl uncordon worker-0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Nous pouvons maintenant lister les noeuds :

kubectl get nodes

kubectl get nodes
NAME       STATUS   ROLES           AGE   VERSION
master     Ready    control-plane   25m   v1.28.8
worker-0   Ready    <none>          19m   v1.28.8
worker-1   Ready    <none>          19m   v1.27.9

Passez à la mise à jour du noeud worker-1

Et lister les pods pour vérifier que tout est fonctionnel :

kubectl get pods -A

NAMESPACE     NAME                             READY   STATUS    RESTARTS   AGE
kube-system   coredns-f9fd979d6-jhcg9          1/1     Running   0          7m44s
kube-system   coredns-f9fd979d6-mjfzf          1/1     Running   0          7m44s
kube-system   etcd-master                      1/1     Running   1          11m
kube-system   kube-apiserver-master            1/1     Running   0          11m
kube-system   kube-controller-manager-master   1/1     Running   0          11m
kube-system   kube-proxy-4mvtr                 1/1     Running   0          14m
kube-system   kube-proxy-lkvxn                 1/1     Running   0          13m
kube-system   kube-scheduler-master            1/1     Running   0          11m
kube-system   weave-net-t2h8r                  2/2     Running   0          24m
kube-system   weave-net-zxg6p                  2/2     Running   1          23m

Note : le CNI doit être mis à jour indépendamment


18: Backup and restaure ETCD

  1. Sauvegarde de la base de données ETCD:
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
  --cacert=<trusted-ca-file> --cert=<cert-file> --key=<key-file> \
  snapshot save <backup-file-location>

où trust-ca-file, cert-file et key-file peuvent être obtenus à partir de la description du Pod etcd en mettant sur le master la commande:

sudo cat /etc/kubernetes/manifests/etcd.yaml
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save etcd-bkp

Le snapshot a créé un fichier nommé: etcd-bkp. Vérifier le status du snapshot:

ETCDCTL_API=3 \
etcdctl --write-out=table snapshot status etcd-bkp
  1. Créez un pod nommé after-backup avec l’image nginx
kubectl run after-backup --image=nginx
  1. Restaurer maintenant la base de données ETCD:
ETCDCTL_API=3 etcdctl --endpoints 10.2.0.9:2379 snapshot restore etcd-bkp

Remarquez que l’opération de restauration a généré un repertoire default.etcd

  1. Utiliser la base de données restaurée

Avant de remplacer l’ETCD par la nouvelle restauration:

sudo mv /etc/kubernetes/manifests/*.yaml /tmp/
sudo mv /var/lib/etcd/member /var/lib/etcd/member.bak
sudo mv default.etcd/member /var/lib/etcd/
sudo mv /tmp/*.yaml /etc/kubernetes/manifests/
  1. Vérifier que la base de données a été restaurée à l’état désiré

Le pod créé après la sauvegarde ne doit plus exister après la restauration

kubectl get pods

19: Lab final


A vous de jouer !


  1. Créer un namespace nommé kubeops

  2. Créer un pod avec les caractéristiques suivantes :

Nom : webserver
Nom du conteneur : webserver
Image : dernière version de nginx
Namespace : kubeops
- Sur quel nœud se trouve votre pod ?

- Connectez-vous au conteneur du pod et vérifiez son OS avec la commande cat /etc/os-release

- Vérifiez les logs du pod
  1. Ajoutez un nouveau conteneur du nom de webwatcher au pod créé précédemment, avec une image : afakharany/watcher:latest.

  2. Lancez un Deployment nommé « nginx-deployment » avec 2 réplicas comprenant un conteneur nommé “nginxcont” dans le namespace “kubeops” avec l’image Nginx version 1.17.10 et définissez le port 80 comme port d’exposition.

  3. Augmentez le nombre de réplicas du déploiement à 4 avec la commande kubectl scale

  4. Mettez à jour l’image de votre application à une nouvelle version nginx :1.9.1 avec la commande kubectl set image et observez l’évolution de la mise à jour de l’application

  5. Faites un Rollback de votre mise à jour du déploiement

  6. Exposez votre application avec un service de type Nodeport sur le port 30000 des workers

  7. Créez un Daemonset nommé prometheus-daemonset conteneur nommé prometheus dans le namespace “kubeops” avec l’image prom/node-exporter et définissez le port 9100 comme port d’exposition.

  8. Le Daemonset précédemment créé est présent sur tous les nœuds du cluster. Nous ne souhaitons plus qu’il tourne sur le nœud worker-1. Trouvez la bonne stratégie pour que prometheus-daemonset ne soit présent que sur le worker-0 et le master

  9. Cet exercice vise à montrer l’utilisation d’un secret et d’un configmap.

  10. Nous souhaitons déployer une base de données mysql sur le cluster. Créez un déploiement « mysql-database » avec 1 réplica comprenant un conteneur nommé « database » avec la dernière version de Mysql.

  11. Utilisateurs et droits dans Kubernetes

  12. Créez un pod statique avec une image redis. Rajoutez un request de 100Mi RAM et 100m CPU puis une limite à 200Mi RAM et 150m CPU

  13. Installez le helm chart de wordress disponible sur ce lien. Modifier le type de service par défaut définit dans le fichier values.yaml en NodePort.

  14. TroubleShooting : Faire en sorte que l’application fonctionne correctement et puisse afficher une page web avec le calcul de Pi. Corrigez toutes les erreurs dans le deploymentet les service


# 19: BUT : faire fonctionner l'application sur curl http://QuelqueChose:8020 
 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pi-web
  labels:
    k8s.alterwaylabs.fr: troubleshooting
spec:
  replicas: 0
  selector:
    matchLabels:
      app: pi-web
  template:
    metadata:
      labels:
        app: pi-web-app
    spec:
      containers:
        - image: kiamol/ch05-pi-app
          command: ["donet", "Pi.Web.dll", "-m", "web"]
          name: web
          ports:
            - containerPort: 80
              name: http
          resources:
            limits:
              cpu: "32"
              memory: "128Gi"
          readinessProbe:
            tcpSocket:
              port: 8020
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthy
              port: 80
            periodSeconds: 30
            failureThreshold: 1
 
 
---
 
apiVersion: v1
kind: Service
metadata:
  name: pi-np
  labels:
    k8s.alterwaylabs.fr: troubleshooting
spec:
  selector:
    app: pi-web-pod
  ports:
    - name: http
      port: 8020
      targetPort: app
      nodePort: 8020
  type: NodePort

---

apiVersion: v1
kind: Service
metadata:
  name: pi-lb
  labels:
    k8s.alterwaylabs.fr: troubleshooting
spec:
  selector:
    app: pi-web-pod
  ports:
    - name: http
      port: 8020
      targetPort: app
  type: ClusterIP


20: Solutions pour le lab


Troubleshooting Deployments

Fix:

Deployments

Services

```yaml apiVersion: apps/v1 kind: Deployment metadata: name: pi-web labels: k8s.alterwaylabs.fr: troubleshooting spec: replicas: 1 selector: matchLabels: app: pi-web template: metadata: labels: app: pi-web spec: containers: - image: kiamol/ch05-pi command: [“dotnet”, “Pi.Web.dll”, “-m”, “web”] name: web ports: - containerPort: 80 name: http resources: limits: cpu: “0.5”
memory: “1Gi” readinessProbe: tcpSocket: port: 80 periodSeconds: 5 livenessProbe: httpGet: path: / port: 80 periodSeconds: 30 failureThreshold: 1


apiVersion: v1 kind: Service metadata: name: pi-np labels: k8s.alterwaylabs.fr: troubleshooting spec: selector: app: pi-web ports: - name: http port: 8020 targetPort: http nodePort: 30020 type: NodePort — apiVersion: v1 kind: Service metadata: name: pi-lb labels: k8s.alterwaylabs.fr: troubleshooting spec: selector: app: pi-web ports: - name: http port: 8020 targetPort: http type: LoadBalancer