これが構築するもの
自分のフォージに接続されたCIランナー。リポジトリをチェックアウトし、デーモンもルートもなしにコンテナイメージをビルドし、その結果を同じインスタンス上で動作するプライベートレジストリにプッシュします。特権ソケットはどこにもマウントされておらず、パイプライン内のプロセスはuidゼロでは実行されません。
ほとんどのセルフホストCIセットアップは、ホストのコンテナデーモンをジョブにマウントすることでイメージビルドを解決します。それは機能し、すべてのパイプライン(誰かがプルリクエストを開いたものも含む)にマシンの完全な制御を渡します。ルートレスモードのBuildahは、それなしで同じ仕事をし、専用コアでは遅くありません。
始める前に
- R-8。イメージビルドはほとんどのチームが実行する中で最もシングルコアに依存するものであり、Zen 5は当社が販売する中でコアあたり最速です。400ギガバイトのNVMeは非常に多くのレイヤーを保持します。
- Actionsプロトコルを話す既に実行中のフォージと、その中にランナー登録トークンを作成する権限。
- ホスト名: ランナーに
ci.example.com、レジストリにregistry.example.com。
1. 名前空間範囲を持つ非特権ユーザー
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これらの2つの範囲がルートレスコンテナを可能にします: runnerアカウントは65,000の従属IDを所有し、コンテナ内でルートであると信じているプロセスは、外部では非特権IDにマッピングされます。Lingeringはユーザーセッションを維持し、そのアカウント下のsystemdユニットがログアウト後も存続するようにします。
Nodeがインストールされているのは、ほとんどの再利用可能なアクションがJavaScriptであり、この構成ではランナーがそれらをホスト上で実行するためです。チェックアウトステップでそれを発見することは、よくある10分の回り道です。
2. 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}}"最後のコマンドはoverlay trueと答えるはずです。vfsドライバーはfuse-overlayfsが欠落していることを意味し、vfsはビルドのたびにすべてのレイヤーを完全にコピーするため、90秒のパイプラインが6分になります。
3. プライベートレジストリ
apt install -y docker-registry
htpasswd -c /etc/docker/registry/htpasswd ci
certbot certonly --standalone -d registry.example.comレジストリをループバックにバインドし、認証を含む外向きのすべてをnginxに任せます。/etc/docker/registry/config.ymlで:
version: 0.1
storage:
filesystem:
rootdirectory: /srv/registry
delete:
enabled: true
http:
addr: 127.0.0.1:5000レジストリ自体は資格情報を保持しません。プロキシを通過していないリクエストを受信しないためです。パスワードをチェックする場所が1つあることは、矛盾する可能性のある2つの場所よりも優れています。
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はアップロード制限を削除します。nginxのデフォルトをそのままにしておくと、1メガバイトを超えるレイヤーはプッシュの約3分の2で413で失敗します。これは印象的な午後を過ごすことになります。
systemctl enable --now docker-registry nginx4. ランナー
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生成されたファイルを編集して、ジョブがコンテナ内ではなくホスト上で実行されるようにします。Buildahがすでに分離を提供しており、ネストは複雑さを追加するだけだからです:
runner:
capacity: 2
timeout: 1h
labels:
- "debian-13:host"
host:
workdir_parent: /home/runner/work
cache:
enabled: true
dir: /home/runner/cache8コアでの容量2は意図的です: ビルドはほぼシリアルであり、4コアずつの2つの同時ジョブは、同じキャッシュをめぐって競合する4つのジョブよりも早く終了します。生成されたトークンでフォージに登録します:
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次に、非特権アカウントとして実行されるユニット:
[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.targetrunnerアカウントの実際のuidをXDG_RUNTIME_DIRで置き換えます; id -u runnerがそれを表示します。ルートレスpodmanはそのディレクトリが存在することを必要とし、これはステップ1のlinger設定が保証するものです。
systemctl daemon-reload && systemctl enable --now act-runner5. ビルドしてプッシュするワークフロー
リポジトリ内の.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はレイヤーキャッシュを有効にし、これは毎コミットでの依存関係の再ビルドと、変更時の再ビルドの違いです。
検証
ユニットが開始されてから数秒以内に、ランナーがフォージのランナーリストにオンラインとして表示されるはずです。次にコミットをプッシュし、マシンからジョブを監視します:
journalctl -fu act-runner終了したら、イメージが単に成功を報告するのではなく、実際に到着したことを確認します:
skopeo inspect --creds ci:<password> docker://registry.example.com/app:latest | head -20
skopeo list-tags --creds ci:<password> docker://registry.example.com/appダイジェスト、レイヤーリスト、両方のタグが必要です。次に、それを実行できることを証明します。手元に別のマシンがあれば:
podman run --rm registry.example.com/app:latest --version最後に、このビルド全体が依存する主張です。ジョブが実行されている間に、プロセスを誰が所有しているかを確認します:
ps -eo user,pid,comm | grep -E "buildah|podman" | head
sudo -u runner podman info --format "{{.Host.Security.Rootless}}"すべてのプロセスはrunnerに属し、セキュリティチェックはtrueと答えます。パイプライン内の何もルートを保持していないため、侵害されたビルドスクリプトは非特権アカウントと名前空間を取得し、レジストリキーとハイパーバイザーは取得しません。
その後
レジストリストレージは、古いタグを削除する何かがない限り無制限に成長するため、保持ルールを信じるようになったら、週次タイマーでregistry garbage-collectを実行します。ビルドがテストではなくボトルネックになった場合、比較ページは次のサイズアップがどのように見えるかを示します; 多くの人が期待するほどコアは役に立たず、速いコアははるかに役立ちます。