Développement d'applications

Développez une configuration pour déployer votre charge de travail d'application sur IBM Cloud® Kubernetes Service. Kubernetes étant une plateforme d'orchestration de conteneurs extensible qui n'est pas tributaire d'une langue ou d'une application spécifique, vous pouvez exécuter diverses charges de travail, notamment des applications sans ou avec état et des applications de traitement de données écrites dans la langue de votre choix.

Spécification des exigences relatives à votre application dans votre fichier YAML

Dans Kubernetes, vous décrivez votre application dans un fichier YAML qui déclare la configuration de l'objet Kubernetes. Le serveur d'API Kubernetes traite ensuite le fichier YAML et stocke la configuration et l'état requis de l'objet dans le magasin de données etcd. Le planificateur de Kubernetes planifie vos charges de travail sur les noeuds worker au sein de votre cluster, en tenant compte de la spécification dans votre fichier YAML, de toutes les règles de cluster définies par l'administrateur et de la capacité disponible du cluster.

Consultez une copie du fichier YAML complet. Puis, consultez les sections suivantes pour comprendre comment améliorer le déploiement de votre application.

Vous voulez en savoir plus sur la façon dont les objets Kubernetes fonctionnent ensemble pour votre déploiement ? Consultez la rubrique Description des objets Kubernetes pour des applications.

Métadonnées de déploiement de base

Utilisez la version d'API appropriée correspondant au type (kind) de l'objet Kubernetes que vous déployez. La version d'API détermine les fonctions prises en charge pour l'objet Kubernetes qui vous sont accessibles. Le nom que vous indiquez dans la section metadata est le nom de l'objet et non pas son libellé. Vous utilisez le nom lors de l'interaction avec votre objet, tel que kubectl get deployment <name>.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty

Ensemble de répliques

Pour augmenter la disponibilité de votre application, vous pouvez spécifier un ensemble de répliques dans votre déploiement. Dans un ensemble de répliques, vous définissez le nombre d'instances de votre application que vous souhaitez déployer. Les ensembles de répliques sont gérés et surveillés par votre déploiement Kubernetes. En cas de défaillance d'une instance d'application, Kubernetes lance automatiquement une nouvelle instance de votre application pour conserver le nombre indiqué d'instances de l'application.

spec:
  replicas: 3

Labels

Avec les libellés, vous pouvez marquer différents types de ressources dans votre cluster avec la même paire clé-valeur (key: value). Vous pouvez ensuite indiquer le sélecteur pour correspondre au libellé de sorte que vous puissiez créer ces autres ressources par dessus. Si vous envisagez d'exposer votre application au public, vous devez utiliser un libellé qui corresponde au sélecteur que vous spécifiez dans le service. Dans l'exemple, la spécification de déploiement utilise le modèle qui correspond au libellé app: wasliberty.

Vous pouvez extraire des objets labellisés dans votre cluster, pour voir des composants staging ou production. Par exemple, répertoriez toutes les ressources avec le libellé env: production sur tous les espaces de noms dans le cluster. Remarque : vous devez accéder à tous les espaces de noms pour exécuter cette commande.

kubectl get all -l env=production --all-namespaces
selector:
  matchLabels:
    app: wasliberty
template:
  metadata:
    labels:
      app: wasliberty

Affinité

Spécifiez l'affinité (colocalisation) lorsque vous souhaitez avoir davantage de contrôle sur les nœuds de travail sur lesquels les pods sont planifiés. L'affinité n'affecte les pods qu'au moment de la planification. Par exemple, pour étendre le déploiement sur les noeuds worker au lieu de permettre la planification des pods sur le même noeud, utilisez l'option podAntiAffinity avec vos clusters standard. Vous pouvez définir deux types d'anti-affinité de pod : préféré (preferred) ou obligatoire (required).

Pour plus d'informations, consultez la documentation d' Kubernetes relative à l'affectation de pods à des nœuds.

