GitHub Actions VPN'inize ulaşamıyor: kendi runner'ınız ya da tünel
Barındırılan runner, ağınızın dışındaki yeni bir VM'dir. Ya runner'ı o ağın içine koyun ya da job'dan tünel açıp gerçek bir rotayı bekleyin.
Dağıtım adımınız dahili bir ana bilgisayar adında takılıyor, oysa aynı komut dizüstü bilgisayarınızdan anında yanıt veriyor ve istemci derlemesi iki makinede de aynı. Job, GitHub'ın barındırdığı bir runner'da çalışıyor: o tek job için GitHub'ın bulutunda oluşturulmuş bir sanal makine; özel adres alanınıza giden rota yok, dahili bölge için resolver kaydı yok ve özel endpoint'in kabul edeceği sertifika yok. Bu kısıtlamaya yalnızca iki tasarım sığar ve hiçbiri iş akışını yeniden kurmadan sonradan açılamaz. İlk YAML satırından önce aralarında seçim yapın, çünkü sonradan taşınmak ilk denemeden daha pahalıdır.
Seçim, ağın zaten bittiği yere yerleştirilmiş bir runner ile yalnızca bir job boyunca yaşayan bir tünel arasındadır. İkisi de yıllardır üretim dağıtımlarını taşıyor ve farklı biçimlerde başarısız oluyorlar.
Sınır gerçekte nerede
Dizüstü bilgisayarınıza kurduğunuz her şey dizüstünde durur ve job sırasında kurduğunuz her şey runner ile birlikte kaybolur. İstemcinin çalıştırmalar arasında bağlı kalabileceği kalıcı bir ağ ad alanı yok, bu yüzden her çalıştırma aynı ulaşılamaz durumdan başlar. Azure bu sınırı Microsoft tarafından barındırılan ajanlar için belgeliyor: özel ağlara giden VPN ve ExpressRoute bağlantıları oralarda desteklenmiyor ve GitHub'ın barındırdığı runner'lar ayrı bir mekanizma değil aynı kısıtlama biçimini taşıyor. Bunu ürünün bir özelliği olarak okuyun, etrafından dolaşılacak bir kusur olarak değil.
Hiçbir depo ayarı bunu gevşetmez. Job etiketi işi hangi havuzun alacağını belirler ve barındırılan etiket, çalıştırma boyunca çevre biriminizin dışındaki bir makine anlamına gelir.
Bu yüzden özel erişilebilirlik, sahip olduğunuz bir şeyden gelmek zorunda. Ağın zaten bittiği yere konmuş bir runner ya da bir job yaşayan bir tünel. Yazının geri kalanı, üretimde faturayı keşfetmek yerine bu ikisinden birini bilinçli olarak ödemekle ilgili.
Sıklığa göre karar verin, aşinalığa göre değil
Dağıtımlar günde birkaç kez geliyorsa, job birden fazla özel porta dokunuyorsa ya da platform ekibi yamalama ve kimlik bilgisi döndürmeyi zaten üstlenmişse VPC veya ofis ağı içinde kendi runner'ınızı kullanın. O makinedeki job, güvenlik duvarının arkasındaki herhangi bir ana bilgisayar gibi çözer ve yönlendirir. Ağ işi kurulum sırasında bir kez olur, her çalıştırmada değil.
Erişim seyrekse ve ağınıza kimlik bilgisi tutan uzun ömürlü bir ajan istemiyorsanız tüneli job içinde kurun. Job birkaç dakika kendi ağ istemcisi olur ve arkasında hiçbir şey bırakmaz; çoğu zaman bütün mesele budur.
Tüneli seçmeden önce maliyetini tahmin edin. Her çalıştırma profil oluşturma, istemci başlatma, hazır olma ve sökme için ödeme yapar. Ayağa kalkan ama hiç doğrulanmayan bir tünel, yarı yolda başarısız olan bir dağıtım üretir ve özel durumu kimsenin planlamadığı bir biçimde bırakır. Sonrasındaki yeniden deneme nadiren temizdir ve ikinci hatadan genellikle istemci sürümü sorumlu tutulur.
Profili adım iptal olamayacak şekilde yazmak
Profilleri ve kimlik bilgilerini deponun dışında tutun ve ihtiyaç duyan adımda secret'lardan oluşturun. Sıra, job'un hayatta kalıp kalmayacağını belirler: sudo altında oluşturulan dosya root'a aittir ve adımın ayrıcalıksız kabuğu artık ona yazamaz.
- 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"O bloktaki iki ayrıntı da sonraki adımlar için var. umask 077 ile profil 0600 kipinde runner kullanıcısına ait olarak iner, sudo hâlâ okuyabilir ve istemci günlüğünü runner'ın zaten sahip olduğu dosyaya yazar; böylece hazır olma döngüsü o günlüğü ikinci bir sudo olmadan okuyabilir. --writepid sökmenin hedefleyeceği PID'i kaydeder.
İstemci sertifikasıyla kimlik doğrulayan profil anahtarının da aynı biçimde yazılmasını gerektirir ve static-challenge profili eşleşen auth kipine ihtiyaç duyar. DNS seçeneklerini iten profiller Ubuntu ve Debian'da openvpn-systemd-resolved gerektirir, çünkü itmenin kendisi temel imgede hiçbir şeyi değiştirmez.
Hazır olma bir yoklamadır, duraklama değil
Sabit bir sleep tam olarak yoğunlaştırıcı yavaşlayana kadar dayanır ve sonra dağıtım adımı rota oluşmadan çalışır. Hata daha sonra dağıtım aracının içinde özel ana bilgisayara karşı timeout olarak görünür; bu, tünel hakkında hiçbir şey söylemez.
- 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 1Döngü iki şeyi izler çünkü her biri bozuk yarı olabilir. getent hosts itilen resolver'ın o ad için yanıt verdiğini kanıtlar, socket'in açılması dağıtımın kullanacağı portta birinin dinlediğini kanıtlar; yalnızca rotası olan bir profil gerçek adresi ve getent satırının silinmesini gerektirir.
Döngü dolduğunda, üç farklı arızayı ayıran tek şey günlüktür: reddedilen sertifika, yanlış auth kipi ve hiç ulaşmayan rota. tail'den önce filtrelemek bu çıktıyı job hâlâ başarısızken okunabilecek kadar kısa tutar. Son satır yerine ilk hatayı okuyun, çünkü kimlik doğrulayamayan istemci döngü pes edene kadar kendini tekrarlar. ip -4 route show dev tun0 rotanın hiç itilip itilmediği sorusuna yanıt verir ve aynı döngü içinde tek satır maliyeti vardır.
Sökme ve artefakt sorusu
Job'u if: always() ile korunan ve yalnızca sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" çalıştıran bir adımla kapatın. Paylaşılan kendi runner'ınızda asla süreç adıyla öldürmeyin: başka bir job aynı binary ve benzer argümanlara sahip bir istemciye sahip olabilir. Hata o zaman ikinci çalışan job'a düşer ve günlüğü hiçbir yanlış yapmayan bir workflow'u işaret eder.
Koruma önemlidir çünkü başarısız bir dağıtım yine de canlı bir tünel bırakır. always() olmadan temizleme adımı tam da gerektiği anda atlanır ve sonraki çalıştırma zaten bağlı bir istemciye karşı başlar.
Her iki tasarımdan birini benimsemeden önce bir soru kalıyor: derleme aşamasının gerçekten özel bir rotaya ihtiyacı var mı? Zaten ağın içinde oturan bir dağıtım ajanı onaylanmış artefaktı çekebilir ve bu, sıradan derlemeleri özel yoldan uzak tutar. Tünel tutan daha az job, eski bir rotanın saklanabileceği daha az yer demektir.