Разворачиваем приложение в Google Kubernetes Engine

Есть у меня проектик RSS-2-KINDLE на github, который я использую для экспериментов с новыми технологиями и фреймворками, которые на работе мы вряд ли будем использовать, а если и будем, то не скоро. «Кровавый энтерпрайз» не любит рисковых инноваций. Как бы то ни было, в свое время я затеял мой проект, чтобы поупражняться с Apache Camel и MongoDB. Позже я решил добавить туда REST API, потом UI на Bootstrap, потом Spring Security, потом все как в тумане. И вот, у меня уже довольно большой аппликейшн с микросервисной архитектурой, который можно деплоить через Docker.

«Вау» — подумал я, — «С докером разобрались. Пора браться за kubernetes«. Взяться за дело я решил амбициозно и поймать сразу двух зайцев — разобраться с kubernetes и разобраться, как это дело работает в облачной инфраструктуре. В качестве облака я выбрал Google Cloud Platform (GCP), который предеостваляет сервис Google Kubernetes Engine (GKE). Выбрал по двум причинам: во-первых, у меня там есть на год предоплаченный аккаунт, во-вторых, интерфейс и юзабилити GCP мне пришлись по душе гораздо больше нежели монструозный и абсолютно неинтуитивный AWS.

Деплоймент архитектура

Итак, понеслась. Деплоймент архитектура приложения следующая:

  • база данных MongoDB — деплоится как отдельный контейнер rss2kindle-mongo. Никакого шардинга я не делал, поэтому это контейнер, который существует в единственном числе
  • Основное приложение с REST API — деплоится как отдельный контейнер rss2kindle-api. Это масштабируемый микросервис, поэтому можем разворачивать столько контейнеров, сколько нужно. Внутри это tomcat с java-приложением, которое отвечает за взаимодействие с базой данных и за функционал.
  • Юзер-интерфейс- деплоится как отдельный контейнер rss2kindle-web. Внутри это еще один tomcat с java-приложением, которое взаимодействует только с REST API, а с базой данных напрямую не связано. Это также масштабируемый микросервис. Однако, поскольку это юзер-интерфейс, связанный с передачей информации между вэб-страницами в рамках одной http-сессии, то масштабирование зависит от настроек лоад-балансера
  • лоад-балансер traefik — деплоится как отдельный контейнер. Про него отдельная песня.
  • smtp-сервер — деплоится как отдельный контейнер rss2kindle-mailhog. В теории это также масштабируемый микросервис, но на практике нам это не нужно и используем один контейнер. Для тестовых целей я использую mailhog.

Подготовка к деплойменту:

Здесь по сути описывается стандартный CI/CD pipeline применительно к докеру. Иными словами, на первом шаге билдим артефакты — в данном случае — докер образы, затем заливаем эти артефакты в репозиторий- в данном случае — Container Registry. Это все при желании автоматизируется, но до автоматизации нужно разобраться с каждым шагом вручную.

Создание docker образов на локальной машине

  • У нас уже есть готовые конфигурации под докер для всех компонент
  • Билдим приложение, используя mvn clean install
  • Создаем докер образы на локальной машине используя docker-compose build
  • Проверяем, что нужные нам образы на месте. Вывод docker images должен содержать образы:
    • rss2kindle/mailhog
    • rss2kindle/mongo
    • rss2kindle/rss2kindle-api
    • rss2kindle/rss2kindle-web

Заливка образов в container registry

GCP предлагает собственный Container Registry для управления докер образами, который классно интегрирован с GKE. Container Registry можно управлять через UI и можно через gcloud CLI. Полагаю, можно использовать любой другой container registry, но я не пробовал и не знаю, какие подводные камни могут быть при этом.

Итак, логинимся в GCP и создаем там проект, в котором мы собираемся разворачивать наше приложение. В моем случае проект называется roundkick-studio. В списке сервисов находим Container Registry и кликаем в подменю Settings. Ищем, что написано в Container Registry Host. В моем случае, это gcr.io. Имя хоста и имя проекта, нам нужны, что правильно запушить наши докер образы в репозиторий.

  • Делаем docker tag, чтобы подготовить локальные образы для пуша в облачный репозиторий.
  • Делаем docker push в облачный репозиторий
docker tag rss2kindle/mailhog:3.3 gcr.io/roundkick-studio/rss2kindle-mailhog:3.3
docker push gcr.io/roundkick-studio/rss2kindle-mailhog:3.3

docker tag rss2kindle/mongo:3.3 gcr.io/roundkick-studio/rss2kindle-mongo:3.3
docker push gcr.io/roundkick-studio/rss2kindle-mongo:3.3

docker tag rss2kindle/rss2kindle-api:3.3 gcr.io/roundkick-studio/rss2kindle-api:3.3
docker push gcr.io/roundkick-studio/rss2kindle-api:3.3

docker tag rss2kindle/rss2kindle-web:3.3 gcr.io/roundkick-studio/rss2kindle-web:3.3
docker push gcr.io/roundkick-studio/rss2kindle-web:3.3

Можем проверить, что наши образы на месте с помощью gcloud CLI.

gcloud container images list

Создаем кластер в GKE

Теперь все готово, чтобы начать работу с kubernetes. Логинимся в GCP и в списке сервисов находим Kubernetes Engine. Выбираем подменю Clusters. Кликаем Create cluster. Появляется первая страница настроек, где надо ввести имя кластера и выбрать Location Type. С Location Type надо быть внимательным. Лично я потратил кучу времени, пытаясь решить проблему, которая возникла у меня из-за неправильного выбора Location Type.

Дело было в том, что у меня к этому моменту уже был развернут работающий сервер с выделенным постоянным внешним IP-адресом. В частности, на этом сервере хостится данный блог. Для разворачивания кластера требуется лоад балансер, а для лоад балансера требуется постоянный IP адрес. GCP позволяет создавать только один постоянный IP адрес в рамках одной локации.

Моей ошибкой было, что я пытался создавать kubernetes кластер в той же самой локации, где у меня уже крутился сервер с постоянным IP. И каждый раз, когда я пытался поднять лоад балансер, я получал ошибку, что GKE не может получить постоянный IP адрес. Я бился с этим несколько дней, пока не поменял локацию при создании кластера. То бишь, если у нас в данной локации уже есть выделенный постоянный IP адрес, то кластер надо создавать в другой локации.

Конфиги для kubernetes

Для каждого компонента архитектуры мы должны создать как минимум два конфига: Deployment и Service.

Начнем с самого простого — с mailhog

mailhog-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mailhog
  labels:
    app: mailhog
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mailhog
  template:
    metadata:
      labels:
        app: mailhog
    spec:
      containers:
      - name: mailhog
        image: gcr.io/roundkick-studio/rss2kindle-mailhog:3.3
        ports:
        - containerPort: 8025
        - containerPort: 1025

Тут в основном интересна секция spec. В image мы ссылаемся на образ внутри Google Container Registry. В ports мы декларируем два порта: 8025 — для доступа к mailhog UI, 1025 — smtp порт

mailhog-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mailhog
  name: mailhog
spec:
  type: NodePort
  ports:
    - name: http
      port: 8025
      targetPort: 8025
      nodePort: 30021
      protocol: TCP
    - name: smtp
      port: 1025
      targetPort: 1025
      protocol: TCP
  selector:
    app: mailhog

Здесь нужно обратить внимание на type: NodePort. Это сделано не случайно. Цель — сделать порт 8025 доступным за пределами кластера. Ниже в секции ports я делаю маппинг порта 8025 на порт 30021 с помощью директивы nodePort: 30021. По умолчанию kubernetes разрешает определять nodePort только в диапазоне 30000-32767. Именно поэтому порт 8025 маппится на странный номер 30021 вместо того, чтобы замапиться на тот же номер 8025. Директива targetPort: 8025 показывает, какой порт контейнера мы хотим использовать. Внутри кластера порт маппится на тот же самый номер с помощью директивы port: 8025. Порт 1025 открывается только внутри кластера. Для маппинга внутри кластера используется директива port: 1025 Снаружи он будет недоступен. Далее секция selector, где мы определяем имя сервиса, по которому он будет доступен внутри кластера. В данном случае selector -> app: mailhog означает, что для коннекта к smtp серверу внутри кластера другие сервисы должны будут обращаться к сервису как mailhog:1025.

Теперь конфигурация для MongoDB

mongo-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongodb
  labels:
    app: mongodb
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      containers:
      - name: mongodb
        image: gcr.io/roundkick-studio/rss2kindle-mongo:3.3
        ports:
        - containerPort: 27017

Опять смотрим на секцию spec. Указываем image из нашего Google Container Registry. Декларируем стандартный для mongodb порт 27017. Обращаем внимание, что replicas: 1. Мы не хотим заморачиваться с шардингом, и поэтому определяем для базы данных ровно один контейнер в надежде, что kubernetes не сглючит, mongo контейнер не упадет и мы не потеряем все данные.

mongo-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mongodb
  name: mongodb
spec:
  type: ClusterIP
  ports:
    - name: tcp
      port: 27017
      targetPort: 27017
      protocol: TCP
  selector:
    app: mongodb

Здесь type: ClusterIP указан, чтобы сделать mongo контейнер доступным только внутри кластера. Прямого доступа снаружи к mongo не будет. Однако, мы все равно должны открыть порт 27017 внутри кластера, что достигается с помощью маппинга port: 27017. Имя сервиса внутри кластера определяем как mongodb в selector->app: mongodb

Теперь самое интересное. Начинаем конфигурировать наш REST API.

rest-api-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: rss2kindle-api
  labels:
    app: rss2kindle-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: rss2kindle-api
  template:
    metadata:
      labels:
        app: rss2kindle-api
    spec:
      containers:
      - name: rss2kindle-api
        image: gcr.io/roundkick-studio/rss2kindle-api:3.3
        env:
        - name: mongodb.host
          value: mongodb
        - name: mongodb.port
          value: "27017"
        - name: smtp.host
          value: mailhog
        - name: smtp.port
          value: "1025"
        ports:
        - containerPort: 8443

Первое, что видим — replicas: 3. Как уже упоминалось выше, REST API — это stateless микросервис, который легко горизонтально масшабируется. В терминах конфигурации kubernetes, нам достаточно, просто поменять цифру в параметре replicas, чтобы изменить количество активных инстансов.

Далее, интересная секция env, где конфигурируются переменные окружения, которые необходимы для работы приложения. В данном случае, приложению требуются переменные для коннекта к базе данных mongodb и к серверу smtp:

  • mongodb.host = mongodb
  • mongodb.port = 27017
  • smtp.host = mailhog
  • smtp.port = 1025

В конце декларируем порт 8443, который позже откроем для доступа к REST API.

rest-api-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: rss2kindle-api
  name: rss2kindle-api
  annotations:
    cloud.google.com/app-protocols: '{"https":"HTTPS"}'
spec:
  type: NodePort
  ports:
    - name: https
      port: 8443
      targetPort: 8443
      nodePort: 30022
      protocol: TCP
  selector:
    app: rss2kindle-api

Опять обращаем внимание, что type: NodePort, то есть мы хотим открыть порт для доступа из-вне. Мапим порт 8443 на внешний порт 30022. Внутри кластера порт маппим на тоже самый номер 8443 с помощью port: 8443. Имя сервиса внутри кластера определяем как rss2kindle-api через selector->app: rss2kindle-api

web-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: rss2kindle-web
  labels:
    app: rss2kindle-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: rss2kindle-web
  template:
    metadata:
      labels:
        app: rss2kindle-web
    spec:
      containers:
      - name: rss2kindle-web
        image: gcr.io/roundkick-studio/rss2kindle-web:3.3
        env:
        - name: rest.host
          value: https://rss2kindle-api
        - name: rest.port
          value: "8443"
        ports:
        - containerPort: 8443

Здесь опять обращаем внимание на параметр replicas: 2, то есть сервис масштабируемый. Далее в секции env описываются параметры коннекта к REST API. В данном случае, это переменные:

  • rest.host = https://rss2kindle-api
  • rest.port = 8443

В конце декларируем порт 8443, через который откроем доступ к вэб-интерфейсу.

web-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: rss2kindle-web
  name: rss2kindle-web
  annotations:
    cloud.google.com/app-protocols: '{"https":"HTTPS"}'
spec:
  type: NodePort
  ports:
    - name: https
      port: 443
      targetPort: 8443
      nodePort: 30023
      protocol: TCP
  selector:
    app: rss2kindle-web

Снова видим, что type: NodePort, то есть мы хотим открыть порт для доступа из-вне. Мапим порт 8443 на внешний 30023 с помощью nodePort: 30023. Внутри кластера порт маппим на стандартный SSL-порт 443 с помощью port: 443. Имя сервиса внутри кластера определяем как rss2kindle-web через selector->app: rss2kindle-web

Лоад балансер traefik

Описанные выше конфиги можно смело деплоить в GKE, они развернут всю необходимую инфраструктуру и будут работать. Если мы в настройках файервола GCP откроем порты 30021, 30022, 30023, то мы даже сможем получить доступ к нашим приложениям, коннектясь к конкретным нодам кластера. Интерфейс GCP позволяет нам понять какие pods развернуты на каких нодах. Однако, все это выглядит неполноценным, потому что не хватает ключевого компонента любой кластерной архитектуры — лоад балансера. GCP предлагает собственный лоад-балансер, но у меня с ним как-то не заладилось, и я решил использовать traefik, который можно развернуть и в docker и в kubernetes.

Сначала нужно развернуть traefik в качестве kubernetes контроллера. Приведенный ниже конфиг я взял из официальной документации traefik, не особо вникая в детали.

traefik-controller.yaml

#Resource definition
# All resources definition must be declared
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutes.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRoute
    plural: ingressroutes
    singular: ingressroute
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: middlewares.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: Middleware
    plural: middlewares
    singular: middleware
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutetcps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteTCP
    plural: ingressroutetcps
    singular: ingressroutetcp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsoptions.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSOption
    plural: tlsoptions
    singular: tlsoption
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: traefikservices.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TraefikService
    plural: traefikservices
    singular: traefikservice
  scope: Namespaced

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - tlsoptions
    verbs:
      - get
      - list
      - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: default

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller

Теперь нужно подготовить деплоймент и сервис конфигурации для traefik, где мы уже можем определять кастомные параметры под наши нужды.

traefik-deployment.yaml

kind: Deployment
apiVersion: apps/v1
metadata:
  name: traefik
  labels:
    app: traefik
spec:
  replicas: 1
  selector:
    matchLabels:
      app: traefik
  template:
    metadata:
      labels:
        app: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      containers:
        - name: traefik
          image: traefik:v2.1.4
          args:
            - --log.filePath=/var/log/traefik.log
            - --log.level=DEBUG
            - --api
            - --api.insecure
            - --entrypoints.web.address=:80
            - --entrypoints.websecure.address=:443
            - --providers.kubernetescrd
            - --serverstransport.insecureskipverify=true
            - --certificatesResolvers.myresolver.acme.email=test@localhost.org
            - --certificatesResolvers.myresolver.acme.storage=acme.json
            - --certificatesResolvers.myresolver.acme.httpChallenge.entryPoint=websecure
          ports:
            - name: web
              containerPort: 80
            - name: admin
              containerPort: 8080
            - name: websecure
              containerPort: 443

---
apiVersion: v1
kind: Service
metadata:
  name: traefik
  annotations:
    kubernetes.io/ingress.global-static-ip-name: "rss2kindle-cluster-ip"
spec:
  type: LoadBalancer
  selector:
    app: traefik
  ports:
    - protocol: TCP
      port: 80
      name: web
      targetPort: 80
    - protocol: TCP
      port: 8080
      name: admin
      targetPort: 8080
    - protocol: TCP
      port: 443
      name: websecure
      targetPort: 443   

Здесь лучше начать пояснения с конфигурации сервиса. Первое, на что обращаем внимание — это type: LoadBalancer. Далее, мы открываем три порта: порт 80 — на который будет переправляться весь http трафик , порт 443 — для https трафика и порт 8080 — для доступа к админке самого traefik. В деплоймент конфигурации в секции ports также определены порты 80, 443 и 8080. В целом, с портами все стандартно — то же самое было проделано со всеми предыдущими сервисами.

Более интересная секция в деплоймент конфигурации — это spec -> containers -> args, где определяются параметры, с которыми стартует traefik. Опций довольно много, нужно довольно долго вкуривать официальную документацию и провести множество неудачных экспериментов, чтобы подобрать работающую конфигурацию. Прежде всего, я нашел полезными опции дебага:

--log.filePath=/var/log/traefik.log
--log.level=DEBUG

С ними отлаживать traefik становиться гораздо легче. Без логов чувствуешь себя просто слепым — лоад балансер не работает, но совершенно непонятно почему. Читать логи можно с помощью команды:

kubectl exec [traefik-pod] -it less /var/log/traefik.log

Далее, для наших сервисов необходимы опции:

--entrypoints.web.address=:80
--entrypoints.websecure.address=:443
--providers.kubernetescrd
--serverstransport.insecureskipverify=true
--certificatesResolvers.myresolver.acme.email=test@localhost.org
--certificatesResolvers.myresolver.acme.storage=acme.json
--certificatesResolvers.myresolver.acme.httpChallenge.entryPoint=websecure

Тут пояснения требуют опции касающиеся работы с TLS. Параметры начинающиеся с certificatesResolvers.myresolver.acme специфицируют минимально необходимый набор параметров (согласно докментации) для автогенерируемых SSL сертификатов через Let’s Encrypt. Однако, Let’s Encrypt у меня не подлетел и с этим еще предстоит разобраться. Здесь я упоминаю эти параметры для справки, чтобы показать, что такая опция существует. В общем случае traefik просто создает самоподписанный сертификат для работы через HTTPS. Параметр serverstransport.insecureskipverify=true отключает проверку SSL сертификатов при взаимодействии между traefik и сервисами, которые он обслуживает. Это нужно, потому что наши сервисы rss2kindle-api и rss2kindle-web работают через HTTPS и используют самоподписанные SSL-сертификаты, которые не пройдут валидацию.

Заключительный аккорд в партитуре нашей kuberntes-симфонии — правила маршрутизации для лоад балансера. Делается это с помощью ingress конфигурации. Самое толковое объяснение, что такое ingress, я нашел в статье Kubernetes NodePort vs LoadBalancer vs Ingress? When should I use what? Рекомендую к немедленному прочтению.

traefik-ingressroute.yaml

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: rss2kindle-traefik-ingressroute
  namespace: default
spec:
  entryPoints:
    - web
  routes:
  - match: PathPrefix(`/`) 
    kind: Rule
    services:
    - name: mailhog
      kind: Service 
      port: 8025

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: rss2kindle-traefik-ingressroute-https
  namespace: default
spec:
  entryPoints:
    - websecure
  routes:
  - match: PathPrefix(`/rss2kindle`)
    kind: Rule
    services:
    - name: rss2kindle-api
      kind: Service
      port: 8443
      scheme: https
  - match: PathPrefix(`/r2kweb`)
    kind: Rule
    services:
    - name: rss2kindle-web
      kind: Service
      port: 443
      scheme: https
      sticky:
        cookie:
          httpOnly: true
          secure: true
  tls:
    certresolver: myresolver

Итак, здесь мы описываем отдельно правила для двух entryPoint. Первый entryPoint, который мы специфицировали раннее — web на порту 80 для HTTP трафика. Правило match: PathPrefix(/) означает, что все запросы приходящие на порт 80 будут перенаправляться на сервис mailhog:8025, то есть через порт 80 мы получаем доступ к вэб-интерфейсу mailhog. Второй entryPoint — websecure на порту 443 для HTTPS трафика. Здесь у нас два правила. Правило match: PathPrefix(/rss2kindle) перенаправляет все запросы, где path начинается с /rss2kindle на сервис rss2kindle-api. Другими словами, это правило открывает доступ к REST API. Правило match: PathPrefix(/r2kweb) перенаправляет запросы начинающиеся с /r2kweb на сервис rss2kindle-web:443, что открывает доступ к вэб-интерфейсу нашего сервиса. Другие важные опции специфичные для entryPoints: websecure — это scheme и sticky. scheme: https говорит о том, что traefik должен общаться с внутренними сервисами через https. Для rss2kindle-web мы также конфигурируем sticky -> cookie для того, чтобы при навигации со страницы на страницу внутри нашего вэб-приложения traefik не перебрасывал нас на разные ноды кластера, а помнил на какой ноде кластера началась наша сессия и удерживал нас на той же ноде. Для сервиса rss2kindle-api удерживание сессии не нужно, так как каждый запрос не зависит от предыдущего и можно направлять запросы на разные ноды.

Деплоймент

Все эти упражнения, описанные выше, были сделаны с одной целью — сделать деплоймент легким, быстрым и надежным. Кто имел опыт деплоймента в более-менее сложном окружении, знает, что деплоймент — это боль. Деплоймент никогда не происходит так, как планировалось. Всегда вылезают неожиданные косяки в самых неожиданных местах. И вот это все, вроде как должно быть пофикшено с помощью волшебства kubernetes и docker. На деле, конечно, у этого волшебства тоже есть свои ограничения. Теперь просто вместо инфраструктуры заказчика с его безумными файрволами, сетевыми настройками и секьюрити процедурами нужно будет бороться с облачной инфраструктурой GCP или AWS. Однако, с kubernetes, победив однажды и приготовив правильные конфиги, мы можем рассчитывать, что теперь деплоймент превратится в обычную рутинную процедуру. Все, что требуется — это запушить новые версии образов, соответственно обновить версии image в deployment-конфигах и применить новые конфиги.

Собственно, вот так будет выглядеть деплоймент в GKE.

Конфигурируем kubectl для доступа к GKE:

gcloud container clusters get-credentials [cluster-name] --zone [zone]

Применяем наши kubernetes конфигурации:

kubectl apply -f mailhog-deployment.yaml
kubectl apply -f mailhog-service.yaml

kubectl apply -f mongo-deployment.yaml
kubectl apply -f mongo-service.yaml

kubectl apply -f rest-api-deployment.yaml
kubectl apply -f rest-api-service.yaml

kubectl apply -f web-deployment.yaml
kubectl apply -f web-service.yaml    

kubectl apply -f traefik-controller.yaml
kubectl apply -f traefik-deployment.yaml
kubectl apply -f traefik-ingressroute.yaml   

Все.

Ждем какое-то время, пока GKE создаст всю необходимую инфраструктуру и настроит файервол. Если все прошло классно, то при доступе на http://[address]:8080 мы должны попасть в админку traefik:

При доступе на https://[address]:443/r2kweb мы должны попасть на стартовую страничку вэб-приложения:

При доступе на http://[address] мы должны попасть в админку mailhog: