GitHub Actions ne doseže vašega VPN: lastni runner ali tunel
Gostovani runner je svež VM zunaj vašega omrežja. Bodisi postavite runner v to omrežje bodisi naredite tunel iz joba in počakate na pravo pot.
Vaš korak uvajanja obvisi na notranjem imenu gostitelja, medtem ko isti ukaz s prenosnika odgovori takoj, gradnja odjemalca pa je na obeh strojih enaka. Job teče na runnerju, ki ga gosti GitHub: na virtualnem stroju, ustvarjenem za ta en job v GitHubovem oblaku, brez poti v vaš zasebni naslovni prostor, brez vnosa resolverja za notranjo cono in brez potrdila, ki bi ga zasebna končna točka sprejela. Temu omejitvi ustrezata le dve zasnovi in nobene ni mogoče vklopiti pozneje brez ponovne gradnje workflowa. Izberite med njima pred prvo vrstico YAML, ker selitev za nazaj stane več kot prvi poskus.
Izbira je med runnerjem, postavljenim tja, kjer se omrežje že konča, in tunelom, ki obstaja za čas enega joba. Oba že leta nosita produkcijske uvode in oba odpovedujeta na različne načine.
Kje meja v resnici leži
Vse, kar namestite na prenosnik, se ustavi na prenosniku, in vse, kar namestite med jobom, izgine z runnerjem. Ni trajnega omrežnega imenskega prostora, na katerega bi odjemalec ostal pripet med zagoni, zato se vsak zagon začne v istem nedosegljivem stanju. Azure to mejo dokumentira pri agentih, ki jih gosti Microsoft, kjer povezave VPN in ExpressRoute v zasebna omrežja niso podprte, runnerji, ki jih gosti GitHub, pa nosijo enako obliko omejitve namesto ločenega mehanizma. Berite to kot lastnost izdelka, ne kot napako, ki bi jo bilo treba obiti.
Nobena nastavitev repozitorija tega ne omili. Oznaka joba odloči, kateri pool prevzame delo, gostovana oznaka pa pomeni stroj zunaj vašega obsega za ves zagon.
Zasebna dosegljivost mora torej priti iz nečesa, kar imate v lasti. Runner, postavljen tja, kjer se omrežje že konča, ali tunel, ki živi en job. Preostanek članka govori o tem, da zavestno plačate za eno od tega, namesto da bi račun odkrili v produkciji.
Odločajte po pogostosti zagonov, ne po domačnosti
Segajte po lastnem runnerju v VPC ali pisarniškem omrežju, ko uvajanja pridejo večkrat na dan, ko se job dotakne več kot enih zasebnih vrat ali ko ekipa za platformo že skrbi za popravke in rotacijo poverilnic. Job na tem stroju razrešuje in usmerja kot vsak drug gostitelj za požarnim zidom. Omrežno delo se zgodi enkrat, ob namestitvi, namesto ob vsakem zagonu.
Zgradite tunel znotraj joba, ko je dostop občasen in nočete dolgoživega agenta, ki bi držal poverilnico v vaše omrežje. Job za nekaj minut postane svoj omrežni odjemalec in za seboj ne pusti ničesar, kar je pogosto ves smisel.
Ocenite stroške tunela, preden ga izberete. Vsak zagon plača materializacijo profila, zagon odjemalca, pripravljenost in demontažo. Tunel, ki se dvigne, a ni nikoli preverjen, ustvari uvedbo, ki odpove na pol poti in pusti zasebno stanje v obliki, ki si je ni nihče zamislil. Ponovni poskus zatem je redko čist in druga napaka se običajno pripiše različici odjemalca.
Napisati profil tako, da korak ne more prekiniti
Držite profile in poverilnice zunaj repozitorija in jih materializirajte iz skrivnosti v koraku, ki jih potrebuje. Vrstni red odloči, ali job preživi: datoteka, ustvarjena pod sudo, pripada rootu, in neprivilegirana lupina koraka vanjo ne more več pisati.
- 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"Obe podrobnosti v tem bloku obstajata za poznejše korake. Z umask 077 profil pristane v načinu 0600 in pripada uporabniku runnerja, sudo ga še vedno lahko prebere, odjemalec pa svoj dnevnik piše v datoteko, ki jo runner že ima, zato lahko zanka pripravljenosti ta dnevnik bere brez drugega sudo. --writepid zabeleži PID, na katerega bo ciljala demontaža.
Profil, ki se overja s potrdilom odjemalca, potrebuje svoj ključ zapisan na enak način, profil s statičnim izzivom pa potrebuje ustrezen način overjanja. Profili, ki potiskajo možnosti DNS, potrebujejo openvpn-systemd-resolved na Ubuntu in Debianu, ker potisk sam po sebi na osnovni sliki ne spremeni ničesar.
Pripravljenost je sonda, ne premor
Fiksni sleep zdrži točno do takrat, ko koncentrator postane počasen, in takrat se korak uvajanja zažene, preden pot obstaja. Napaka se nato pokaže znotraj orodja za uvajanje kot timeout proti zasebnemu gostitelju, kar o tunelu ne pove ničesar.
- 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 1Zanka opazuje dve stvari, ker je lahko vsaka od njiju zlomljena polovica. getent hosts dokaže, da potisnjeni resolver odgovarja za to ime, odprtje vtičnice pa dokaže, da nekaj posluša na vratih, ki jih bo uporabila uvedba; profil samo s potjo potrebuje dobesedni naslov in izbrisano vrstico getent.
Ko zanka poteče, je dnevnik edina stvar, ki loči tri različne okvare: zavrnjeno potrdilo, napačen način overjanja in pot, ki ni nikoli prispela. Filtriranje pred tail ohrani ta izpis dovolj kratek za branje, medtem ko job še vedno odpoveduje. Berite prvo napako namesto zadnje vrstice, ker se odjemalec, ki se ne more overiti, ponavlja, dokler zanka ne obupa. ip -4 route show dev tun0 odgovori na preprostejše vprašanje, ali je bila pot sploh potisnjena, in stane eno vrstico v isti zanki.
Demontaža in vprašanje artefakta
Zaprite job s korakom, ki ga varuje if: always() in izvede sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" in nič drugega. Na skupnem lastnem runnerju nikoli ne ubijajte po imenu procesa: drug job ima lahko odjemalca z isto binarno datoteko in podobnimi argumenti. Napaka nato pade na job, ki je tekel drugi, in njegov dnevnik kaže na workflow, ki ni naredil nič narobe.
Varovalka je pomembna, ker neuspešna uvedba vseeno pusti živ tunel. Brez always() se korak čiščenja preskoči natanko takrat, ko je potreben, in naslednji zagon se začne proti odjemalcu, ki je že povezan.
Ostaja eno vprašanje, preden sprejmete katero od zasnov: ali faza gradnje sploh potrebuje zasebno pot? Agent za uvajanje, ki že sedi v omrežju, lahko potegne odobren artefakt, kar drži običajne gradnje stran od zasebne poti. Manj jobov, ki držijo tunel, pomeni manj mest, kjer se lahko skrije zastarela pot.