GatewayAPI

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" и модули с новой версией приложения