Dwanaście instalacji

Ciężarówka CI, która buduje obrazy kontenerów bez roota

Samodzielnie hostowany runner Actions na instancji Ryzen, budujący obrazy OCI bez roota za pomocą Buildah i wypychający je do prywatnego rejestru na tej samej maszynie.

Co to buduje

Runner CI podpięty do własnego forge, który pobiera repozytorium, buduje obraz kontenera bez demona i bez roota, i wypycha wynik do prywatnego rejestru działającego na tej samej instancji. Nigdzie nie jest zamontowany uprzywilejowany socket i nic w pipeline nie działa jako uid zero.

Większość samodzielnie hostowanych konfiguracji CI rozwiązuje budowanie obrazów przez montowanie demona kontenerów hosta do zadania. To działa i daje każdemu pipeline, w tym temu otwartemu przez pull request, pełną kontrolę nad maszyną. Buildah w trybie rootless robi to samo bez tego wszystkiego, a na dedykowanych rdzeniach nie jest wolniejszy.

Zanim zaczniesz

  • R-8. Budowanie obrazów jest najbardziej ograniczonym pojedynczym rdzeniem zadaniem, jakie większość zespołów uruchamia, a Zen 5 jest najszybszy na rdzeń, jaki sprzedajemy. Czterysta gigabajtów NVMe pomieści bardzo wiele warstw.
  • Forge, które już prowadzisz i które mówi protokołem Actions, oraz uprawnienie do utworzenia tokena rejestracji runnera w nim.
  • Nazwy hostów: ci.example.com dla runnera i registry.example.com dla rejestru.

1. Nieuprzywilejowany użytkownik z zakresem przestrzeni nazw

apt update && apt install -y podman buildah skopeo fuse-overlayfs uidmap slirp4netns git nodejs nginx apache2-utils
useradd -m -s /bin/bash runner
echo "runner:200000:65536" >> /etc/subuid
echo "runner:200000:65536" >> /etc/subgid
loginctl enable-linger runner

Te dwa zakresy umożliwiają kontenery rootless: konto runner posiada sześćdziesiąt pięć tysięcy podrzędnych identyfikatorów, więc proces, który wierzy, że jest rootem wewnątrz kontenera, jest mapowany na nieuprzywilejowany identyfikator na zewnątrz. Lingering utrzymuje sesję użytkownika przy życiu, więc jednostki systemd pod tym kontem przetrwają wylogowanie.

Node jest zainstalowany, ponieważ większość wielokrotnego użytku akcji to JavaScript, a runner wykonuje je na hoście w tej konfiguracji. Odkrycie tego na etapie checkout to częsta dziesięciominutowa objazdka.

2. Pamięć rootless na NVMe

sudo -u runner mkdir -p /home/runner/.config/containers
sudo -u runner tee /home/runner/.config/containers/storage.conf <<EOF
[storage]
driver = "overlay"
graphroot = "/home/runner/.local/share/containers/storage"

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
EOF
sudo -u runner podman info --format "{{.Store.GraphDriverName}} {{.Host.Security.Rootless}}"

To ostatnie polecenie powinno odpowiedzieć overlay true. Sterownik vfs zamiast tego oznacza brak fuse-overlayfs, a vfs kopiuje każdą warstwę w całości przy każdym budowaniu, co zamienia dziewięćdziesięciosekundowy pipeline w sześciominutowy.

3. Prywatny rejestr

apt install -y docker-registry
htpasswd -c /etc/docker/registry/htpasswd ci
certbot certonly --standalone -d registry.example.com

Przypnij rejestr do loopback i pozwól nginxowi obsługiwać wszystko skierowane na zewnątrz, w tym uwierzytelnianie. W /etc/docker/registry/config.yml:

version: 0.1
storage:
  filesystem:
    rootdirectory: /srv/registry
  delete:
    enabled: true
http:
  addr: 127.0.0.1:5000

Sam rejestr nie przenosi żadnych poświadczeń, ponieważ nigdy nie otrzymuje żądania, które nie przeszło już przez proxy. Jedno miejsce do sprawdzenia hasła jest lepsze niż dwa miejsca, które mogą się nie zgadzać.

server {
  listen 443 ssl;
  server_name registry.example.com;
  ssl_certificate     /etc/letsencrypt/live/registry.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/registry.example.com/privkey.pem;
  client_max_body_size 0;
  chunked_transfer_encoding on;

  location /v2/ {
    auth_basic "restricted";
    auth_basic_user_file /etc/docker/registry/htpasswd;
    proxy_pass http://127.0.0.1:5000;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 900s;
  }
}

client_max_body_size 0 usuwa limit przesyłania. Pozostaw domyślne ustawienie nginx na miejscu, a każda warstwa powyżej jednego megabajta zakończy się błędem 413 w około dwóch trzecich pusha, co jest pamiętnym popołudniem.

systemctl enable --now docker-registry nginx

4. Runner

cd /usr/local/bin
wget -O act_runner https://code.forgejo.org/forgejo/runner/releases/download/v6.3.1/forgejo-runner-6.3.1-linux-amd64
chmod +x act_runner
sudo -u runner mkdir -p /home/runner/.runner-cfg
cd /home/runner/.runner-cfg && sudo -u runner /usr/local/bin/act_runner generate-config > config.yaml

Edytuj wygenerowany plik, aby zadania działały na hoście, a nie wewnątrz kontenera, ponieważ Buildah już zapewnia izolację, a zagnieżdżanie dodaje tylko złożoność:

runner:
  capacity: 2
  timeout: 1h
  labels:
    - "debian-13:host"
host:
  workdir_parent: /home/runner/work
cache:
  enabled: true
  dir: /home/runner/cache

Pojemność dwa na ośmiu rdzeniach jest celowa: budowanie jest w dużej mierze szeregowe, a dwa równoczesne zadania z czterema rdzeniami każde kończą się szybciej niż cztery zadania walczące o ten sam cache. Zarejestruj się w swoim forge z wygenerowanym tokenem:

cd /home/runner/.runner-cfg
sudo -u runner /usr/local/bin/act_runner register --no-interactive \
  --instance https://forge.example.com --token <registration token> \
  --name ci-ams --labels debian-13:host

Następnie jednostka, działająca jako nieuprzywilejowane konto:

[Unit]
Description=Actions runner
After=network-online.target

[Service]
User=runner
WorkingDirectory=/home/runner/.runner-cfg
ExecStart=/usr/local/bin/act_runner daemon --config /home/runner/.runner-cfg/config.yaml
Restart=always
Environment=HOME=/home/runner
Environment=XDG_RUNTIME_DIR=/run/user/3001
NoNewPrivileges=yes

[Install]
WantedBy=multi-user.target

Podstaw rzeczywiste uid konta runner w XDG_RUNTIME_DIR; id -u runner wypisze je. Rootless podman potrzebuje, aby ten katalog istniał, co gwarantuje ustawienie linger z kroku pierwszego.

systemctl daemon-reload && systemctl enable --now act-runner

5. Workflow, który buduje i wypycha

W repozytorium, w .forgejo/workflows/image.yaml:

on:
  push:
    branches: [main]

jobs:
  image:
    runs-on: debian-13
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: |
          buildah bud --layers --format oci -t app:${{ github.sha }} .
      - name: Push
        run: |
          buildah login -u ci -p ${{ secrets.REGISTRY_PASSWORD }} registry.example.com
          buildah push app:${{ github.sha }} docker://registry.example.com/app:${{ github.sha }}
          buildah push app:${{ github.sha }} docker://registry.example.com/app:latest

--layers włącza cache warstw, co jest różnicą między przebudowywaniem zależności przy każdym commitcie a przebudowywaniem ich, gdy się zmienią.

Zweryfikuj to

Runner powinien pojawić się jako online na liście runnerów forge w ciągu kilku sekund od uruchomienia jednostki. Następnie wypchnij commit i obserwuj zadanie z maszyny:

journalctl -fu act-runner

Gdy się zakończy, potwierdź, że obraz naprawdę dotarł, a nie tylko zgłosił sukces:

skopeo inspect --creds ci:<password> docker://registry.example.com/app:latest | head -20
skopeo list-tags --creds ci:<password> docker://registry.example.com/app

Chcesz digest, listę warstw i oba tagi. Teraz udowodnij, że działa, na innej maszynie, jeśli masz pod ręką:

podman run --rm registry.example.com/app:latest --version

Na koniec twierdzenie, na którym opiera się cała ta budowa. Podczas gdy zadanie działa, sprawdź, do kogo należą procesy:

ps -eo user,pid,comm | grep -E "buildah|podman" | head
sudo -u runner podman info --format "{{.Host.Security.Rootless}}"

Każdy proces należy do runner, a kontrola bezpieczeństwa odpowiada true. Nic w pipeline nie ma roota, co oznacza, że skompromitowany skrypt budowania otrzymuje nieuprzywilejowane konto i przestrzeń nazw, a nie twoje klucze rejestru i twój hiperwizor.

Potem

Pamięć rejestru rośnie bez ograniczeń, chyba że coś usuwa stare tagi, więc uruchamiaj registry garbage-collect na cotygodniowym timerze, gdy już ustalisz regułę przechowywania, w którą wierzysz. Jeśli budowanie stanie się wąskim gardłem, a nie testy, strona porównawcza pokazuje, jak wygląda następny rozmiar; więcej rdzeni pomaga znacznie mniej, niż większość ludzi oczekuje, a szybsze pomagają znacznie więcej.

Gotowi, gdy jesteś

Wybierz miasto. Wybierz rozmiar. Płać kryptowalutą.

Bez formularzy o tym, kim jesteś, bez czekania na akceptację człowieka, bez telefonu w celu weryfikacji. Faktura zostaje uregulowana, a dane logowania trafiają do Twojej skrzynki.