Yang akan dibangun
Sebuah runner CI yang terhubung ke forge Anda sendiri, yang melakukan checkout repositori, membangun image container tanpa daemon dan tanpa root, serta mengirim hasilnya ke registry privat yang berjalan di instance yang sama. Tidak ada socket privileged yang di-mount di mana pun, dan tidak ada apa pun dalam pipeline yang berjalan sebagai uid zero.
Sebagian besar pengaturan CI self-hosted menyelesaikan pembangunan image dengan me-mount daemon container host ke dalam job. Itu berfungsi dan menyerahkan setiap pipeline, termasuk yang seseorang buka pull request-nya, kendali penuh atas mesin. Buildah dalam mode rootless melakukan pekerjaan yang sama tanpa semua itu, dan pada core khusus tidak lebih lambat.
Sebelum memulai
- Sebuah R-8. Pembangunan image adalah hal yang paling terikat single-core yang dijalankan sebagian besar tim, dan Zen 5 adalah yang tercepat per core yang kami jual. Empat ratus gigabyte NVMe menampung banyak layer.
- Sebuah forge yang sudah Anda jalankan dan mendukung protokol Actions, serta izin untuk membuat token registrasi runner di dalamnya.
- Nama host:
ci.example.comuntuk runner danregistry.example.comuntuk registry.
1. Pengguna tanpa hak istimewa dengan rentang namespace
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 runnerKedua rentang tersebut adalah yang memungkinkan container rootless: akun runner memiliki enam puluh lima ribu id bawahan, sehingga proses yang meyakini dirinya root di dalam container dipetakan ke id tanpa hak istimewa di luarnya. Lingering menjaga sesi pengguna tetap hidup sehingga unit systemd di bawah akun tersebut bertahan setelah logout.
Node diinstal karena sebagian besar action yang dapat digunakan kembali adalah JavaScript dan runner mengeksekusinya di host dalam konfigurasi ini. Menemukan itu pada langkah checkout adalah jalan memutar sepuluh menit yang umum.
2. Penyimpanan rootless di 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}}"Perintah terakhir itu harus menjawab overlay true. Driver vfs sebagai gantinya berarti fuse-overlayfs hilang, dan vfs menyalin setiap layer secara penuh pada setiap build, yang mengubah pipeline sembilan puluh detik menjadi enam menit.
3. Registry privat
apt install -y docker-registry
htpasswd -c /etc/docker/registry/htpasswd ci
certbot certonly --standalone -d registry.example.comBind registry ke loopback dan biarkan nginx memiliki semua yang menghadap keluar, termasuk autentikasi. Di /etc/docker/registry/config.yml:
version: 0.1
storage:
filesystem:
rootdirectory: /srv/registry
delete:
enabled: true
http:
addr: 127.0.0.1:5000Registry itu sendiri tidak membawa kredensial karena tidak pernah menerima permintaan yang belum melewati proxy. Satu tempat untuk memeriksa kata sandi lebih baik daripada dua tempat yang bisa berselisih.
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 menghapus batas unggah. Biarkan default nginx tetap dan setiap layer di atas satu megabyte akan gagal dengan 413 sekitar dua pertiga jalan melalui push, yang merupakan sore yang tak terlupakan.
systemctl enable --now docker-registry nginx4. 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.yamlEdit file yang dihasilkan agar job berjalan di host daripada di dalam container, karena Buildah sudah menyediakan isolasi dan nesting keduanya tidak menambah apa pun selain kompleksitas:
runner:
capacity: 2
timeout: 1h
labels:
- "debian-13:host"
host:
workdir_parent: /home/runner/work
cache:
enabled: true
dir: /home/runner/cacheKapasitas dua pada delapan core adalah disengaja: build sebagian besar serial, dan dua job bersamaan dengan masing-masing empat core selesai lebih cepat daripada empat job yang berebut cache yang sama. Daftarkan ke forge Anda dengan token yang dihasilkannya:
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:hostLalu sebuah unit, berjalan sebagai akun tanpa hak istimewa:
[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.targetGanti dengan uid sebenarnya dari akun runner di XDG_RUNTIME_DIR; id -u runner mencetaknya. Podman rootless membutuhkan direktori itu ada, yang dijamin oleh pengaturan linger pada langkah pertama.
systemctl daemon-reload && systemctl enable --now act-runner5. Workflow yang membangun dan mengirim
Di repositori, di .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 mengaktifkan caching layer, yang menjadi perbedaan antara membangun ulang dependensi pada setiap commit dan membangun ulang dependensi saat berubah.
Verifikasi
Runner akan muncul sebagai online dalam daftar runner forge dalam beberapa detik setelah unit dimulai. Lalu push sebuah commit dan tonton job dari mesin:
journalctl -fu act-runnerSetelah selesai, konfirmasikan bahwa image benar-benar tiba dan bukan hanya melaporkan keberhasilan:
skopeo inspect --creds ci:<password> docker://registry.example.com/app:latest | head -20
skopeo list-tags --creds ci:<password> docker://registry.example.com/appAnda menginginkan digest, daftar layer, dan kedua tag. Sekarang buktikan berjalan, di mesin yang berbeda jika Anda memilikinya:
podman run --rm registry.example.com/app:latest --versionTerakhir, klaim yang menjadi dasar seluruh build ini. Selama job berjalan, lihat siapa pemilik proses:
ps -eo user,pid,comm | grep -E "buildah|podman" | head
sudo -u runner podman info --format "{{.Host.Security.Rootless}}"Setiap proses milik runner, dan pemeriksaan keamanan menjawab true. Tidak ada apa pun dalam pipeline yang memegang root, yang berarti skrip build yang disusupi mendapatkan akun tanpa hak istimewa dan namespace, bukan kunci registry dan hypervisor Anda.
Setelahnya
Penyimpanan registry tumbuh tanpa batas kecuali sesuatu menghapus tag lama, jadi jalankan registry garbage-collect dengan timer mingguan setelah Anda memiliki aturan retensi yang Anda yakini. Jika build menjadi bottleneck daripada tes, halaman perbandingan menunjukkan seperti apa ukuran berikutnya; lebih banyak core membantu jauh lebih sedikit daripada yang orang harapkan, dan yang lebih cepat membantu jauh lebih banyak.