Настройка Gitea Actions для Go проекта: Полное руководство

Настройка Gitea Actions для Go проекта: Полное руководство

Если коротко: пишем YAML с баш скриптом, жмём тег, делаем вид, что всё так и задумывалось. It works on my machine™

TL;DR для тех кто куда то спешит

  • Что делаем: собираем мультиплатформенные бинарники, варим Docker-образы, шипим релизы, толкаем тег в release.
  • Как делаем: ставим галочку Enable Actions, добавляем секреты, комитим теги.
  • Где может заболеть: YAML-отступы, токены, сборка для arm64.
  • Исходный код тут: https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea

Содержание

  1. Введение
  2. Подготовка проекта
  3. Настройка Gitea Actions
  4. Создание workflow файла
  5. Настройка секретов
  6. Тестирование и запуск
  7. Мониторинг и отладка
  8. Заключение

Приложения

  1. Инфраструктура
  2. Установка Gitea в кластере K3s
  3. Настройка Gitea Runner в LXC контейнере

Введение

Gitea Actions — это встроенная система непрерывной интеграции и развертывания (CI/CD) в Gitea, которая позволяет автоматизировать процессы сборки, тестирования и развертывания ваших проектов. Большой плюс этой системы в том, что она достаточна не требовательна к ресурсам и может быть развернута в собственном изолированном окружении.

В этой статье мы рассмотрим как работать с данной системой, на примере настройки Gitea Actions для Go проекта с автоматической сборкой мультиплатформенных бинарников, созданием Docker образов и публикацией релизов.

Исходный код тут: https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea

Кратко: это тот момент, когда YAML становится языком программирования, а мы — его интерпретатором. Ошибка в одном пробеле — и ваш кластер узнает, что такое настоящее приключение.

Итак, что мы будем делать

  • Настроим автоматическую сборку Go приложения для разных платформ
  • Создадим Docker образы для Linux AMD64 и ARM64
  • Настроим публикацию в Docker Hub
  • Автоматизируем создание релизов с бинарниками
  • Сделаем коммит в отдельную ветку - которую можно связать с CD системой (flux/ArgoCD)
  • Настроим триггеры на основе Git тегов

Подготовка проекта

Структура проекта

Собственно функциональная часть проекта 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)
}

Dockerfile

Помимо того, что мы будем собирать бинарники для разных платформ, мы также настроим сборку 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 Actions

Включение Actions в Gitea

  1. Проверьте версию Gitea

    Gitea Actions доступны начиная с версии 1.17.0. Убедитесь, что ваш сервер Gitea поддерживает Actions.

  2. Включите Actions в настройках репозитория

    • Перейдите в настройки репозитория
    • Найдите раздел “Actions”
    • Включите “Enable Actions”

Если не нашли галочку — проверьте версию Gitea и права доступа. Выключить и включить интернет тоже можно, но это для настроения.

Создание токена доступа

  1. Создайте токен для Actions

    • Перейдите в настройки профиля → “Applications”
    • Создайте новый токен с правами на репозиторий
    • Скопируйте токен (он понадобится позже)

Создание workflow файла

Структура директории

Создайте директорию .gitea/workflows/ в корне вашего проекта:

mkdir -p .gitea/workflows

Основной workflow файл

Создайте файл .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

Разберемся как работает workflow

Триггеры:

  • Workflow запускается при пуше в Gitea тега, начинающегося с 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

create-release:

  • Эта таска запускается первой
  • Управляется переменной BUILD_CREATE_RELEASE
  • Использует контейнер собранный на базе golang:1.24 для сборки бинарников (image: ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest)
  • Собирает бинарники для всех платформ (Linux, Windows, macOS)
  • Создает архивы с бинарниками
  • Создает релиз с именем текущей версии через Gitea API
  • Загружает бинарники как assets релиза

для работы данного 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-

create-docker-image: создание образов docker с нашим проектом

  • Управляется переменной BUILD_CREATE_DOCKER_IMAGE
  • Используем Docker-in-Docker контейнер (image: docker:28.3.2-dind)
  • Настраиваем Docker Buildx для мультиплатформенной сборки (docker buildx create --use docker buildx inspect --bootstrap)
  • Авторизуемся в Docker Hub (echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin)
  • Собираем образы для Linux AMD64 и ARM64 (команда docker buildx build \ …)
  • Публикуем образы с тегом версии и 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.