Anti-affinité obligatoire
vous ne pouvez déployer que le nombre de répliques pour lesquelles vous disposez de noeuds worker. Par exemple, si vous disposez de trois noeuds worker dans votre cluster mais que vous définissez cinq répliques dans votre fichier YAML, seules trois répliques sont déployées. Chaque réplique réside sur un noeud worker distinct. Les deux autres répliques restent en attente. Si vous ajoutez un autre noeud worker dans votre cluster, l'une de ces répliques restantes se déploie automatiquement sur ce nouveau noeud. En cas de défaillance d'un noeud worker, le pod n'est pas replanifié car la règle d'affinité est obligatoire. Pour un exemple de fichier YAML avec l'option required, consultez l'application Liberty avec l'anti-affinité de pod requise.
Anti-affinité préférée
vous pouvez déployer vos pods sur les noeuds avec de la capacité disponible, ce qui offre davantage de flexibilité pour votre charge de travail. Dès que c'est possible, les pods sont planifiés sur différents noeuds worker. Par exemple, si vous disposez de trois noeuds worker avec une capacité suffisante dans votre cluster, cinq pods de réplique peuvent être planifiés sur ces noeuds. Cependant, si vous ajoutez deux nœuds de travail supplémentaires à votre cluster, la règle d’affinité n’oblige pas les deux pods supplémentaires qui s’exécutent sur les nœuds existants à se réaffecter sur le nœud disponible.
Affinité des noeuds worker
vous pouvez configurer votre déploiement pour qu'il s'exécute uniquement sur certains noeuds worker, par exemple des noeuds bare metal. Pour plus d'informations, voir Déploiement d'applications sur des noeuds worker spécifiques à l'aide de libellés.

Exemple d'anti-affinité préférée

spec:
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - wasliberty
          topologyKey: kubernetes.io/hostname

Image de conteneur

Indiquez l'image que vous envisagez d'utiliser pour vos conteneurs, l'emplacement de l'image ainsi que sa politique d'extraction. Si vous ne spécifiez pas de balise d'image, par défaut, l'image balisée est extraire latest.

Evitez d'utiliser la balise "latest" pour les charges de travail de production. Vous n'avez peut-être pas testé votre charge de travail avec la dernière image si vous utilisez un référentiel public ou privé, de type Docker Hub ou IBM Cloud Container Registry.

Par exemple, pour répertorier les balises des images IBM publiques :

  1. Basculez dans la région de la base de registre globale.
    ibmcloud cr region-set global
    
  2. Répertoriez les images IBM.
    ibmcloud cr images --include-ibm
    

La règle par défaut imagePullPolicy est définie avec IfNotPresent, ce qui indique que l'image est extraite uniquement si elle n'est pas déjà présente en local. Pour que l'image soit extraite chaque fois que le conteneur démarre, spécifiez imagePullPolicy: Always.

containers:
- name: wasliberty
  image: icr.io/ibm/liberty:webProfile8
  imagePullPolicy: Always

Port du service de l'application

Sélectionnez un port de conteneur pour ouvrir les services de l'application sur ce port. Pour voir les ports qui doivent être ouverts, reportez-vous aux spécifications de votre application ou au fichier Dockerfile. Le port est accessible depuis le réseau privé mais pas depuis une connexion de réseau public. Pour exposer l'application au public, vous devez créer un service NodePort, LoadBalancer ou Ingress. Vous utilisez ce même numéro de port lorsque vous créez un objet Service.

Le port 25 est bloqué pour tous les services dans IBM Cloud.

ports:
- containerPort: 9080

Demandes et limites de ressources

Les administrateurs de clusters s'assurent que les équipes qui partagent un cluster n'utilisent pas plus que leur part équitable de ressources de calcul (mémoire et CPU) en créant un objet ResourceQuota pour chaque espace de noms Kubernetes au sein du cluster. Si l'administrateur du cluster définit un quota de ressources de calcul, chaque conteneur dans le modèle de déploiement doit spécifier des demandes et des limites de ressources pour la mémoire et le nombre de processeurs, autrement la création de pod échouera.

  1. Vérifiez si un quota de ressources est défini pour un espace de noms.
    kubectl get quota --namespace=<namespace>
    
  2. Examinez quelles sont les limites du quota.
    kubectl describe quota <quota_name> --namespace=<namespace>
    

Même si aucun quota de ressources n'est défini, vous pouvez inclure des demandes et des limites de ressources dans votre déploiement pour une meilleure gestion des ressources de noeuds worker.

