Flux2 и GitOps: деплой Traefik Ingress Controller и демо-приложения

Введение

В данной статье будет рассмотрен один из подходов для деплоя в кластер K8s под названием GitOps с использованием Flux v2.

В качестве приложения, развертываемого в кластере, используется Traefik в роли Ingress Controller и демонстрационное приложение whoami от команды Traefik.

Инструкция и манифесты для деплоя Traefik взяты за основу из официального репозитория с некоторыми доработками для упрощения\понимания и дополнительными комментариями.

Да кто такой этот ваш GitOps?

Наверняка многие слышали про IaC, DevOps… Но существует ещё одна методология под названием GitOps. Если в двух словах, то GitOps – это тот же IaC, но сделанный правильно со всеми лучшими практиками. То есть вся конфигурация, будь то описание инфраструктуры, приложения или даже сети и каких-то policy – всё это хранится в гите. В данном случае гит является единственным источником истины.

Как организовывается доставка кода или подготовка инфраструктуры в типовом случае? Вносятся ручные изменения со своего рабочего места на тест\стейдж окружение, проводятся различные тест-кейсы, а потом уже выполняется тоже самое для прода и последующий деплой. Даже если вся инфраструктура и конфиги хранятся в гите, то данный подход нельзя назвать идеальным. Для этого есть ряд причин, описанных ниже:

  • описание инфраструктуры может храниться в гите, но любой человек из команды может склонировать репозиторий к себе локально и со своего рабочего места применить новые изменения к целевому объекту (кластеру K8s) – понять другим коллегам, что именно и кем было сделано в таком случае, уже сложнее;
  • если нет проверки или ревью, то может быть допущена ошибка, которая приведет к поломке приложения или инфраструктуры;
  • необходимо предоставлять доступ в целевой кластер для деплоя разным людям и встают вопросы безопасности и отслеживания прав доступа.

Выше описана классическая push-модель доставки изменений в кластер, т.е. что-то делается извне (сборка, QA-тесты – т.н. CI), а потом всё это как-то доставляется в целевой кластер (CD).

Но также существует и pull-модель, которая и является основой GitOps. Весь CI выполняется, как и ранее, но доставка изменений в целевой кластер выполняется… из самого кластера. Существует некая GitOps-система. Это может быть Flux или ArgoCD. Их условно можно назвать неким оператором, который запущен в кластере и отслеживает все изменения, сделанные в Git-репозитории. А также отслеживает текущее состояние кластера. И если состояние кластера отлично от того, что в описано в гите, возвращает его к исходному – к описанному в Git. Таким образом достигаются следующие преимущества:

  • вся конфигурация (IaC & etc) хранится только в одном месте и применяется только из гита;
  • видно, кто вносил изменения через историю коммитов – все изменения теперь более прозрачно можно просмотреть;
  • доступ в кластер теперь не нужен всем пользователям, т.к. за это отвечает лишь один gitops-агент внутри кластера K8s;
  • возможность быстрого отката в случае ошибок через git revert.

Схематично pull-модель можно изобразить так:

Команда разработки вносит необходимые изменения в приложение или инфраструктуру, манифесты или чарты которых хранятся в системе контроля версий. Оператор flux непрерывно проверяет наличие изменений в репозитории и при наличии таковых самостоятельно выполняет деплой в кластер Kubernetes.

Что у Flux под капотом?

Ниже описаны основные концепции Flux, необходимые для дальнейшего понимания.

Sources

Source содержит в себе источник (например, ссылка на гит-репозиторий или helm-чарт) желаемого состояния приложения или инфраструктуры. Этот источник будут использовать другие компоненты Flux для дальнейшего деплоя.

Содержимое источника проверяется с заданной периодичностью на наличие изменений и при наличии таковых передается другим компонентам для дальнейшего применения их в кластер K8s.

Все источники хранятся в виде Custom Resources в кластере K8s и имеют типы GitRepository, HelmRepository (самые основные, но есть и другие).

Reconciliation

Reconciliation отвечает за то, чтобы текущее состояние кластера K8s было таким же, как описано в в используемых Source, то есть в гите, хелм-чарте и т.д.

Kustomization

