Как стать автором
Обновить
Флант
DevOps-as-a-Service, Kubernetes, обслуживание 24×7

Готовим Gitea со вкусом werf CI/CD и Dex-авторизации в кластере Deckhouse Kubernetes Platform. Часть 3

Уровень сложностиСредний
Время на прочтение14 мин
Количество просмотров1.4K

Привет, Хабр! С вами не опять, а снова Виктор Ашакин, DevOps-инженер компании «Флант», и мы заканчиваем приготовление экосистемы управления кодом и его развёртывания в Deckhouse Kubernetes Platform на основе Gitea. В прошлой части статьи мы сделали всё необходимое для создания тестового репозитория и написания Gitea Actions-пайплайнов и можем двигаться дальше. 

В этом — завершающем — материале создадим репозиторий с кодом приложения, подготовим простенький Helm-чарт и Gitea Actions-пайплайн, в котором опишем автоматический процесс сборки и деплоя приложения в кластер Kubernetes.

Ключевым компонентом автоматизации CI/CD будет werf, Open Source-утилита, созданная «Флантом» и пополнившая ряды Sandbox-проектов CNCF. Она организует полный цикл доставки приложения в Kubernetes и использует Git как единый источник истины для состояния приложения, развёрнутого в кластере. Утилита werf позволяет собирать и упаковывать код приложения в контейнеры, эффективно кэшировать стадии и изменения при сборке, что значительно ускоряет пайплайн. 

С выходом версии 2.0 и переходом на новый движок werf ещё качественнее развёртывает приложения в кластер Kubernetes. В процессе активной работы над приложениями в container registry и на раннере накапливается значительное количество ненужных образов контейнеров — werf позволяет эффективно чистить весь лишний мусор.

Все эти полезные функции мы применим в нашем пайплайне.

Подготовка тестового репозитория

Для начала создадим в Gitea тестовый репозиторий hello-world внутри организации: в правом верхнем углу кнопка «+» → Owner: your_organization → Create Repository.

Клонируем репозиторий к себе:

git clone git@your_gitea.com:team-romeo/hello-world.git
cd hello-worl
touch README.md
git add README.md
git commit -m "first commit"
git push

Теперь добавим код нашего приложения. В моём случае это простая HTML-страница, которая лежит в каталоге app/index.html:

<!DOCTYPE html>
<html>
<body>
    <header>
        <h1>
            Hi! I'm another one typical nginx!
        </h1>
        <h2>
            Kubernetes, Kubernetes everywhere!
        </h2>
    </header>
</body>
</html>

Теперь нужно подготовить файл werf.yaml для сборки нашего приложения. В моём примере происходит простое копирование файла в контейнер nginx. В реальной ситуации может происходить многоуровневая сборка с переиспользованием стадий сборки нескольких контейнеров.

werf нативно работает с файлами Dockerfile, но при этом мы теряем преимущество кэширования стадий сборки. Для каждого изменения состояния репозитория

будет запускаться повторный сборочный процесс, что зачастую избыточно. Зачем пересобирать приложение, если мы добавили в репозиторий Helm-чарт,

поправили README.md или файл, который в коде не участвует? Также избыточно постоянно переустанавливать в контейнере одни и те же пакеты.

Я покажу, как оптимизировать сборку с помощью werf stapel. В корне создаём файл werf.yaml — это аналог Dockerfile, сценарий сборки:

# Название приложения, оно будет использоваться в чарте как .Chart.Name
project: hello-world
configVersion: 1
---
image: nginx
from: nginx:1.24.0-bullseye
# Директива указывает, что и куда кладём,
# можно задать несколько директив списком
git:
- add: /app
  to: /app
# Директива указывает, что исключать при добавлении в контейнер,
# также изменения в этих файлах не будут отслеживаться
  excludePaths:
  - .helm
  - werf.yaml
  - .gitea
  - README.md
# Указываем, что на стадии install отслеживаем все файлы в каталоге /app.
# Можно фильтровать файлы по маскам, например */*.html
  stageDependencies:
    install:
    - "*/**"
# В этой директиве управляем процессом сборки,
# стадии удобно комбинировать со стадиями добавления кода
# Разные стадии кэшируются отдельно
shell:
  install:
  - sed -i 's/Kubernetes/Deckhouse/g' app/index.html
  setup:
  - rm /etc/nginx/conf.d/default.conf

Процесс сборки у меня достаточно прост: в Docker-образ nginx я помещаю каталог /app по пути /app, после выполняю команду редактирования index.html, а затем удаляю дефолтный конфиг nginx. Последний шаг нужен потому, что мы подложим наш конфиг в виде configmap для удобства редактирования и выката. Но об этом чуть позже. 

В моём werf.yaml я оставил комментарии, из которых понятны структура файла и директива управления сборкой. Пример выше расширен для наглядности, но его можно максимально упростить:

project: hello-world
configVersion: 1
---
image: nginx
from: nginx:1.24.0-bullseye
git:
- add: /app
  to: /app
  stageDependencies:
    install:
    - "*/**"
shell:
  setup:
  - sed -i 's/Kubernetes/Deckhouse/g' app/index.html
  - rm /etc/nginx/conf.d/default.conf

Helm-чарт

Теперь подготовим Helm-чарт приложения. По умолчанию werf ищет каталог с чартом в корне репозитория в каталоге .helm. Структура нашего чарта будет выглядеть так:

├── .helm
         ├── templates
         │         ├── deployment.yaml
         │         ├── ingress.yaml
         │         ├── nginx-config-cm.yaml
         │         └── service.yaml
         └── values.yaml
Создаём ресурсы чарта
# deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
  annotations:
    # Аннотация включает автоматическое отслеживание CM и secrets
    # Перезапускает под в случае изменения
    "pod-reloader.deckhouse.io/auto": "true"
spec:
  revisionHistoryLimit: 1
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  replicas: 1
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      volumes:
      - name: configs
        configMap:
          name: {{ .Chart.Name }}
      imagePullSecrets:
        # Название секрета для подключения к container registry
        - name: gitea-regsecret
      containers:
      - name: nginx
        image: {{ .Values.werf.image.nginx }}
        ports:
        - containerPort: 80
        volumeMounts:
        - name: configs
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
spec:
  rules:
  # С помощью переменной $WERF_ENV, которая передаётся при развёртывании, определяем, какой url подставить в окружение
  - host: {{ pluck .Values.werf.env .Values.app.url | first | default .Values.app.url._default }}
    http:
      paths:
      - path: "/"
        pathType: ImplementationSpecific
        backend:
          service:
            name: {{ .Chart.Name }}
            port:
              number: 80
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
spec:
  selector:
    app: {{ .Chart.Name }}
  ports:
    - name: http
      port: 80
---
# nginx-config-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
data:
  nginx.conf: |
    error_log  /dev/stderr;
    events {
      worker_connections 100000;
      multi_accept on;
    }
    http {
        charset utf-8;

        server {
            listen 80;
            index index.html;
            root /app;
            error_log /dev/stderr;
            location / {
                try_files $uri /index.html$is_args$args;
            }
        }
    }
---
# values.yaml
app:
  url:
    _default: 'hello-dev.example-domain.com'
    stage: 'hello-flant.example-domain.com'
    prod: 'hello.example-domain.com'

Внимательный читатель заметит, что в Helm-чарте активно используется переменная .Chart.Name, но при этом сам файл chart.yaml, из которого обычно берётся эта переменная, отсутствует. Ошибки нет, .Chart.Name подставляется из переменной project: hello-world, указанной в файле werf.yaml

В манифесте deployment.yaml в директиве image: используется переменная {{ .Values.werf.image.nginx }}. При деплое werf подставит туда image:tag из стадии build. Эту переменную нужно подставлять везде, где используются собранные образы из werf.yaml. 

В ресурсе ingress.yaml мы подставляем доменное имя приложения согласно окружению, в которое выкатываем приложение. Название окружения задаётся на стадии выката через ключ команды werf converge --env $ENV или через переменную окружения WERF_ENV. Подобным методом — через Helm-функцию pluck — удобно шаблонизировать переменные.

В ресурсе deployment.yaml есть аннотация "pod-reloader.deckhouse.io/auto": "true", которая показывает модулю DKP pod-reloader, что нужно следить за данным ресурсом и автоматически перезагружать контейнеры, если в связанных конфигурационных файлах или секретах произошли изменения.

Наш чарт готов, добавляем в репозиторий всё, что создали в каталоге:

git add .
git commit -am 'add chart'
git push

Gitea Actions CI/CD-пайплайн

Gitea Actions копирует подход GitHub Actions, их синтаксисы практически идентичны. Большинство пайплайнов, написанных для GitHub Actions, скорее всего, будут работать и в Gitea Actions.

Gitea Actions поддерживает контекстные переменные от GitHub Actions. На момент написания статьи не поддерживался только метод on: workflow_dispatch (в релизе v1.22), что исключает ручной запуск пайплайна. Разработчики обещают добавить данный функционал в релизе 1.23.

Пайплайн будет состоять из двух стадий — сборки и развёртывания. Триггер для запуска пайплайна — коммит в Gitea.

Сборка (build-and-publish) — на этой стадии в раннер загружается код приложения, затем утилита werf на основании сценария сборки (werf.yaml) собирает и упаковывает код в образ Docker-контейнера. Сборка происходит на основании состояния кода, зафиксированного в коммите, который запустил пайплайн. В финале werf отправляет собранный Docker-образ в container registry.

Развёртывание (deploy) — на этой стадии werf формирует Helm-чарт (производит рендер YAML-манифестов), определяет образ, собранный на предыдущем шаге и подставляет его в Helm-чарт. Далее werf развёртывает Helm-чарт в кластере Kubernetes как Helm-релиз.

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

В моём примере сборка происходит из любого коммита, то есть Docker-образ будет одинаков для тестового и prod-окружения и займёт меньше места в хранилище образов. Разделение между продуктовым кодом и тестовым желательно проводить за счёт переменных окружения, которые передаются в Helm-релиз на этапе развёртывания.

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

  1. Выкат в dev-окружение из любых веток и тегов, кроме веток master, stage и тегов с маской release-*.

  2. Выкат в prod-окружение из ветки мастер или тега с маской release-*.

  3. Выкат в stage-окружение из ветки stage.

Пайплайны должны находиться в каталоге .gitea/workflows и иметь формат .yaml. Создадим каталог и пайплайн с произвольным названием:

mkdir -vp .gitea/workflows
vim ci-cd.yaml

А теперь рассмотрим готовый пайплайн. Все пояснения будут после.

Готовый пайплайн
name: build and deploy
run-name: ${{ gitea.actor }} is testing out Gitea Actions
# Условие для запуска пайплайна — запушили коммит в репозиторий
on: [push]

# Переменные, заданные на глобальном уровне, будут доступны на глобальном уровне во всех стадиях пайплайна
env:
  # Обязательные переменные
  APP_NAME: hello-world
  WERF_VERSION: 2 stable
  WERF_REPO: ${{ vars.WERF_REPO }}/${{ gitea.repository }}
  WERF_IMAGE_REPO_USER: ${{ vars.WERF_IMAGE_REPO_USER }}
  WERF_IMAGES_REPO_TOKEN: ${{ secrets.WERF_IMAGES_REPO_TOKEN }}

  # Устанавливаем аннотации, нужные при развёртывании (необязательные, но полезные переменные)
  WERF_ADD_ANNOTATION_WERF_RELEASE_CHANNEL: 'werf.io/release-channel=${{ env.WERF_VERSION }}'
  WERF_ADD_ANNOTATION_PROJECT_GIT: 'project.werf.io/git=${{ gitea.event.repository.html_url }}'
  WERF_ADD_ANNOTATION_CI_COMMIT: 'ci.werf.io/commit=${{ gitea.event.head_commit.url }}'
  WERF_ADD_ANNOTATION_GITEA_CI_PIPELINE_URL: 'gitea.ci.werf.io/pipeline-url=${{ gitea.event.repository.html_url }}/actions/runs/${{ gitea.run_id }}'
  WERF_ADD_ANNOTATION_GITEA_CI_JOB_URL: 'gitea.ci.werf.io/job-url=${{ gitea.event.repository.html_url }}/actions/runs/${{ gitea.run_id }}/jobs/${{ gitea.action }}'

# Стадии пайплайна
jobs:
  # Стадия сборки
  build-and-publish:
    name: Build and Publish
    # Указываем тег, определяющий раннер
    runs-on: werf

    # Обязательный шаг всех стадий, подгружаем репозиторий и его состояние
    steps:
    - name: Checkout code
      uses: actions/checkout@v3
      with:
        fetch-depth: 0

    # Необязательный шаг, добавлен для возможности просмотра массива переменных контекста пайплайна
    # Используя контекстные переменные, строим условия для запуска стадий и шагов пайплайна
    - name: Dump Gitea context
      env:
        JOB_CONTEXT: ${{ toJson(gitea) }}
      run: echo "$JOB_CONTEXT"

    # werf логинится в репозиторий и производит сборку
    - name: Build
      run: |
        source "$(~/bin/trdl use werf 2 stable)"
        werf cr login -u $WERF_IMAGE_REPO_USER -p ${{ secrets.WERF_IMAGES_REPO_TOKEN }} $WERF_REPO
        werf build

  # Развёртывание в dev
  deploy-dev:
    name: Deploy dev
    needs: build-and-publish
    runs-on: werf
    # Условие выполнения текущей стадии deploy-dev
    # Пайплайн запускается не из ветки master, не из ветки stage, не из тега, который начинается с release-
    if: gitea.ref != 'refs/heads/master' && gitea.ref != 'refs/heads/stage' && ! startsWith(gitea.ref, 'refs/tags/release-')

    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      # Шаг развёртывания приложения в кластер
      # Активируем werf с указанием версии, в данном случае 
      # переменная WERF_ENV участвует в формировании Helm-чарта
      - name: Deploy
        env:
          WERF_ENV: dev
        run: |
          source "$(~/bin/trdl use werf ${{ env.WERF_VERSION }})"
          werf converge -Z

  # Развёртывание в prod
  deploy-prod:
    name: Deploy prod
    needs: build-and-publish
    runs-on: werf
    if: gitea.ref == 'refs/heads/master' || startsWith(gitea.ref, 'refs/tags/release-')

    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Deploy
        env:
          WERF_ENV: prod
        run: |
          source "$(~/bin/trdl use werf ${{ env.WERF_VERSION }})"
          werf converge -Z

  # Развёртывание в stage
  deploy-stage:
    name: Deploy stage
    needs: build-and-publish
    runs-on: werf
    if: gitea.ref == 'refs/heads/stage'

    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      # Добавлены ключи для converge
      # Меняют поведение по умолчанию
      # set -x добавлен для демонстрации отладки
      - name: Deploy
        env:
          WERF_ENV: stage
        run: |
          source "$(~/bin/trdl use werf ${{ env.WERF_VERSION }})"
          set -x
          werf converge -Z \
            --env ${WERF_ENV} \
            --namespace "${APP_NAME}-${WERF_ENV}" \
            --release "${APP_NAME}-${WERF_ENV}"

Анатомия пайплайна

Краткая структура пайплайна:

name: build and deploy # Произвольное название пайплайна
on: [push] # Условие для запуска пайплайна — запушили коммит в репозиторий
env: {} # Список переменных окружения, используемых в стадиях и шагах
jobs: # Секция стадий
  build: # Стадия 
    runs-on: werf # Лейбл раннера, на котором запустим код
    if: some conditions # Условия запуска стадии
    steps: [] # Шаги стадии 
  deploy:
    needs: build-and-publish # Зависимость стадии от выполнения других стадий
    steps: []

На верхнем уровне Gitea Actions-пайплайна задаются глобальные директивы, такие как название, триггеры запуска on: [] и переменные env: []. Триггами запуска пайплайна могут быть несколько событий, например создание issue, форк или удаление ветки. В моём случае это push изменений в репозиторий.

Директиву env: {} можно задать на разных уровнях, и переменные в разных стадиях могут переопределять друг друга. В данном случае задаются глобальные переменные, которые будут использованы на шагах сборки и развёртывания. Важно отметить, что при определении переменных доступа к container registry используются контексты vars и secrets.

env:
  WERF_REPO: ${{ vars.WERF_REPO }}/${{ gitea.repository }}
  WERF_IMAGE_REPO_USER: ${{ vars.WERF_IMAGE_REPO_USER }}
  WERF_IMAGES_REPO_TOKEN: ${{ secrets.WERF_IMAGES_REPO_TOKEN }}

vars и secrets — это массив переменных, которые задаются в Gitea-репозитории, группе или организации. Переменные WERF_REPO, WERF_IMAGE_REPO_USER и WERF_IMAGES_REPO_TOKEN мы задавали в разделе настройки CI/CD.

Переменные вида WERF_ADD_ANNOTATION_* добавляются всем YAML-манифестам во время деплоя Helm-релиза в качестве аннотаций. По ним удобно определять

репозиторий, из которого был развёрнут ресурс, сам пайплайн и его номер. Можно перейти по ссылке и перевыкатить ресурс или отследить изменение в репозитории, которое привело к поломке.

Рассмотрим основные моменты стадий пайплайна.

Директива runs-on: werf с помощью лейбла задаёт раннер, на котором будет запущена стадия.

С помощью директивы if: определяется условие выполнения стадии. В пайплайне в директиве if: используется контекст массива переменных Gitea. if: gitea.ref != 'refs/heads/master' && gitea.ref != 'refs/heads/stage' && ! startsWith(gitea.ref, 'refs/tags/release-'). Данный контекст аналогичен контексту GitHub из GitHub Actions, он тоже поддерживается в Gitea Actions.

needs: — директива, которая задаёт зависимость одной стадии от другой, в данном случае прямая зависимость стадии выката от сборки.

Шаги steps — отдельные процессы внутри стадии. Шагами могут быть shell-команды, например run: echo "$JOB_CONTEXT" или werf build. Также можно использовать уже готовые шаги, встроенные из внешних репозиториев.

Уже готовый функционал используется в шаге Checkout code с помощью директивы uses:

- name: Checkout code
  uses: actions/checkout@v3
  with:
    fetch-depth: 0

На шаге Checkout code в стадию подгружается репозиторий, это нужно делать в каждой стадии пайплайна.

Рассмотрим подробнее шаги Build и Deploy.

- name: Build
  run: |
    source "$(~/bin/trdl use werf ${{ env.WERF_VERSION }})"
    werf cr login -u $WERF_IMAGE_REPO_USER -p ${{ secrets.WERF_IMAGES_REPO_TOKEN }} $WERF_REPO
    werf build

В шаге Build с помощью | используется многострочный shell. Команда source активирует werf нужной версии, а с помощью переменной $WERF_VERSION мы можем управлять версиями. Это может быть удобно при обновлении версии или использовании нового функционала. Можно создать эту переменную на глобальном уровне организации, группы или репозитория.

werf cr login — логинимся к container registry, используя переменные и секреты. Можно обращаться к массивам vars или secrets при описании shell-команд.

werf build — запускается сборка Docker-образа на основе сценария werf.yaml.

Шаг Deploy stage:

deploy-stage:
  steps:
  - name: Deploy
    env:
      WERF_ENV: stage
    run: |
      source "$(~/bin/trdl use werf ${{ env.WERF_VERSION }})"
      set -x
      werf converge -Z \
        --env ${WERF_ENV} \
        --namespace "${APP_NAME}-${WERF_ENV}" \
        --release "${APP_NAME}-${WERF_ENV}"

На данном примере наглядно показано, как можно управлять релизами и деплоем. Что и куда будет развёртываться, определяется переменными окружения или ключами командной строки.

WERF_ENV — окружение.

WERF_NAMESPACE — пространство имён, в которое будет развёрнуто приложение, по умолчанию берётся значение project из werf.yaml + WERF_ENV (project_$WERF_ENV).

WERF_RELEASE — имя, которое будет присвоено Helm-release после развёртывания, по умолчанию аналогично WERF_NAMESPACE.

С помощью данных переменных или ключей --env, --namespace, --release мы управляем развёртыванием.

Развёртывания в dev- и prod-окружения производятся командой werf converge -Z без дополнительных ключей, так как отрабатывает поведение по умолчанию, передаём лишь WERF_ENV. Данная переменная в Helm-чарте будет доступна при обращении к встроенному массиву переменных werf {{ .Values.werf.env }}.

Коммитим изменения и отправляем их в репозиторий:

git add .
git commit -am 'first deploy'
git push

Проверяем, включен ли Gitea Actions: страница репозитория → Settings → Enable Repository Actions.

Теперь можно посмотреть на список пайплайнов и их стадии: страница репозитория → Actions:

Названием пайплайна будет message коммита, на котором пайплайн сработал. Давайте взглянем на стадии выполнения: здесь видно, что сборка и выкат прошли успешно.

Посмотреть процесс сборки можно, нажав на название шага стадии
Посмотреть процесс сборки можно, нажав на название шага стадии

Листинг выката:

werf выдаёт подробную информацию по своим процессам, что удобно при отладке.

В листинге выката видны название Helm-релиза и пространство имён, в которое развернулось приложение: Succeeded release "hello-world-dev" (namespace: "hello-world-dev"). В моём случае выкат был не из master-ветки, поэтому приложение было развёрнуто в dev-окружение. Соответственно, при выкате в prod-окружение Helm-release и пространство имён будут называться hello-world-prod.

Бонусом в стадию deploy можно добавить шаг werf plan — эта команда покажет план развёртывания и укажет, какие ресурсы будут изменены при следующем развертывании. А если что-то не так с Helm-чартом, то она выдаст ошибку и расскажет, в чём именно проблема.

- name: Deploy
  env:
    WERF_ENV: dev
  run: |
    source "$(~/bin/trdl use werf ${{ env.WERF_VERSION }})"
    werf plan

Очистка container registry

Со временем в container registry накапливаются Docker-образы, которые уже не нужны. Для их эффективной очистки создадим в репозитории пайплайн, который будет выполняться по расписанию и удалять устаревшие Docker-образы проекта.

Пайплайн cleanup.yaml желательно иметь в каждом репозитории, в котором для сборки и развёртывания используется werf.

Файл создаём в директории .gitea/workflows/:

# .gitea/workflows/cleanup.yaml
---
name: Cleanup container registry
run-name: Cleanup container registry
on:
  schedule:
    - cron:  '05 00 * * *'

env:
  WERF_REPO: ${{ vars.WERF_REPO }}/${{ gitea.repository }}
  WERF_VERSION: '2 stable'

jobs:
  cleanup:
    name: Cleanup
    runs-on: werf
    steps:

      - name: Checkout code
        uses: actions/checkout@v3

      - name: Fetch all history for all tags and branches
        run: git fetch --prune --unshallow

      - name: Cleanup
        env:
          WERF_ENV: stage
        run: |
          set -x
          source "$(~/bin/trdl use werf ${WERF_VERSION:-'2 stable'} )"
          werf cleanup $WERF_REPO

Особенность пайплайна cleanup.yaml заключается в его триггере активации. Он срабатывает по расписанию и имеет cron-синтаксис:

on:
  schedule:
    - cron:  '05 00 * * *'

Запуск будет происходить каждый день в 00:05 согласно часовому поясу Gitea.

В финале наш репозиторий будет выглядеть так:

hello-world
├── app
│         └── index.html
├── .gitea
│         └── workflows
│             ├── ci-cd.yaml
│             └── cleanup.yaml
├── .helm
│         ├── templates
│         │         ├── deployment.yaml
│         │         ├── ingress.yaml
│         │         ├── nginx-config-cm.yaml
│         │         └── service.yaml
│         └── values.yaml
├── README.md
└── werf.yaml

Заключение

Мы прошли долгий путь и проделали большую работу: настроили Gitea, провели его интеграцию с Deckhouse Kubernetes Platform, настроили автоматизацию CI/CD, научились писать пайплайны Gitea Actions и, наконец, познакомились с отличным лучшим инструментом CI/CD — werf.

Поздравляю, вы великолепны!

P. S.

Читайте также в нашем блоге:

Теги:
Хабы:
+26
Комментарии0
1

Публикации

Информация

Сайт
flant.ru
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Александр Лукьянов