Si un conteneur dépasse ses limites, il peut être redémarré ou risque d'échouer. Si un conteneur dépasse une demande, son pod peut être expulsé si le noeud worker est à cours de la ressource demandée qui fait l'objet du dépassement. Pour plus d'informations sur le traitement des incidents, voir Les pods ne parviennent pas à redémarrer à plusieurs reprises ou sont retirés de manière imprévisible.

Demande
quantité minimale de ressources réservée par le planificateur à l'usage du conteneur. Si cette quantité est égale à la limite, la demande est assurée. Si la quantité est inférieure à la limite, la demande est toujours assurée mais le planificateur peut utiliser le différentiel entre la demande et la limite pour compléter les ressources d'autres conteneurs.
Limite
quantité maximale de ressources pouvant être consommées par le conteneur. Si la quantité de ressources totale qui est utilisée sur les conteneurs dépasse la quantité disponible sur le noeud worker, les conteneurs peuvent être expulsés pour libérer de l'espace. Pour éviter cela, définissez la demande de ressources avec une valeur égale à la limite du conteneur. Si aucune limite n'est indiquée, la valeur par défaut correspond à la capacité du noeud worker.

Pour plus d'informations, consultez la documentation d' Kubernetes.

resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "1024Mi"
    cpu: "1000m"

Sondes Liveness Probe et Readiness Probe

Par défaut, Kubernetes envoie le trafic à vos pods d'application après le démarrage de tous les conteneurs dans le pod et redémarre les conteneurs lorsqu'ils plantent. Cependant, vous pouvez définir des diagnostics d'intégrité pour améliorer la robustesse du routage du trafic de service.

Par exemple, votre application peut avoir un délai de démarrage. Les processus d'application peuvent commencer avant que l'application ne soit totalement prête, ce qui peut affecter les réponses, notamment lors d'une augmentation d'échelle sur de nombreuses instances. Avec les diagnostics d'intégrité, votre système peut savoir si votre application est en cours d'exécution et prête à recevoir des demandes. En définissant ces sondes, vous pouvez également éviter des durées d'indisponibilité lorsque vous effectuez une mise à jour en continu de votre application. Vous pouvez définir deux types de diagnostic d'intégrité avec les sondes Liveness Probe et Readiness Probe.

Sonde Liveness Probe
configurez une sonde Liveness Probe pour vérifier si le conteneur est en cours d'exécution. En cas d'échec de cette sonde, le conteneur est redémarré. Si le conteneur ne spécifie aucune sonde Liveness Probe, la sonde réussit car elle suppose que le conteneur est actif lorsqu'il est à l'état Running.
Sondage sur l'état de préparation
configurez une sonde Readiness Probe pour vérifier si le conteneur est prêt à recevoir des demandes et du trafic externe. Si la sonde échoue, l'adresse IP du pod est retirée des adresses IP utilisables pour les services, qui correspondent au pod, mais le conteneur n'est pas redémarré. Il est particulièrement important de configurer un test de disponibilité avec un délai initial si le démarrage de votre application prend un certain temps. Avant le délai d'attente, la sonde ne démarre pas, ce qui laisse au conteneur le temps de démarrer. Si le conteneur ne spécifie aucune sonde Readiness Probe, la sonde réussit car elle suppose que le conteneur est actif lorsqu'il est à l'état Running.

Vous pouvez définir les sondes sous forme de commandes, de demandes HTTP ou de sockets TCP. L'exemple suivant utilise des demandes HTTP. Octroyez plus de temps à la sonde Liveness Probe qu'à la sonde Readiness Probe. Pour plus d'informations, consultez la documentation d' Kubernetes.

livenessProbe:
  httpGet:
    path: /
    port: 9080
  initialDelaySeconds: 300
  periodSeconds: 15
readinessProbe:
  httpGet:
    path: /
    port: 9080
  initialDelaySeconds: 45
  periodSeconds: 5

Objet PodDisruptionBudget

Pour améliorer la disponibilité de votre application, vous pouvez contrôler la manière dont celle-ci réagit aux perturbations en fonction du type de disponibilité souhaité à l'aide d'un objet PodDisruptionBudget.

