Подготовка инфраструктуры

Важно: раздел описывает подготовку инфраструктуры для self-hosted GitHub Runner

Требования

  • GitHub Actions;
  • Кластер Kubernetes;
  • Helm;
  • Хост для запуска GitHub Runner, с установленным:

    • Bash;

Включите поддержку непривилегированных пространств имён пользователей (требуется на worker-нодах)

sudo sysctl -w kernel.unprivileged_userns_clone=1
echo 'kernel.unprivileged_userns_clone = 1' | sudo tee -a /etc/sysctl.conf

Установка контроллера GitHub Actions Runner (ARC)

Установите Helm-чарт контроллера ARC:

helm install arc \
  --namespace arc-systems \
  --create-namespace \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller

Создание Kubernetes ресурсов

kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: job-template-gha-runner-werf
  namespace: arc-runners
data:
  content: |
    spec:
      serviceAccountName: arc-runner-sa
      securityContext:
        runAsUser: 1001
      containers:
        - name: \$job
EOF
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: arc-runner-sa
  namespace: arc-runners
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: arc-runner-cluster-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: arc-runner-sa
    namespace: arc-runners
EOF

Примечание Для повышения безопасности рекомендуется создать более ограниченную роль (ClusterRole/Role) и использовать её вместо cluster-admin.

Развертывание

Создайте файл values.yaml со следующим содержимым:

githubConfigUrl: "https://github.com/myenterprise/myorg/myrepo"
githubConfigSecret:
  github_token: "<PAT>"
template:
  spec:
    serviceAccountName: arc-runner-sa
    containers:
      - name: runner
        image: ghcr.io/actions/actions-runner:latest
        command: ["/home/runner/run.sh"]
        env:
          - name: ACTIONS_RUNNER_CONTAINER_HOOKS
            value: /home/runner/k8s/index.js
          - name: ACTIONS_RUNNER_POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER
            value: "true"
          - name: ACTIONS_RUNNER_CONTAINER_HOOK_TEMPLATE
            value: /home/runner/job-template/content
        volumeMounts:
          - name: work
            mountPath: /home/runner/_work
          - name: job-template
            mountPath: /home/runner/job-template
            readOnly: true
        resources:
          requests:
            cpu: 400m
            memory: 800Mi
    volumes:
      - name: job-template
        configMap:
          name: job-template-gha-runner-werf
      - name: work
        ephemeral:
          volumeClaimTemplate:
            spec:
              accessModes: ["ReadWriteOnce"]
              storageClassName: "<your-storageClassName>"
              resources:
                requests:
                  storage: 1Gi
job:
  enabled: true
  resources:
    requests:
      cpu: 500m
      memory: 1024Mi
    limits:
      memory: 2048Mi
helm install arc-runner-set -f values.yaml \
  --create-namespace \
  --namespace arc-runners \
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set

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

Требования

  • Self-hosted GitHub runner.

Настройка проекта GitHub

  • Создайте и сохраните access token для очистки ненужных образов из container registry со следующей конфигурацией:

    • Token name: werf-images-cleanup;

    • Scopes: read:packages и delete:packages.

  • В секреты проекта добавьте следующую переменную:

    • Access token для очистки ненужных образов:

      • Name: REGISTRY_CLEANUP_TOKEN;

      • Secret: <сохранённый "werf-images-cleanup" access token>.

  • Сохраните kubeconfig-файл для доступа к Kubernetes-кластеру в зашифрованный секрет KUBECONFIG_BASE64, предварительно закодировав его в Base64.

Конфигурация CI/CD проекта

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

.github
.helm
app
werf.yaml
name: cleanup
on:
  schedule:
    - cron: "0 3 * * *"

jobs:
  cleanup:
    name: cleanup
    runs-on: arc-runner-set
    container:
      image: ghcr.io/werf/werf:2-stable-ubuntu
    steps:
      - uses: actions/checkout@v3
      - run: git fetch --prune --unshallow
      
      - run: |
          . "$(werf ci-env github --as-file)"
          werf cleanup
        env:
          WERF_KUBECONFIG_BASE64: ${{ secrets.KUBECONFIG_BASE64 }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          WERF_REPO_GITHUB_TOKEN: ${{ secrets.REGISTRY_CLEANUP_TOKEN }}
name: prod
on:
  push:
    branches:
      - main

jobs:
  prod:
    name: prod
    runs-on: arc-runner-set
    container:
      image: ghcr.io/werf/werf:2-stable-ubuntu
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - run: |
          . "$(werf ci-env github --as-file)"
          werf converge
        env:
          WERF_ENV: prod
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          WERF_KUBECONFIG_BASE64: ${{ secrets.KUBECONFIG_BASE64 }}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
      - name: app
        image: {{ .Values.werf.image.app }}

apiVersion: v1
kind: Service
metadata:
  name: app
spec:
  selector:
    app: app
  ports:
  - name: app
    port: 80

FROM node

WORKDIR /app
COPY . .
RUN npm ci

CMD ["node", "server.js"]

{
  "name": "app",
  "version": "1.0.0",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {
    "": {
      "name": "app",
      "version": "1.0.0"
    }
  }
}

{
  "name": "app",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  }
}

const http = require('http');

const hostname = '127.0.0.1';
const port = 80;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

configVersion: 1
project: myproject
---
image: app
dockerfile: Dockerfile
context: ./app

Дополнительно:

  • Если вы не используете GitHub Container Registry в качестве реестра контейнеров, выполните следующие действия:

    • Установите переменную окружения WERF_REPO в адрес вашего реестра контейнеров;
    • Выполните вход в реестр с помощью команды werf cr login;
    • При выполнении очистки обязательно ознакомьтесь с особенностями работы выбранного реестра, которые могут повлиять на поведение очистки.
  • Подробнее о подключении к Kubernetes смотрите в документации по аутентификации.