Если коротко: пишем YAML с баш скриптом, жмём тег, делаем вид, что всё так и задумывалось. It works on my machine™
release
.Gitea Actions — это встроенная система непрерывной интеграции и развертывания (CI/CD) в Gitea, которая позволяет автоматизировать процессы сборки, тестирования и развертывания ваших проектов. Большой плюс этой системы в том, что она достаточна не требовательна к ресурсам и может быть развернута в собственном изолированном окружении.
В этой статье мы рассмотрим как работать с данной системой, на примере настройки Gitea Actions для Go проекта с автоматической сборкой мультиплатформенных бинарников, созданием Docker образов и публикацией релизов.
Исходный код тут: https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea
Кратко: это тот момент, когда YAML становится языком программирования, а мы — его интерпретатором. Ошибка в одном пробеле — и ваш кластер узнает, что такое настоящее приключение.
Собственно функциональная часть проекта Go не блещет оригинальностью и имеет следующую простую структуру:
hello_gitea/
├── .gitea/ # Конфигурация Gitea Actions
└── workflows/
└── build.yaml # Workflow для сборки
├── main.go # Основной код приложения
├── go.mod # Зависимости Go
├── go.sum # Хеши зависимостей
├── Dockerfile # Docker образ
├── Dockerfile.builder # Docker образ для образа-билдера проекта
├── scripts/ # Вспомогательные скрипты
└── release-interactive.sh # Скрипт для пушинга релиза
├── README.md # Документация
└── .gitignore # Исключения Git
Для упрощения восприятия не применяются сложные архитектурные паттерны и концепции - нам надо просто минимальное Go приложение. Тем не менее это работоспособный http api сервер, который можно расширить парой тройкой эндпойнтов и использовать в других проектах как заглушку или тестовый api.
Основная задача статьи не в написании api сервера на Go, а в автоматизации процесса его сборки.
Наше приложение — это простой REST API сервер на Go с использованием Gin framework:
Никаких микросервисов и gRPC. Только честный HTTP и немного Gin — все как мы любим.
package main
import (
"net/http"
"os"
"github.com/gin-gonic/gin"
)
const version = "1.0.0"
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
// CORS middleware
r.Use(func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
})
// Endpoints
r.GET("/healthz", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"version": version,
})
})
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello, World!",
"version": version,
})
})
// API group
api := r.Group("/api/v1")
{
api.GET("/info", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"service": "hello-api",
"version": version,
"status": "running",
})
})
api.POST("/echo", func(c *gin.Context) {
var request struct {
Message string `json:"message"`
}
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid JSON",
})
return
}
c.JSON(http.StatusOK, gin.H{
"echo": request.Message,
"version": version,
})
})
}
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
r.Run(":" + port)
}
Помимо того, что мы будем собирать бинарники для разных платформ, мы также настроим сборку docker image в котором будем запускать наш сервер api - это может быть полезным для развертывания нашего приложения, если мы настроим такую автоматизацию - например через ArgoCD или flux (впрочем это тема отдельной статьи).
Итак, для контейнеризации используем многоэтапную сборку:
Многоэтапная сборка — потому что мы не возим компилятор в прод. «Толстые» образы — не наш путь.
# Build stage
FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
RUN apk --no-cache add git ca-certificates
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG TARGETOS
ARG TARGETARCH
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -installsuffix cgo -o hello-api main.go
# Final stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates jq
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/hello-api .
RUN chown appuser:appgroup hello-api
USER appuser
EXPOSE 8080
CMD ["./hello-api"]
Проверьте версию Gitea
Gitea Actions доступны начиная с версии 1.17.0. Убедитесь, что ваш сервер Gitea поддерживает Actions.
Включите Actions в настройках репозитория
Если не нашли галочку — проверьте версию Gitea и права доступа. Выключить и включить интернет тоже можно, но это для настроения.
Создайте токен для Actions
Создайте директорию .gitea/workflows/
в корне вашего проекта:
mkdir -p .gitea/workflows
Создайте файл .gitea/workflows/build.yaml
:
## ВНИМАНИЕ: YAML любит отступы больше, чем вы любите прод. Не дайте ему повода.
name: Conditional Release Build
on:
push:
tags:
- v*
jobs:
# Придётся признать: без условий джобы ведут себя как котёнок с лазерной указкой
debug-conditions:
runs-on: ubuntu-latest
steps:
- name: Show build conditions
run: |
echo "Build conditions:"
echo " BUILD_CREATE_RELEASE: ${{ vars.BUILD_CREATE_RELEASE == 'true' }}"
echo " BUILD_CREATE_DOCKER_IMAGE: ${{ vars.BUILD_CREATE_DOCKER_IMAGE == 'true' }}"
echo " BUILD_UPDATE_RELEASE_BRANCH: ${{ vars.BUILD_UPDATE_RELEASE_BRANCH == 'true' }}"
create-release:
runs-on: ubuntu-latest
if: ${{ vars.BUILD_CREATE_RELEASE == 'true' }}
container:
image: ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest
steps:
- name: Checkout repository
run: |
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
cd hello_gitea
git checkout ${{ github.ref }}
- name: Setup Go
run: |
git --version
go version
jq --version
- name: Build all binaries
run: |
cd hello_gitea
# Проверяем, изменились ли зависимости
if [ ! -f go.sum ] || ! go mod verify >/dev/null 2>&1; then
echo "Dependencies changed, downloading..."
go mod download
fi
mkdir -p bin
echo "Building for all platforms..."
# Build for all platforms using direct go build commands
echo "Building for linux amd64..."
GOOS=linux GOARCH=amd64 go build -o bin/hello-api-linux-amd64 main.go
echo "Building for linux arm64..."
GOOS=linux GOARCH=arm64 go build -o bin/hello-api-linux-arm64 main.go
echo "Building for windows amd64..."
GOOS=windows GOARCH=amd64 go build -o bin/hello-api-windows-amd64.exe main.go
echo "Building for darwin amd64..."
GOOS=darwin GOARCH=amd64 go build -o bin/hello-api-darwin-amd64 main.go
echo "Building for darwin arm64..."
GOOS=darwin GOARCH=arm64 go build -o bin/hello-api-darwin-arm64 main.go
# Create archives
echo "Creating archives..."
cd bin
# Create archives with correct file names
tar -czf hello-api-linux-amd64.tar.gz hello-api-linux-amd64
tar -czf hello-api-linux-arm64.tar.gz hello-api-linux-arm64
tar -czf hello-api-windows-amd64.tar.gz hello-api-windows-amd64.exe
tar -czf hello-api-darwin-amd64.tar.gz hello-api-darwin-amd64
tar -czf hello-api-darwin-arm64.tar.gz hello-api-darwin-arm64
echo "Listing bin directory..."
ls -la
echo "Если тут пусто — где-то плачет один DevOps."
- name: Create Release
run: |
cd hello_gitea
# Create release using Gitea API
echo "Creating release..."
curl -X POST \
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"tag_name": "${{ github.ref_name }}",
"name": "Release ${{ github.ref_name }}",
"body": "Automated release with multi-platform binaries and Docker image",
"draft": false,
"prerelease": false
}' \
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases"
echo "Getting release id..."
RELEASE_ID=$(curl -s -H "Authorization: token ${{ secrets.GITEATOKEN }}" \
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases/tags/${{ github.ref_name }}" | \
jq -r '.id')
# Upload all binaries
echo "Uploading assets..."
for file in bin/*.tar.gz; do
echo "Uploading $file..."
curl -X POST \
-H "Authorization: token ${{ secrets.GITEATOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @$file \
"https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases/$RELEASE_ID/assets?name=$(basename $file)"
done
echo "Release created successfully"
echo "Если релиз не появился — проверьте токен. А потом ещё раз проверьте токен."
create-docker-image:
runs-on: ubuntu-latest
# needs: create-release
if: ${{ vars.BUILD_CREATE_DOCKER_IMAGE == 'true' }}
container:
image: docker:28.3.2-dind
steps:
- name: Checkout repository
run: |
apk add --no-cache git
echo "=== GitHub Variables ==="
echo "github.ref = ${{ github.ref }}"
echo "github.ref_name = ${{ github.ref_name }}"
echo "github.sha = ${{ github.sha }}"
echo "github.repository = ${{ github.repository }}"
echo "========================"
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
cd hello_gitea
git checkout ${{ github.ref }}
- name: Setup Docker Buildx
run: |
# Docker is already installed in docker:dind image
docker --version
echo "Setting up Docker Buildx for multi-platform builds..."
# Setup Docker Buildx for multi-platform builds
docker buildx create --name go-buildx --use
docker buildx inspect --bootstrap
- name: Login to Docker Hub
run: |
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
- name: Build multi-platform Docker images
run: |
cd hello_gitea
echo "Building multi-platform images using buildx..."
# Build multi-platform images using buildx
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ${{ secrets.DOCKERHUB_USERNAME }}/hello-api:${{ github.ref_name }} \
--tag ${{ secrets.DOCKERHUB_USERNAME }}/hello-api:latest \
--push \
.
echo "Multi-platform images built successfully"
- name: Remove buildx
run: |
echo "Removing buildx..."
docker buildx rm go-buildx
update-to-release-branch:
runs-on: ubuntu-latest
# needs: create-docker-image
if: ${{ vars.BUILD_UPDATE_RELEASE_BRANCH == 'true' }}
container:
image: docker:28.3.2-dind
steps:
- name: Create Release Branch
run: |
apk add --no-cache git
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
cd hello_gitea
git config user.email "info@direct-dev.ru"
git config user.name "Direct-Dev-Robot"
if git ls-remote --heads origin release | grep -q release; then
git checkout release
git pull origin release
else
git checkout -b release
fi
git reset --hard ${{ github.ref_name }}
git push origin release --force
Триггеры:
v*
(например, v1.1.29
)Jobs:
у всех jobs имеется условный оператор выполнения
if: ${{ vars.BUILD_UPDATE_RELEASE_BRANCH == 'true' }}
то есть для того, чтобы тот или иной job запустился нужно установить соответствующую переменную в настройках репозитория в Gitea https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea/settings/actions/variables
для работы данного job используется кастомный образ - если бы мы использовали просто golang:1.24, то при каждом запуске данного job необходимо было бы скачивать jq и устанавливаться в контейнере (возможно со временем, что-то еще потребовалось бы …)
- name: Setup Go container
run: |
# Install jq for JSON parsing
apt-get update && apt-get install -y jq
git --version
go version
jq --version
решение в лоб: применено, чтобы убрать эту повторяющуюся работу при каждом выполнении workflow - сделан свой образ - “${DOCKERHUB_USERNAME}”/my-build-golang-runner:latest
# базовый образ
FROM golang:1.24
# Устанавливаем пакеты (одинаково работают на amd64/arm64)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git \
ca-certificates \
jq && \
rm -rf /var/lib/apt/lists/*
# Создаем рабочую директорию
WORKDIR /app
# Копируем файлы зависимостей
COPY go.mod go.sum ./
# Предварительно загружаем все зависимости
RUN go mod download && go mod verify
# Устанавливаем переменные окружения
ENV GOPATH=/go
ENV PATH=$PATH:/go/bin:/usr/local/bin
# (Опционально) Можно добавить команду по умолчанию
CMD ["bash"]
Сначала собрал его и запушил (желательно с мультиплатформенностью) на каком-то локальном АРМ (не раннере)
#!/bin/bash
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ${DOCKERHUB_USERNAME:-defaultdockeruser}/my-build-golang-runner:latest \
--push \
-f Dockerfile_for_runner_image \
.
решение рабочее, но надо всегда иметь под рукой комп с buildx - лучше автоматизировать все и сделать задачу для сборки на раннере .gitea/workflows/build-builder.yaml
name: Build Builder Docker Image
on:
push:
tags:
- builder-*
jobs:
create-builder-docker-image:
runs-on: ubuntu-latest
container:
image: docker:28.3.2-dind
steps:
- name: Checkout repository
run: |
# Install git
apk add --no-cache git
echo "=== GitHub Variables ==="
echo "github.ref = ${{ github.ref }}"
echo "github.ref_name = ${{ github.ref_name }}"
echo "github.sha = ${{ github.sha }}"
echo "github.repository = ${{ github.repository }}"
echo "========================"
echo "Cloning..."
git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea
cd hello_gitea
echo "Checkout to ${{ github.ref }} ..."
git checkout ${{ github.ref }}
- name: Setup Docker Buildx
run: |
# Docker is already installed in docker:dind image
echo "look at docker version"
docker --version
# Setup Docker Buildx for multi-platform builds
echo "setup buildx"
docker buildx create --name builder-builx --use
docker buildx inspect --bootstrap
- name: Login to Docker Hub
run: |
echo "login to docker hub ..."
echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
- name: Build multi-platform Docker images
run: |
cd hello_gitea
echo "Build multi-platform images using buildx ..."
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:${{ github.ref_name }} \
--tag ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest \
--push \
-f Dockerfile.builder \
.
- name: Remove buildx
run: |
docker buildx rm builder-builx
эта задача будет запущена на ранере при пуше тега с префиксом builder-
latest
(опция --push команды docker buildx build …)чтобы авторизация сработала на докерхабе надо внести секреты DOCKERHUB_TOKEN, DOCKERHUB_USERNAME или вцелом для всего инстанса gitea в настройках инстанса или в настройках конкретного репозитория.
Секреты — это как пароли от вайфая в офисе: у всех есть, но вслух никто не признаётся. Не кладите их в логи.
Уже после ряда тестовых релизов выяснил, что запуски workflow build.yaml
да собственно как и build-builder.yaml
порождают на хосте раннера зависшие докер контейнеры buildx
СONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2a9ad63c5a31 moby/buildkit:buildx-stable-1 "buildkitd --allow-i…" 11 seconds ago Up 10 seconds buildx_buildkit_test-buldx0
843eee192570 moby/buildkit:buildx-stable-1 "buildkitd --allow-i…" 22 minutes ago Up 22 minutes buildx_buildkit_modest_haibt0
Это происходит потому, что раннер запускает докер в докере контейнер так: docker run --privileged -it --rm -v /var/run/docker.sock:/var/run/docker.sock docker:28.3.2-dind sh
, то есть пробрасывает хостовый сокет докер демона в контейнер и когда там выполняется docker buildx create --use
контейнер запускается на хосте, а не внутри docker:28.3.2-dind …
Со временем это может превратиться в утечку ресурсов …
Решения как минимум два - добавить параметр --name - docker buildx create --name go-buildx --use
- тогда подхватится существующий контейнер или создастся новый. Второй способ удалять buildx docker buildx rm --name go-buildx
А если забыли удалить — не баг, а фича: у вас теперь есть питомец buildkitd.
needs: create-docker-image
В настройках репозитория (или инстанса) → “Secrets and variables” → “Actions” добавьте следующие секреты:
GITEATOKEN
https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea/settings/actions/secrets
DOCKERHUB_USERNAME
https://direct-dev.ru/gitea/user/settings/actions/secrets
DOCKERHUB_TOKEN
https://direct-dev.ru/gitea/user/settings/actions/secrets
Gitea Token:
Docker Hub Token:
1 Проверим синтаксис workflow:
ну если мы в ide, то наверное все хорошо и уже автоматом отформатировано … но тем не менее …
# Убедитесь, что YAML синтаксис корректен
yamllint .gitea/workflows/build.yaml
Если yamllint ругается — это не вы, это он. Но отступы всё же поправьте.
Священная война пробелов и табов: здесь побеждают пробелы.
2 Протестируем сборку локально:
пока ручное тестирование
# Сборка для текущей платформы
go build -o hello-api main.go
# Сборка для других платформ
GOOS=linux GOARCH=amd64 go build -o hello-api-linux-amd64 main.go
GOOS=linux GOARCH=arm64 go build -o hello-api-linux-arm64 main.go
3 Тестирование Docker образа:
# Сборка образа
docker build -t hello-api:test .
# Запуск контейнера
docker run -p 8080:8080 hello-api:test
# Тестирование API
curl http://localhost:8080/healthz
тут надо погонять curl по эндпойнтам, убедиться что приложение работает (пока тоже вручную)
Если curl отвечает 200 — можно смело говорить, что «интеграционные прошли». Мы же среди своих.
scripts/release-interactive.sh
)
git tag v1.1.20
git push origin v1.1.20
# текст скрипта - запуск можно сделать через make
#release-interactive:
# @./scripts/release-interactive.sh
#!/bin/bash
# Интерактивный скрипт для автоматизации релиза
# Использование: ./scripts/release-interactive.sh [version]
# Если версия не указана, скрипт запросит её интерактивно
set -e # Остановить выполнение при ошибке
# Функция для получения версии интерактивно
get_version_interactive() {
echo "🚀 Создание нового релиза"
echo ""
# Показываем текущую версию
CURRENT_VERSION=$(grep 'const version = "' main.go | sed 's/const version = "\([^"]*\)"/\1/')
echo "📋 Текущая версия: $CURRENT_VERSION"
echo ""
# Запрашиваем новую версию
read -r -p "Введите новую версию (формат X.Y.Z): " VERSION
# Проверяем, что версия не пустая
if [ -z "$VERSION" ]; then
echo "❌ Версия не может быть пустой"
exit 1
fi
# Проверяем формат версии
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Неверный формат версии. Используйте формат X.Y.Z (например, 1.0.25)"
exit 1
fi
# Подтверждение
echo ""
echo "📝 Подтверждение:"
echo " Текущая версия: $CURRENT_VERSION"
echo " Новая версия: $VERSION"
echo ""
read -r -p "Продолжить? (y/N): " CONFIRM
if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then
echo "❌ Релиз отменен"
exit 0
fi
}
# Проверяем, передана ли версия как аргумент
if [ $# -eq 0 ]; then
# Версия не указана, запрашиваем интерактивно
get_version_interactive
else
# Версия указана как аргумент
VERSION=$1
# Проверка формата версии
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Ошибка: Неверный формат версии. Используйте формат X.Y.Z (например, 1.0.25)"
exit 1
fi
fi
echo "🚀 Начинаем релиз версии v$VERSION..."
# Проверяем, что мы в git репозитории
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "Ошибка: Не найден git репозиторий"
exit 1
fi
# Проверяем, что нет незакоммиченных изменений
# if ! git diff-index --quiet HEAD --; then
# echo "Ошибка: Есть незакоммиченные изменения. Сначала закоммитьте их."
# exit 1
# fi
# Обновляем версию в main.go
echo "📝 Обновляем версию в main.go..."
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
sed -i '' "s/const version = \"[^\"]*\"/const version = \"$VERSION\"/" main.go
else
# Linux
sed -i "s/const version = \"[^\"]*\"/const version = \"$VERSION\"/" main.go
fi
# Проверяем, что изменение применилось
if ! grep -q "const version = \"$VERSION\"" main.go; then
echo "Ошибка: Не удалось обновить версию в main.go"
exit 1
fi
echo "✅ Версия обновлена в main.go"
# Обновляем версию в makefile
echo "📝 Обновляем версию в makefile..."
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
sed -i '' "s/^VERSION=.*/VERSION=$VERSION/" makefile
else
# Linux
sed -i "s/^VERSION=.*/VERSION=$VERSION/" makefile
fi
# Проверяем, что изменение применилось
if ! grep -q "^VERSION=$VERSION" makefile; then
echo "Ошибка: Не удалось обновить версию в makefile"
exit 1
fi
echo "✅ Версия обновлена в makefile"
# Выполняем git команды
echo "📦 Добавляем изменения в git..."
git add .
echo "💾 Создаем коммит..."
git commit -m "Release v$VERSION"
echo "🏷️ Создаем тег..."
git tag -a "v$VERSION" -m "Release v$VERSION"
echo "🚀 Отправляем изменения и теги..."
git push
git push --tags
echo "🎉 Релиз v$VERSION успешно завершен!"
echo "📋 Выполненные действия:"
echo " - Обновлена версия в main.go"
echo " - Обновлена версия в makefile"
echo " - Создан коммит с сообщением 'Release v$VERSION'"
echo " - Создан тег v$VERSION"
echo " - Изменения отправлены в удаленный репозиторий"
После успешного выполнения:
hello-api-linux-amd64.tar.gz
hello-api-linux-arm64.tar.gz
hello-api-windows-amd64.tar.gz
hello-api-darwin-amd64.tar.gz
hello-api-darwin-arm64.tar.gz
username/hello-api:v1.1.20
username/hello-api:latest
Если ничего из этого не появилось — открываем раздел «Мониторинг и отладка» и вспоминаем, что DevOps — это приключение.
90% «мистических» проблем — это токены, 9% — отступы, 1% — космические лучи. Начинайте с токенов.
DOCKERHUB_TOKEN
GITEATOKEN
go.mod
В этой статье мы рассмотрели полный процесс настройки Gitea Actions для Go проекта. Мы создали автоматизированный pipeline, который:
Gitea Actions предоставляет мощные возможности для автоматизации процессов разработки, и с правильной настройкой вы можете значительно упростить процесс доставки вашего ПО.
P.S. Если что-то внезапно «пало» — это было нагрузочное тестирование. Так и запишем.
Главное — релизнуть до пятницы. Всё, что случилось после — официально относится к сфере SRE-фольклора.
Этот и последующие разделы описывают инфраструктуру, на которой я проверял все описанное выше. Если у вас используются другие подходы, то можете пропустить чтение этих разделов или ознакомиться для общего развития.
Итак инфраструктура состоит из следующих компонентов:
Proxmox VE (ARM64)
├── K3s Cluster
├── Master Node
└── Gitea Server (Helm Chart)
└── Worker Nodes
└── LXC Containers
└── Gitea Runner Container
├── Docker Engine
├── Go Toolchain
└── Build Tools
Proxmox VE:
K3s Cluster:
LXC Container:
Текущая конфигурация:
Узлы кластера:
kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k3s-control-01 Ready control-plane,etcd,master 544d v1.33.2+k3s1 10.xxx.x.2 10.xx.x.2 Ubuntu 20.04.6 LTS 5.10.160-rockchip-rk3588 containerd://2.0.5-k3s1
k3s-control-02 Ready control-plane,etcd,master 544d v1.33.2+k3s1 10.xxx.x.3 10.xx.x.3 Ubuntu 20.04.6 LTS 5.10.160-rockchip-rk3588 containerd://2.0.5-k3s1
k3s-control-03 Ready control-plane,etcd,master 544d v1.33.2+k3s1 10.xxx.x.4 10.xx.x.4 Ubuntu 20.04.6 LTS 6.1.31-sun50iw9 containerd://2.0.5-k3s1
Существующие компоненты:
# Проверка подов Gitea
kubectl get pods -n gitea
NAME READY STATUS RESTARTS AGE
gitea-798c56b58f-bsp2h 1/1 Running 0 30h
gitea-postgres-0 2/2 Running 10 (10d ago) 357d
gitea-valkey-cluster-0 1/1 Running 0 30h
gitea-valkey-cluster-1 1/1 Running 0 30h
gitea-valkey-cluster-2 1/1 Running 0 30h
# Проверка сервисов
kubectl get svc -n gitea
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gitea-http ClusterIP None <none> 3000/TCP 543d
gitea-postgres ClusterIP 10.xx.xx.xx <none> 5432/TCP 543d
gitea-ssh ClusterIP None <none> 22/TCP 543d
gitea-valkey-cluster ClusterIP 10.xx.xx.xx <none> 6379/TCP 30h
gitea-valkey-cluster-headless ClusterIP None <none> 6379/TCP,16379/TCP 30h
# Проверка persistent storage
kubectl get pvc -n gitea
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
gitea-shared-storage Bound pvc-c869aea8-7b49-4b40-9b86-1a7fbe9b2ce8 10Gi RWO nfs 543d
pgdata-gitea-postgres-0 Bound pvc-c3405a46-594d-401f-91ae-e7398b3c5cc3 15Gi RWO nfs 543d
valkey-data-gitea-valkey-cluster-0 Bound pvc-a927baf5-b8f8-4c0b-b12f-68fbc63162d9 8Gi RWO nfs 30h
valkey-data-gitea-valkey-cluster-1 Bound pvc-b43a399b-fdf4-4c27-baf4-7056ecc4143b 8Gi RWO nfs 30h
valkey-data-gitea-valkey-cluster-2 Bound pvc-368f0ab9-7851-423f-afb9-443287c0c728 8Gi RWO nfs 30h
Gitea версия: 1.24.3 (rootless) База данных: PostgreSQL с persistent storage (15Gi) Кэш: Valkey cluster (Redis) с 3 репликами Storage: NFS Storage Class Ingress: Traefik
Доступ к gitea лучше сделать через ingress или на худой конец через port-forward:
1 Временный доступ через port-forward:
# Доступ к Gitea через port-forward
kubectl port-forward -n gitea svc/gitea-http 3000:3000
# В другом терминале проверьте доступ
curl http://localhost:3000
2 Создание IngressRoute для постоянного доступа:
# gitea-ingressroute.yaml
# image: docker.io/traefik:v3.0.0 поэтому используется абстракция
# apiVersion: traefik.io/v1alpha1
# kind: IngressRoute
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-dns-cloudflare
creationTimestamp: "2024-05-24T03:50:33Z"
name: gitea-https-route
namespace: gitea
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`direct-dev.ru`) && PathPrefix(`/gitea`)
middlewares:
- name: strip-gitea-prefix
services:
- name: gitea-http
port: 3000
tls:
secretName: le-root-direct-dev-ru
# Применение ingressroute
KUBECONFIG=~/.kube/config_hlab kubectl apply -f gitea-ingress.yaml
Таким образом мой инстанс gitea доступен с внешнего адреса https://direct-dev.ru/gitea
в /data/gitea/conf/app.ini
[server]
ROOT_URL = <https://direct-dev.ru/gitea>
DOMAIN = direct-dev.ru
Gitea установлен через Helm.
Версия: Gitea 1.24.3 (rootless) Архитектура:
helm repo add gitea-charts https://dl.gitea.io/charts/
helm repo update
kubectl create namespace gitea
helm install gitea gitea-charts/gitea -n gitea
установка с кастомными значениями параметров
helm show values gitea-charts/gitea > gitea-values.yaml
#Отредактируйте файл (например, database или service).
helm install gitea gitea-charts/gitea -n gitea -f gitea-values.yaml
Обновление
helm repo update
helm upgrade gitea gitea-charts/gitea -n gitea -f gitea-values.yaml
Проверка конфигурации:
# Проверка Helm релиза
helm list -n gitea
# Просмотр конфигурации
helm get values gitea -n gitea
USER-SUPPLIED VALUES:
gitea:
admin:
existingSecret: xxxxxx
config:
cache:
ADAPTER: memory
database:
DB_TYPE: postgres
HOST: gitea-postgres.gitea.svc.cluster.local:5432
NAME: gitea
PASSWD: xxxxxxx
SCHEMA: gitea
USER: xxxx
indexer:
ISSUE_INDEXER_TYPE: bleve
REPO_INDEXER_ENABLED: true
queue:
TYPE: level
server:
APP_DATA_PATH: /data
DOMAIN: direct-dev.ru
ENABLE_PPROF: false
HTTP_PORT: 3000
PROTOCOL: http
ROOT_URL: https://direct-dev.ru/gitea
SSH_DOMAIN: git.k3s-cluster-01.direct-dev.ru
SSH_LISTEN_PORT: 2222
SSH_PORT: 22
service:
DISABLE_REGISTRATION: true
SHOW_REGISTRATION_BUTTON: false
session:
PROVIDER: db
persistence:
enabled: true
storageClass: nfs
postgresql:
enabled: false
postgresql-ha:
enabled: false
redis-cluster:
enabled: false
Как видите, я организовал доступ через внешний IP к URL https://direct-dev.ru/gitea
, то есть через префикс роута. Опыт работы с Actions показал, что лучше организовать доступ через поддомен третьего уровня: что-то типа https://gitea.direct-dev.ru
— в этом случае предопределённые jobs вроде checkout@v3
будут корректно отрабатывать клонирование.
# Проверка секретов
kubectl get secrets -n gitea
1 Проверка включения Actions:
# Проверка конфигурации Actions
kubectl exec -n gitea deployment/gitea -c gitea -- cat /data/gitea/conf/app.ini"
2 Включение Actions через веб-интерфейс:
3 Проверка работы Actions:
# Проверка логов Gitea на предмет ошибок Actions
kubectl logs -n gitea deployment/gitea | grep -i action
Инструкция по установке Cert-Manager не приводится - инструкций вагон и маленькая тележка - нет смысла повторяться:
# Проверка Cert-Manager
kubectl get pods -n cert-manager
# Проверка ClusterIssuer
kubectl get clusterissuer
Добавить A-запись в DNS (у меня Cloudflare)
gitea.your-domain.com -> внешний (белый) Ip - за ним пробросы портов если нужно ...
чтобы в итоге запрос поступил на traefik
Если DNS не обновился — просто подождите. Потом ещё. Потом перезапустите роутер для кармы.
2 Создание ClusterIssuer (если не существует):
# Создание ClusterIssuer для Let's Encrypt - лучше через dns resolver
cat <<EOF | KUBECONFIG=~/.kube/{configfile} kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
annotations:
name: letsencrypt-dns-cloudflare
spec:
acme:
email: info@somedomain.ru
privateKeySecretRef:
name: le-issuer-acct-key
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
key: api-token
name: cloudflare-api-token-secret
email: info@somedomain.ru
selector:
dnsZones:
- somedomain.ru
- '*.somedomain.ru'
EOF
1 Временный доступ через port-forward:
# Доступ к Gitea через port-forward
kubectl port-forward -n gitea svc/gitea-http 3000:3000
# В браузере http://localhost:3000/gitea
2 Постоянный доступ через ingressroute:
# Проверка сертификата
kubectl get certificate -n gitea
3 Проверка доступа:
# Проверка через curl
curl -I https://gitea.your-domain.com
# Проверка SSL сертификата
openssl s_client -connect gitea.your-domain.com:443 -servername gitea.your-domain.com
если нет сертификата
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
annotations:
name: root-somedomain-ru
namespace: default
spec:
commonName: somedomain.ru
dnsNames:
- somedomain.ru
- www.somedomain.ru
issuerRef:
kind: ClusterIssuer
name: letsencrypt-dns-cloudflare
secretName: le-root-somedomain-ru
Учитывая мою ARM64 архитектуру (Rockchip RK3588), контейнер надо создать с правильным шаблоном:
# Или через веб-интерфейс Proxmox:
# 1. Перейдите в Datacenter → local → CT Templates
# 2. Скачайте ubuntu-22.04-standard_22.04-1_arm64.tar.zst
# 3. Создайте контейнер с параметрами:
# - Template: ubuntu-22.04-standard_22.04-1_arm64
# - Memory: 4096 MB
# - Cores: 2
# - Root disk: 20 GB
# - Network: DHCP
2 Настройка сети:
# В контейнере
ip addr show
# в силу особенностей развертывания gitea раннер будет получать к нему доступ через интернет - в моем случае контейнер надо выпустить в интернет
# В контейнере
apt update && apt upgrade -y
apt install -y curl wget git vim htop
2 Установка Docker:
# Удаление старых версий
apt remove -y docker docker-engine docker.io containerd runc
# Установка зависимостей
apt install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
# Добавление GPG ключа Docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Добавление репозитория Docker
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# Установка Docker
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Добавление пользователя в группу docker
usermod -aG docker $USER
# Запуск Docker
systemctl enable docker
systemctl start docker
# Проверка установки
docker --version
3 Установка Go:
# Скачивание Go для ARM64
wget https://go.dev/dl/go1.21.0.linux-arm64.tar.gz
# Распаковка
tar -C /usr/local -xzf go1.21.0.linux-arm64.tar.gz
# Настройка переменных окружения
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
echo 'export GOPATH=$HOME/go' >> ~/.bashrc
echo 'export GOROOT=/usr/local/go' >> ~/.bashrc
source ~/.bashrc
# Проверка установки
go version
# Должно показать: go version go1.21.0 linux/arm64
# Создание рабочей директории
mkdir -p $HOME/go/{bin,src,pkg}
4 Установка дополнительных инструментов:
# Установка build tools
apt install -y build-essential
# Установка jq для JSON обработки
apt install -y jq
# Установка yamllint для проверки YAML
apt install -y yamllint
# Установка Docker Buildx
docker buildx version
# Определение архитектуры
ARCH=$(uname -m)
case $ARCH in
x86_64) ARCH=amd64 ;;
aarch64) ARCH=arm64 ;;
armv7l) ARCH=armv7 ;;
esac
# Скачивание последней версии
# visit https://dl.gitea.com/act_runner/ copy link to version and arch you need
wget https://dl.gitea.com/act_runner/0.2.12/act_runner-0.2.12-linux-arm64
# Переименование и установка
mv act_runner-0.2.12-linux-arm64 /usr/local/bin/act_runner
chmod +x /usr/local/bin/act_runner
# Проверка установки
act_runner --version
2 Создание пользователя для runner:
# Создание пользователя
useradd -m -s /bin/bash gitea-runner
usermod -aG docker gitea-runner
# Создание директории для конфигурации
mkdir -p /opt/gitea-runner
chown gitea-runner:gitea-runner /opt/gitea-runner
3 Настройка конфигурации runner:
# Переключение на пользователя runner
su - gitea-runner
# Создание конфигурационного файла
cat > /opt/gitea-runner/config.yaml <<EOF
# just run `./act_runner generate-config > config.yaml` to generate a config file.
log:
# The level of logging, can be trace, debug, info, warn, error, fatal
level: info
runner:
# Where to store the registration result.
file: .runner
# Execute how many tasks concurrently at the same time.
capacity: 1
# Extra environment variables to run jobs.
envs:
A_TEST_ENV_NAME_1: a_test_env_value_1
A_TEST_ENV_NAME_2: a_test_env_value_2
# Extra environment variables to run jobs from a file.
# It will be ignored if it's empty or the file doesn't exist.
env_file: .env
# The timeout for a job to be finished.
# Please note that the Gitea instance also has a timeout (3h by default) for the job.
# So the job could be stopped by the Gitea instance if it's timeout is shorter than this.
timeout: 3h
# The timeout for the runner to wait for running jobs to finish when shutting down.
# Any running jobs that haven't finished after this timeout will be cancelled.
shutdown_timeout: 0s
# Whether skip verifying the TLS certificate of the Gitea instance.
insecure: true
# The timeout for fetching the job from the Gitea instance.
fetch_timeout: 5s
# The interval for fetching the job from the Gitea instance.
fetch_interval: 2s
# The labels of a runner are used to determine which jobs the runner can run, and how to run them.
# Like: "macos-arm64:host" or "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
# Find more images provided by Gitea at https://gitea.com/docker.gitea.com/runner-images .
# If it's empty when registering, it will ask for inputting labels.
# If it's empty when execute `daemon`, will use labels in `.runner` file.
labels:
- "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
- "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
- "ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04"
cache:
# Enable cache server to use actions/cache.
enabled: true
# The directory to store the cache data.
# If it's empty, the cache data will be stored in $HOME/.cache/actcache.
dir: ""
# The host of the cache server.
# It's not for the address to listen, but the address to connect from job containers.
# So 0.0.0.0 is a bad choice, leave it empty to detect automatically.
host: ""
# The port of the cache server.
# 0 means to use a random available port.
port: 0
# The external cache server URL. Valid only when enable is true.
# If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself.
# The URL should generally end with "/".
external_server: ""
container:
# Specifies the network to which the container will connect.
# Could be host, bridge or the name of a custom network.
# If it's empty, act_runner will create a network automatically.
network: ""
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
privileged: false
# And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway).
options:
# The parent directory of a job's working directory.
# NOTE: There is no need to add the first '/' of the path as act_runner will add it automatically.
# If the path starts with '/', the '/' will be trimmed.
# For example, if the parent directory is /path/to/my/dir, workdir_parent should be path/to/my/dir
# If it's empty, /workspace will be used.
workdir_parent:
# Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob
# You can specify multiple volumes. If the sequence is empty, no volumes can be mounted.
# For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to:
# valid_volumes:
# - data
# - /src/*.json
# If you want to allow any volume, please use the following configuration:
# valid_volumes:
# - '**'
valid_volumes: []
# overrides the docker client host with the specified one.
# If it's empty, act_runner will find an available docker host automatically.
# If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers.
# If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work.
docker_host: ""
# Pull docker image(s) even if already present
force_pull: true
# Rebuild docker image(s) even if already present
force_rebuild: false
host:
# The parent directory of a job's working directory.
# If it's empty, $HOME/.cache/act/ will be used.
workdir_parent:
EOF
2 Регистрация runner:
# В контейнере LXC под пользователем gitea-runner
act_runner register \
--instance https://gitea.your-domain.com \
--token YOUR_REGISTRATION_TOKEN \
--name "k3s-runner-arm64" \
--labels "ubuntu-latest:docker://node:18,ubuntu-22.04:docker://node:18,self-hosted:docker://golang:1.21,arm64:docker://golang:1.21" \
--no-interactive
3 Создание systemd сервиса:
# Создание файла сервиса
cat > /etc/systemd/system/gitea-runner.service <<EOF
[Unit]
Description=Gitea Actions Runner
After=network.target docker.service
Requires=docker.service
[Service]
Type=simple
User=gitea-runner
Group=gitea-runner
WorkingDirectory=/opt/gitea-runner
ExecStart=/usr/local/bin/act_runner daemon --config /opt/gitea-runner/config.yaml
Restart=always
RestartSec=10
Environment=PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[Install]
WantedBy=multi-user.target
EOF
# Перезагрузка systemd
systemctl daemon-reload
# Включение и запуск сервиса
systemctl enable gitea-runner
systemctl start gitea-runner
# Проверка статуса
systemctl status gitea-runner
cat > /opt/gitea-runner/monitor.sh <<'EOF'
#!/bin/bash
LOG_FILE="/opt/gitea-runner/logs/monitor.log"
RUNNER_STATUS=$(systemctl is-active gitea-runner)
echo "$(date): Runner status: $RUNNER_STATUS" >> $LOG_FILE
if [ "$RUNNER_STATUS" != "active" ]; then
echo "$(date): Restarting gitea-runner service" >> $LOG_FILE
systemctl restart gitea-runner
fi
# Проверка свободного места
DISK_USAGE=$(df /opt/gitea-runner | tail -1 | awk '{print $5}' | sed 's/%//')
if [ $DISK_USAGE -gt 80 ]; then
echo "$(date): High disk usage: ${DISK_USAGE}%" >> $LOG_FILE
# Очистка старых логов и кэша
find /opt/gitea-runner/logs -name "*.log" -mtime +7 -delete
docker system prune -f
fi
EOF
chmod +x /opt/gitea-runner/monitor.sh
2 Настройка cron для мониторинга:
# Добавление в crontab
echo "*/5 * * * * /opt/gitea-runner/monitor.sh" | crontab -
# Проверка статуса runner в Gitea
curl -H "Authorization: token YOUR_GITEA_TOKEN" \
https://gitea.your-domain.com/api/v1/actions/runners
2 Создание тестового workflow:
# .gitea/workflows/test-runner.yaml
name: Test Runner
# on:
# push:
# branches: [main]
on:
push:
tags:
- v*
jobs:
test:
runs-on: self-hosted
steps:
- name: Checkout
run: |
git clone https://oauth2:${{ secrets.GITEATOKEN }}@gitea.your-domain.com/your-username/your-repo.git
cd your-repo
- name: Test Go
run: |
go version
go mod download
go build -o test-app main.go
- name: Test Docker
run: |
docker --version
docker run hello-world
- name: Test ARM64 Build
run: |
# Тестирование сборки для ARM64
GOOS=linux GOARCH=arm64 go build -o test-app-arm64 main.go
file test-app-arm64
# Должно показать: ELF 64-bit LSB executable, ARM aarch64
# Создание daemon.json
cat > /etc/docker/daemon.json <<EOF
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"default-ulimits": {
"nofile": {
"Hard": 64000,
"Name": "nofile",
"Soft": 64000
}
}
}
EOF
systemctl restart docker
2 Настройка ограничений ресурсов:
# Обновление systemd сервиса с ограничениями
cat > /etc/systemd/system/gitea-runner.service <<EOF
[Unit]
Description=Gitea Actions Runner
After=network.target docker.service
Requires=docker.service
[Service]
Type=simple
User=gitea-runner
Group=gitea-runner
WorkingDirectory=/opt/gitea-runner
ExecStart=/usr/local/bin/act_runner daemon --config /opt/gitea-runner/config.yaml
Restart=always
RestartSec=10
Environment=PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Ограничения ресурсов
MemoryMax=3G
CPUQuota=200%
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl restart gitea-runner
cat > /opt/gitea-runner/backup.sh <<'EOF'
#!/bin/bash
BACKUP_DIR="/opt/gitea-runner/backups"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# Резервное копирование конфигурации
tar -czf $BACKUP_DIR/config_$DATE.tar.gz /opt/gitea-runner/config.yaml
# Резервное копирование логов
tar -czf $BACKUP_DIR/logs_$DATE.tar.gz /opt/gitea-runner/logs/
# Удаление старых резервных копий (старше 30 дней)
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
echo "Backup completed: $DATE"
EOF
chmod +x /opt/gitea-runner/backup.sh
# Добавление в crontab (ежедневно в 2:00)
echo "0 2 * * * /opt/gitea-runner/backup.sh" | crontab -
# Установка переменной окружения для работы с кластером
export KUBECONFIG=~/.kube/{configfile}
# Проверка состояния кластера
kubectl get nodes -o wide
kubectl get pods -A
# Работа с Gitea
kubectl get pods -n gitea
kubectl logs -n gitea deployment/gitea
kubectl port-forward -n gitea svc/gitea-http 3000:3000
# Проверка storage
kubectl get pvc -n gitea
kubectl get storageclass
# Проверка ingress и сервисов
kubectl get svc -n default | grep traefik
kubectl get crds
kubectl get ingressroutes.traefik.io -A
# Проверка сертификатов
kubectl get certificates -A -o wide
# Проверка ресурсов кластера
kubectl top nodes
kubectl top pods -n gitea
# Проверка событий
kubectl get events -n gitea --sort-by='.lastTimestamp'
# Резервное копирование Gitea
kubectl exec -n gitea deployment/gitea -- gitea dump -c /data/gitea/conf/app.ini
# Обновление Gitea
helm repo update
helm upgrade gitea gitea-charts/gitea -n gitea --values gitea-values.yaml
# Проверка логов Gitea
kubectl logs -n gitea deployment/gitea -f
# Проверка логов PostgreSQL
kubectl logs -n gitea statefulset/gitea-postgres -c postgres
# Проверка логов Valkey
kubectl logs -n gitea statefulset/gitea-valkey-cluster -c valkey
# Проверка сетевой связности
kubectl exec -n gitea deployment/gitea -- ping gitea-postgres
kubectl exec -n gitea deployment/gitea -- ping gitea-valkey-cluster