Un objet PodDisruptionBudget peut vous aider à planifier le comportement de votre application lors d'interruptions volontaires, par exemple lorsque vous lancez un redémarrage direct en mettant à jour le déploiement de l'application, ou d'interruptions involontaires, telles qu'une panique du noyau. minAvailable : vous pouvez spécifier le nombre ou le pourcentage de pods qui doivent toujours être disponibles après une interruption.

maxUnavailable
vous pouvez spécifier le nombre ou le pourcentage de pods qui peuvent être non disponibles après une interruption. L'exemple utilise la valeur maxUnavailable: 1.
selector
Saisissez le nom du label pour sélectionner l'ensemble de pods auquel il PodDisruptionBudget s'applique. Notez que si vous avez utilisé ce même libellé dans d'autres déploiements de pod, le pod s'applique également à ces derniers.

Pour plus d'informations, consultez la documentation d' Kubernetes.

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: wasliberty
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: wasliberty

Service d'exposition de l'application

Vous pouvez créer un service pour exposer votre application. Dans la section spec, vérifiez que les valeurs de port et de libellés (labels) correspondent à celles que vous avez utilisées dans le déploiement. Le service expose des objets qui correspondent au libellé, tels que app: wasliberty dans l'exemple suivant.

  • Par défaut, un service utilise ClusterIP, ce qui rend le service accessible uniquement au sein du cluster, mais pas en dehors de celui-ci.
  • Vous pouvez créer un service NodePort, LoadBalancer ou Ingress pour exposer l'application au public. Ces services ont deux adresses IP, une externe et une interne. Lorsque le trafic est reçu sur l'adresse IP externe, il est transmis à l'adresse IP du cluster interne. Ensuite, le trafic est routé depuis l'adresse IP interne du cluster vers l'adresse IP du conteneur de l'application.
  • Dans cet exemple, un service NodePort est utilisé pour exposer le service en dehors du cluster. Pour plus d'informations sur la configuration de l'accès externe, voir Choisir un service NodePort, LoadBalancer ou Ingress.
apiVersion: v1
kind: Service
metadata:
  name: wasliberty
  labels:
    app: wasliberty
spec:
  ports:
  - port: 9080
  selector:
    app: wasliberty
    type: NodePort

Si vous devez déployer des pods hostNetwork pour écouter des ports spécifiques ou pour utiliser un hostPort pour exposer vos pods d'application sur un port spécifique du noeud worker, utilisez un port dans la plage 11000-11200. IBM Cloud Kubernetes Service désigne la plage de ports 11000-11200 sur les nœuds worker à cette fin afin d'éviter les conflits avec les ports locaux et les autres ports utilisés par IBM Cloud Kubernetes Service. Etant donné que les pods hostNetwork et les ports hostPorts font référence à une adresse IP de noeud worker spécifique, les pods ne peuvent s'exécuter que sur ce noeud worker. Si un imprévu se produit, comme la suppression du nœud worker en cours ou un manque de ressource, votre pod ne peut pas être replanifié. Si vous souhaitez exposer le port d'un pod sur le noeud worker, utilisez plutôt un serviceNodePort. Pour plus d'informations, consultez la documentation Kubernetes sur les meilleures pratiques.

Objets ConfigMap pour les variables d'environnement de conteneur

Les objets ConfigMap fournissent des informations de configuration non sensibles pour vos charges de travail de déploiement.

L'exemple suivant illustre comment référencer des valeurs de votre ConfigMap en tant que variables d'environnement dans la section de spécification (spec) de conteneurs de votre fichier YAML de déploiement. En référençant des valeurs à partir de votre objet ConfigMap, vous pouvez découpler ces informations dans votre déploiement pour que votre application conteneurisée soit toujours portable.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  template:
    ...
    spec:
      ...
      containers:
      - name: wasliberty
        ...
        env:
          - name: VERSION
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: VERSION
          - name: LANGUAGE
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: LANGUAGE
        ...
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: wasliberty
  labels:
    app: wasliberty
data:
  VERSION: "1.0"
  LANGUAGE: en

Objets Secret pour les variables d'environnement de conteneur

Les objets Secret fournissent des informations de configuration sensibles telles que des mots de passe pour vos charges de travail de déploiement.

