Eseguire il deployment e l'aggiornamento di un'app .NET Core in Google Kubernetes Engine

1. Panoramica

Microsoft .NET Core è una versione open source e multipiattaforma di .NET che può essere eseguita in modo nativo nei container. .NET Core è disponibile su GitHub ed è gestito da Microsoft e dalla community .NET. Questo lab esegue il deployment di un'app .NET Core containerizzata in Google Kubernetes Engine (GKE).

Questo lab segue un tipico pattern di sviluppo in cui le applicazioni vengono sviluppate in un ambiente locale di sviluppo e poi distribuite in produzione. Nella prima parte del lab, viene convalidata un'app .NET di esempio utilizzando un container in esecuzione in Cloud Shell. Una volta convalidata, viene eseguito il deployment dell'app in Kubernetes mediante GKE. Il lab include i passaggi per creare un cluster GKE.

Nella seconda parte del lab viene apportata una piccola modifica all'app che mostra il nome host del container su cui è in esecuzione l'istanza dell'app. L'applicazione aggiornata viene quindi convalidata in Cloud Shell e il deployment viene aggiornato per utilizzare la nuova versione. L'illustrazione seguente mostra la sequenza di attività in questo lab:

Diagramma di sequenza della demo

Costi

Se esegui questo lab esattamente come è scritto, verranno applicati i normali costi per i seguenti servizi

2. Configurazione e requisiti

Prerequisiti

Per completare questo lab, sono necessari un account e un progetto Google Cloud. Per istruzioni più dettagliate su come creare un nuovo progetto, consulta questo codelab.

In questo lab viene utilizzato Docker in esecuzione in Cloud Shell, che è disponibile tramite la console Google Cloud ed è preconfigurato con molti strumenti utili, come gcloud e Docker. Di seguito è mostrato l'accesso a Cloud Shell. Fai clic sull'icona di Cloud Shell in alto a destra per visualizzarla nel riquadro inferiore della finestra della console.

Cloud Shell

Opzioni di configurazione alternative per il cluster GKE (facoltativo)

Questo lab richiede un cluster Kubernetes. Nella sezione successiva, viene creato un cluster GKE con una configurazione semplice. Questa sezione mostra alcuni comandi gcloud che forniscono opzioni di configurazione alternative da utilizzare durante la creazione di un cluster Kubernetes mediante GKE. Ad esempio, utilizzando i comandi riportati di seguito è possibile identificare diversi tipi di macchina, zone e persino GPU (acceleratori).

  • Elenca i tipi di macchina con questo comando gcloud compute machine-types list
  • Elenca le GPU con questo comando gcloud compute accelerator-types list
  • Elenca le zone di computing con questo comando gcloud compute zones list
  • Ottieni assistenza per qualsiasi comando gcloud gcloud container clusters --help
    • Ad esempio, fornisci dettagli sulla creazione di un cluster Kubernetes gcloud container clusters create --help

Per un elenco completo delle opzioni di configurazione per GKE, consulta questo documento

Preparati a creare il cluster Kubernetes

In Cloud Shell, è necessario impostare alcune variabili di ambiente e configurare il client gcloud. Per farlo, usa i comandi seguenti.

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}

Crea un cluster GKE

Poiché questo lab esegue il deployment dell'app .NET Core su Kubernetes, è necessario creare un cluster. Utilizza questo comando per creare un nuovo cluster Kubernetes in Google Cloud utilizzando 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 è il numero di nodi da aggiungere per zona e può essere scalato in un secondo momento
  • --node-locations è un elenco di zone separate da virgole. In questo caso, vengono utilizzate la zona identificata nella variabile di ambiente indicata sopra e us-central1-b
    • NOTA: questo elenco non può contenere duplicati
  • --workload-pool stabilisce l'identità dei carichi di lavoro in modo che i carichi di lavoro GKE possano accedere ai servizi Google Cloud

Durante la creazione del cluster viene visualizzato quanto segue

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

Configura kubectl

L'interfaccia a riga di comando kubectl è il modo principale di interagire con un cluster Kubernetes. Per utilizzarlo con il nuovo cluster appena creato, deve essere configurato per l'autenticazione a fronte del cluster. Per farlo, deve seguire il comando seguente.

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

Ora dovrebbe essere possibile utilizzare kubectl per interagire con il 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. Esegui i test in locale e conferma la funzionalità desiderata

Questo lab utilizza le seguenti immagini container del repository .NET ufficiale nell'hub Docker.

Esegui il container in locale per verificare la funzionalità

In Cloud Shell, verifica che Docker sia attivo e in esecuzione correttamente e che il container .NET funzioni come previsto eseguendo questo comando Docker:

$ 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

Conferma la funzionalità dell'app web

Un'applicazione web di esempio può essere convalidata anche in Cloud Shell. Il comando Docker run riportato di seguito crea un nuovo container che espone la porta 80 e lo mappa alla porta localhost 8080. Ricorda che localhost in questo caso si trova in Cloud Shell.

$ 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

Poiché si tratta di un'app web, deve essere visualizzata e convalidata in un browser web. La sezione successiva mostra come farlo in Cloud Shell utilizzando Anteprima web.

4. Accesso ai servizi da Cloud Shell utilizzando "Anteprima web"

Cloud Shell offre Anteprima web, una funzionalità che consente di utilizzare un browser per interagire con i processi in esecuzione nell'istanza di Cloud Shell.

Utilizza "Anteprima web" per visualizzare le app in Cloud Shell

In Cloud Shell, fai clic sul pulsante dell'anteprima web e scegli "Anteprima sulla porta 8080" (o qualsiasi porta sia impostata per l'utilizzo da parte dell'Anteprima web).

Cloud Shell

Si aprirà una finestra del browser con un indirizzo simile al seguente:

https://8080-cs-754738286554-default.us-central1.cloudshell.dev/?authuser=0

Visualizza l'applicazione di esempio .NET utilizzando Anteprima web

Ora è possibile visualizzare l'app di esempio avviata nell'ultimo passaggio avviando l'Anteprima web e caricando l'URL fornito. Il sito dovrebbe avere il seguente aspetto:

Screenshot dell&#39;app .NET V1

5. Eseguire il deployment in Kubernetes

Crea il file YAML e applica

Il passaggio successivo richiede un file YAML che descrive due risorse Kubernetes: un deployment e un Service. Crea un file denominato dotnet-app.yaml in Cloud Shell e aggiungici i contenuti seguenti.

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

Ora utilizza kubectl per applicare questo file a Kubernetes.

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

Osserva i messaggi che indicano che sono state create le risorse desiderate.

Esplora le risorse risultanti

Possiamo utilizzare l'interfaccia a riga di comando kubectl per esaminare le risorse create sopra. Innanzitutto, diamo un'occhiata alle risorse del deployment e verifichiamo che il nuovo deployment sia già presente.

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

Ora dai un'occhiata ai ReplicaSet. Dovrebbe essere presente un ReplicaSet creato dal deployment di cui sopra.

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

Infine, dai un'occhiata ai pod. Il deployment ha indicato che dovrebbero esserci tre istanze. Il comando seguente dovrebbe mostrare che sono presenti tre istanze. Viene aggiunta l'opzione -o wide in modo che i nodi in cui sono in esecuzione queste istanze vengano mostrati.

$ 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>

Rivedi la risorsa del servizio

Una risorsa Servizio in Kubernetes è un bilanciatore del carico. Gli endpoint sono determinati da etichette sui pod. In questo modo, non appena vengono aggiunti nuovi pod al deployment tramite l'operazione kubectl scale deployment descritta sopra, i pod risultanti sono immediatamente disponibili per le richieste gestite da quel servizio.

Il comando seguente dovrebbe mostrare la risorsa Service.

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

Usa il comando seguente per visualizzare ulteriori dettagli sul servizio.

$ 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>

Nota che il servizio è di tipo ClusterIP. Ciò significa che qualsiasi pod all'interno del cluster può risolvere il nome del servizio dotnet-service nel relativo indirizzo IP. Le richieste inviate al servizio verranno bilanciate in tutte le istanze (pod). Il valore Endpoints riportato sopra mostra gli IP dei pod attualmente disponibili per questo servizio. Confronta queste informazioni con gli IP dei pod di cui è stato eseguito l'output sopra.

Verifica l'app in esecuzione

A questo punto l'applicazione è attiva e pronta per le richieste degli utenti. Per accedervi, utilizza un proxy. Il comando seguente crea un proxy locale che accetta le richieste sulla porta 8080 e le passa al cluster Kubernetes.

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

Ora utilizza Web Preview in Cloud Shell per accedere all'applicazione web.

Aggiungi il seguente all'URL generato da Anteprima web: /api/v1/namespaces/default/services/dotnet-service:8080/proxy/. Il risultato sarà simile al seguente:

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

Complimenti per aver eseguito il deployment di un'app .NET Core in Google Kubernetes Engine. Ora apporteremo una modifica all'app ed eseguire di nuovo il deployment.

6. Modifica l'app

In questa sezione, l'applicazione verrà modificata per mostrare l'host su cui è in esecuzione l'istanza. In questo modo sarà possibile confermare che il bilanciamento del carico funziona e che i pod disponibili rispondono come previsto.

Recupera il codice sorgente

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

Aggiorna l'app in modo che includa il nome host

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

Crea una nuova immagine container ed eseguine test in locale

Crea la nuova immagine container con il codice aggiornato.

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

Come prima, testa la nuova applicazione localmente

$ 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

Come in precedenza, è possibile accedere all'app tramite Anteprima web. Questa volta il parametro Host dovrebbe essere visibile, come mostrato qui:

Cloud Shell

Apri una nuova scheda in Cloud Shell ed esegui docker ps per vedere che l'ID contenitore corrisponde al valore Host mostrato sopra.

$ 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

Tagga l'immagine ed eseguine il push in modo che sia disponibile per Kubernetes

L'immagine deve essere taggata e inviata per consentire a Kubernetes di eseguirne il pull. Inizia elencando le immagini container e identifica l'immagine che ti interessa.

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

Poi, tagga l'immagine ed eseguine il push in Google Container Registry. Utilizzando l'ID IMAGE sopra, apparirà come questo

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

7. Esegui di nuovo il deployment dell'applicazione aggiornata

Modifica il file YAML

Torna alla directory in cui è salvato il file dotnet-app.yaml. Trova la seguente riga nel file YAML

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

Questo valore deve essere modificato in modo che faccia riferimento all'immagine container che è stata creata e sottoposta a push a gcr.io qui sopra.

        image: gcr.io/PROJECT_ID/aspnetapp:alpine

Non dimenticare di modificarlo per utilizzare PROJECT_ID. Al termine, dovrebbe essere simile a questo

        image: gcr.io/myproject/aspnetapp:alpine

Applica il file YAML aggiornato

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

Tieni presente che la risorsa Deployment viene visualizzata come aggiornata, mentre la risorsa Service non è stata modificata. I pod aggiornati possono essere visualizzati come prima con il comando kubectl get pod, ma questa volta aggiungeremo il valore -w, che osserva tutte le modifiche man mano che vengono apportate.

$ 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

L'output riportato sopra mostra l'aggiornamento in sequenza nel momento in cui si verifica. Per prima cosa vengono avviati i nuovi container e, quando sono in esecuzione, quelli precedenti vengono terminati.

Verifica l'app in esecuzione

A questo punto l'applicazione è aggiornata e pronta per le richieste dell'utente. Come prima, è possibile accedervi utilizzando un proxy.

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

Ora utilizza Web Preview in Cloud Shell per accedere all'applicazione web.

Aggiungi il seguente all'URL generato da Anteprima web: /api/v1/namespaces/default/services/dotnet-service:8080/proxy/. Il risultato sarà simile al seguente:

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

Verifica che il servizio Kubernetes distribuisca il carico

Aggiorna questo URL più volte e nota che l'host cambia man mano che le richieste vengono bilanciate in diversi pod dal servizio. Confronta i valori Host con l'elenco dei pod in alto per vedere che tutti i pod ricevono traffico.

Fai lo scale up delle istanze

La scalabilità delle app in Kubernetes è facile. Il comando seguente consentirà di scalare il deployment fino a un massimo di 6 istanze dell'applicazione.

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

Con questo comando è possibile visualizzare i nuovi pod e il loro stato attuale

kubectl get pod -w

Nota che l'aggiornamento della stessa finestra del browser mostra che il traffico viene ora bilanciato in tutti i nuovi pod.

8. Complimenti!

In questo lab, un'applicazione web di esempio .NET Core è stata convalidata in un ambiente di sviluppo e di cui è stato eseguito il deployment in Kubernetes utilizzando GKE. L'app è stata poi modificata in modo da visualizzare il nome host del container in cui era in esecuzione. Il deployment di Kubernetes è stato quindi aggiornato alla nuova versione ed è stato fatto lo scale up dell'app per dimostrare come il carico viene distribuito tra le istanze aggiuntive.

Per saperne di più su .NET e Kubernetes, considera questi tutorial. Si basano su quanto appreso in questo lab introducendo Istio Service Mesh per pattern di routing e resilienza più sofisticati.

9. Esegui la pulizia

Per evitare costi indesiderati, utilizza i comandi seguenti per eliminare il cluster e l'immagine container che sono state create in questo lab.

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