update-to-release-branch: создание ветки release, в том случае если ее нет, и вставка туда нашего коммита (в дальнейшем по комиту в этой ветке можно настроить систему развертывания, например ArgoCD)

  • Управляется переменной BUILD_UPDATE_RELEASE_BRANCH
  • можно добавить зависимость от create-docker-image если нужно needs: create-docker-image
  • Используем Docker-in-Docker контейнер (image: docker:28.3.2-dind) - там уже есть git и образ скачан
  • Пушим наш тег в ветку release (если ее нет она создастся)

Настройка секретов

Необходимые секреты

В настройках репозитория (или инстанса) → “Secrets and variables” → “Actions” добавьте следующие секреты:

  1. GITEATOKEN

    • Токен доступа к Gitea API
    • Используется для клонирования репозитория и создания релизов
    • Лучше задать на уровне репозитория https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea/settings/actions/secrets
  2. DOCKERHUB_USERNAME

    • Ваше имя пользователя в Docker Hub
    • Используется для публикации образов
    • Можно задать на уровне инстанса https://direct-dev.ru/gitea/user/settings/actions/secrets
  3. DOCKERHUB_TOKEN

    • Токен доступа к Docker Hub
    • Используется для авторизации в Docker Hub
    • Можно задать на уровне инстанса https://direct-dev.ru/gitea/user/settings/actions/secrets

Создание токенов

Gitea Token:

  1. Перейдите в настройки профиля → “Applications”
  2. Создайте новый токен с правами на репозиторий
  3. Скопируйте токен
  4. В разделе Actions репозитория создайте секрет уровня репозитория - я создал с именем GITEATOKEN (такой не даст сделать: GITEA_TOKEN)

Docker Hub Token:

  1. Войдите в Docker Hub
  2. Перейдите в Account Settings → Security
  3. Создайте новый Access Token
  4. Скопируйте токен
  5. В разделе Actions настроек инстанса gitea создайте секреты уровня инстанса (если много пользователей работает в gitea, то можно внести в уровень репозитория) я создал с именами DOCKERHUB_USERNAME, DOCKERHUB_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 — можно смело говорить, что «интеграционные прошли». Мы же среди своих.

Запуск Actions

Создаем тег вручную или скриптом (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 "   - Изменения отправлены в удаленный репозиторий" 

Мониторинг выполнения

  • Переходим в репозиторий → “Actions”
  • Находим запущенный workflow
  • Отслеживаем выполнение каждого job

Ожидаемый результат

После успешного выполнения:

Релиз будет создан в Gitea с бинарниками

  • 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

Docker образы будут опубликованы в Docker Hub

  • username/hello-api:v1.1.20
  • username/hello-api:latest

В ветке release появится новый коммит - на него можно, например, настроить деплой ArgoCD/flux в кластере k3s

Если ничего из этого не появилось — открываем раздел «Мониторинг и отладка» и вспоминаем, что DevOps — это приключение.

Мониторинг и отладка

Просмотр логов

В Gitea:

  • Переходим в репозиторий → “Actions”
  • Выберем workflow
  • Нажмем на job для просмотра логов

Отладка ошибок:

  • Проверим правильность секретов
  • Убедимся в корректности путей к репозиторию
  • Проверим права доступа токенов
  • Засунем код в Chat GPT

90% «мистических» проблем — это токены, 9% — отступы, 1% — космические лучи. Начинайте с токенов.

Частые проблемы

Ошибка авторизации в Docker Hub:

  • Проверим правильность DOCKERHUB_TOKEN
  • Убедимся, что токен не истек

Ошибка создания релиза:

  • Проверим права токена GITEATOKEN
  • Убедимся, что тег не существует

Ошибка сборки:

  • Проверим зависимости в go.mod
  • Убедимся в корректности Dockerfile

Заключение

В этой статье мы рассмотрели полный процесс настройки Gitea Actions для Go проекта. Мы создали автоматизированный pipeline, который:

  • ✅ Собирает мультиплатформенные бинарники
  • ✅ Создает Docker образы для разных архитектур
  • ✅ Публикует образы в Docker Hub
  • ✅ Создает релизы с бинарниками
  • ✅ Запускается автоматически при создании тегов

Преимущества такого подхода

  1. Автоматизация: Минимизация ручной работы
  2. Консистентность: Одинаковые условия сборки
  3. Мультиплатформенность: Поддержка разных ОС и архитектур
  4. Безопасность: Использование секретов для токенов
  5. Масштабируемость: Легко добавить новые платформы или этапы

Следующие возможные шаги

  1. тестирование в workflow
  2. автоматическое развертывание
  3. уведомления о результатах сборки
  4. мониторинг и алерты

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:

  • ARM64 архитектура (orangepi 5 Plus)
  • Минимум 8GB RAM (16Gb)
  • 100GB свободного места (1 Gb)
  • Поддержка LXC контейнеров (+)

K3s Cluster:

  • Kubernetes 1.24+
  • Helm 3.8+
  • Ingress Controller (Traefik)
  • Persistent Storage (NFS)

LXC Container:

  • Ubuntu 22.04 LTS
  • 4GB RAM
  • 20GB дискового пространства
  • Docker Engine

Установка Gitea в кластере K3s

Анализ существующего кластера

Текущая конфигурация:

Узлы кластера:

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

Существующие компоненты:

  • Kubernetes v1.33.2+k3s1
  • 3 узла control-plane с etcd
  • Traefik Ingress Controller
  • NFS Storage Class для persistent storage
  • Cert-Manager для SSL сертификатов
  • Gitea уже установлен и работает

Проверка существующей установки Gitea

# Проверка подов 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

Доступ к 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

Gitea установлен через Helm.

Версия: Gitea 1.24.3 (rootless) Архитектура:

  • PostgreSQL для базы данных (развернут отдельно )
  • Valkey cluster (Redis) для кэширования
  • NFS Storage Class для persistent storage
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

Настройка Actions в существующем инстансе Gitea

1 Проверка включения Actions:

# Проверка конфигурации Actions
kubectl exec -n gitea deployment/gitea -c gitea -- cat /data/gitea/conf/app.ini"

2 Включение Actions через веб-интерфейс:

  • Откройте Gitea через port-forward или ingress
  • Перейдите в Site Administration → Actions
  • Включите “Enable Actions”
  • Настройте “Default Actions URL” (например, https://gitea.com)

3 Проверка работы Actions:

# Проверка логов Gitea на предмет ошибок Actions
kubectl logs -n gitea deployment/gitea | grep -i action

Настройка DNS и SSL

Инструкция по установке Cert-Manager не приводится - инструкций вагон и маленькая тележка - нет смысла повторяться:

# Проверка Cert-Manager
kubectl get pods -n cert-manager

# Проверка ClusterIssuer
kubectl get clusterissuer
  1. Настройка DNS записи:
Добавить 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

Доступ к Gitea

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

Настройка Gitea Runner в LXC контейнере

Создание LXC контейнера в Proxmox

Учитывая мою ARM64 архитектуру (Rockchip RK3588), контейнер надо создать с правильным шаблоном:

  1. Создание контейнера через веб-интерфейс:

# Или через веб-интерфейс 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 раннер будет получать к нему доступ через интернет - в моем случае контейнер надо выпустить в интернет

Подготовка контейнера

  1. Обновление системы:
# В контейнере
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

Установка Gitea Runner

  1. Скачивание Gitea Runner:
# Определение архитектуры
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

Регистрация Runner в Gitea

  1. Получение токена регистрации:
  • Войдите в веб-интерфейс Gitea
  • Перейдите в Settings → Actions → Runners
  • Нажмите “New Runner”
  • Скопируйте токен регистрации

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

Настройка мониторинга

  1. Cкрипт мониторинга:
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

  1. Проверка подключения:
# Проверка статуса 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

Оптимизация производительности

  1. Настройка Docker daemon:
# Создание 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

Резервное копирование

  1. Создание скрипта резервного копирования:
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
← Вернуться к списку статей