Deploy-vaiheesi jää jumiin sisäiseen isäntänimeen, vaikka sama komento vastaa välittömästi kannettavaltasi, ja asiakasohjelman build on molemmissa koneissa identtinen. Job pyörii GitHubin hostaamalla runnerilla: virtuaalikone, joka luotiin juuri sitä yhtä jobia varten GitHubin pilveen, ilman reittiä yksityiseen osoiteavaruuteesi, ilman resolver-merkintää sisäiselle vyöhykkeelle ja ilman sertifikaattia, jonka yksityinen endpoint hyväksyisi. Tähän rajoitukseen mahtuu vain kaksi mallia, eikä kumpaakaan voi kytkeä päälle myöhemmin workflowta uudelleen rakentamatta. Valitse niiden välillä ennen ensimmäistä YAML-riviä, koska jälkikäteen siirtäminen maksaa enemmän kuin ensimmäinen yritys.

Valinta on runnerin, joka sijoitetaan sinne missä verkko jo päättyy, ja tunnelin, joka elää vain yhden jobin ajan, välillä. Molemmat ovat kantaneet tuotantodeployta vuosia, ja ne epäonnistuvat eri tavoin.

Missä raja oikeasti kulkee

Kaikki, mitä asennat kannettavalle, pysähtyy kannettavaan, ja kaikki, mitä asennat jobin aikana, katoaa runnerin mukana. Ei ole pysyvää verkkonimiavaruutta, johon asiakasohjelma jäisi kiinni ajojen välillä, joten jokainen ajo alkaa samasta saavuttamattomasta tilasta. Azure dokumentoi tämän rajan Microsoftin hostaamille agenteille: VPN- ja ExpressRoute-yhteydet yksityisiin verkkoihin eivät ole tuettuja, ja GitHubin hostaamat runnerit kantavat saman muotoista rajoitusta eivätkä erillistä mekanismia. Lue tämä tuotteen ominaisuutena, älä vikana, joka pitäisi kiertää.

Mikään repositorion asetus ei löysennä sitä. Jobin label päättää, mikä pool ottaa työn, ja hostattu label tarkoittaa konetta perimetrisi ulkopuolella koko ajon ajan.

Yksityisen saavutettavuuden on siis tultava jostakin, joka on sinun. Runner sijoitettuna sinne missä verkko jo päättyy, tai tunneli, joka elää yhden jobin. Artikkelin loppu käsittelee sen maksamista tarkoituksella sen sijaan, että lasku löytyisi tuotannosta.

Päätä ajon tiheyden, älä tottumuksen mukaan

Käytä omaa runneria VPC:n tai toimiston verkon sisällä, kun deployt saapuvat useita kertoja päivässä, kun job koskettaa useampaa kuin yhtä yksityistä porttia tai kun alustatiimi hoitaa jo paikkaukset ja tunnistetietojen kierron. Sen koneen job ratkaisee ja reitittää kuten mikä tahansa palomuurin takainen isäntä. Verkkotyö tehdään kerran asennuksen yhteydessä, ei joka ajolla.

Rakenna tunneli jobin sisään, kun pääsy on satunnaista etkä halua pitkäikäistä agenttia pitämään tunnistetietoja verkkoosi. Jobista tulee omaksi verkkoasiakkaakseen muutamaksi minuutiksi eikä se jätä mitään jälkeensä, mikä on usein koko pointti.

Arvioi tunnelin kustannus ennen kuin valitset sen. Jokainen ajo maksaa profiilin luonnista, asiakasohjelman käynnistyksestä, valmiudesta ja purkamisesta. Tunneli, joka nousee mutta jota ei koskaan vahvisteta, tuottaa deployn, joka epäonnistuu puolivälissä ja jättää yksityisen tilan muotoon, jota kukaan ei suunnitellut. Sen jälkeinen uusiyritys on harvoin siisti, ja toinen epäonnistuminen laitetaan yleensä asiakasohjelman version syyksi.

Kirjoita profiili niin, ettei vaihe voi keskeytyä

