Forgejo Actions
В данной статье мы расскажем, как построить CI/CD на базе Forgejo Actions. Установим Forgejo Runner в кластер Kubernetes. Зарегистрируем Forgejo Runner в Forgejo. Реализуем с помощью Forgejo Actions: вытягивание кода из Git-репозитория, сборку Docker-образов и развертывание в кластер Kubernetes
1. Введение
Forgejo Actions - это автоматически подгружаемые модули, из которых как из кирпичиков можно создавать рабочие потоки Forgejo. Список Forgejo Actions можно найти по ссылке и ознакомиться с их назначением и функциями. Forgejo Actions для работы с Docker можно найти тут
2. Рабочие потоки Forgejo
Рабочие потоки Forgejo - это аналог пайплайнов GitLab или Jenkins. Манифесты рабочих потоков размещают в директории .forgejo/workflows Git-репозитория разрабатываемого приложения. Рабочий поток запускается по событию (on.push, on.issues, on.pull_request и т.д) или вручную и может принимать параметры из Web-интерфейса Forgejo. Рабочий поток состоит из Job-ов, Job-ы состоят из шагов Steps, а в шагах можно выполнять Forgejo Actions или команды консоли в секции "run:"
3. Установка Forgejo Runners в Kubernetes
Рабочие потоки не выполняются самим экземпляром Forgejo. Вместо этого они передаются исполнителям Forgejo Runners, которые выполняют рабочие потоки и возвращают результат
Мы реализуем CI/CD процессы в кластере Kubernetes. Инструкция по установке Forgejo Runners в кластер Kubernetes отсутствует в официальной документации Forgejo. Однако, есть пример установки Forgejo Runners в Docker Compose, от которого мы и оттолкнемся
Прежде всего, нужно получить токен для регистрации Forgejo Runner в Forgejo в меню "Действия" -> "Исполнители" панели администратора Forgejo
Затем, зарегистрировать ранер в Forgejo. Мы используем для этого Job в Kubernetes
apiVersion: batch/v1
kind: Job
metadata:
name: forgejo-runner-1-registration
namespace: forgejo
spec:
template:
metadata:
spec:
restartPolicy: "Never"
containers:
- name: forgejo-runner-1-register
image: "code.forgejo.org/forgejo/runner:12.6.4"
imagePullPolicy: IfNotPresent
env:
- name: CONFIG_INSTANCE
value: "https://forgejo.gitorion.ru"
- name: CONFIG_NAME
value: "forgejo-runner-1"
- name: CONFIG_TOKEN
value: "Y7HJQBZKLybvoUOPgz2PzEvpFWqKIbOccsKY6AhE"
command:
- sh
- -c
- |
/bin/forgejo-runner register --no-interactive --token "${CONFIG_TOKEN}" --name "${CONFIG_NAME}" --instance "${CONFIG_INSTANCE}"
cat /data/.runner
CONFIG_INSTANCE - URL инстанса Forgejo
CONFIG_NAME - имя ранера в Web-интерфейсе Forgejo
CONFIG_TOKEN - токен для регистрации ранера в Forgejo
В случае успеха, команда регистрации "forgejo-runner" соханит в файле /data/.runner параметры аутентификации, которые Forgejo Runner будет использовать при подключении к Forgejo
user@dc2-plane:~$ kubectl logs job/forgejo-runner-1-registration -n forgejo
level=info msg="Registering runner, arch=amd64, os=linux, version=v12.6.4."
level=warning msg="Runner in user-mode."
level=info msg="No configuration file specified; using default settings."
level=debug msg="Successfully pinged the Forgejo instance server"
level=info msg="Runner registered successfully."
{
"WARNING": "This file is automatically generated by forgejo-runner. Do not edit it manually unless you know what you are doing. Removing this file will cause act runner to re-register as a new runner.",
"id": 17,
"uuid": "42f428a8-42a0-4861-8d74-eea6864f797b",
"name": "forgejo-runner-1",
"token": "f4dbec24a9783dac428f72119dc76f0a2b1ec976",
"address": "https://forgejo.gitorion.ru",
"labels": [
"docker:docker://data.forgejo.org/oci/node:lts"
]
}
Выделенный фрагмент сохраняем в файл .runner и создаем из него Secret в Kubernetes
kubectl create secret generic forgejo-runner-1-credentials --from-file=.runner -n forgejo
Создаем ConfigMap с конфигурационным файлом Forgejo Runner
apiVersion: v1
kind: ConfigMap
metadata:
name: forgejo-runner-1-config
namespace: forgejo
data:
config.yaml: |
cache:
dir: ""
enabled: true
external_server: ""
host: ""
port: 0
container:
docker_host: ''
enable_ipv6: false
force_pull: false
network: ""
options: ""
privileged: false
valid_volumes: []
workdir_parent: null
host:
workdir_parent: null
log:
job_level: info
level: info
runner:
capacity: 1
env_file: .env
envs:
A_TEST_ENV_NAME_1: a_test_env_value_1
A_TEST_ENV_NAME_2: a_test_env_value_2
fetch_interval: 2s
fetch_timeout: 5s
file: .runner
insecure: false
labels: []
timeout: 3h
Следуя примеру для Docker Compose, создаем Deploymet в Kubernetes, который запустит модуль Forgejo Runner. В Deployment подключаем Secret и ConfigMap, созданные выше, к модулю Forgejo Runner
apiVersion: apps/v1
kind: Deployment
metadata:
name: forgejo-runner-1
namespace: forgejo
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: forgejo-runner-1
app.kubernetes.io/instance: forgejo-runner-1
template:
metadata:
labels:
app.kubernetes.io/name: forgejo-runner-1
app.kubernetes.io/instance: forgejo-runner-1
spec:
containers:
- name: runner
securityContext:
privileged: true
image: "code.forgejo.org/forgejo/runner:12.6.4"
imagePullPolicy: IfNotPresent
command:
- "sh"
- "-c"
- |
while ! nc -z 127.0.0.1 2376 </dev/null; do
echo 'waiting for docker daemon...';
sleep 5;
done
/bin/forgejo-runner --config /etc/runner/config.yaml daemon
resources:
{}
env:
- name: DOCKER_HOST
value: tcp://127.0.0.1:2376
- name: DOCKER_CERT_PATH
value: /certs/client
- name: DOCKER_TLS_VERIFY
value: "1"
volumeMounts:
- name: runner-config
mountPath: /etc/runner
- name: docker-certs
mountPath: /certs
- name: runner-credentials
mountPath: /data/.runner
subPath: .runner
- name: dind
securityContext:
privileged: true
image: "docker.io/library/docker:29.3.0-dind"
imagePullPolicy: IfNotPresent
resources:
{}
env:
- name: DOCKER_TLS_CERTDIR
value: /certs
volumeMounts:
- name: docker-certs
mountPath: /certs
volumes:
- name: runner-config
configMap:
name: forgejo-runner-1-config
- name: runner-credentials
secret:
secretName: forgejo-runner-1-credentials
- name: docker-certs
emptyDir: {}
Зарегистрированный модуль Forgejo Runner появится в Web-интерфейсе Forgejo
В модуле Forgejo Runner запущены два контейнера:
"runner" - в этом контейнере запущен процесс forgejo-runner, получающий от Forgejo задание на выполнение рабочих потоков и возвращающий результат
"dind" - контейнер Docker-in-Docker, о назначении которого мы расскажем в следующем пункте
Forgejo Runner и Forgejo запущены в одном namespace Kubernetes
user@dc2-plane:~$ kubectl get pod -n forgejo
NAME READY STATUS RESTARTS AGE
forgejo-7d874cffc7-7j9bn 1/1 Running 2 (141m ago) 34h
forgejo-runner-1-77f9d78b5c-spdfb 2/2 Running 7 (7m38s ago) 30h
4. Выполнение рабочих потоков Forgejo
Рабочие потоки Forgejo выполняются в контейнере, запускаемом в "dind". Данный контейнер создается из Docker-образа от разработчиков Forgejo:
data.forgejo.org/oci/node:lts
На картинке ниже приведен лог рабочего потока Forgejo. Красным выделен момент закачки образа "node:tls" и запуск контейнера. Чуть ниже скачиваются и инициализируются Forgejo Actions: "actions/checkout@v4", "docker/login-action@v4", "docker/setup-buildx-action@v3", "docker/build-push-action@v6"
Подключимся к контейнеру "dind" и продемонстрируем Docker-образ "node:tls" и контейнер, который был создан из этого Docker-образа и запущен в "dind"
user@dc2-plane:~$ kubectl get pod -n forgejo
NAME READY STATUS RESTARTS AGE
forgejo-7d874cffc7-7j9bn 1/1 Running 4 (7h49m ago) 2d9h
forgejo-runner-1-77f9d78b5c-t7b62 2/2 Running 0 6h33m
user@dc2-plane:~$ kubectl exec -it forgejo-runner-1-77f9d78b5c-t7b62 -n forgejo -c dind -- /bin/sh
/ # docker image ls
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
data.forgejo.org/oci/node:lts 5a593d74b632 1.64GB 424MB
moby/buildkit:buildx-stable-1 37539dd4d60f 352MB 110MB U
/ # docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
96676137e946 moby/buildkit:buildx-stable-1 "/usr/bin/buildkitd-…" 2 minutes ago Up 2 minutes buildx_buildkit_builder-4704e496-bc68-4c0a-b084-ed8934d237c00
c3eb664565dd data.forgejo.org/oci/node:lts "tail -f /dev/null" 2 minutes ago Up 2 minutes FORGEJO-ACTIONS-TASK-734_WORKFLOW-ad1047f74ca190334cd4c2a3fcea535849f43339dbb9bbd4b28082043a8032f2_JOB-staging
5. Вытягиваем Git-репозиторий проекта
Git-репозиторий разрабатываемого проекта содержит: код проекта, Dockerfile для сборки Docker-образа проекта и helm-чарт для развертывания проекта в кластер Kubernetes, которые потребуются рабочим потокам Forgejo. Вытягиваем Git-репозиторий проекта в контейнер рабочего потока с помощью Forgejo Action "actions/checkout@v4"
- name: checkout
uses: actions/checkout@v4
6. Передача секретов в рабочий поток Forgejo
В рабочих потоках Forgejo потребуется собирать Docker-образы приложений и отправлять их в приватный репозиторий Docker-образов Forgejo. Приватный репозитори Docker-образов Forgejo потребует аутентификацию с помощью логина и пароля. Чтобы не скомпрометировать логин и пароль, передадим их в рабочий поток через секреты.
Создаем секреты в меню "Действия" -> "Секреты" настроек Git-репозитория проекта
Используем созданные выше секреты в Forgejo Actions "docker/login-action@v4", выполняющем аутентификацию в приватном реестре Docker-образов
- name: Docker Login
uses: docker/login-action@v4
with:
registry: forgejo.gitorion.ru
username: ${{ secrets.DOCKER_REGISTRY_LOGIN }}
password: ${{ secrets.DOCKER_REGISTRY_PASS }}
Попробуем вывести логин и пароль в окно лога рабочего потока командой
- name: Docker Login Show
run: |
echo username: ${{ secrets.DOCKER_REGISTRY_LOGIN }}
echo password: ${{ secrets.DOCKER_REGISTRY_PASS }}
Все данные, переданные через секреты в рабочий поток Forgejo, будут скрыты в окне лога рабочего потока
7. Сборка Docker-образа
Прежде всего, инициализируем плагин buildx, используя Forgejo Action "docker/setup-buildx-action@v3". Тут можно передать дополнительные параметры. Например, мы передаем в параметрах CA-сертификат, которым подписан доменный сертификат приватного реестра Docker-образов Forgejo
- name: Buildx Setup
uses: docker/setup-buildx-action@v3
with:
config-inline: |
[registry."forgejo.gitorion.ru"]
ca=["/etc/ssl/certs/cicd.pem"]
Теперь можно собирать Docker-образ проекта с помощью Forgejo Action "docker/build-push-action@v6"
- name: Docker Build and Push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: forgejo.gitorion.ru/owneruser/frontend/main:latest
cache-from: type=registry,ref=forgejo.gitorion.ru/owneruser/frontend/main:buildcache
cache-to: type=registry,ref=forgejo.gitorion.ru/owneruser/frontend/main:buildcache,mode=max
Docker-образ проекта и кэш сборки Docker-образа будут сохранены в приватном репозитории Docker-образов Forgejo
При последующих сборках, Forgejo Action "docker/build-push-action@v6" возьмет неизменившиеся слои Docker-образа из кэша сборки Docker-образа и не будет тратить время и ресурсы на их пересборку
8. Развертывание проекта в Kubernetes
К сожалению, мы не нашли Forgejo Actions от разработчиков Forgejo для работы с Kubernetes. Однако, нам ничего не мешает в рабочем потоке Forgejo запускать команду helm с помощью "run:"
Команда Helm подключается к API Kubernetes, используя адрес и аутентификационные данные из файла контекста. Файл контекста $HOME/.kube/config должен присутствовать в домашней директории пользователя, запускающего команду Helm. В случае Forgejo Actions это пользователь, от имени которого выполняется рабочий поток Forgejo. Безопасно передать контекст Kubernetes в рабочий поток Forgejo можно с помощью секретов. Создаем секрет KUBECONFIG в меню "Действия" -> "Секреты" настроек Git-репозитория проекта. Значение секрета заполняем содержимым файла контекста Kubernetes
В рабочем потоке Forgejo переносим содержимое секрета KUBECONFIG в файл $HOME/.kube/config и выполняем команду Helm
- name: Deploy
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBECONFIG }}" > $HOME/.kube/config
helm upgrade --namespace staging --create-namespace frontend-main --history-max 50 --install ./helm --set image=forgejo.gitorion.ru/owneruser/frontend/main:latest
echo secrets.KUBECONFIG: "${{ secrets.KUBECONFIG }}"
cat $HOME/.kube/config
Если попытаться вывести секрет ${{ secrets.KUBECONFIG }} или файл $HOME/.kube/config в окно лога рабочего потока, то Forgejo скроет их содержимое
9. Защита рабочих потоков Forgejo
Обязательно нужно запретить доступ к директории с рабочими потоками .forgejo/workflows всем пользователям и разрешить только пользователям из белого списка
В рабочих потоках Forgejo предоставляется доступ к API Kubernetes, поэтому доступ к рабочим потокам Forgejo должны иметь только DevOps и системные администраторы, которым разрешено администировать кластер Kubernetes
Решить эту задачу можно добавив серверный pre-receive хук в Git-репозиторий проекта. Pre-receive - это серверный скрипт, который запускается в тот момент, когда кто-то пытается отправить свои изменения в Git-сервер (команда git push). В данном скрипте нужно выяснить, что изменения вносятся в директорию .forgejo/workflows. И если пользователь не принадлежит к списку разрешенных, вывести сообщение об ошибке и завершить скрипт с кодом ошибки "1"
Пользователь "ivanov" не принадлежит к списку разрешенных пользователей, и его попытка внести изменения в рабочий поток .forgejo/workflows/production.yaml потерпела неудачу
