Есть у меня проектик 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: