Loading search data...

Kubernetes - Ecrire ses premiers manifests Kubernetes

Publié le : 10 janvier 2022 | Mis à jour le : 22 janvier 2023

logo kubernetes

Maintenant que nous avons vu les concepts et le fonctionnement de Kubernetes, il va falloir commencer à écrire ses premiers manifests. Mais comment le faire de manière efficace ?

Je ne suis pas l’adepte du copier/coller à gogo et j’aime maîtriser ce que je fais. Je crée mes propres conteneurs Docker, mes propres playbooks et rôles Ansible. Mais comme vous j'utilise beaucoup d’outils devops et je switche sans cesse de projet, je suis parfois un peu perdu dans les syntaxes. Alors je vous propose une méthode qui vous sera utile lors du passage des certifications Kubernetes (CKA, CKS,CKD) ou lors de l’écriture de vos manifests.

Kubectl

Avant de démarrer dans le vif du sujet, je pense qu’il est important de connaitre quelques fonctionnalités du client officiel Kubernetes : Kubectl.

Pour utiliser ces commandes vous pouvez soit utiliser kind ou minkube. Pour ceux qui passent les certifications ma sandbox peut être un bon point de départ.

L’autocomplétion

Kubectl est fourni avec de l’autocomplétion, c’est pratique et ça permet d’aller très vite dans la création des commandes kubectl :

sudo apt install bash-completion
kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null

Redémarrer votre shell et tapez kubectl suivi de tabulation. Vous ferez par exemple pour changer de namespace, on verra la création plus tard, il suffit de taper kubectl -n tab et la liste de tous les namespaces existant sur votre cluster s’affichera.

kubectl -n
default          kube-node-lease  kube-public      kube-system
kubectl config set-context --current --namespace=kube-system
Context "kubernetes-admin@kubernetes" modified.

Pour aller encore plus on peut utiliser un alias :

alias k=kubectl
complete -F __start_kubectl k
k tab
annotate       apply          autoscale      completion

Pour le rendre permanent ajouter ces deux lignes à la fin de votre fichier .bashrc

Obtenir de la documentation

Pour obtenir de l’aide sur la syntaxe de kubectl il suffit de taper classiqement l’option --help :

kubectl --help
# ou pour une commande
kubectl run pod --help

De plus, pour créer des ressources vous pourriez chercher la documentation sur le site Kubernetes, mais il est utile de savoir que Kubectl intègre une documentation complète des ressources qu’il est possible de créer dans un cluster Kubernetes : nodes, pod, service, deployment, nodes

Exemple sur les pods :

kubectl explain pods
KIND:     Pod
VERSION:  v1

DESCRIPTION:
     Pod is a collection of containers that can run on a host. This resource is
     created by clients and scheduled onto hosts.

FIELDS:
   apiVersion   <string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind <string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata     <Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

   spec <Object>
     Specification of the desired behavior of the pod. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

   status       <Object>
     Most recently observed status of the pod. This data may not be up to date.
     Populated by the system. Read-only. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

Pour obtenir la documentation d’un sous-objet il suffit d’ajouter un point plus le nom de l’objet :

Exemple pods.spec.affinity :

kubectl explain pods.spec.affinity
kubectl explain pods.spec.affinity
KIND:     Pod
VERSION:  v1

RESOURCE: affinity <Object>

DESCRIPTION:
     If specified, the pod's scheduling constraints

     Affinity is a group of affinity scheduling rules.

FIELDS:
   nodeAffinity <Object>
     Describes node affinity scheduling rules for the pod.

   podAffinity  <Object>
     Describes pod affinity scheduling rules (e.g. co-locate this pod in the
     same node, zone, etc. as some other pod(s)).

   podAntiAffinity      <Object>
     Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod
     in the same node, zone, etc. as some other pod(s)).

Pour afficher toutes les informations d’objet et de ces sous-objets on utilise l’option --recursive. Attention c’est violent !

Pour trouver la liste des ressources pouvant être déclarées, avec leur kind et versions d’api respectives, il suffit d’utiliser la commande api-ressources :

kubectl api-resources
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
bindings                                       v1                                     true         Binding
componentstatuses                 cs           v1                                     false        ComponentStatus
configmaps                        cm           v1                                     true         ConfigMap
endpoints                         ep           v1                                     true         Endpoints
events                            ev           v1                                     true         Event
limitranges                       limits       v1                                     true         LimitRange
namespaces                        ns           v1                                     false        Namespace
nodes                             no           v1                                     false        Node
persistentvolumeclaims            pvc          v1                                     true         PersistentVolumeClaim
persistentvolumes                 pv           v1                                     false        PersistentVolume
pods                              po           v1                                     true         Pod
....

Ce sont ces informations qui seront déclarées au début d’un manifest.

Gros avantage la documentation est à jour et correspond à celle de votre cluster. Bien sûr, la version de Kubectl doit correspondre à celle du cluster. Pour le vérifier rien de plus simple :

kubectl version
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.1", GitCommit:"86ec240af8cbd1b60bcc4c03c20da9b98005b92e", GitTreeState:"clean", BuildDate:"2021-12-16T11:41:01Z", GoVersion:"go1.17.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.5", GitCommit:"5c99e2ac2ff9a3c549d9ca665e7bc05a3e18f07e", GitTreeState:"clean", BuildDate:"2021-12-16T08:32:32Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}

Mince ce n’est pas mon cas ! Allez on fait la mise à jour de kubectl (on pourrait faire l’upgrade du cluster plutôt) :

apt-cache madison kubectl |grep 1.22.5
apt install kubectl=1.22.5-00
kubectl version
Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.5", GitCommit:"5c99e2ac2ff9a3c549d9ca665e7bc05a3e18f07e", GitTreeState:"clean", BuildDate:"2021-12-16T08:38:33Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.5", GitCommit:"5c99e2ac2ff9a3c549d9ca665e7bc05a3e18f07e", GitTreeState:"clean", BuildDate:"2021-12-16T08:32:32Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}

kubectl get

La commande get permet de rechercher et d’afficher la configuration d’un objet. Regardons un exemple avec un pod du namespace kube-system.

apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/etcd.advertise-client-urls: https://192.168.121.82:2379
    kubernetes.io/config.hash: 413446f10a47cd29048961fc9e411de6
    kubernetes.io/config.mirror: 413446f10a47cd29048961fc9e411de6
    kubernetes.io/config.seen: "2022-01-10T09:31:44.361934226Z"
    kubernetes.io/config.source: file
    seccomp.security.alpha.kubernetes.io/pod: runtime/default
  creationTimestamp: "2022-01-10T09:31:44Z"
  labels:
    component: etcd
    tier: control-plane
  name: etcd-master1
kubectl get pod -A
kubectl -n kube-system get pod etcd-master1 -o yaml

La commande nous retourne toutes les informations du pod ETCD.

Utilisation du mode dry-run de Kubectl

Pour rappel il existe deux modes de création des ressources Kubernetes :

  • le mode déclaratif utilisant ce qu’on appelle des manifests écrit en YAML
  • le mode impératif créant les ressources à la volée.

Il est possible d’utiliser le mode impératif pour créer vos manifests en ajoutant simplement le mode dry-run couplé à la sortie de type yaml.

Création des ressources simples

Création d’un namespace

Pour ceux qui ne connaissent pas les NameSpace je vous renvoie à mon introdution à Kubernetes.

kubectl create namespace test --dry-run=client --output=yaml > namespace.yaml
cat namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: null
  name: test
spec: {}
status: {}

Pour l’appliquer :

kubectl apply -f namespace.yaml
namespace/test created

Une commande kubectl que nous n’avions pas vu auparavant est la commande describe qui permet d’obtenir une description détaillée d’une ressource. Vous allez me demander c’est quoi la différence avec get? Describe affiche plus d’informations et de manière plus simple. Il affiche par exemple les ressources en relation de l’objet et les événements. Elle sera très utile pour debuger !

kubectl describe namespace test

Name:         test
Labels:       kubernetes.io/metadata.name=test
Annotations:  <none>
Status:       Active

No resource quota.

No LimitRange resource.

Dans le cadre d’un Namespace c’est peu utile, mais on peut facilement voir que nous avons défini de limite de ressources dans ce Namespace.

Création d’un pod

Pour créer un pod, ce qui sera très rare, puisque nous n’utiliserons que des objets plus complexes, comme les deployments, nous utiliserons non pas la commande create mais run. En effet, create ne créé que les objets suivants :

  • clusterrole
  • configmap
  • deployment
  • job
  • poddisruptionbudget
  • quota
  • rolebinding
  • service
  • clusterrolebinding
  • cronjob
  • ingress
  • namespace
  • priorityclass
  • role
  • secret
  • serviceaccount

Lancement d’un premier pod qui ne fait rien :)

kubectl run busybox --image=busybox --dry-run=client -o yaml > pod.yaml
cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - image: busybox
    name: busybox
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

On vérifie :

kubectl -n kube-system get pod
NAME                                       READY   STATUS      RESTARTS   AGE
busybox                                    0/1     Completed   0          9s

Il est possible d’ajouter d’autres options lors de la création d’un pod. Il suffit de taper la commande kubectl run pod --help. Un exemple empêchant que le pod ne redémarre tout le temps:

kubectl run busybox --image=busybox --restart=Never --dry-run=client -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - image: busybox
    name: busybox
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
status: {}

Pour ajouter des limites de ressources il faudra ici utiliser la commande set :

kubectl run nginx --image=nginx --dry-run=client -o yaml | kubectl set resources --limits=cpu=200m,memory=512Mi --local -f -  -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources:
      limits:
        cpu: 200m
        memory: 512Mi
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Pour ajouter d’autres paramètres il faudra passer par l’édition du fichier yaml. En ajoutant les paramètres récupérés par la commande kubectl explain :

kubectl explain pods.spec.containers.resources

KIND:     Pod
VERSION:  v1

RESOURCE: resources <Object>

DESCRIPTION:
     Compute Resources required by this container. Cannot be updated. More info:
     https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/

     ResourceRequirements describes the compute resource requirements.

FIELDS:
   limits       <map[string]string>
     Limits describes the maximum amount of compute resources allowed. More
     info:
     https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/

   requests     <map[string]string>
     Requests describes the minimum amount of compute resources required. If
     Requests is omitted for a container, it defaults to Limits if that is
     explicitly specified, otherwise to an implementation-defined value. More
     info:
     https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/

Les liens seront bien utiles lors du passage des certifications, car vous n’aurez droit qu’à ce site.

Pour les ressources on retrouve la syntaxe :

---
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Détruire une ressource Kubernetes

Pour détruire une ressource on utilise la commande delete

kubectl delete pod busybox

Pour détruire un objet qui a été déclaré avec un manifest :

kubectl -n kube-system delete -f pod.yaml

Attention si on détruit un namespace tous les objets de ce NameSpace le seront également et cela prend du temps !

Création d’un déploiement

Un deployement Kubernetes est un objet de niveau supérieur à ceux que nous avons vu jusqu’à maintenant. En effet il est de type Workload (Ressource) qui gère le clycle de vie des pods. Parmi les autres objets de ce type on retrouve le StatefulSet, le DaemonSet et les Jobs. Je vous renvoie à mon introduction sur Kubernetes pour leur définition.

Un déployement permet donc de gérer le cycle de vie d’une application, en définissant les images à utiliser, le nombre de replica (Replicaset) et bien d’autres paramètres.

Nous allons continuer à utiliser les commandes create et run pour générer les manifests.

kubectl create deployment --image=nginx nginx --dry-run=client -o yaml > deployment.yaml
cat deployement.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}
kubectl create -f deployment.yaml
deployment.apps/nginx created

Et voilà nous avons créé notre premier deployment. Etudions le avec la commande describe :

kubectl describe deployment nginx
Name:                   nginx
Namespace:              test
CreationTimestamp:      Fri, 14 Jan 2022 06:17:10 +0000
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=nginx
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-85b98978db (1/1 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  80s   deployment-controller  Scaled up replica set nginx-85b98978db to 1

On peut voir que lorsqu’on n’indique pas de propriété, par exemple la stratégie, Kubernetes en applique une par défaut. Ici, on retrouve la stratégie RollingUpdate qui permet de déployer automatiquement une nouvelle version d’une image tout en conservant les existantes pour qu’il n’y ait pas de coupure de service.

Vous avez certainement remarqué qu’un deployement s’appuie sur un Replicaset. C’est cet objet qui gère l’état des pods. Le nombre de replicas étant géré par le paramètre replicas du manifest. Nous pourrions le modifier dans le manifest mais je vais vous montrer comment le gérer.

Pour gérer le nombre de replicas, à la volée il faut utiliser la commande scale de kubectl

kubectl scale deployment -n test nginx --replicas 2
NAMESPACE       NAME                                        READY   STATUS              RESTARTS      AGE
test            nginx-85b98978db-z9jnx                      0/1     ContainerCreating   0             6s
test            nginx-85b98978db-znb4f                      1/1     Running             0             10m

Une autre méthode via l’utilisation de la commande kubectl edit qui permet de modifier un manifest stocké en base.

kubectl edit deployments.apps nginx

Rechercher replicas, elle est normalement à 2. Passez la à 3 et sauvegarder (commande vi : r et :wq )

deployment.apps/nginx edited
NAME                      READY   STATUS    RESTARTS   AGE
nginx-85b98978db-qf25p    1/1     Running   0          36s
nginx-85b98978db-z9jnx    1/1     Running   0          3m27s
nginx-85b98978db-znb4f    1/1     Running   0          13m

Création d’un service

Un service est un objet permettant d’exposer une application composé de plusieurs pods pouvant tourner sur plusieurs nœuds du cluster Kubernetes.

En effet, les pods possèdent leurs propres adresse IP, qui peuvent changer à tout moment lors du changement de version de l’image ou quand elle meurt suite à une erreur.

Un service est donc la couche d’abstraction qui permet d’accéder facilement aux pods de votre application.

Écrivons notre premier manifest de services exposant notre déploiement créé précédemment :

kubectl expose deployment nginx --port 80 --dry-run=client -o yaml > service.yaml
cat service.yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
status:
  loadBalancer: {}

kubectl apply -f service.yaml
service/nginx created

Cette fois, nous avons utilisé la commande expose pour créer l’objet service. La section spec.ports permet de définir quel port du pod est à exposer et sur quel port de l’IP du service (targetPort).

Affichons l’état du service et les pods :

kubectl describe service nginx
Name:              nginx
Namespace:         test
Labels:            app=nginx
Annotations:       <none>
Selector:          app=nginx
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.108.196.175
IPs:               10.108.196.175
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         192.168.189.76:80,192.168.235.148:80,192.168.235.149:80
Session Affinity:  None
Events:            <none>

webapp-545557d95c-xc2tf   1/1     Running   0          16h
❯ kubectl get pod -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP                NODE      NOMINATED NODE   READINESS GATES
nginx-85b98978db-qf25p    1/1     Running   0          27m   192.168.235.149   worker1   <none>           <none>
nginx-85b98978db-z9jnx    1/1     Running   0          30m   192.168.189.76    worker2   <none>           <none>
nginx-85b98978db-znb4f    1/1     Running   0          40m   192.168.235.148   worker1   <none>           <none>

On voit que le service possède :

  • des entrées référençant les IP des pods: Endpoints
  • un type. Ici ClusterIP

Les «Service Type» de Kubernetes peuvent être de type :

  • ClusterIP: Expose le service sur une IP interne au cluster. Le choix de cette valeur rend le service uniquement accessible à partir du cluster. Il s’agit du ServiceType par défaut.
  • NodePort: Expose le service sur l’IP de chaque nœud sur un port statique (le NodePort).
  • LoadBalancer: Expose le service en externe à l’aide LoadBalancer.
  • ExternalName: Mappe le service au contenu du champ externalName (par exemple foo.bar.example.com), en renvoyant un enregistrement CNAME avec sa valeur.

Je ne vais pas rentrer dans le détail sur ces types, car cela fera le sujet du prochain billet avec les ingress. Un vaste sujet que je ne maitrise qu’en partie pour le moment.

Utilisation des labels et selector

Pour le moment nous n’avions pas parlé des labels. Les labels sont des données de type clé/valeur qui sont attachés aux objets permettant de les identifier. Regardez le contenu du fichier deployement et vous remarquerez qu’il possède dans la partie metadata un tableau labels possédant une étiquette app: nginx.

Pour afficher les labels d’un objet il suffit d’ajouter l’option --show-labels à la commande kubectl get:

kubectl get pod --show-labels
NAME                      READY   STATUS    RESTARTS   AGE   LABELS
nginx-85b98978db-qf25p    1/1     Running   0          15m   app=nginx,pod-template-hash=85b98978db
nginx-85b98978db-z9jnx    1/1     Running   0          18m   app=nginx,pod-template-hash=85b98978db
nginx-85b98978db-znb4f    1/1     Running   0          28m   app=nginx,pod-template-hash=85b98978db

On vient bien notre label app=nginx. Un exemple de commande on va utiliser les labels pour afficher les logs de tous les pods possédant le label app=nginx. Bien pratique :

kubectl logs -f -l app=nginx

/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/01/14 06:27:51 [notice] 1#1: using the "epoll" event method
2022/01/14 06:27:51 [notice] 1#1: nginx/1.21.5
2022/01/14 06:27:51 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/01/14 06:27:51 [notice] 1#1: OS: Linux 5.4.0-91-generic
2022/01/14 06:27:51 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/01/14 06:27:51 [notice] 1#1: start worker processes
2022/01/14 06:27:51 [notice] 1#1: start worker process 30

Perso je préfère utiliser une petite application répondant au nom de stern. Revenons à nos labels.

Comment le service sait qu’elle pod exposé? Et bien il utilise un selector. Ce selector, qui se trouve dans la partie spec, spécifie quel label doit être utilisé pour identifier le(s) pod(s) à exposer.

Pour vérifier que le service est bien exposé, il suffit de se connecter sur un des noeuds du cluster et d’utiliser un bon vieux curl :

ssh master1

curl http://10.108.196.175
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
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>

Pour aller plus loin

Nous avons vu comment générer rapidement des manifests, en attendant le prochain billet sur les objets un peu plus évolué vous pouvez regarder ce billet et testez par exemple popeye qui vous indiquera les faiblesses de vos premiers manifests. Attention ne prenez pas l’habitude de les utiliser si vous passez les certifs !

Mots clés :

devops tutorials kubernetes

Si vous avez apprécié cet article de blog, vous pouvez m'encourager à produire plus de contenu en m'offrant un café sur  Ko-Fi. Vous pouvez aussi passer votre prochaine commande sur amazon, sans que cela ne vous coûte plus cher, via  ce lien . Vous pouvez aussi partager le lien sur twitter ou Linkedin via les boutons ci-dessous. Je vous remercie pour votre soutien.

Autres Articles


Commentaires: