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

Cloud Shell

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

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

  • Чтобы посмотреть список типов машин, выполните команду gcloud compute machine-types list.
  • Выведите список графических процессоров с помощью команды gcloud compute accelerator-types list.
  • Вывести список зон Compute с помощью команды 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 устанавливает Workload Identity, чтобы рабочие нагрузки GKE могли получать доступ к сервисам Google Cloud.

Пока кластер создается, отображается следующее:

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

Настройка kubectl

kubectl CLI – это основной способ взаимодействия с кластером 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.

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

В Cloud Shell убедитесь, что 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 и сопоставляет его с портом localhost 8080. Помните, что в этом случае 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

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

4. Как получить доступ к сервисам из Cloud Shell с помощью функции "Веб-просмотр"

В Cloud Shell есть функция Web Preview, которая позволяет использовать браузер для взаимодействия с процессами, запущенными в экземпляре Cloud Shell.

Как использовать веб-просмотр для работы с приложениями в Cloud Shell

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

Cloud Shell

Откроется окно браузера с адресом, похожим на этот:

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

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

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

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

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

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

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

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

Наконец, рассмотрим пакеты. В развертывании указано, что должно быть три экземпляра. Приведенная ниже команда должна показать, что существует три экземпляра. Параметр -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>

Проверьте ресурс Service

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

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

$ 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-адрес. Запросы, отправленные в сервис, будут распределяться между всеми экземплярами (подами). Значение Endpoints выше показывает IP-адреса подов, доступных для этого сервиса. Сравните их с IP-адресами подов, которые были выведены выше.

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

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

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

Теперь используйте Web Preview в 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. Изменить приложение

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

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

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

Откройте новую вкладку в 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. Если использовать идентификатор изображения, указанный выше, то получится следующее:

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 остался без изменений. Обновленные поды можно посмотреть, как и раньше, с помощью команды 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

Теперь используйте Web Preview в 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 со списком подов выше, чтобы убедиться, что все поды получают трафик.

Увеличить количество экземпляров

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

$ 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, которая позволяет использовать более сложные шаблоны маршрутизации и отказоустойчивости.

9. Организовать

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

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