Pidä profiilit ja tunnistetiedot repositorion ulkopuolella ja materialisoi ne secretistä siinä vaiheessa, joka niitä tarvitsee. Järjestys ratkaisee, selviääkö job: sudon alla luotu tiedosto kuuluu rootille, eikä vaiheen oikeudeton shell voi enää kirjoittaa siihen.

- 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"

Molemmat tuon lohkon yksityiskohdat ovat myöhempiä vaiheita varten. umask 077 kanssa profiili laskeutuu tilaan 0600 runner-käyttäjän omistuksessa, sudo voi yhä lukea sen, ja asiakasohjelma kirjoittaa lokinsa tiedostoon, joka runnerilla on jo, joten valmiussilmukka voi lukea lokin ilman toista sudoa. --writepid tallentaa PIDin, johon purku kohdistuu.

Asiakasohjelman sertifikaatilla tunnistautuva profiili tarvitsee avaimensa kirjoitettuna samalla tavalla, ja static-challenge-profiili tarvitsee vastaavan auth-tilan. DNS-asetuksia työntävät profiilit tarvitsevat openvpn-systemd-resolved Ubuntussa ja Debianissa, koska työntö yksin ei muuta peruskuvassa mitään.

Valmius on luotaus, ei tauko

Kiinteä sleep kestää juuri siihen hetkeen, kun keskittäjä hidastuu, ja silloin deploy-vaihe ajetaan ennen kuin reitti on olemassa. Virhe tulee sitten esiin deploy-työkalun sisällä timeoutina yksityiseen isäntään, mikä ei kerro tunnelista mitään.

- 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 1

Silmukka seuraa kahta asiaa, koska kumpi tahansa voi olla rikkinäinen puolisko. getent hosts todistaa, että työnnetty resolver vastaa tuohon nimeen, ja socketin avautuminen todistaa, että jokin kuuntelee portissa, jota deploy käyttää; pelkän reitin profiili tarvitsee kirjaimellisen osoitteen ja getent rivin poistamisen.

Kun silmukka vanhenee, loki on ainoa asia, joka erottaa kolme eri vikaa: hylätty sertifikaatti, väärä auth-tila ja reitti, joka ei koskaan saapunut. Suodatus ennen tailia pitää tulosteen riittävän lyhyenä luettavaksi, kun job yhä epäonnistuu. Lue ensimmäinen virhe äläkä viimeistä riviä, koska asiakasohjelma, joka ei voi tunnistautua, toistaa itseään kunnes silmukka luovuttaa. ip -4 route show dev tun0 vastaa yksinkertaisempaan kysymykseen siitä, työnnettiinkö reitti ylipäätään, ja se maksaa yhden rivin samassa silmukassa.

Purku ja artefaktikysymys

Sulje job vaiheella, jota suojaa if: always() ja joka suorittaa sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" eikä muuta. Jaetulla omalla runnerilla älä koskaan tapa prosessin nimellä: toisella jobilla voi olla asiakasohjelma, jolla on sama binääri ja samankaltaiset argumentit. Vika lankeaa silloin sille jobille, joka ajoi toisena, ja sen loki osoittaa workflowta, joka ei tehnyt mitään väärää.

Suoja on tärkeä, koska epäonnistunut deploy jättää silti elävän tunnelin. Ilman always() siivousvaihe ohitetaan juuri silloin kun sitä tarvitaan, ja seuraava ajo alkaa asiakasohjelmaa vastaan, joka on jo yhdistetty.

Yksi kysymys jää ennen kuin otat kumman tahansa mallin: tarvitseeko build-vaihe ylipäätään yksityistä reittiä? Verkon sisällä jo istuva deployment-agentti voi hakea hyväksytyn artefaktin, ja se pitää tavalliset buildit poissa yksityiseltä polulta. Harvempi tunnelia pitävä job tarkoittaa harvempia paikkoja, joihin vanhentunut reitti voi piiloutua.

Aiheeseen liittyvät artikkelit