Развертывание и обновление приложения .NET Core в Google Kubernetes Engine

1. Обзор

Microsoft .NET Core — это версия .NET с открытым исходным кодом и кроссплатформенная, которая может работать в контейнерах. .NET Core доступен на GitHub и поддерживается Microsoft и сообществом .NET. В этой лабораторной работе развертывается контейнеризированное приложение .NET Core в Google Kubernetes Engine (GKE).

Данная лабораторная работа следует типичному шаблону разработки, при котором приложения разрабатываются в локальной среде разработчика, а затем развертываются в производственной среде. В первой части лабораторной работы проверяется пример приложения .NET Core с использованием контейнера, работающего в Cloud Shell . После проверки приложение развертывается в Kubernetes с использованием GKE. Лабораторная работа включает в себя шаги по созданию кластера GKE.

Во второй части лабораторной работы в приложение вносятся незначительные изменения, отображающие имя хоста контейнера, в котором запущен данный экземпляр приложения. Затем обновленное приложение проверяется в Cloud Shell, и развертывание обновляется для использования новой версии. На следующем рисунке показана последовательность действий в этой лабораторной работе:

Диаграмма последовательности демонстрации

Затраты

Если вы выполните эту лабораторную работу в точности по инструкции, будут применяться стандартные тарифы на следующие услуги.

2. Настройка и требования

Предварительные требования

Для выполнения этой лабораторной работы необходимы учетная запись Google Cloud и проект. Более подробные инструкции по созданию нового проекта см. в этой лабораторной работе .

В этой лабораторной работе используется Docker, работающий в Cloud Shell , который доступен через консоль Google Cloud и поставляется с предустановленными полезными инструментами, такими как gcloud и Docker. Как получить доступ к Cloud Shell, показано ниже. Щелкните значок Cloud Shell в правом верхнем углу, чтобы отобразить его в нижней панели окна консоли.

Облачная оболочка

Альтернативные параметры конфигурации кластера GKE (необязательно)

Для выполнения этой лабораторной работы требуется кластер Kubernetes. В следующем разделе будет создан кластер GKE с простой конфигурацией. В этом разделе показаны некоторые команды gcloud , которые предоставляют альтернативные параметры конфигурации для использования при создании кластера Kubernetes с использованием GKE. Например, с помощью приведенных ниже команд можно определить различные типы машин, зоны и даже графические процессоры (ускорители).

  • Вывести список типов вычислительных машин можно с помощью команды gcloud compute machine-types list
  • Вывести список графических процессоров можно с помощью команды gcloud compute accelerator-types list
  • Вывести список вычислительных зон можно с помощью команды gcloud compute zones list
  • Получите справку по любой команде gcloud: gcloud container clusters --help
    • Например, это предоставляет подробную информацию о создании кластера Kubernetes gcloud container clusters create --help

Полный список параметров конфигурации GKE см. в этом документе.

Подготовка к созданию кластера Kubernetes

В Cloud Shell необходимо установить некоторые переменные среды и настроить клиент gcloud. Это делается с помощью следующих команд.

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}

Создайте кластер GKE.

Поскольку в этой лабораторной работе приложение .NET Core развертывается в Kubernetes, необходимо создать кластер. Используйте следующую команду для создания нового кластера Kubernetes в Google Cloud с помощью 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 — это количество узлов, добавляемых в каждую зону ; масштабирование можно изменить позже.
  • --node-locations — это список зон, разделённых запятыми. В данном случае используются зона, указанная в переменной среды выше, и us-central1-b
    • Примечание: Этот список не может содержать дубликатов.
  • --workload-pool устанавливает идентификатор рабочей нагрузки, чтобы рабочие нагрузки GKE могли получать доступ к сервисам Google Cloud.

В процессе формирования кластера отображается следующее.

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

Настройте kubectl

Интерфейс командной строки kubectl является основным способом взаимодействия с кластером Kubernetes. Для его использования с только что созданным кластером необходимо настроить аутентификацию в кластере. Это делается с помощью следующей команды.

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

Теперь должна появиться возможность использовать kubectl для взаимодействия с кластером.

$ 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. Протестируйте локально и подтвердите желаемую функциональность.

В этой лабораторной работе используются следующие образы контейнеров из официального репозитория .NET на Docker Hub.

Запустите контейнер локально, чтобы проверить его работоспособность.

В CloudShell убедитесь, что Docker запущен и работает должным образом, а контейнер .NET функционирует корректно, выполнив следующую команду 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

Подтвердите работоспособность веб-приложения.

Пример веб-приложения также можно проверить в Cloud Shell. Приведенная ниже команда Docker run создает новый контейнер, который открывает порт 80 и сопоставляет его с портом 8080 localhost . Помните, что в данном случае localhost находится в 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

Поскольку это веб-приложение, его необходимо просматривать и проверять в веб-браузере. В следующем разделе показано, как это сделать в облачной оболочке с помощью функции Web Preview.

4. Доступ к сервисам из облачной оболочки осуществляется с помощью функции «Веб-предварительный просмотр».

Cloud Shell предлагает функцию веб-предварительного просмотра , которая позволяет взаимодействовать с процессами, работающими в экземпляре Cloud Shell, через браузер.

Используйте функцию «Веб-предварительный просмотр», чтобы просмотреть приложения в Cloud Shell.

В Cloud Shell нажмите кнопку предварительного просмотра веб-страницы и выберите « Предварительный просмотр на порту 8080 » (или на том порту, который настроен для предварительного просмотра веб-страницы).

Облачная оболочка

Это откроет окно браузера с адресом примерно такого вида:

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

Просмотрите пример приложения .NET с помощью веб-предпросмотра.

Теперь запущенное на предыдущем шаге тестовое приложение можно просмотреть, запустив веб-предварительный просмотр и загрузив предоставленный URL-адрес. Оно должно выглядеть примерно так:

Скриншот приложения .NET версии 1

5. Развертывание в Kubernetes

Создайте YAML-файл и примените его.

Для следующего шага потребуется YAML-файл, описывающий два ресурса Kubernetes: развертывание (Deployment) и службу (Service). Создайте файл с именем dotnet-app.yaml в Cloud Shell и добавьте в него следующее содержимое.

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

Теперь используйте kubectl , чтобы применить этот файл к Kubernetes.

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

Обратите внимание на сообщения, указывающие на то, что необходимые ресурсы созданы.

Изучите полученные ресурсы.

Мы можем использовать CLI kubectl для проверки созданных выше ресурсов. Сначала давайте посмотрим на ресурсы развертывания и убедимся, что новое развертывание там присутствует.

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

Далее взгляните на ReplicaSets. Там должен быть ReplicaSet, созданный в результате вышеуказанного развертывания.

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

Наконец, взгляните на Pods . В Deployment указано, что должно быть три экземпляра. Приведенная ниже команда должна показать, что их три. Добавлена ​​опция -o wide , чтобы отображались узлы, на которых запущены эти экземпляры.

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

Ознакомьтесь с ресурсом службы.

В Kubernetes сервисный ресурс представляет собой балансировщик нагрузки. Конечные точки определяются метками на подах. Таким образом, как только новые поды добавляются в развертывание с помощью операции kubectl scale deployment описанной выше, полученные поды немедленно становятся доступны для запросов, обрабатываемых этим сервисом.

Следующая команда должна отобразить ресурс Service.

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

Более подробную информацию о сервисе можно получить с помощью следующей команды.

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

Обратите внимание, что тип сервиса — ClusterIP . Это означает, что любой Pod в кластере может разрешить имя сервиса, dotnet-service в свой IP-адрес. Запросы, отправляемые в сервис, будут распределяться между всеми экземплярами (Podами) с помощью балансировки нагрузки. Значение Endpoints выше показывает IP-адреса Podов, доступных в данный момент для этого сервиса. Сравните их с IP-адресами Podов, указанными выше.

Проверьте запущенное приложение.

На данном этапе приложение запущено и готово к обработке запросов пользователей. Для доступа к нему используйте прокси. Следующая команда создаст локальный прокси, который принимает запросы на порту 8080 и передает их в кластер Kubernetes.

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

Теперь воспользуйтесь функцией предварительного просмотра веб-страниц в Cloud Shell, чтобы получить доступ к веб-приложению.

Добавьте к URL-адресу, сгенерированному веб-превью, следующее: /api/v1/namespaces/default/services/dotnet-service:8080/proxy/ . В итоге это будет выглядеть примерно так:

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

Поздравляем с развертыванием приложения .NET Core в Google Kubernetes Engine. Далее мы внесем изменения в приложение и выполним повторное развертывание.

6. Модифицировать приложение

В этом разделе приложение будет изменено таким образом, чтобы отображать хост, на котором запущен экземпляр. Это позволит убедиться в работоспособности балансировки нагрузки и в том, что доступные Pod-ы отвечают должным образом.

Получите исходный код

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

Обновите приложение, добавив в него имя хоста.

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

Создайте новый образ контейнера и протестируйте его локально.

Создайте новый образ контейнера с обновленным кодом.

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

Как и прежде, протестируйте новое приложение локально.

$ 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

Как и прежде, доступ к приложению можно получить через веб-предварительный просмотр. На этот раз параметр Host должен быть виден, как показано здесь:

Облачная оболочка

Откройте новую вкладку в Cloud Shell и выполните команду docker ps чтобы убедиться, что идентификатор контейнера совпадает со значением Host, указанным выше.

$ 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

Пометьте образ тегом и отправьте его в Kubernetes, чтобы он стал доступен.

Для того чтобы Kubernetes смог его загрузить, образ необходимо пометить тегом и отправить. Начните с перечисления образов контейнеров и выберите нужный образ.

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

Далее, добавьте тег к этому изображению и загрузите его в Google Container Registry . Используя указанный выше IMAGE ID, это будет выглядеть так.

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

7. Повторно разверните обновленное приложение.

Отредактируйте YAML-файл

Вернитесь в каталог, где сохранен файл dotnet-app.yaml . Найдите в файле YAML следующую строку.

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

Необходимо внести изменения, чтобы указать ссылку на образ контейнера, созданный и загруженный в gcr.io выше.

        image: gcr.io/PROJECT_ID/aspnetapp:alpine

Не забудьте изменить его, указав свой PROJECT_ID . В итоге должно получиться примерно так.

        image: gcr.io/myproject/aspnetapp:alpine

Примените обновленный YAML-файл.

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

Обратите внимание, что ресурс Deployment отображается как обновленный, а ресурс Service — как неизмененный. Обновленные Pod-ы можно увидеть, как и раньше, с помощью команды kubectl get pod , но на этот раз мы добавим параметр -w , который будет отслеживать все изменения по мере их возникновения.

$ 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

Приведенный выше вывод демонстрирует процесс поэтапного обновления по мере его выполнения. Сначала запускаются новые контейнеры, а когда они работают, старые контейнеры завершают свою работу.

Проверьте запущенное приложение.

На данный момент приложение обновлено и готово к обработке запросов пользователей. Как и прежде, доступ к нему можно получить через прокси-сервер.

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

Теперь воспользуйтесь функцией предварительного просмотра веб-страниц в Cloud Shell, чтобы получить доступ к веб-приложению.

Добавьте к URL-адресу, сгенерированному веб-превью, следующее: /api/v1/namespaces/default/services/dotnet-service:8080/proxy/ . В итоге это будет выглядеть примерно так:

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

Убедитесь, что служба Kubernetes распределяет нагрузку.

Обновите этот URL несколько раз и обратите внимание, что значение Host меняется, поскольку запросы распределяются между различными Pod-ами сервисом. Сравните значения Host со списком Pod-ов, приведенным выше, чтобы убедиться, что все Pod-ы получают трафик.

Масштабирование экземпляров

Масштабирование приложений в Kubernetes — это просто. Следующая команда позволит масштабировать развертывание до 6 экземпляров приложения.

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

Новые модули и их текущее состояние можно просмотреть с помощью этой команды.

kubectl get pod -w

Обратите внимание, что при обновлении того же окна браузера видно, что трафик теперь равномерно распределяется между всеми новыми Pod-ами.

8. Поздравляем!

В этой лабораторной работе было протестировано тестовое веб-приложение на .NET Core в среде разработки, а затем развернуто в Kubernetes с использованием GKE. После этого приложение было изменено таким образом, чтобы отображать имя хоста контейнера, в котором оно работало. Затем развертывание в Kubernetes было обновлено до новой версии, и приложение было масштабировано для демонстрации распределения нагрузки между дополнительными экземплярами.

Чтобы узнать больше о .NET и Kubernetes, ознакомьтесь с этими руководствами. Они развивают знания, полученные в этой лабораторной работе, и знакомят с Istio Service Mesh для более сложных схем маршрутизации и обеспечения отказоустойчивости.

9. Уборка

Чтобы избежать непредвиденных затрат, используйте следующие команды для удаления кластера и образа контейнера, созданных в ходе этой лабораторной работы.

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