Deploy-trinnet ditt henger på et internt vertsnavn mens den samme kommandoen svarer umiddelbart fra laptopen, og klientbygget på begge maskinene er identisk. Jobben kjører på en runner som GitHub hoster: en virtuell maskin som ble laget for nettopp denne ene jobben i skyen til GitHub, uten rute inn i det private adresserommet ditt, uten resolver-oppføring for den interne sonen og uten sertifikat som det private endepunktet vil godta. Bare to design passer til denne begrensningen, og ingen av dem kan slås på senere uten å bygge workflowen på nytt. Velg mellom dem før den første YAML-linjen, fordi å flytte i ettertid koster mer enn det første forsøket.

Valget står mellom en runner plassert der nettverket allerede slutter, og en tunnel som bare lever én jobb. Begge har båret produksjonsdeploy i årevis, og de feiler på ulike måter.

Hvor grensen faktisk ligger

Alt du installerer på laptopen stopper på laptopen, og alt du installerer i en jobb forsvinner sammen med runneren. Det finnes ikke et varig nettnavnerom som klienten kan henge på mellom kjøringer, så hver kjøring starter i den samme utilgjengelige tilstanden. Azure dokumenterer denne grensen for agenter som Microsoft hoster, der VPN- og ExpressRoute-lenker inn i private nett ikke støttes, og runnere som GitHub hoster bærer den samme formen for begrensning i stedet for en egen mekanisme. Les dette som en egenskap ved produktet, ikke som en feil som skal omgås.

Ingen repository-innstilling myker opp dette. Etiketten på jobben avgjør hvilken pool som tar arbeidet, og en hostet etikett betyr en maskin utenfor perimeteret ditt hele kjøringen.

Privat tilgjengelighet må derfor komme fra noe du eier. En runner plassert der nettverket allerede slutter, eller en tunnel som lever én jobb. Resten av artikkelen handler om å betale for en av disse med vilje i stedet for å oppdage regningen i produksjon.

Avgjør etter kjøringsfrekvens, ikke etter vane

Bruk egen runner i VPC-en eller kontornettet når deploy kommer flere ganger om dagen, når jobben rører mer enn én privat port, eller når et plattformteam allerede har ansvar for patching og rotasjon av påloggingsdetaljer. En jobb på den maskinen løser og ruter som enhver annen vert bak brannmuren. Nettarbeidet skjer én gang, ved installasjon, ikke ved hver kjøring.

Bygg tunnelen inne i jobben når tilgangen er sporadisk og du ikke vil ha en langlevd agent som holder en påloggingsdetalj inn i nettverket ditt. Jobben blir sin egen nettklient i noen minutter og legger ikke igjen noe, noe som ofte er hele poenget.

Anslå kostnaden for tunnelen før du velger den. Hver kjøring betaler for materialisering av profilen, start av klienten, klargjøring og riving. En tunnel som kommer opp men aldri blir bekreftet, gir et deploy som feiler halvveis og legger den private tilstanden igjen i en form ingen planla. Det nye forsøket etterpå er sjelden rent, og den andre feilen blir vanligvis lagt på klientversjonen.

Skrive profilen slik at trinnet ikke kan avbrytes

Hold profiler og påloggingsdetaljer utenfor repositoryet og materialiser dem fra secret i trinnet som trenger dem. Rekkefølgen avgjør om jobben overlever: en fil som lages under sudo tilhører root, og skallet til trinnet uten privilegier kan da ikke skrive i den.

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

Begge detaljene i den blokken er for senere trinn. Med umask 077 lander profilen i modus 0600 og tilhører runner-brukeren, sudo kan fortsatt lese den, og klienten skriver loggen sin til en fil som runneren allerede eier, så klargjøringsløkken kan lese den loggen uten en andre sudo. --writepid registrerer PID-en som rivingen skal peke på.

En profil som autentiserer med klient-sertifikat trenger nøkkelen sin skrevet på samme måte, og en static-challenge-profil trenger den samsvarende auth-modusen. Profiler som pusher DNS-valg trenger openvpn-systemd-resolved på Ubuntu og Debian, fordi pushingen alene ikke endrer noe på et grunnbilde.

Klargjøring er en sondering, ikke en pause

En fast sleep holder akkurat til konsentratoren blir treg, og da kjører deploy-trinnet før ruten finnes. Feilen kommer da opp inne i deploy-verktøyet som en timeout mot den private verten, noe som ikke sier noe om tunnelen.

- 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

Løkken følger med på to ting fordi hver av dem kan være den ødelagte halvdelen. getent hosts beviser at den pusha resolveren svarer for det navnet, og åpningen av socketen beviser at noe lytter på porten som deployet skal bruke; en profil med bare rute trenger den bokstavelige adressen og den sletta linjen getent.

Når løkken går ut, er loggen det eneste som skiller tre ulike feil: et avvist sertifikat, en galen auth-modus og en rute som aldri kom. Filtrering før tail holder den utskriften kort nok til å leses mens jobben fortsatt feiler. Les den første feilen i stedet for den siste linjen, fordi en klient som ikke kan autentisere seg repeterer seg til løkken gir opp. ip -4 route show dev tun0 svarer på det enklere spørsmålet om en rute ble pushet i det hele tatt, og det koster én linje i den samme løkken.

Riving og spørsmålet om artefakten

Lukk jobben med et trinn som er vernet av if: always() og som kjører sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" og ingenting annet. På en delt egen runner drep aldri etter prosessnavn: en annen jobb kan eie en klient med samme binærfil og lignende argumenter. Feilen lander da på den jobben som kjørte sist, og loggen dens peker på en workflow som ikke gjorde noe galt.

Vernet er viktig fordi et mislykket deploy likevel legger igjen en levende tunnel. Uten always() blir oppryddingstrinnet hoppet over nettopp når det trengs, og neste kjøring starter mot en klient som allerede er tilkoblet.

Ett spørsmål står igjen før du tar i bruk ett av designene: trenger byggetrinnet i det hele tatt en privat rute? En deployment-agent som allerede sitter inne i nettet kan hente en godkjent artefakt, og det holder vanlige bygg unna den private veien. Færre jobber som holder en tunnel betyr færre steder der en gammel rute kan gjemme seg.

Relaterte artikler