Déployer et mettre à jour une application .NET Core dans Google Kubernetes Engine

1. Présentation

Microsoft .NET Core est une version Open Source et multiplate-forme de .NET qui peut s'exécuter de manière native dans des conteneurs. .NET Core est disponible sur GitHub et est géré par Microsoft et la communauté .NET. Cet atelier déploie une application .NET Core conteneurisée dans Google Kubernetes Engine (GKE).

Cet atelier suit un modèle de développement classique dans lequel les applications sont développées dans l'environnement local d'un développeur, puis déployées en production. Dans la première partie de l'atelier, un exemple d'application .NET Core est validé à l'aide d'un conteneur exécuté dans Cloud Shell. Une fois validée, l'application est déployée sur Kubernetes à l'aide de GKE. L'atelier inclut les étapes de création d'un cluster GKE.

Dans la deuxième partie de l'atelier, une modification mineure est apportée à l'application pour afficher le nom d'hôte du conteneur qui exécute cette instance d'application. L'application mise à jour est ensuite validée dans Cloud Shell, et le déploiement est mis à jour pour utiliser la nouvelle version. L'illustration suivante montre la séquence d'activités de cet atelier :

Diagramme de séquence de la démonstration

Coûts

Si vous suivez exactement les instructions de cet atelier, les coûts normaux des services suivants s'appliqueront :

2. Préparation

Prérequis

Pour réaliser cet atelier, vous devez disposer d'un compte et d'un projet Google Cloud. Pour obtenir des instructions plus détaillées sur la création d'un projet, consultez cet atelier de programmation.

Cet atelier utilise Docker exécuté dans Cloud Shell, qui est disponible via la console Google Cloud et est préconfiguré avec de nombreux outils utiles, tels que gcloud et Docker. La procédure d'accès à Cloud Shell est présentée ci-dessous. Cliquez sur l'icône Cloud Shell en haut à droite pour l'afficher dans le volet inférieur de la fenêtre de la console.

Cloud Shell

Autres options de configuration pour le cluster GKE (facultatif)

Cet atelier nécessite un cluster Kubernetes. Dans la section suivante, un cluster GKE avec une configuration simple est créé. Cette section présente des commandes gcloud qui fournissent d'autres options de configuration à utiliser lors de la création d'un cluster Kubernetes à l'aide de GKE. Par exemple, les commandes ci-dessous permettent d'identifier différents types de machines, zones et même GPU (accélérateurs).

  • Répertoriez les types de machines à l'aide de la commande gcloud compute machine-types list.
  • Répertoriez les GPU à l'aide de la commande gcloud compute accelerator-types list.
  • Listez les zones de calcul à l'aide de la commande gcloud compute zones list.
  • Obtenir de l'aide sur n'importe quelle commande gcloud gcloud container clusters --help
    • Par exemple, vous trouverez ici des informations sur la création d'un cluster Kubernetes gcloud container clusters create --help.

Pour obtenir la liste complète des options de configuration pour GKE, consultez ce document.

Préparer la création du cluster Kubernetes

Dans Cloud Shell, vous devez définir des variables d'environnement et configurer le client gcloud. Pour ce faire, exécutez les commandes suivantes.

export PROJECT_ID=YOUR_PROJECT_ID
export DEFAULT_ZONE=us-central1-c

gcloud config set project ${PROJECT_ID}
gcloud config set compute/zone ${DEFAULT_ZONE}

Créer un cluster GKE

Comme cet atelier déploie l'application .NET Core sur Kubernetes, vous devez créer un cluster. Utilisez la commande suivante pour créer un cluster Kubernetes dans Google Cloud à l'aide de GKE.

gcloud container clusters create dotnet-cluster \
  --zone ${DEFAULT_ZONE} \
  --num-nodes=1 \
  --node-locations=${DEFAULT_ZONE},us-central1-b \
  --enable-stackdriver-kubernetes \
  --machine-type=n1-standard-1 \
  --workload-pool=${PROJECT_ID}.svc.id.goog \
  --enable-ip-alias
  • --num-nodes est le nombre de nœuds à ajouter par zone. Vous pourrez le modifier ultérieurement.
  • --node-locations est une liste de zones séparées par une virgule. Dans ce cas, la zone que vous identifiez dans la variable d'environnement ci-dessus et us-central1-b sont utilisées.
    • REMARQUE : Cette liste ne peut pas contenir de doublons.
  • --workload-pool établit Workload Identity afin que les charges de travail GKE puissent accéder aux services Google Cloud.

Pendant la création du cluster, le message suivant s'affiche :

Creating cluster dotnet-cluster in us-central1-b... Cluster is being deployed...⠼

Configurer kubectl

La CLI kubectl est le principal moyen d'interagir avec un cluster Kubernetes. Pour l'utiliser avec le nouveau cluster qui vient d'être créé, vous devez le configurer pour qu'il s'authentifie auprès du cluster. Pour ce faire, exécutez la commande suivante :

$ gcloud container clusters get-credentials dotnet-cluster --zone ${DEFAULT_ZONE}
Fetching cluster endpoint and auth data.
kubeconfig entry generated for dotnet-cluster.

Vous devriez maintenant pouvoir utiliser kubectl pour interagir avec le cluster.

$ kubectl get nodes
NAME                                            STATUS   ROLES    AGE     VERSION
gke-dotnet-cluster-default-pool-02c9dcb9-fgxj   Ready    <none>   2m15s   v1.16.13-gke.401
gke-dotnet-cluster-default-pool-ed09d7b7-xdx9   Ready    <none>   2m24s   v1.16.13-gke.401

3. Tester localement et confirmer la fonctionnalité souhaitée

Cet atelier utilise les images de conteneur suivantes du dépôt .NET officiel sur Docker Hub.

Exécuter le conteneur localement pour vérifier son fonctionnement

Dans Cloud Shell, vérifiez que Docker est opérationnel et que le conteneur .NET fonctionne comme prévu en exécutant la commande Docker suivante :

$ docker run --rm mcr.microsoft.com/dotnet/samples

      Hello from .NET!
      __________________
                        \
                        \
                            ....
                            ....'
                            ....
                          ..........
                      .............'..'..
                  ................'..'.....
                .......'..........'..'..'....
                ........'..........'..'..'.....
              .'....'..'..........'..'.......'.
              .'..................'...   ......
              .  ......'.........         .....
              .                           ......
              ..    .            ..        ......
            ....       .                 .......
            ......  .......          ............
              ................  ......................
              ........................'................
            ......................'..'......    .......
          .........................'..'.....       .......
      ........    ..'.............'..'....      ..........
    ..'..'...      ...............'.......      ..........
    ...'......     ...... ..........  ......         .......
  ...........   .......              ........        ......
  .......        '...'.'.              '.'.'.'         ....
  .......       .....'..               ..'.....
    ..       ..........               ..'........
            ............               ..............
          .............               '..............
          ...........'..              .'.'............
        ...............              .'.'.............
        .............'..               ..'..'...........
        ...............                 .'..............
        .........                        ..............
          .....
  
Environment:
.NET 5.0.1-servicing.20575.16
Linux 5.4.58-07649-ge120df5deade #1 SMP PREEMPT Wed Aug 26 04:56:33 PDT 2020

Confirmer le fonctionnement de l'application Web

Vous pouvez également valider un exemple d'application Web dans Cloud Shell. La commande d'exécution Docker ci-dessous crée un conteneur qui expose le port 80 et le mappe sur le port 8080 de localhost. N'oubliez pas que localhost se trouve dans Cloud Shell dans ce cas.

$ docker run -it --rm -p 8080:80 --name aspnetcore_sample mcr.microsoft.com/dotnet/samples:aspnetapp
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
      Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {64a3ed06-35f7-4d95-9554-8efd38f8b5d3} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app

Comme il s'agit d'une application Web, elle doit être affichée et validée dans un navigateur Web. La section suivante explique comment procéder dans Cloud Shell à l'aide de l'aperçu sur le Web.

4. Accéder aux services depuis Cloud Shell à l'aide de l'aperçu sur le Web

Cloud Shell propose la fonctionnalité Aperçu sur le Web, qui permet d'utiliser un navigateur pour interagir avec les processus exécutés dans l'instance Cloud Shell.

Utiliser "Aperçu sur le Web" pour afficher des applications dans Cloud Shell

Dans Cloud Shell, cliquez sur le bouton "Aperçu sur le Web", puis sélectionnez Prévisualiser sur le port 8080 (ou sur le port défini pour l'aperçu sur le Web).

Cloud Shell

Une fenêtre de navigateur s'ouvre avec une adresse semblable à celle-ci :

https://8080-cs-754738286554-default.us-central1.cloudshell.dev/

Afficher l'exemple d'application .NET à l'aide de l'aperçu sur le Web

Vous pouvez maintenant afficher l'application exemple que vous avez lancée à l'étape précédente en démarrant l'aperçu sur le Web et en chargeant l'URL fournie. Voici un exemple :

Capture d'écran de l'application .NET V1

5. Déployer sur Kubernetes

Créer le fichier YAML et l'appliquer

L'étape suivante nécessite un fichier YAML décrivant deux ressources Kubernetes : un déploiement et un service. Créez un fichier nommé dotnet-app.yaml dans Cloud Shell et ajoutez-y le contenu suivant.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dotnet-deployment
  labels:
    app: dotnetapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dotnetapp
  template:
    metadata:
      labels:
        app: dotnetapp
    spec:
      containers:
      - name: dotnet
        image: mcr.microsoft.com/dotnet/samples:aspnetapp
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: dotnet-service
spec:
    selector:
      app: dotnetapp
    ports:
      - protocol: TCP
        port: 8080
        targetPort: 80

Utilisez maintenant kubectl pour appliquer ce fichier à Kubernetes.

$ kubectl apply -f dotnet-app.yaml
deployment.apps/dotnet-deployment created
service/dotnet-service created

Notez les messages indiquant que les ressources souhaitées ont été créées.

Explorer les ressources obtenues

Nous pouvons utiliser la CLI kubectl pour examiner les ressources créées ci-dessus. Commençons par examiner les ressources de déploiement et vérifier que le nouveau déploiement est bien présent.

$ kubectl get deployment
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
dotnet-deployment   3/3     3            3           80s

Examinez ensuite les ReplicaSets. Un ReplicaSet doit avoir été créé par le déploiement ci-dessus.

$ kubectl get replicaset
NAME                           DESIRED   CURRENT   READY   AGE
dotnet-deployment-5c9d4cc4b9   3         3         3       111s

Enfin, examinez les pods. Le déploiement indiquait qu'il devait y avoir trois instances. La commande ci-dessous devrait indiquer qu'il y a trois instances. L'option -o wide est ajoutée pour que les nœuds sur lesquels ces instances sont exécutées soient affichés.

$ kubectl get pod -o wide
NAME                                 READY   STATUS    RESTARTS   AGE     IP          NODE                                            NOMINATED NODE   READINESS GATES
dotnet-deployment-5c9d4cc4b9-cspqd   1/1     Running   0          2m25s   10.16.0.8   gke-dotnet-cluster-default-pool-ed09d7b7-xdx9   <none>           <none>
dotnet-deployment-5c9d4cc4b9-httw6   1/1     Running   0          2m25s   10.16.1.7   gke-dotnet-cluster-default-pool-02c9dcb9-fgxj   <none>           <none>
dotnet-deployment-5c9d4cc4b9-vvdln   1/1     Running   0          2m25s   10.16.0.7   gke-dotnet-cluster-default-pool-ed09d7b7-xdx9   <none>           <none>

Examiner la ressource Service

Une ressource Service dans Kubernetes est un équilibreur de charge. Les points de terminaison sont déterminés par les étiquettes des pods. Ainsi, dès que de nouveaux pods sont ajoutés au déploiement via l'opération kubectl scale deployment ci-dessus, les pods résultants sont immédiatement disponibles pour les requêtes gérées par ce service.

La commande suivante doit afficher la ressource Service.

$ kubectl get svc
NAME             TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
dotnet-service   ClusterIP   10.20.9.124   <none>        8080/TCP   2m50s
...

Vous pouvez afficher plus de détails sur le service à l'aide de la commande suivante.

$ kubectl describe svc dotnet-service
Name:              dotnet-service
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=dotnetapp
Type:              ClusterIP
IP:                10.20.9.124
Port:              <unset>  8080/TCP
TargetPort:        80/TCP
Endpoints:         10.16.0.7:80,10.16.0.8:80,10.16.1.7:80
Session Affinity:  None
Events:            <none>

Notez que le service est de type ClusterIP. Cela signifie que n'importe quel pod du cluster peut résoudre le nom du service, dotnet-service, en son adresse IP. Les requêtes envoyées au service seront équilibrées entre toutes les instances (pods). La valeur Endpoints ci-dessus indique les adresses IP des pods actuellement disponibles pour ce service. Comparez-les aux adresses IP des pods qui ont été générées ci-dessus.

Vérifier l'application en cours d'exécution

À ce stade, l'application est active et prête à recevoir les requêtes des utilisateurs. Pour y accéder, utilisez un proxy. La commande suivante crée un proxy local qui accepte les requêtes sur le port 8080 et les transmet au cluster Kubernetes.

$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080

Utilisez maintenant l'aperçu sur le Web dans Cloud Shell pour accéder à l'application Web.

Ajoutez l'élément suivant à l'URL générée par l'aperçu sur le Web : /api/v1/namespaces/default/services/dotnet-service:8080/proxy/. Le résultat devrait ressembler à ceci :

https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/

Félicitations ! Vous avez déployé une application .NET Core sur Google Kubernetes Engine. Nous allons maintenant modifier l'application et la redéployer.

6. Modifier l'application

Dans cette section, l'application sera modifiée pour afficher l'hôte sur lequel l'instance est exécutée. Cela permettra de confirmer que l'équilibrage de charge fonctionne et que les pods disponibles répondent comme prévu.

Obtenir le code source

git clone https://github.com/dotnet/dotnet-docker.git
cd dotnet-docker/samples/aspnetapp/

Mettre à jour l'application pour inclure le nom d'hôte

vi aspnetapp/Pages/Index.cshtml
    <tr>
        <td>Host</td>
        <td>@Environment.MachineName</td>
    </tr>

Créer une image de conteneur et la tester localement

Créez la nouvelle image de conteneur avec le code mis à jour.

docker build --pull -t aspnetapp:alpine -f Dockerfile.alpine-x64 .

Comme précédemment, testez la nouvelle application en local.

$ docker run --rm -it -p 8080:80 aspnetapp:alpine
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
      Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {f71feb13-8eae-4552-b4f2-654435fff7f8} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app

Comme précédemment, l'application est accessible à l'aide de l'aperçu sur le Web. Cette fois, le paramètre "Host" devrait être visible, comme illustré ici :

Cloud Shell

Ouvrez un nouvel onglet dans Cloud Shell et exécutez docker ps pour vérifier que l'ID du conteneur correspond à la valeur de l'hôte indiquée ci-dessus.

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
ab85ce11aecd        aspnetapp:alpine    "./aspnetapp"       2 minutes ago       Up 2 minutes        0.0.0.0:8080->80/tcp   relaxed_northcutt

Ajouter un tag à l'image et la transférer pour qu'elle soit disponible pour Kubernetes

L'image doit être taguée et transférée pour que Kubernetes puisse l'extraire. Commencez par lister les images de conteneurs et identifiez l'image souhaitée.

$ docker image list
REPOSITORY                                         TAG                 IMAGE ID            CREATED             SIZE
aspnetapp                                          alpine              95b4267bb6d0        6 days ago          110MB

Ensuite, ajoutez un tag à cette image et transférez-la vers Google Container Registry. En utilisant l'ID d'image ci-dessus, cela ressemblera à ceci :

docker tag 95b4267bb6d0 gcr.io/${PROJECT_ID}/aspnetapp:alpine
docker push gcr.io/${PROJECT_ID}/aspnetapp:alpine

7. Redéployer l'application mise à jour

Modifier le fichier YAML

Revenez au répertoire dans lequel le fichier dotnet-app.yaml est enregistré. Recherchez la ligne suivante dans le fichier YAML :

        image: mcr.microsoft.com/dotnet/core/samples:aspnetapp

Vous devez modifier cette valeur pour référencer l'image de conteneur créée et transférée vers gcr.io ci-dessus.

        image: gcr.io/PROJECT_ID/aspnetapp:alpine

N'oubliez pas de le modifier pour utiliser votre PROJECT_ID. Une fois que vous avez terminé, le résultat devrait ressembler à ceci :

        image: gcr.io/myproject/aspnetapp:alpine

Appliquer le fichier YAML mis à jour

$ kubectl apply -f dotnet-app.yaml
deployment.apps/dotnet-deployment configured
service/dotnet-service unchanged

Notez que la ressource Deployment est indiquée comme mise à jour, tandis que la ressource Service est indiquée comme inchangée. Les pods mis à jour peuvent être affichés comme précédemment avec la commande kubectl get pod, mais cette fois, nous ajouterons l'option -w, qui surveillera toutes les modifications au fur et à mesure qu'elles se produisent.

$ kubectl get pod -w
NAME                                 READY   STATUS              RESTARTS   AGE
dotnet-deployment-5c9d4cc4b9-cspqd   1/1     Running             0          34m
dotnet-deployment-5c9d4cc4b9-httw6   1/1     Running             0          34m
dotnet-deployment-5c9d4cc4b9-vvdln   1/1     Running             0          34m
dotnet-deployment-85f6446977-tmbdq   0/1     ContainerCreating   0          4s
dotnet-deployment-85f6446977-tmbdq   1/1     Running             0          5s
dotnet-deployment-5c9d4cc4b9-vvdln   1/1     Terminating         0          34m
dotnet-deployment-85f6446977-lcc58   0/1     Pending             0          0s
dotnet-deployment-85f6446977-lcc58   0/1     Pending             0          0s
dotnet-deployment-85f6446977-lcc58   0/1     ContainerCreating   0          0s
dotnet-deployment-5c9d4cc4b9-vvdln   0/1     Terminating         0          34m
dotnet-deployment-85f6446977-lcc58   1/1     Running             0          6s
dotnet-deployment-5c9d4cc4b9-cspqd   1/1     Terminating         0          34m
dotnet-deployment-85f6446977-hw24v   0/1     Pending             0          0s
dotnet-deployment-85f6446977-hw24v   0/1     Pending             0          0s
dotnet-deployment-5c9d4cc4b9-cspqd   0/1     Terminating         0          34m
dotnet-deployment-5c9d4cc4b9-vvdln   0/1     Terminating         0          34m
dotnet-deployment-5c9d4cc4b9-vvdln   0/1     Terminating         0          34m
dotnet-deployment-85f6446977-hw24v   0/1     Pending             0          2s
dotnet-deployment-85f6446977-hw24v   0/1     ContainerCreating   0          2s
dotnet-deployment-5c9d4cc4b9-cspqd   0/1     Terminating         0          34m
dotnet-deployment-5c9d4cc4b9-cspqd   0/1     Terminating         0          34m
dotnet-deployment-85f6446977-hw24v   1/1     Running             0          3s
dotnet-deployment-5c9d4cc4b9-httw6   1/1     Terminating         0          34m
dotnet-deployment-5c9d4cc4b9-httw6   0/1     Terminating         0          34m

Le résultat ci-dessus montre la mise à jour progressive en cours. Tout d'abord, de nouveaux conteneurs sont démarrés. Une fois qu'ils sont en cours d'exécution, les anciens conteneurs sont arrêtés.

Vérifier l'application en cours d'exécution

À ce stade, l'application est mise à jour et prête à répondre aux requêtes des utilisateurs. Comme précédemment, il est accessible via un proxy.

$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080

Utilisez maintenant l'aperçu sur le Web dans Cloud Shell pour accéder à l'application Web.

Ajoutez l'élément suivant à l'URL générée par l'aperçu sur le Web : /api/v1/namespaces/default/services/dotnet-service:8080/proxy/. Le résultat devrait ressembler à ceci :

https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/

Vérifier que le service Kubernetes répartit la charge

Actualisez cette URL plusieurs fois et notez que l'hôte change à mesure que les requêtes sont équilibrées entre différents pods par le service. Comparez les valeurs d'hôte à la liste des pods ci-dessus pour vérifier que tous les pods reçoivent du trafic.

Effectuer un scaling à la hausse des instances

Il est facile de faire évoluer des applications dans Kubernetes. La commande suivante va faire passer le déploiement à six instances de l'application.

$ kubectl scale deployment dotnet-deployment --replicas 6
deployment.apps/dotnet-deployment scaled

Vous pouvez afficher les nouveaux pods et leur état actuel à l'aide de cette commande :

kubectl get pod -w

Notez que l'actualisation de la même fenêtre de navigateur montre que le trafic est désormais équilibré entre tous les nouveaux pods.

8. Félicitations !

Dans cet atelier, un exemple d'application Web .NET Core a été validé dans un environnement de développement, puis déployé sur Kubernetes à l'aide de GKE. L'application a ensuite été modifiée pour afficher le nom d'hôte du conteneur dans lequel elle s'exécutait. Le déploiement Kubernetes a ensuite été mis à jour vers la nouvelle version, et l'application a fait l'objet d'un scaling à la hausse pour montrer comment la charge est répartie entre les instances supplémentaires.

Pour en savoir plus sur .NET et Kubernetes, consultez ces tutoriels. Ils s'appuient sur ce que vous avez appris dans cet atelier en introduisant le maillage de services Istio pour des modèles de routage et de résilience plus sophistiqués.

9. Effectuer un nettoyage

Pour éviter des frais involontaires, utilisez les commandes suivantes pour supprimer le cluster et l'image de conteneur créés dans cet atelier.

gcloud container clusters delete dotnet-cluster --zone ${DEFAULT_ZONE}
gcloud container images delete gcr.io/${PROJECT_ID}/aspnetapp:alpine