GatewayAPI
В данной статье мы расскажем, как мы организуем доступ из Итернета к сервисам CI/CD платформы Gitorion и разрабатываемому с ее помощью приложению
1. Введение
Все сервисы CI/CD платформы Gitorion и разрабатываемого приложения запущены в Docker-контейнерах, развернутых в кластере Kubernetes. В ранних инсталляциях мы создавали точки входа в кластер Kubernetes с помощью Ingress. Теперь же перешли на GatewayAPI - новое поколение API Kubernetes Ingress, Load Balancing и Service Mesh
2. Реализации
Существует несколько реализаций GatewayAPI или Implementations. Мы устанавливаем Cilium в качестве сетевого плагина в кластер Kubernetes, поэтому используем реализацию GatewayAPI от Cilium
3. Установка
В первую очередь установите необходимый набор CRDs в кластер Kubernetes. Уточните в официальном руководстве, какую версию Gateway API реализует Cilium на текущий момент. На момент написания статьи Cilium реализовал Gateway API v1.2.0. В официальном руководстве GatewayAPI найдите команду установки CRDs. В команде установки CRDs задайте версию Gateway API, реализуемую Cilium. Установите CRDs в кластер Kubernetes командой:
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/experimental-install.yaml Установите Cilium с поддержкой GatewayAPI в кластер Kubernetes, добавив ключ "--set gatewayAPI.enabled=true" в команду установки Cilium из официального руководства:
helm install cilium cilium/cilium --version 1.18.2 \
--namespace kube-system \
--set gatewayAPI.enabled=true \
--set nodePort.enabled=true
Еще мы добавили ключ "--set nodePort.enabled=true", речь о котором пойдет ниже
4. Шлюз
Шлюз Gateway принимает трафик из Интернета и передает маршрутизаторам HTTPRoute или TCPRoute/UDPRoute, которые направляют трафик в тот или иной сервис Kubernetes. Пример YAML-манифеста шлюза:
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: https-gateway
namespace: default
spec:
gatewayClassName: cilium
listeners:
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
shared-gateway-access: "true"
tls:
certificateRefs:
- kind: Secret
name: infrastructure-tls
В параметре "spec.gatewayClassName" задайте реализацию (Implementation) GatewayAPI. Мы используем в кластере реализацию GatewayAPI от Cilium, поэтому задали значение папаметра "cilium"
В параметре "allowedRoutes.namespaces" задайте метку shared-gateway-access: "true". Данную метку навесьте на все namespace кластера Kubernetes, в которые шлюзу Gateway разрешено направлять трафик. Пример команды, навешивающей метку shared-gateway-access: "true" на namespace с именем production
kubectl label namespace production shared-gateway-access=true В параметре "tls.certificateRefs" подключите SSL-сертификат, который шлюз Gateway задействует для терминации входящего HTTPs трафика
5. Назначаем IP-адрес шлюзу
При создании шлюза Gateway c именем "https-gateway" автоматически будет создана служба Kubernetes с именем "cilium-gateway-https-gateway" типа LoadBalancer, которая будет принимать трафик из Интернета и направлять его в модули кластера Kubernetes
$ kubectl get gateway
NAME CLASS ADDRESS PROGRAMMED AGE
https-gateway cilium True 3s
user@plane2:~$ kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cilium-gateway-https-gateway LoadBalancer 10.106.46.45 <pending> 443:32131/TCP 9m36s
В поле ADDRESS шлюза Gateway пусто, а поле EXTERNAL-IP службы "cilium-gateway-https-gateway" имеет значение <pending>. Шлюз не готов к приему трафика из Интернета. Теперь вам нужно определиться, какие ноды вашего кластера Kubernetes будут принимать трафик из Интернета и назначить их внешние IP-адреса шлюзу Gateway. Для примера создадим две точки входа через worker-ноды c внешними IP-адресами 5.5.5.5 и 7.7.7.7. Создайте пул IP-адресов с помощью YAML-манифеста:
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
name: "ip-pool"
spec:
blocks:
- cidr: "5.5.5.5/32"
- cidr: "7.7.7.7/32"
Добавьте выбранные IP-адреса в аннотацию службы "cilium-gateway-https-gateway":
$ kubectl edit svc cilium-gateway-https-gateway
apiVersion: v1
kind: Service
metadata:
annotations:
lbipam.cilium.io/ips: 5.5.5.5,7.7.7.7
labels:
gateway.networking.k8s.io/gateway-name: https-gateway
io.cilium.gateway/owning-gateway: https-gateway
name: cilium-gateway-https-gateway
namespace: default
...
Спустя некоторое время шлюзу Gateway будут назначены выбранные вами IP-адреса, и он будет готов к приему трафика из Интернета
$ kubectl get gateway
NAME CLASS ADDRESS PROGRAMMED AGE
https-gateway cilium 5.5.5.5,7.7.7.7 True 2m
user@plane2:~$ kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cilium-gateway-https-gateway LoadBalancer 10.106.46.45 5.5.5.5,7.7.7.7 443:32131/TCP 15m36s
6. HTTPRoute
Теперь трафик из Интернета, принимаемый шлюзом Gateway, нужно направить в сервисы кластера Kubernetes. Этим занимаются маршрутизаторы HTTPRoute. Маршрутизаторы анализируют URL запроса и направляют трафик в сервис Kubernetes, который должен обработать запросы для данного URL. Рассмотрим YAML-манифесты маршрутизаторов HTTPRoute приложения, состоящего из бэкенда и фронтенда
Маршрутизатор HTTPRoute для фронтенда:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: prod-frontend-main
namespace: production
spec:
parentRefs:
- name: https-gateway
namespace: default
hostnames:
- "gitorion.ru"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: prod-frontend-main
port: 80
С помощью "spec.parentRefs" маршрутизатор HTTPRoute подключается к шлюзу Gateway с именем "https-gateway", созданному в п.4, и направляет запросы для корневого URL сайта gitorion.ru в службу Kubernetes с именем "prod-frontend-main", которая в свою очередь направляет трафик на обработку в модули фронтенда приложения
Маршрутизатор HTTPRoute для бэкенда:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: prod-backend-main
namespace: production
spec:
parentRefs:
- name: https-gateway
namespace: default
hostnames:
- "gitorion.ru"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: prod-backend-main
port: 8080
Аналогично маршрутизатор HTTPRoute подключается с помощью "spec.parentRefs" к шлюзу Gateway с именем "https-gateway" и направляет запросы, содержащие в URL "/api", в службу Kubernetes с именем "prod-backend-main", которая в свою очередь направляет трафик на обработку в модули бэкенда приложения
7. TCPRoute и UDPRoute
GatewayAPI реализует проброс TCP и UDP трафика из Интернета в службы Kubernetes с помощью маршрутизаторов TCPRoute и UDPRoute. Данная функциональность потребовалась нам для организации доступа Git-клиентов к Git-серверу Forgejo. Однако, TCPRoute и UDPRoute в Cilium на момент написания статьи не реализованы Issues 21929. Поэтому на данном этапе мы организовали доступ к Git-серверу по протоколу TCP с помощью штатной службы Kubernetes типа NodePort:
apiVersion: v1
kind: Service
metadata:
name: forgejo-gateway-api-ssh
namespace: forgejo
spec:
type: NodePort
selector:
app.kubernetes.io/instance: forgejo
app.kubernetes.io/name: forgejo
ports:
- port: 2222
targetPort: 2222
nodePort: 30000
Для этого и потребовался ключ --set nodePort.enabled=true в команде установки Cilium в п.3
Теперь можно клонировать репозитории с Git-сервера Forgejo на свой лэптоп командой:
git clone ssh://git@forgejo.gitorion.ru:30000/owneruser/backend.git 8. Canary-релизы
В заключение расскажем, как мы реализуем канареечные развертывания с помощью GatewayAPI. Канареечные развертывания применяют для тестирования новых релизов приложения, когда есть вероятность в случае ошибки в программном обеспечении повредить данные большого числа пользователей или получить непрогнозируемое поведение нового релиза под нагрузкой. В этом случае рядом c актуальным релизом приложения в кластере Kubernetes развертывают релиз с новой версией приложения и направляют на него часть запросов от реальных пользователей
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: prod-frontend-main
namespace: production
spec:
parentRefs:
- name: https-gateway
namespace: default
hostnames:
- "gitorion.ru"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: prod-frontend-main
port: 80
weight: 90
- name: prod-frontend-main-canary
port: 80
weight: 10
Приведенный выше маршрутизатор HTTPRoute направит 90% запросов в службу Kubernetes c именем "prod-frontend-main" в модули со старой версией приложения и 10% запросов отправит в службу "prod-frontend-main-canary" и модули с новой версией приложения