GitHub Actions nedosáhne na vaši VPN: vlastní runner, nebo tunel
Hostovaný runner je čerstvý virtuální stroj mimo vaši síť. Buď umístěte runner do této sítě, nebo z jobu vytvořte tunel a počkejte na skutečnou trasu.
Váš krok nasazení visí na interním názvu hostitele, zatímco stejný příkaz z notebooku odpovídá okamžitě, a build klienta je na obou strojích stejný. Job běží na runneru hostovaném GitHubem: na virtuálním stroji vytvořeném pro tento jediný job v cloudu GitHubu, bez trasy do vašeho privátního adresního prostoru, bez záznamu resolveru pro interní zónu a bez certifikátu, který by privátní endpoint přijal. Tomuto omezení odpovídají jen dva návrhy a žádný z nich nelze později zapnout bez přestavby workflow. Vyberte mezi nimi ještě před prvním řádkem YAML, protože migrace dodatečně stojí víc než první pokus.
Volba je mezi runnerem umístěným tam, kde síť už končí, a tunelem, který existuje po dobu jednoho jobu. Oba roky vozí produkční nasazení a oba selhávají jiným způsobem.
Kde hranice skutečně leží
Vše, co nainstalujete na notebook, skončí na notebooku, a vše, co nainstalujete během jobu, zmizí s runnerem. Neexistuje trvalý síťový namespace, ke kterému by klient zůstal připojený mezi běhy, takže každý běh začíná ve stejném nedosažitelném stavu. Azure tuto hranici dokumentuje u agentů hostovaných Microsoftem, kde se připojení VPN a ExpressRoute do privátních sítí nepodporují, a runnery hostované GitHubem nesou stejnou formu omezení místo samostatného mechanismu. Čtěte to jako vlastnost produktu, ne jako vadu, kterou je třeba obejít.
Žádné nastavení repozitáře to nezmírní. Štítek jobu rozhoduje, který pool práci převezme, a hostovaný štítek znamená stroj mimo váš perimetr po celý běh.
Privátní dosažitelnost tedy musí přijít z něčeho, co vlastníte. Runner umístěný tam, kde síť už končí, nebo tunel, který žije jeden job. Zbytek článku je o tom, zaplatit za jednu z těchto možností vědomě, místo abyste účet objevili v produkci.
Rozhodujte podle frekvence běhů, ne podle zvyku
Sáhněte po vlastním runneru ve VPC nebo v kancelářské síti, když nasazení přicházejí několikrát denně, když job sahá na víc než jeden privátní port, nebo když tým platformy už vlastní patchování a rotaci přihlašovacích údajů. Job na tom stroji resolvuje a routuje jako kterýkoli jiný host za firewallem. Síťová práce se stane jednou, při instalaci, místo při každém běhu.
Postavte tunel uvnitř jobu, když je přístup občasný a nechcete dlouhověkého agenta držícího přihlašovací údaj do vaší sítě. Job se na pár minut stane vlastním síťovým klientem a nic po sobě nenechá, což je často celý smysl.
Odhadněte cenu tunelu, než ho vyberete. Každý běh platí za materializaci profilu, start klienta, připravenost a demontáž. Tunel, který naskočí, ale nikdy se neověří, vytvoří nasazení, které selže v polovině a nechá privátní stav v podobě, kterou nikdo neplánoval. Opakování potom je málokdy čisté a druhý neúspěch se obvykle svede na verzi klienta.
Napsat profil tak, aby krok nemohl spadnout
Držte profily a přihlašovací údaje mimo repozitář a materializujte je z tajemství v kroku, který je potřebuje. Pořadí rozhoduje, zda job přežije: soubor vytvořený pod sudo patří rootovi a neprivilegovaný shell kroku do něj už nemůže zapisovat.
- 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"Oba detaily v tom bloku existují pro pozdější kroky. S umask 077 přistane profil v režimu 0600 a patří uživateli runneru, sudo ho pořád přečte a klient zapisuje svůj log do souboru, který runner už vlastní, takže smyčka připravenosti může ten log číst bez druhého sudo. --writepid zaznamená PID, na který bude demontáž cílit.
Profil, který se autentizuje klientským certifikátem, potřebuje svůj klíč zapsaný stejně, a profil se statickou výzvou potřebuje odpovídající režim autentizace. Profily, které tlačí volby DNS, potřebují openvpn-systemd-resolved na Ubuntu a Debianu, protože samotné vytlačení na základním obrazu nic nezmění.
Připravenost je sonda, ne pauza
Pevný sleep vydrží přesně do chvíle, kdy koncentrátor zpomalí, a pak krok nasazení běží dřív, než trasa existuje. Selhání se pak vynoří uvnitř nástroje pro nasazení jako timeout proti privátnímu hostu, což o tunelu nic neříká.
- 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 1Smyčka sleduje dvě věci, protože kterákoli z nich může být ta rozbitá polovina. getent hosts dokazuje, že vytlačený resolver pro ten název odpovídá, a otevření socketu dokazuje, že na portu, který nasazení použije, něco poslouchá; profil jen s trasou potřebuje doslovnou adresu a smazaný řádek getent.
Když smyčka vyprší, log je jediná věc, která odděluje tři různé závady: odmítnutý certifikát, špatný režim autentizace a trasu, která nikdy nedorazila. Filtrování před tail udrží ten výstup dost krátký na přečtení, zatímco job pořád selhává. Čtěte první chybu, ne poslední řádek, protože klient, který se nedokáže autentizovat, se opakuje, dokud to smyčka nevzdá. ip -4 route show dev tun0 odpovídá na jednodušší otázku, zda byla trasa vůbec vytlačena, a stojí jeden řádek ve stejné smyčce.
Demontáž a otázka artefaktu
Uzavřete job krokem střeženým if: always(), který spustí sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" a nic jiného. Na sdíleném vlastním runneru nikdy nezabíjejte podle názvu procesu: jiný job může vlastnit klienta se stejnou binárkou a podobnými argumenty. Selhání pak padne na job, který běžel druhý, a jeho log ukazuje na workflow, které neudělalo nic špatně.
Pojistka má smysl, protože neúspěšné nasazení i tak nechá tunel živý. Bez always() se úklidový krok přeskočí přesně tehdy, kdy je potřeba, a další běh startuje proti klientovi, který už je připojený.
Zbývá jedna otázka, než přijmete kterýkoli z návrhů: potřebuje fáze buildu vůbec privátní trasu? Agent nasazení, který už sedí v síti, může stáhnout schválený artefakt, což drží běžné buildy mimo privátní cestu. Méně jobů držících tunel znamená méně míst, kde se může schovat zastaralá trasa.