Implantar e atualizar um app .NET Core no Google Kubernetes Engine

1. Visão geral

O Microsoft .NET Core é uma versão multiplataforma e de código aberto do .NET que pode ser executada nativamente em contêineres. O .NET Core está disponível no GitHub e é mantido pela Microsoft e pela comunidade do .NET. Neste laboratório, você vai implantar um app .NET Core conteinerizado no Google Kubernetes Engine (GKE).

Este laboratório segue um padrão de desenvolvimento típico em que os aplicativos são desenvolvidos em um ambiente local e depois implantados na produção. Na primeira parte do laboratório, um exemplo de app .NET Core é validado usando um contêiner em execução no Cloud Shell. Depois de validado, o app é implantado no Kubernetes usando o GKE. O laboratório inclui as etapas para criar um cluster do GKE.

Na segunda parte do laboratório, uma pequena mudança é feita no app que mostra o nome do host do contêiner que está executando essa instância do app. O aplicativo atualizado é validado no Cloud Shell, e a implantação é atualizada para usar a nova versão. A ilustração a seguir mostra a sequência de atividades neste laboratório:

Diagrama de sequência da demonstração

Custos

Se você executar este laboratório exatamente como descrito, os custos normais dos seguintes serviços serão aplicados:

2. Configuração e requisitos

Pré-requisitos

Para concluir este laboratório, é necessário ter uma conta e um projeto do Google Cloud. Para instruções mais detalhadas sobre como criar um novo projeto, consulte este codelab.

Neste laboratório, usamos o Docker em execução no Cloud Shell, que está disponível no console do Google Cloud e vem pré-configurado com muitas ferramentas úteis, como o gcloud e o Docker. Veja abaixo como acessar o Cloud Shell. Clique no ícone do Cloud Shell no canto superior direito para que ele apareça no painel inferior da janela do console.

Cloud Shell

Opções de configuração alternativas para o cluster do GKE (opcional)

Este laboratório requer um cluster do Kubernetes. Na próxima seção, um cluster do GKE com uma configuração simples será criado. Nesta seção, mostramos alguns comandos gcloud que fornecem opções de configuração alternativas para usar ao criar um cluster do Kubernetes usando o GKE. Por exemplo, usando os comandos abaixo, é possível identificar diferentes tipos de máquina, zonas e até GPUs (aceleradores).

  • Liste os tipos de máquina com este comando: gcloud compute machine-types list
  • Liste as GPUs com este comando: gcloud compute accelerator-types list
  • Liste as zonas do Compute com este comando: gcloud compute zones list
  • Receber ajuda sobre qualquer comando gcloud gcloud container clusters --help
    • Por exemplo, isso fornece detalhes sobre como criar um cluster do Kubernetes gcloud container clusters create --help

Para ver uma lista completa de opções de configuração do GKE, consulte este documento.

Preparar-se para criar o cluster do Kubernetes

No Cloud Shell, é necessário definir algumas variáveis de ambiente e configurar o cliente gcloud. Isso é feito com os comandos a seguir.

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}

Criar um cluster do GKE

Como este laboratório implanta o app .NET Core no Kubernetes, é necessário criar um cluster. Use o comando a seguir para criar um cluster do Kubernetes no Google Cloud usando o 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 é o número de nós a serem adicionados por zona, que pode ser escalonado posteriormente.
  • --node-locations é uma lista de zonas separadas por vírgulas. Nesse caso, a zona identificada na variável de ambiente acima e us-central1-b são usadas
    • OBSERVAÇÃO: esta lista não pode conter duplicatas
  • --workload-pool estabelece a identidade da carga de trabalho para que as cargas de trabalho do GKE possam acessar os serviços do Google Cloud

Enquanto o cluster está sendo criado, o seguinte é exibido:

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

Configurar o kubectl

A CLI kubectl é a principal forma de interagir com um cluster do Kubernetes. Para usá-lo com o novo cluster que acabou de ser criado, ele precisa ser configurado para autenticação no cluster. Para isso, use o comando a seguir.

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

Agora é possível usar kubectl para interagir com o 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. Testar localmente e confirmar a funcionalidade desejada

Este laboratório usa as seguintes imagens de contêiner do repositório oficial do .NET no Docker Hub.

Executar o contêiner localmente para verificar a funcionalidade

No Cloud Shell, verifique se o Docker está funcionando corretamente e se o contêiner do .NET funciona como esperado executando o seguinte comando do 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

Confirmar a funcionalidade do app da Web

Um aplicativo da Web de amostra também pode ser validado no Cloud Shell. O comando de execução do Docker abaixo cria um novo contêiner que expõe a porta 80 e a mapeia para a porta 8080 do localhost. Lembre-se de que, neste caso, localhost está no 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

Como esse é um app da Web, ele precisa ser visualizado e validado em um navegador da Web. A próxima seção mostra como fazer isso no Cloud Shell usando a visualização da Web.

4. Acessar serviços do Cloud Shell usando a "Visualização da Web"

O Cloud Shell oferece a Visualização da Web, um recurso que permite usar um navegador para interagir com processos em execução na instância do Cloud Shell.

Usar a "Visualização da Web" para ver apps no Cloud Shell

No Cloud Shell, clique no botão de visualização da Web e escolha Visualizar na porta 8080 (ou qualquer porta que a visualização da Web esteja configurada para usar).

Cloud Shell

Isso vai abrir uma janela do navegador com um endereço como este:

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

Visualizar o aplicativo de exemplo do .NET usando a Visualização da Web

Para ver o app de exemplo iniciado na última etapa, inicie a Visualização na Web e carregue o URL fornecido. O código será semelhante a este:

Captura de tela do app .NET V1

5. Implantar no Kubernetes

Criar o arquivo YAML e aplicar

A próxima etapa requer um arquivo YAML que descreva dois recursos do Kubernetes: uma implantação e um serviço. Crie um arquivo chamado dotnet-app.yaml no Cloud Shell e adicione o seguinte conteúdo a ele.

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

Agora use kubectl para aplicar esse arquivo ao Kubernetes.

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

Observe as mensagens que indicam que os recursos desejados foram criados.

Analisar os recursos resultantes

Podemos usar a CLI kubectl para examinar os recursos criados acima. Primeiro, vamos analisar os recursos de implantação e confirmar se a nova implantação está lá.

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

Em seguida, confira os ReplicaSets. Deve haver um ReplicaSet criado pela implantação acima.

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

Por fim, confira os Pods. A implantação indicou que deveria haver três instâncias. O comando abaixo deve mostrar que há três instâncias. A opção -o wide é adicionada para que os nós em que essas instâncias estão em execução sejam mostrados.

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

Analisar o recurso de serviço

Um recurso de serviço no Kubernetes é um balanceador de carga. Os endpoints são determinados por rótulos nos pods. Dessa forma, assim que novos pods forem adicionados à implantação pela operação kubectl scale deployment acima, os pods resultantes estarão imediatamente disponíveis para as solicitações processadas por esse serviço.

O comando a seguir mostra o recurso de serviço.

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

Para ver mais detalhes sobre o serviço, use o comando a seguir.

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

Observe que o serviço é do tipo ClusterIP. Isso significa que qualquer pod no cluster pode resolver o nome do Serviço, dotnet-service, para o endereço IP dele. As solicitações enviadas ao serviço terão o balanceamento de carga entre todas as instâncias (pods). O valor Endpoints acima mostra os IPs dos pods disponíveis no momento para esse serviço. Compare-os com os IPs dos pods que foram mostrados acima.

Verificar o app em execução

Nesse momento, o aplicativo está ativo e pronto para solicitações de usuários. Para acessá-lo, use um proxy. O comando a seguir cria um proxy local que aceita solicitações na porta 8080 e as transmite para o cluster do Kubernetes.

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

Agora use a Visualização da Web no Cloud Shell para acessar o aplicativo da Web.

Adicione o seguinte ao URL gerado pela Visualização da Web: /api/v1/namespaces/default/services/dotnet-service:8080/proxy/. O resultado será parecido com este:

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

Parabéns por implantar um app .NET Core no Google Kubernetes Engine. Em seguida, vamos fazer uma alteração no app e reimplantá-lo.

6. Modificar o app

Nesta seção, o aplicativo será modificado para mostrar o host em que a instância está sendo executada. Isso vai permitir confirmar se o balanceamento de carga está funcionando e se os pods disponíveis estão respondendo conforme o esperado.

Conseguir o código-fonte

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

Atualizar o app para incluir o nome do host

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

Criar uma nova imagem de contêiner e testar localmente

Crie a nova imagem do contêiner com o código atualizado.

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

Como antes, teste o novo aplicativo 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

Como antes, o app pode ser acessado usando a Visualização da Web. Desta vez, o parâmetro Host deve estar visível, como mostrado aqui:

Cloud Shell

Abra uma nova guia no Cloud Shell e execute docker ps para verificar se o ID do contêiner corresponde ao valor do host mostrado acima.

$ 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

Marcar e enviar a imagem para que ela fique disponível para o Kubernetes

A imagem precisa ser marcada e enviada para que o Kubernetes possa extraí-la. Comece listando as imagens do contêiner e identifique a imagem desejada.

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

Em seguida, marque essa imagem e envie-a para o Google Container Registry. Usando o ID DA IMAGEM acima, o comando vai ficar assim:

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

7. Reimplantar o aplicativo atualizado

Editar o arquivo YAML

Volte para o diretório em que o arquivo dotnet-app.yaml está salvo. Encontre a seguinte linha no arquivo YAML:

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

Isso precisa ser alterado para fazer referência à imagem do contêiner que foi criada e enviada para gcr.io acima.

        image: gcr.io/PROJECT_ID/aspnetapp:alpine

Não se esqueça de modificá-lo para usar seu PROJECT_ID. O resultado será semelhante a este:

        image: gcr.io/myproject/aspnetapp:alpine

Aplique o arquivo YAML atualizado:

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

O recurso de implantação aparece como atualizado, e o recurso de serviço aparece como inalterado. Os pods atualizados podem ser vistos como antes com o comando kubectl get pod, mas desta vez vamos adicionar o -w, que vai monitorar todas as mudanças conforme elas acontecem.

$ 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

A saída acima mostra a atualização gradual conforme ela acontece. Primeiro, novos contêineres são iniciados e, quando estão em execução, os antigos são encerrados.

Verificar o app em execução

Nesse momento, o aplicativo é atualizado e fica pronto para as solicitações do usuário. Como antes, ele pode ser acessado usando um proxy.

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

Agora use a Visualização da Web no Cloud Shell para acessar o aplicativo da Web.

Adicione o seguinte ao URL gerado pela Visualização da Web: /api/v1/namespaces/default/services/dotnet-service:8080/proxy/. O resultado será parecido com este:

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

Confirmar se o serviço do Kubernetes está distribuindo a carga

Atualize esse URL várias vezes e observe que o host muda conforme as solicitações são balanceadas por balanceamento de carga entre diferentes pods pelo serviço. Compare os valores de host com a lista de pods acima para verificar se todos os pods estão recebendo tráfego.

Escalonar instâncias verticalmente

É fácil escalonar apps no Kubernetes. O comando a seguir vai escalonar a implantação para até seis instâncias do aplicativo.

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

Os novos pods e o estado atual deles podem ser visualizados com este comando:

kubectl get pod -w

Atualize a mesma janela do navegador para ver que o tráfego agora está sendo balanceado entre todos os novos pods.

8. Parabéns!

Neste laboratório, um aplicativo da Web de amostra do .NET Core foi validado em um ambiente de desenvolvimento e, em seguida, implantado no Kubernetes usando o GKE. O app foi modificado para mostrar o nome do host do contêiner em que estava sendo executado. Depois, a implantação do Kubernetes foi atualizada para a nova versão, e o app foi escalonado verticalmente para demonstrar como a carga é distribuída entre as instâncias adicionais.

Para saber mais sobre o .NET e o Kubernetes, confira estes tutoriais. Eles se baseiam no que foi aprendido neste laboratório, apresentando a malha de serviço do Istio para padrões de roteamento e resiliência mais sofisticados.

9. Limpar

Para evitar custos indesejados, use os comandos a seguir para excluir o cluster e a imagem do contêiner criados neste laboratório.

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