L'exemple suivant illustre comment référencer des valeurs de votre secret en tant que variables d'environnement dans la section de spécification (spec) de conteneurs de votre fichier YAML de déploiement. Vous pouvez également monter l'objet Secret en tant que volume. En référençant des valeurs à partir de votre objet Secret, vous pouvez découpler ces informations dans votre déploiement pour que votre application conteneurisée soit toujours portable.

Pour une gestion centralisée de tous les secrets entre les clusters et une injection au moment de l'exécution de l'application, essayez IBM Cloud Secrets Manager.

apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: wasliberty
  spec:
    replicas: 3
    template:
      ...
      spec:
        ...
        containers:
        - name: wasliberty
          ...
          env:
          - name: username
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: username
          - name: password
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: password
          ...
  ---
  apiVersion: v1
  kind: Secret
  metadata:
    name: wasliberty
    labels:
      app: wasliberty
  type: Opaque
  data:
    username: dXNlcm5hbWU=
    password: cGFzc3dvcmQ=

Volumes persistants pour le stockage de conteneur

Les volumes persistants (PV) interagissent avec le stockage physique pour offrir un stockage de données persistant pour vos charges de travail de conteneur.

L'exemple suivant illustre comment ajouter du stockage persistant à votre application. Pour mettre à disposition du stockage persistant, vous créez une réservation de volume persistant (PVC) pour décrire le type et la taille du stockage de fichiers dont vous voulez disposer. Une fois le PVC créé, le volume persistant et le stockage physique sont automatiquement créés à l'aide de l'allocation dynamique. En référençant la PVC avec votre fichier YAML de déploiement, le stockage est automatiquement monté sur votre pod d'application. Lorsque le conteneur de votre pod écrit des données dans le répertoire du chemin de montage /test, les données sont stockées sur l'instance de stockage de fichiers NFS. Pour connaître les options d'autres types de stockage que vous pouvez mettre à disposition, voir Planification de stockage persistant à haute disponibilité.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  template:
    ...
    spec:
      ...
      containers:
      - name: wasliberty
        ...
        volumeMounts:
        - name: pvmount
          mountPath: /test
      volumes:
      - name: pvmount
        persistentVolumeClaim:
          claimName: wasliberty
        ...
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wasliberty
  annotations:
    volume.beta.kubernetes.io/storage-class: "ibmc-file-bronze"
  labels:
    billingType: "hourly"
    app: wasliberty
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 24Gi

Exemple de fichier YAML de déploiement complet

L'exemple suivant illustre une copie du fichier YAML de déploiement qui a été explicité section par section précédemment. Vous pouvez également télécharger le fichier YAML à l'adresse GitHub.

Pour appliquer le fichier YAML :

kubectl apply -f file.yaml [-n <namespace>]

Exemple de fichier YAML

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wasliberty
  template:
    metadata:
      labels:
        app: wasliberty
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - wasliberty
              topologyKey: kubernetes.io/hostname
      containers:
      - name: wasliberty
        image: icr.io/ibm/liberty:latest
        env:
          - name: VERSION
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: VERSION
          - name: LANGUAGE
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: LANGUAGE
          - name: username
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: username
          - name: password
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: password
        ports:
          - containerPort: 9080
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1024Mi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /
            port: 9080
          initialDelaySeconds: 300
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /
            port: 9080
          initialDelaySeconds: 45
          periodSeconds: 5
        volumeMounts:
        - name: pvmount
          mountPath: /test
      volumes:
      - name: pvmount
        persistentVolumeClaim:
          claimName: wasliberty
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: wasliberty
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: wasliberty
---
apiVersion: v1
kind: Service
metadata:
  name: wasliberty
  labels:
    app: wasliberty
spec:
  ports:
  - port: 9080
  selector:
    app: wasliberty
  type: NodePort
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: wasliberty
  labels:
    app: wasliberty
data:
  VERSION: "1.0"
  LANGUAGE: en
---
apiVersion: v1
kind: Secret
metadata:
  name: wasliberty
  labels:
    app: wasliberty
type: Opaque
data:
  username: dXNlcm5hbWU=
  password: cGFzc3dvcmQ=
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wasliberty
  annotations:
    volume.beta.kubernetes.io/storage-class: "ibmc-file-bronze"
  labels:
    billingType: "hourly"
    app: wasliberty
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 24Gi