Kustomize в кубере – это набор инструментов, позволяющих вносить свои кастомные изменения в yaml-манифесты, не изменяя исходных манифестов. Это примерно как использование исходного докер-образа, поверх которого выполняются уже свои инструкции. Для более подробного понимания Kustomize стоит почитать статью у Фланта.

В случае с Flux, Kustomization является сущностью, которой придётся оперировать достаточно часто. По сути наличие файла Kustomization.yaml будет указывать Reconciliation для согласования и внесения локальных изменений в кластер на основе того, что указано в конечных yaml-манифестах, переопределенных через Kustomize.

Bootstrap

Установка Flux в кластер K8s выполняется через одноименную утилиту flux или через Terraform и этот процесс называется bootstrap. В процессе установки утилита flux, используя текущий контекст kubectl, выполняет создание сущностей GitRepository и Kustomization для самого Flux-оператора, после чего пушит созданные сущности в гит-репозиторий (передается через аргументы)

Таким образом, если кратко описать описанные выше сущности, получается следующее:

Source – ссылка на требуемое состояние кода или инфраструктуры
Reconciliation – согласование состояний кластера и источника
Kustomization – согласование изменений в кластер на основе переопределённых файлов из Source

А как всё выглядит на практике?

Подготовка и установка Flux

Нижеописанные действия будут выполняться в кластере Kubernetes v1.22.5 на базе Docker Desktop, но подойдет любой имеющийся кластер кубера.

Помимо готового кластера куба понадобится также следующее:

  • установленная утилита flux
curl -s https://fluxcd.io/install.sh | sudo bash
. <(flux completion bash)
  • имеющийся пустой гит-репозиторий; в статье будет использоваться github и доступ по токену

Перед началом работ необходимо выполнить проверку совместимости:

flux check --pre

► checking prerequisites
✔ Kubernetes 1.22.5 >=1.20.6-0
✔ prerequisites checks passed

После прохождения проверки можно выполнить установку Flux в кластер:

export GITHUB_TOKEN=<ТОКЕН_С_ГИТХАБА>
export GITHUB_USER=rmn-lux # имя аккаунта на гитхабе

REPO=gitops-traefik # имя пустого репозитория
OWNER=rmn-lux

flux bootstrap github \
  --owner=$OWNER \
  --repository=$REPO \
  --branch=main \
  --path=clusters/production \
  --personal

В результате установки произошло следующее:

  • по пути clusters/production (был задан при bootstrap) Flux создал манифесты gotk-components.yaml и gotk-sync.yaml, после чего подготовил файл kustomization.yaml и применил их в кластер;
  • запушил на основе текущего контекста kubectl и переданного токена эти манифесты в гит-репозиторий с именем traefik-gitops.

Запущены следующие поды:

kubectl get po -n flux-system
NAME                                       READY   STATUS    RESTARTS      AGE
helm-controller-845959fb9f-2mjtb           1/1     Running   8 (31m ago)   4d18h
kustomize-controller-6f44c8d499-qrkr5      1/1     Running   7 (32m ago)   4d18h
notification-controller-5c7b759c64-h2bg6   1/1     Running   6 (31m ago)   4d18h
source-controller-6b6c7bc4bb-b44fm         1/1     Running   6 (32m ago)   4d18h

Таким образом флакс оказался запущен в кластере, а все его “установочные” файлы-манифесты оказались в гите со следующей структурой:

├── clusters
│   ├── production
│   │   ├── flux-system
│   │   │   ├── gotk-components.yaml
│   │   │   ├── gotk-sync.yaml
│   │   │   └── kustomization.yaml

Несмотря на то, что поды запущены, необходимо убедиться, что Flux успешно применил манифесты. Для этого можно воспользоваться снова утилитой flux, которая покажет все внутренние ресурсы и их состояние:

flux get all

NAME                            READY   MESSAGE                                 REVISION        SUSPENDED
gitrepository/flux-system       True    Fetched revision: main/9fc58d9          main/9fc58d9    False

NAME                            READY   MESSAGE                                 REVISION        SUSPENDED
kustomization/flux-system       True    Applied revision: main/9fc58d9          main/9fc58d9    False

Как видно, у флакса появились сущности gitrepository и kustomization для приложения flux-system, т.е. сам флакс является обычным приложением и управляется также, как и другие приложения, которые будут развернуты.

Стоит обратить внимание на столбец READY, все сущности должны быть в статусе True, т.е. успешно применены в кластер. А также в столбце MESSAGE можно увидеть ветку и хеш коммита, на основе которого эти изменения были применены.

Структура репозитория

Теперь, когда Flux установлен, необходимо подготовить дальнейшую структуру репозитория для установки Traefik в роли Ingress Controller.

Подразумевается, что в репозитории по пути ./clusters будет несколько окружений – production и staging, поэтому будут созданы соответствующие каталоги, но в рамках данной статьи работа будет вестись только с одним кластером по пути ./clusters/production. Установка флакса в другие кластеры ничем не отличается от установки, описанной выше, необходимо только указать в параметре –path новый путь.

Далее необходимо склонировать локально репозиторий и создать в нём каталоги:

mkdir -pv ./apps/{base,staging,production}/traefik  ./clusters/staging ./infrastructure/crds

Получается следующая структура:

tree

.
├── apps
│   ├── base
│   │   └── traefik
│   ├── production
│   │   └── traefik
│   └── staging
│       └── traefik
├── clusters
│   └── staging
|   └── production
│   │   ├── flux-system
│   │   │   ├── gotk-components.yaml
│   │   │   ├── gotk-sync.yaml
│   │   │   └── kustomization.yaml
└── infrastructure
    ├── crds

Назначение каталога clusters по идее должно быть уже понятно – там хранятся манифесты флакса для кластеров K8s. Их может быть огромное количество, в данном случае для примера только один – production.

Каталог apps содержит в себе манифесты для приложений, которые будут запускаться в кластере K8s. Для этого создан подкаталог base/traefik, который определяет базовые манифесты, а в каталогах с именем нужного кластера (production/traefik или staging/traefik) будут вноситься переопределения для того или иного окружения, что очень удобно.

Каталог infrastructure аналогичен каталогу apps, но здесь хранятся манифесты инфраструктурных приложений, в данном случае для Traefik необходимы отдельные CRD, подробнее об этом далее.

Подготовка манифестов Traefik

В каталог base/traefik необходимо разместить манифесты типа ClusterRole, ClusterRoleBinding, ServiceAccount:

cat > ./apps/base/traefik/rbac.yaml <<EOF
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: traefik-ingress-controller
rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
      - networking.k8s.io
    resources:
      - ingresses
      - ingressclasses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
      - middlewaretcps
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - ingressrouteudps
      - tlsoptions
      - tlsstores
      - serverstransports
    verbs:
      - get
      - list
      - watch

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

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
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: traefik
EOF

И, наконец, сам деплоймент:

cat > ./apps/base/traefik/deployment.yaml << EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik
  labels:
    app.kubernetes.io/instance: traefik
    app.kubernetes.io/name: traefik
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: traefik
      app.kubernetes.io/instance: traefik
  template:
    metadata:
      labels:
        app.kubernetes.io/name: traefik
        app.kubernetes.io/instance: traefik
    spec:
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 60
      containers:
        - name: traefik
          image: traefik:2.5.4
          args:
            - "--entryPoints.web.address=:8000/tcp"
            - "--entryPoints.websecure.address=:8443/tcp"
            - "--entryPoints.traefik.address=:9000/tcp"
            - "--api=true"
            - "--api.dashboard=true"
            - "--ping=true"
            - "--providers.kubernetescrd"
            - "--providers.kubernetescrd.allowCrossNamespace=true"
          readinessProbe:
            httpGet:
              path: /ping
              port: 9000
            failureThreshold: 1
            initialDelaySeconds: 5
            periodSeconds: 5
            successThreshold: 1
            timeoutSeconds: 2

          livenessProbe:
            httpGet:
              path: /ping
              port: 9000
            failureThreshold: 3
            initialDelaySeconds: 5
            periodSeconds: 5
            successThreshold: 1
            timeoutSeconds: 2

          resources:
            limits:
              cpu: 1000m
              memory: 1000Mi
            requests:
              cpu: 100m
              memory: 50Mi

          ports:
            - name: web
              containerPort: 8000
              protocol: TCP

            - name: websecure
              containerPort: 8443
              protocol: TCP

            - name: traefik
              containerPort: 9000
              protocol: TCP

          volumeMounts:
            - mountPath: /data
              name: storage-volume
      volumes:
        - name: storage-volume
          emptyDir: {}
EOF

Также понадобится сервис типа LoadBalancer:

cat > ./apps/base/traefik/svc.yaml << EOF
---
apiVersion: v1
kind: Service
metadata:
  name: traefik
  labels:
    app.kubernetes.io/instance: traefik
    app.kubernetes.io/name: traefik
spec:
  selector:
    app.kubernetes.io/instance: traefik
    app.kubernetes.io/name: traefik
  type: LoadBalancer
  externalTrafficPolicy: Local
  ports:
    - port: 80
      name: web
      targetPort: web
      protocol: TCP
    - port: 443
      name: websecure
      targetPort: websecure
      protocol: TCP
EOF
Для публичного доступа к ресурсам нужен будет внешний балансировщик для работы сервиса типа LoadBalancer.

В данном случае финальная проверка результатов деплоя будет выполняться локально, т.е. обращения будут идти на localhost в случае с Docker Desktop и встроенным в него K8s.

Для того, чтобы все вышеописанные манифесты были “замечены” Flux, необходимо создать kustomization.yaml:

cat > ./apps/base/traefik/kustomization.yaml << EOF
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - rbac.yaml
  - deployment.yaml
  - svc.yaml
EOF

Теперь, когда в каталоге apps/base/traefik расположены необходимые файлы, необходимо переходить к каталогу apps/production/traefik и разместить там уже настройки, специфичные для конкретного кластера.

В прошлых манифестах не был указан namespace – необходимо указать его для нужного кластера, в данном случае production:

cat > ./apps/production/traefik/namespace.yaml << EOF
---
apiVersion: v1
kind: Namespace
metadata:
  name: traefik-production
EOF

Ранее было сказано о специфичных настройках для определенного кластера. Они указываются в файле traefik-patch.yaml. Файл содержит изменения, которые необходимо применить поверх базового деплоймента:

cat > ./apps/production/traefik/traefik-patch.yaml << EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik
spec:
  template:
    spec:
      containers:
        - name: traefik
          args:
            - "--entryPoints.web.address=:8000/tcp"
            - "--entryPoints.websecure.address=:8443/tcp"
            - "--entryPoints.traefik.address=:9000/tcp"
            - "--api=true"
            - "--api.dashboard=true"
            - "--ping=true"
            - "--providers.kubernetescrd"
            - "--providers.kubernetescrd.allowCrossNamespace=true"
            - "--certificatesresolvers.myresolver.acme.storage=/data/acme.json"
            - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
            - "--certificatesresolvers.myresolver.acme.email=admin@it-lux.ru"
EOF

За функционал объединения отвечает Kustomize, поэтому нужно указать, что необходимо пропатчить базовый манифест новыми изменениями:

cat > ./apps/production/traefik/kustomization.yaml << EOF
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: traefik-production
resources:
  - namespace.yaml
  - ../../base/traefik

patchesStrategicMerge:
  - traefik-patch.yaml
EOF

Все вышеописанные действия можно сделать аналогично для кластеров других окружений (например, staging).

Подготовка манифестов Traefik CRD

Как известно, для доступа к приложениям извне создаются сущности ingress. Но в случае с Traefik не всё так просто. Чтобы работали все фичи Traefik, для этого приходилось использовать множество анноатций. Поэтому для упрощения команда разработки запилила кастомные сущности (через CRD), одна из которых называется IngressRoute и используется вместо уже привычного Ingress. Именно поэтому в каталог infrastructure дополнительно необходимо резместить манифесты CRD:

cat > ./infrastructure/crds/traefik-crds.yaml << EOF
---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: traefik-crds
  namespace: flux-system
spec:
  interval: 30m
  url: https://github.com/traefik/traefik-helm-chart.git
  ref:
    tag: v10.3.0
  ignore: |
    # exclude all
    /*
    # path to crds
    !/traefik/crds/
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
  name: traefik-api-crds
  namespace: flux-system
spec:
  interval: 15m
  prune: false
  sourceRef:
    kind: GitRepository
    name: traefik-crds
    namespace: flux-system
  healthChecks:
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: ingressroutes.traefik.containo.us
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: ingressroutetcps.traefik.containo.us
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: ingressrouteudps.traefik.containo.us
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: middlewares.traefik.containo.us
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: middlewaretcps.traefik.containo.us
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: serverstransports.traefik.containo.us
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: tlsoptions.traefik.containo.us
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: tlsstores.traefik.containo.us
  - apiVersion: apiextensions.k8s.io/v1
    kind: CustomResourceDefinition
    name: traefikservices.traefik.containo.us
EOF

По сути CRD представляют собой такую же ссылку на гит-репозиторий, из которого исключается всё лишнее и используются лишь необходимые пути, а в Kustomization дополнительно настраиваются healthcheck`и – такие же можно сделать и для пользовательских приложений в каталоге apps.

Теперь нужно снова создать ещё пару Kustomization для текущего каталога infrastructure/crd и каталога на уровень выше:

cat > ./infrastructure/crds/kustomization.yaml << EOF
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: flux-system
resources:
  - traefik-crds.yaml
EOF

cat > ./infrastructure/kustomization.yaml << EOF
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- crds
EOF

Запуск Traefik

Для того, чтобы Flux “увидел”, что нужно запустить Traefik и необходимые ему CRD, необходимо разместить ещё пару yaml-файлов с типом Kustomization в каталоге cluster/production. Flux уже наблюдает за этим каталогом, т.к. при bootstrap`е именно этот путь был передан через аргумент path. Таким образом всё, что оказывается с типом Kustomization в каталоге cluster/production, будет применено в кластер Kubernetes (при отсутствии ошибок).

Чтобы увидеть первые результаты деплоя, необходимы последние штрихи. Сначала добавить файл infrastructure.yaml, который ссылается на каталог infrastructure в корне репозитория, в котором уже размещены все необходимые манифесты для Traefik:

cat > ./clusters/production/infrastructure.yaml << EOF
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: infrastructure
  namespace: flux-system
spec:
  interval: 10m0s
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./infrastructure
  prune: true
  wait: true
EOF

А также apps.yaml, который ссылается на каталог apps:

cat > ./clusters/production/apps.yaml << EOF
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m0s
  dependsOn:
    - name: infrastructure
  sourceRef:
    kind: GitRepository
    name: flux-system
  path: ./apps/production
  prune: true
  wait: true
EOF

Таким образом на данный момент структура репозитория должна быть следующая:

tree
.
├── apps
│   ├── base
│   │   ├── traefik
│   │   │   ├── kustomization.yaml
│   │   │   ├── rbac.yaml
│   │   │   ├── svc.yaml
│   │   │   └── deployment.yaml
│   ├── production
│   │   ├── traefik
│   │   │   ├── kustomization.yaml
│   │   │   ├── namespace.yaml
│   │   │   └── traefik-patch.yaml
│   └── staging
├── clusters
│   ├── production
│   │   ├── apps.yaml
│   │   ├── flux-system
│   │   │   ├── gotk-components.yaml
│   │   │   ├── gotk-sync.yaml
│   │   │   └── kustomization.yaml
│   │   └── infrastructure.yaml
│   └── staging
├── infrastructure
│   ├── crds
│   │   ├── kustomization.yaml
│   │   └── traefik-crds.yaml
│   └── kustomization.yaml

Теперь можно сделать первый коммит, на основе которого Flux отследит внесенные изменения и попробует их применить в кластер:

git add .
git commit -m 'Added Traefik infra manifests' && git push origin main
Необходимо подождать около 3-5 минут для получения результата, запустив для наблюдения kubectl get po -A -w

Для проверки того, что деплой прошёл успешно, можно проверить запущенные поды в namespace traefik-production:

traefik-production   traefik-9c4b74944-n9cls   1/1  Running   1 (20h ago)  46h

А также ресурсы Flux:

flux get all

Запуск Traefik Dashboard и демо-приложения Whoami

Для проверки работы Traefik в роли Ingress Controller нужно какое-то приложение. В данном случае будет развернуто тестовое приложение от команды Traefik под названием Whoami для демонстрации работы, а также Dashboard Traefik для наглядности.

Трафик от пользователей будет поступать в кластер K8s через сервиса типа LoadBalancer и доходить до Traefik Ingress Controller. В облачных кластерах никаких проблем не возникнет. Такая схема более подробно описана на сайте Traefik.
Для bare-metal \ on-premise кластеров в продакшене понадобится отдельный балансировщик. Например, это может быть MetalLB.
В данном же случае обращения к приложениям будут идти по адресу 127.0.0.1, т.е. на рабочий ПК, где запущен Docker Desktop для Windows, в целях демонстрации.

Деплой Whoami

Создать каталоги в уже существующей структуре, добавив каталог whoami в apps:

mkdir -pv ./apps/{base,staging,production}/whoami

Разместить манифест с деплойментом и сервисом:

cat > ./apps/base/whoami/deployment.yaml << EOF
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: whoamiv1
  labels:
    name: whoamiv1
spec:
  replicas: 1
  selector:
    matchLabels:
      task: whoamiv1
  template:
    metadata:
      labels:
        task: whoamiv1
    spec:
      containers:
        - name: whoamiv1
          image: traefik/traefikee-webapp-demo:v2
          args:
            - -ascii
            - -name=FOO
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /ping
              port: 80
            failureThreshold: 1
            initialDelaySeconds: 2
            periodSeconds: 3
            successThreshold: 1
            timeoutSeconds: 2
          resources:
            requests:
              cpu: 10m
              memory: 128Mi
            limits:
              cpu: 200m
              memory: 256Mi
EOF
cat > ./apps/base/whoami/svc.yaml << EOF
---
apiVersion: v1
kind: Service
metadata:
  name: whoamiv1
  namespace: app
spec:
  ports:
    - name: http
      port: 80
  selector:
    task: whoamiv1
EOF

И самый важный момент – создание манифеста с типом IngressRoute:

cat > ./apps/base/whoami/ingressroute.yaml << EOF
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoami
spec:
  entryPoints:
    - web
  routes:
    - kind: Rule
      match: Host(\`fix.me\`)
      services:
        - kind: Service
          name: whoamiv1
          port: 80
EOF
– Стоит напомнить, что тип IngressRoute будет “принят” кубером благодаря CRD от Traefik;
– Также в поле Host в данном случае содержимое “fix.me” – оно будет исправлено через Kustomize и path в дальнейшем.
В исходном репозитории Traefik данный IngressRoute работает через TLS, но для упрощения в рамках данной статьи работа организована без сертификатов и по http.

И в заключении добавляется kustomize для применения вышесозданных манифестов:

cat > ./apps/base/whoami/kustomization.yaml << EOF
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - ingressroute.yaml
  - svc.yaml
EOF

Базовые манифесты созданы, теперь необходимо создать новые, в которых будут указаны специфичные настройки в зависимости от окружения, в данном случае для production:

cat > ./apps/production/whoami/namespace.yaml << EOF
---
apiVersion: v1
kind: Namespace
metadata:
  name: whoami-production
EOF
cat > ./apps/production/whoami/whoami-patch.yaml << EOF
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: whoamiv1
spec:
  replicas: 8
  template:
    spec:
      containers:
        - name: whoamiv1
          args:
            - -ascii
            - -name=PRODUCTION
EOF

Как было сказано ранее, домен fix.me меняется на необходимый для продакшена, в данном случае условный домен в качестве примера:

cat > ./apps/production/whoami/ingressroute-patch.yaml << EOF
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoami
spec:
  routes:
   - kind: Rule
     match: Host(\`whoami.example.com\`)
     services:
        - kind: Service
          name: whoamiv1
          port: 80
EOF

И снова в заключении kustomize, объединяющий всё вместе:

cat > ./apps/production/whoami/kustomization.yaml << EOF
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: whoami-production
resources:
  - namespace.yaml
  - ../../base/whoami

patchesStrategicMerge:
  - whoami-patch.yaml
  - ingressroute-patch.yaml
EOF

Создать коммит и запушить изменения, ожидать результатов деплоя около 3-5 минут:

git add .
git commit -m 'Deploy whoami ' && git push origin main

В hosts рабочей станции прописать 127.0.0.1 для домена из IngressRoute и проверять:

 curl whoami.example.com
  ____    ____     ___    ____    _   _    ____   _____   ___    ___    _   _
 |  _ \  |  _ \   / _ \  |  _ \  | | | |  / ___| |_   _| |_ _|  / _ \  | \ | |
 | |_) | | |_) | | | | | | | | | | | | | | |       | |    | |  | | | | |  \| |
 |  __/  |  _ <  | |_| | | |_| | | |_| | | |___    | |    | |  | |_| | | |\  |
 |_|     |_| \_\  \___/  |____/   \___/   \____|   |_|   |___|  \___/  |_| \_|

GET / HTTP/1.1
Host: whoami.example.com
User-Agent: curl/7.68.0
Accept: */*
Accept-Encoding: gzip
X-Forwarded-For: 192.168.65.3
X-Forwarded-Host: whoami.example.com
X-Forwarded-Port: 80
X-Forwarded-Proto: http
X-Forwarded-Server: traefik-54c5b48c46-7jl92
X-Real-Ip: 192.168.65.3

