GitHub Actions tidak bisa menjangkau VPN Anda: runner sendiri atau terowongan
Runner terkelola adalah VM baru di luar jaringan Anda. Tempatkan runner di dalam jaringan itu, atau buat terowongan dari job dan tunggu rute yang benar-benar ada.
Langkah deploy Anda menggantung pada nama host internal sementara perintah yang sama menjawab seketika dari laptop, dan build klien di kedua mesin identik. Job berjalan di runner yang dihosting GitHub: mesin virtual yang dibuat untuk job itu saja di dalam cloud GitHub, tanpa rute ke ruang alamat privat Anda, tanpa entri resolver untuk zona internal, dan tanpa sertifikat yang akan diterima endpoint privat. Hanya dua desain yang cocok dengan batasan itu, dan keduanya tidak bisa dinyalakan belakangan tanpa membangun ulang workflow. Pilih di antara keduanya sebelum baris YAML pertama, karena bermigrasi setelahnya lebih mahal daripada percobaan pertama.
Pilihannya antara runner yang ditempatkan di titik jaringan sudah berakhir dan terowongan yang hanya hidup selama satu job. Keduanya sudah menopang deploy produksi bertahun-tahun, dan gagal dengan cara berbeda.
Di mana batasnya sebenarnya
Apa pun yang Anda pasang di laptop berhenti di laptop, dan apa pun yang Anda pasang selama job hilang bersama runner. Tidak ada namespace jaringan persisten tempat klien tetap menempel antar eksekusi, jadi setiap eksekusi mulai dari keadaan tak terjangkau yang sama. Azure mendokumentasikan batas ini untuk agen yang dihosting Microsoft, di mana tautan VPN dan ExpressRoute ke jaringan privat tidak didukung, dan runner yang dihosting GitHub membawa bentuk batasan yang sama, bukan mekanisme terpisah. Bacalah sebagai sifat produk, bukan cacat yang harus diakali.
Tidak ada pengaturan repositori yang melonggarkannya. Label job menentukan pool mana yang mengambil pekerjaan, dan label terkelola berarti mesin di luar perimeter Anda selama seluruh eksekusi.
Karena itu keterjangkauan privat harus datang dari sesuatu yang Anda miliki. Runner yang ditempatkan di titik jaringan sudah berakhir, atau terowongan yang hidup satu job. Sisa artikel ini membahas membayar salah satunya secara sengaja, bukan menemukan tagihannya di produksi.
Putuskan berdasarkan frekuensi eksekusi, bukan kebiasaan
Pakai runner sendiri di dalam VPC atau jaringan kantor ketika deploy masuk beberapa kali sehari, ketika job menyentuh lebih dari satu port privat, atau ketika tim platform sudah menangani patch dan rotasi kredensial. Job di mesin itu me-resolve dan merutekan seperti host lain di belakang firewall. Pekerjaan jaringan terjadi sekali, saat pemasangan, bukan setiap eksekusi.
Bangun terowongan di dalam job ketika akses jarang dan Anda tidak ingin agen berumur panjang memegang kredensial ke jaringan Anda. Job menjadi klien jaringannya sendiri selama beberapa menit dan tidak meninggalkan apa pun, yang sering menjadi inti persoalannya.
Perkirakan biaya terowongan sebelum memilihnya. Setiap eksekusi membayar materialisasi profil, start klien, kesiapan, dan pembongkaran. Terowongan yang naik tetapi tidak pernah diverifikasi menghasilkan deploy yang gagal di tengah dan meninggalkan keadaan privat dalam bentuk yang tidak direncanakan siapa pun. Percobaan ulang setelahnya jarang bersih, dan kegagalan kedua biasanya dituduhkan ke versi klien.
Menulis profil agar langkahnya tidak bisa dibatalkan
Simpan profil dan kredensial di luar repositori dan wujudkan dari secret di langkah yang membutuhkannya. Urutan menentukan apakah job bertahan: file yang dibuat di bawah sudo milik root, dan shell langkah tanpa hak istimewa tidak bisa lagi menulis ke dalamnya.
- name: Bring up OpenVPN
run: |
umask 077
profile="$RUNNER_TEMP/client.ovpn"
printf '%s\n' "$VPN_PROFILE" > "$profile"
: > "$RUNNER_TEMP/openvpn.log"
printf '%s\n' "$VPN_CREDENTIALS" > "$RUNNER_TEMP/credentials.txt"
sudo openvpn --daemon openvpn-job \
--config "$profile" \
--auth-user-pass "$RUNNER_TEMP/credentials.txt" \
--writepid "$RUNNER_TEMP/openvpn.pid" \
--log "$RUNNER_TEMP/openvpn.log"Kedua detail di blok itu ada untuk langkah berikutnya. Dengan umask 077 profil mendarat pada mode 0600 milik pengguna runner, sudo masih bisa membacanya, dan klien menulis log ke file yang sudah dimiliki runner, sehingga loop kesiapan bisa membaca log itu tanpa sudo kedua. --writepid mencatat PID yang akan dibidik pembongkaran.
Profil yang mengautentikasi dengan sertifikat klien perlu kuncinya ditulis dengan cara yang sama, dan profil static-challenge perlu mode auth yang sesuai. Profil yang mendorong opsi DNS perlu openvpn-systemd-resolved di Ubuntu dan Debian, karena dorongan itu saja tidak mengubah apa pun pada image dasar.
Kesiapan adalah probe, bukan jeda
Sleep tetap bertahan persis sampai concentrator melambat, lalu langkah deploy berjalan sebelum rute ada. Kegagalan kemudian muncul di dalam alat deploy sebagai timeout ke host privat, yang tidak menjelaskan apa pun tentang terowongan.
- name: Await private endpoint
run: |
for attempt in $(seq 1 30); do
if getent hosts private.example.internal > /dev/null && \
timeout 3 bash -c 'exec 3<>/dev/tcp/private.example.internal/8443' 2>/dev/null; then
echo "endpoint answered after $attempt attempts"
exit 0
fi
sleep 2
done
grep -Ei 'error|verify|auth' "$RUNNER_TEMP/openvpn.log" | tail -40
exit 1Loop mengamati dua hal karena salah satunya bisa menjadi separuh yang rusak. getent hosts membuktikan resolver yang didorong menjawab untuk nama itu, dan terbukanya socket membuktikan ada sesuatu yang mendengarkan di port yang akan dipakai deploy; profil hanya-rute perlu alamat literal dan baris getent dihapus.
Ketika loop kedaluwarsa, log adalah satu-satunya yang memisahkan tiga kegagalan berbeda: sertifikat ditolak, mode auth salah, dan rute yang tidak pernah tiba. Menyaring sebelum tail menjaga keluaran itu cukup pendek untuk dibaca saat job masih gagal. Baca kesalahan pertama, bukan baris terakhir, karena klien yang tidak bisa mengautentikasi mengulang dirinya sampai loop menyerah. ip -4 route show dev tun0 menjawab pertanyaan yang lebih sederhana, apakah ada rute yang didorong sama sekali, dan harganya satu baris di loop yang sama.
Pembongkaran dan pertanyaan artefak
Tutup job dengan langkah yang dijaga if: always() dan hanya menjalankan sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")". Pada runner sendiri yang dipakai bersama, jangan pernah membunuh berdasarkan nama proses: job lain bisa memiliki klien dengan biner dan argumen serupa. Kegagalan lalu jatuh pada job yang berjalan kedua, dan log-nya menunjuk workflow yang tidak berbuat salah.
Penjagaan itu penting karena deploy yang gagal tetap meninggalkan terowongan hidup. Tanpa always() langkah pembersihan dilewati justru saat dibutuhkan, dan eksekusi berikutnya mulai melawan klien yang sudah terhubung.
Masih ada satu pertanyaan sebelum mengadopsi salah satu desain: apakah tahap build benar-benar butuh rute privat? Agen deployment yang sudah berada di dalam jaringan bisa menarik artefak yang disetujui, dan itu menjauhkan build biasa dari jalur privat. Lebih sedikit job yang memegang terowongan berarti lebih sedikit tempat rute basi bisa bersembunyi.