El teu pas de desplegament es penja en un nom d’amfitrió intern mentre la mateixa ordre respon a l’instant des del portàtil, i la compilació del client a les dues màquines és idèntica. El job s’executa en un runner allotjat per GitHub: una màquina virtual creada per a aquell únic job dins del núvol de GitHub, sense ruta cap al teu espai d’adreces privat, sense entrada de resolver per a la zona interna i sense certificat que l’endpoint privat accepti. Només dos dissenys encaixen amb aquesta restricció, i cap dels dos es pot activar després sense reconstruir el workflow. Tria abans de la primera línia de YAML, perquè migrar després costa més que el primer intent.

L’elecció és entre un runner col·locat on la xarxa ja acaba i un túnel que existeix durant la durada d’un job. Tots dos aguanten desplegaments de producció des de fa anys, i fallen de maneres diferents.

On és realment el límit

Tot el que instal·les al portàtil s’atura al portàtil, i tot el que instal·les durant un job desapareix amb el runner. No hi ha cap espai de noms de xarxa persistent al qual un client pugui romandre enganxat entre execucions, així que cada execució comença en el mateix estat inabastable. Azure documenta aquest límit per als agents allotjats per Microsoft, on els enllaços VPN i ExpressRoute cap a xarxes privades no són compatibles, i els runners allotjats per GitHub arrosseguen aquesta mateixa forma de restricció en lloc d’un mecanisme a part. Llegeix-ho com una propietat del producte, no com un defecte que calgui envoltar.

Cap ajust del repositori ho relaxa. L’etiqueta del job decideix quin pool recull la feina, i una etiqueta allotjada significa una màquina fora del teu perímetre durant tota l’execució.

L’accessibilitat privada ha de venir, doncs, d’alguna cosa que posseeixis. Un runner col·locat on la xarxa ja acaba, o un túnel que viu un sol job. La resta de l’article tracta de pagar una d’aquestes dues coses a propòsit en lloc de descobrir la factura en producció.

Decideix per freqüència d’execució, no per familiaritat

Recorre a un runner propi dins de la VPC o de la xarxa de l’oficina quan els desplegaments arriben diverses vegades al dia, quan el job toca més d’un port privat o quan un equip de plataforma ja s’encarrega del pedaç i de la rotació de credencials. Un job en aquesta màquina resol i encamina com qualsevol altre host darrere del tallafoc. La feina de xarxa passa una vegada, a la instal·lació, en lloc de a cada execució.

Construeix el túnel dins del job quan l’accés és ocasional i no vols un agent de llarga vida sostenint una credencial cap a la teva xarxa. El job es converteix en el seu propi client de xarxa durant uns minuts i no deixa res enrere, que sol ser just l’objectiu.

Calcula el cost del túnel abans de triar-lo. Cada execució paga la materialització del perfil, l’arrencada del client, l’espera de disponibilitat i el desmuntatge. Un túnel que puja però mai no es verifica produeix un desplegament que falla a mig camí i deixa l’estat privat en una forma que ningú no havia planificat. El reintent posterior poques vegades és net, i el segon error sol atribuir-se a la versió del client.

Escriure el perfil perquè el pas no pugui avortar

Mantén els perfils i les credencials fora del repositori i materialitza’ls des de secrets en el pas que els necessita. L’ordre decideix si el job sobreviu: un fitxer creat sota sudo pertany a root, i l’intèrpret sense privilegis del pas ja no hi pot escriure.

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

Els dos detalls d’aquest bloc existeixen per a passos posteriors. Amb umask 077 el perfil queda en mode 0600 i és propietat de l’usuari del runner, sudo encara el pot llegir, i el client escriu el seu log en un fitxer que el runner ja posseeix, així que el bucle de disponibilitat pot llegir aquest log sense un segon sudo. --writepid registra el PID al qual apuntarà el desmuntatge.

Un perfil que s’autentica amb un certificat de client necessita la seva clau escrita de la mateixa manera, i un perfil de desafiament estàtic necessita el mode d’autenticació corresponent. Els perfils que empenyen opcions DNS necessiten openvpn-systemd-resolved a Ubuntu i Debian, perquè l’empenta per si sola no canvia res en una imatge base.

La disponibilitat és una sonda, no una pausa

Un sleep fix aguanta just fins que el concentrador va lent, i llavors el pas de desplegament s’executa abans que existeixi la ruta. El fallo apareix després dins de l’eina de desplegament com un timeout contra l’host privat, cosa que no diu res sobre el túnel.

- 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

El bucle observa dues coses perquè qualsevol de les dues pot ser la meitat trencada. getent hosts demostra que el resolver empès respon per a aquell nom, i l’obertura del socket demostra que alguna cosa escolta al port que farà servir el desplegament; un perfil només de rutes necessita l’adreça literal i que se suprimeixi la línia getent.

Quan el bucle expira, el log és l’única cosa que separa tres fallades diferents: un certificat rebutjat, un mode d’autenticació equivocat i una ruta que mai no va arribar. Filtrar abans del tail manté aquesta sortida prou curta per llegir-la mentre el job encara falla. Llegeix el primer error en lloc de l’última línia, perquè un client que no pot autenticar-se es repeteix fins que el bucle es rendeix. ip -4 route show dev tun0 respon a la pregunta més simple de si s’ha empès alguna ruta, i costa una línia dins del mateix bucle.

Desmuntatge i la qüestió de l’artefacte

Tanca el job amb un pas protegit per if: always() que executi sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" i res més. En un runner propi compartit, no matis mai pel nom del procés: un altre job pot tenir un client amb el mateix binari i arguments semblants. La fallada cau llavors sobre el job que va córrer segon, i el seu log apunta a un workflow que no va fer res malament.

La protecció importa perquè un desplegament fallit deixa igualment un túnel viu. Sense always() el pas de neteja se salta just quan cal, i l’execució següent arrenca contra un client ja connectat.

Queda una pregunta abans d’adoptar qualsevol dels dos dissenys: l’etapa de compilació necessita una ruta privada? Un agent de desplegament que ja és dins de la xarxa pot baixar un artefacte aprovat, cosa que manté les compilacions normals lluny del camí privat. Menys jobs sostenint un túnel significa menys llocs on amagar una ruta obsoleta.

Articles relacionats