GitHub Actions с self-hosted runners: CI/CD на своих серверах

Зачем нужны self-hosted runners

GitHub-hosted runners — удобное решение из коробки, но у них есть ограничения: фиксированные конфигурации (2 vCPU, 7 ГБ RAM), лимит 2000 минут/месяц для бесплатных аккаунтов, и главное — нет доступа к приватной инфраструктуре компании.

Self-hosted runners решают эти проблемы:

  • Доступ к внутренней сети — раннер в корпоративном контуре имеет прямой доступ к серверам, базам данных и приватным реестрам
  • Любое железо — можно использовать мощные серверы с GPU, большим объёмом RAM или специфической архитектурой (ARM)
  • Нет лимитов по времени — self-hosted runners бесплатны и не расходуют минуты
  • Предустановленные инструменты — кеширование зависимостей, SDK, лицензии ПО на раннере
  • Compliance — код и артефакты не покидают корпоративную сеть

Типичные сценарии: деплой на внутренние серверы, сборка Docker-образов для приватного registry, интеграционные тесты с базами данных, сборка проектов на 1С или .NET с лицензионными компонентами.

Установка runner на Linux

Создайте выделенного пользователя для раннера (запуск от root — плохая практика):

sudo useradd -m -s /bin/bash github-runner
sudo usermod -aG docker github-runner  # если нужен Docker
su - github-runner

Перейдите в Settings → Actions → Runners → New self-hosted runner в репозитории или организации. GitHub сгенерирует токен и команды установки:

mkdir actions-runner && cd actions-runner

curl -o actions-runner-linux-x64-2.311.0.tar.gz -L \
  https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz

tar xzf actions-runner-linux-x64-2.311.0.tar.gz

./config.sh --url https://github.com/ORG/REPO \
  --token AXXXXXXXXXXXXXXXXXXXXXXX \
  --name "prod-runner-01" \
  --labels "linux,docker,production" \
  --work _work

Параметр --labels критичен — через метки вы управляете, на каком раннере выполняется workflow. Используйте осмысленные метки: ОС, наличие Docker, окружение (staging/production).

Запуск как systemd-сервис

Установите и запустите раннер как системный сервис:

sudo ./svc.sh install github-runner
sudo ./svc.sh start
sudo ./svc.sh status

Это создаст юнит /etc/systemd/system/actions.runner.*.service. Раннер будет автоматически запускаться при перезагрузке. Проверьте статус:

sudo systemctl status actions.runner.ORG-REPO.prod-runner-01.service
journalctl -u actions.runner.ORG-REPO.prod-runner-01.service -f

Для автоматического обновления раннера добавьте в crontab:

# Еженедельное обновление GitHub Actions Runner
0 3 * * 1 /home/github-runner/actions-runner/bin/Runner.Listener --check-update

Установка runner на Windows

На Windows self-hosted runner может выполнять сборки .NET, PowerShell-скрипты и деплой через WinRM. Скачайте и настройте раннер:

mkdir C:\actions-runner; cd C:\actions-runner

Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-win-x64-2.311.0.zip -OutFile runner.zip
Expand-Archive -Path runner.zip -DestinationPath .

.\config.cmd --url https://github.com/ORG/REPO `
  --token AXXXXXXXXXXXXXXXXXXXXXXX `
  --name "win-runner-01" `
  --labels "windows,dotnet,msbuild" `
  --runasservice

Параметр --runasservice автоматически регистрирует раннер как Windows-службу. Служба запускается под учётной записью NT AUTHORITY\NETWORK SERVICE по умолчанию.

Для сборок, требующих GUI (например, тестирование WPF-приложений), настройте автологин и запуск run.cmd через планировщик задач вместо службы.

Предустановка инструментов

Предустановите необходимые инструменты на раннере:

# Установка через Chocolatey
choco install git nodejs dotnet-sdk python3 docker-desktop -y

# Или через winget
winget install Git.Git
winget install Microsoft.DotNet.SDK.8
winget install OpenJS.NodeJS.LTS

Преимущество self-hosted: инструменты установлены один раз, а не скачиваются каждый запуск. Это ускоряет pipeline на 3–10 минут.

Использование в workflow

Направьте workflow на self-hosted runner через метку runs-on:

name: Build and Deploy
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: [self-hosted, linux, docker]
    steps:
      - uses: actions/checkout@v4
      
      - name: Build Docker image
        run: |
          docker build -t registry.company.ru/app:${{ github.sha }} .
          docker push registry.company.ru/app:${{ github.sha }}
      
  deploy:
    needs: build
    runs-on: [self-hosted, linux, production]
    steps:
      - name: Deploy to production
        run: |
          ssh deploy@prod-server "docker pull registry.company.ru/app:${{ github.sha }} && \
            docker-compose -f /opt/app/docker-compose.yml up -d"

Массив в runs-on работает как AND — задание выполнится только на раннере, у которого есть все указанные метки. Это позволяет точно контролировать маршрутизацию заданий.

Секреты и переменные окружения

Self-hosted runners имеют доступ к GitHub Secrets, но также могут использовать локальные переменные окружения:

# В файле /home/github-runner/.env (на раннере)
DATABASE_URL=postgresql://user:pass@db-internal:5432/app
REGISTRY_PASSWORD=secret123

В workflow:

    steps:
      - name: Run integration tests
        env:
          DB_URL: ${{ secrets.DATABASE_URL }}  # из GitHub Secrets
          LOCAL_CONFIG: ${{ env.LOCAL_CONFIG }}  # из .env раннера
        run: pytest tests/integration/

Рекомендация: критичные секреты храните в GitHub Secrets (они маскируются в логах), а инфраструктурные переменные (пути, порты) — в окружении раннера.

Безопасность self-hosted runners

Self-hosted runner выполняет произвольный код из workflow — это серьёзный вектор атаки. Ключевые меры безопасности:

  • Никогда не используйте self-hosted runners в публичных репозиториях — любой может создать pull request с вредоносным кодом
  • Изолируйте раннер — выделенная VM или контейнер, минимум сетевых доступов
  • Не запускайте от root — используйте выделенного пользователя с минимальными правами
  • Очищайте окружение — после каждого запуска удаляйте артефакты

Настройте файрвол на раннере — ему нужен только исходящий доступ к GitHub:

# iptables для self-hosted runner
iptables -A OUTPUT -d github.com -j ACCEPT
iptables -A OUTPUT -d api.github.com -j ACCEPT
iptables -A OUTPUT -d *.actions.githubusercontent.com -j ACCEPT
iptables -A OUTPUT -d registry.company.ru -j ACCEPT  # приватный registry
iptables -A OUTPUT -j DROP  # блокируем остальное

Эфемерные раннеры

Для максимальной безопасности используйте эфемерные раннеры, которые пересоздаются после каждого задания:

./config.sh --url https://github.com/ORG/REPO \
  --token TOKEN \
  --ephemeral \
  --name "ephemeral-$(date +%s)"

Флаг --ephemeral заставляет раннер обработать ровно одно задание и завершиться. Комбинируйте с Docker или Terraform для автоматического создания и уничтожения VM.

Автоскейлинг раннеров

Для проектов с переменной нагрузкой реализуйте автоскейлинг раннеров. Самый простой подход — через Docker и webhook:

# docker-compose.yml для пула раннеров
version: '3.8'
services:
  runner:
    image: myacr.azurecr.io/github-runner:latest
    environment:
      - GITHUB_URL=https://github.com/ORG
      - RUNNER_TOKEN=${RUNNER_TOKEN}
      - RUNNER_LABELS=linux,docker
      - EPHEMERAL=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '2'
          memory: 4G

Более продвинутый вариант — actions-runner-controller (ARC) для Kubernetes. ARC автоматически масштабирует количество pod-раннеров на основе очереди заданий GitHub.

# Установка ARC через Helm
helm install arc \
  --namespace arc-systems \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller

helm install arc-runner-set \
  --namespace arc-runners \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \
  --set githubConfigUrl="https://github.com/ORG" \
  --set githubConfigSecret.github_token="TOKEN" \
  --set minRunners=1 \
  --set maxRunners=10

Мониторинг и обслуживание

Регулярное обслуживание self-hosted runners необходимо для стабильной работы CI/CD.

Скрипт очистки рабочей директории (добавьте в crontab):

#!/bin/bash
# /opt/scripts/cleanup-runner.sh
RUNNER_WORK="/home/github-runner/actions-runner/_work"

# Удалить сборки старше 7 дней
find "$RUNNER_WORK" -maxdepth 2 -type d -mtime +7 -exec rm -rf {} + 2>/dev/null

# Очистка Docker
docker system prune -af --filter "until=168h"

# Проверка свободного места
FREE=$(df -BG "$RUNNER_WORK" | awk 'NR==2 {print $4}' | tr -d 'G')
if [ "$FREE" -lt 20 ]; then
    echo "WARNING: Runner disk space low: ${FREE}G free" | \
      mail -s "Runner Disk Alert" admin@company.ru
fi

Мониторинг статуса раннеров через GitHub API:

curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
  "https://api.github.com/orgs/ORG/actions/runners" | \
  jq '.runners[] | {name: .name, status: .status, busy: .busy, labels: [.labels[].name]}'

Часто задаваемые вопросы

Да, зарегистрируйте раннер на уровне организации (Settings организации → Actions → Runners), и он будет доступен всем репозиториям. Через метки и Runner Groups можно ограничить доступ определённым репозиториям. Это удобнее, чем настраивать раннер для каждого репозитория отдельно.

Нет. Self-hosted runner устанавливает исходящее HTTPS-соединение с GitHub и поддерживает его через long polling. Входящие подключения не требуются, поэтому раннер работает за NAT и файрволом без проброса портов. Достаточно исходящего доступа к github.com и *.actions.githubusercontent.com на порту 443.

GitHub автоматически обновляет self-hosted runner при запуске нового задания, если доступна новая версия. Для раннеров за прокси или с ограниченным доступом скачайте новую версию вручную и выполните ./config.sh --replace. Раннер, зарегистрированный как systemd-сервис, обновится автоматически при следующем перезапуске.

Доступ к Docker socket (/var/run/docker.sock) фактически равен root-доступу к хосту. Для приватных репозиториев с доверенными разработчиками это допустимо. Для повышения безопасности используйте rootless Docker, Podman или запускайте Docker-in-Docker (dind) с изолированным демоном внутри контейнера раннера.

Один экземпляр раннера выполняет только одно задание за раз. Для параллельного выполнения запустите несколько экземпляров раннера на одном сервере (каждый в своей директории с уникальным именем). Планируйте ресурсы: каждый экземпляр потребляет 200–500 МБ RAM плюс ресурсы самой сборки.

Нужна помощь с настройкой?

Специалисты АйТи Фреш помогут с внедрением и настройкой — 15+ лет опыта, обслуживание от 15 000 ₽/мес

📞 Связаться с нами
#GitHub Actions#self-hosted runner#CI/CD#DevOps#автоматизация сборки#приватный раннер#GitHub runner Linux#continuous integration
Комментарии 0

Оставить комментарий

загрузка...