Деплой Traefik Dashboard

Для деплоя дашборда понадобится внести некоторые изменения в оригинальные манифесты, которые уже были применены в кластере. По сути нижеописанные действия можно назвать процессом обновления или внесения изменений в уже существующие приложения через Git.

Добавить IngressRoute для дашборда:

cat > ./apps/base/traefik/ingressroute.yaml << EOF
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik
spec:
  entryPoints:
    - web
  routes:
    - kind: Rule
      match: Host(\`dashboard.example.com\`) && (PathPrefix(\`/api\`) || PathPrefix(\`/dashboard\`))
      services:
        - kind: Service
          name: traefik
          port: 9000
EOF

Изменить манифест сервиса, добавив 9000 порт:

cat > ./apps/base/traefik/svc.yaml << EOF
---
apiVersion: v1
kind: Service
metadata:
  name: traefik
  labels:
    app.kubernetes.io/instance: traefik
    app.kubernetes.io/name: traefik
spec:
  selector:
    app.kubernetes.io/instance: traefik
    app.kubernetes.io/name: traefik
  type: LoadBalancer
  externalTrafficPolicy: Local
  ports:
    - port: 80
      name: web
      targetPort: web
      protocol: TCP
    - port: 443
      name: websecure
      targetPort: websecure
      protocol: TCP
    - port: 9000
      name: traefik
      targetPort: traefik
      protocol: TCP
EOF

Добавить IngressRoute в kustomization:

cat > ./apps/base/traefik/kustomization.yaml << EOF
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - rbac.yaml
  - deployment.yaml
  - svc.yaml
  - ingressroute.yaml
EOF

Но для работы дашборда без https необходимо ещё внести изменения в деплоймент, добавив строку api.insecure. Это делается через patch:

cat > ./apps/production/traefik/traefik-patch.yaml << EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: traefik
spec:
  template:
    spec:
      containers:
        - name: traefik
          args:
            - "--entryPoints.web.address=:8000/tcp"
            - "--entryPoints.websecure.address=:8443/tcp"
            - "--entryPoints.traefik.address=:9000/tcp"
            - "--api=true"
            - "--api.insecure=true"
            - "--api.dashboard=true"
            - "--ping=true"
            - "--providers.kubernetescrd"
            - "--providers.kubernetescrd.allowCrossNamespace=true"
            - "--certificatesresolvers.myresolver.acme.storage=/data/acme.json"
            - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
            - "--certificatesresolvers.myresolver.acme.email=admin@it-lux.ru"
EOF
В манифесте выше добавлена строка – “–api.insecure=true”, позволяющая запускать дашборд Traefik без https, но для реального продакшена так делать, разумеется, не стоит.

В конечном счёте структура репозитория должна быть следующей:

tree
.
├── apps
│   ├── base
│   │   ├── traefik
│   │   │   ├── ingressroute.yaml
│   │   │   ├── kustomization.yaml
│   │   │   ├── rbac.yaml
│   │   │   ├── svc.yaml
│   │   │   └── traefik.yaml
│   │   └── whoami
│   │       ├── deployment.yaml
│   │       ├── ingressroute.yaml
│   │       └── kustomization.yaml
│   ├── production
│   │   ├── traefik
│   │   │   ├── kustomization.yaml
│   │   │   ├── namespace.yaml
│   │   │   └── traefik-patch.yaml
│   │   └── whoami
│   │       ├── ingressroute-patch.yaml
│   │       ├── kustomization.yaml
│   │       ├── namespace.yaml
│   │       └── whoami-patch.yaml
│   └── staging
├── clusters
│   ├── production
│   │   ├── apps.yaml
│   │   ├── flux-system
│   │   │   ├── gotk-components.yaml
│   │   │   ├── gotk-sync.yaml
│   │   │   └── kustomization.yaml
│   │   └── infrastructure.yaml
│   └── staging
├── infrastructure
│   ├── crds
│   │   ├── kustomization.yaml
│   │   └── traefik-crds.yaml
│   └── kustomization.yaml

На этом можно запушить изменения и наблюдать, как появляются поды с whoami и обновляются поды с traefik через некоторое время (опять от 3 до 5 минут):

git add .
git commit -m 'Deploy whoami and dashboard' && git push origin main

В hosts рабочей станции прописать 127.0.0.1 для домена из IngressRoute и проверять:

Внесение ручных изменений, удаление, отслеживание

Все внесённые изменения будут применены в кластере через время, определенное в поле Interval в clusters/production/apps.yaml и infrastructure.yaml.

Может возникнуть логичный вопрос: а что, если внести изменения в созданные манифесты вручную через kubectl? В таком случае Flux откатит всё ручные изменения через некоторое время к тому состоянию, которое описано в гите. Это очень важный момент, который прекрасно демонстрирует постоянность конфигурации и её единый источник правды – гит.

В случае если изменения руками вносить всё-таки необходимо (но лучше этого избегать), то можно добавить метку, например:

kubectl annotate --field-manager=flux-client-side-apply 

Для эксперимента можно отредактировать деплоймент whoami через kubectl, изменив кол-во реплик, и через некоторое время наблюдать за откатом к исходному состоянию, которое описано в гите.

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

Можно вручную выполнить “синхронизацию”, т.е. Reconciliation для нужного объекта. Например, так:

flux reconcile kustomization infrastructure --with-source

После этого можно наблюдать за изменениями в реалтайме:

flux get kustomizations --watch

Заключение

Репозиторий с примером доступен по ссылке на гитхабе: https://github.com/rmn-lux/gitops-traefik

В статье были описаны базовые демонстрационные варианты подготовки кластера и развертывания в нём приложений. Flux этим функционалом не ограничивается и предоставляет ещё различные фичи, полный список всего можно и нужно посмотреть в документации.

В качестве Source был использован гит-репозиторий, но стоит помнить, что вместо него могут быть и иные объекты, например, Helm Release. Это удобно, т.к. Flux оперирует с уже знакомыми и стандартными инструментами – хелм-чарты, которые могут располагаться как в гите, так и где-то в другом месте и в Helm Release нужно лишь указать ссылку на нужный чарт.

В целом GitOps подход выглядит достаточно новым и может показаться излишне сложным. Может быть и так. Но организация процесса доставки изменнеий в кластер без Flux и GitOps тоже может быть сложной, а в данном случае добавлены очередные абстракции, которые являются неким стандартом, что позволяет унифицировать CI\CD.

В статье уже упоминалось, что есть операторы Flux и ArgoCD. Первый был выбран для примера потому, что разработчики Flux и есть осноположники GitOps-подхода. Тем не менее выбор инструментов сейчас невелик, а потому можно и даже нужно рассмотреть и ArgoCD, чтобы сравнить и выбрать для своих задач необходимый софт.

Используемые источники

https://www.gitops.tech/#what-is-gitops

https://thenewstack.io/tutorial-a-gitops-deployment-with-flux-on-digitalocean-kubernetes/

https://traefik.io/blog/deploy-traefik-proxy-using-flux-and-gitops/

https://medium.com/alterway/manage-your-kubernetes-clusters-with-flux2-82dd1cfe2a6a

https://stackoverflow.com/questions/60177488/what-is-the-difference-between-a-kubernetes-ingress-and-a-ingressroute

https://traefik.io/blog/combining-ingress-controllers-and-external-load-balancers-with-kubernetes/


Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: