O teu paso de despregamento colgase nun nome de host interno mentres o mesmo comando responde ao instante desde o portátil, e a compilación do cliente nas dúas máquinas é idéntica. O job execútase nun runner aloxado por GitHub: unha máquina virtual creada para ese único job dentro da nube de GitHub, sen ruta cara ao teu espazo de enderezos privado, sen entrada de resolver para a zona interna e sen certificado que o endpoint privado acepte. Só dous deseños encaixan con esa restrición, e ningún deles se pode activar despois sen reconstruír o workflow. Escolle antes da primeira liña de YAML, porque migrar despois custa máis que o primeiro intento.

A elección está entre un runner colocado onde a rede xa remata e un túnel que existe durante a duración dun job. Ambos levan anos soportando despregamentos de produción, e fallan de maneiras distintas.

Onde está realmente o límite

Todo o que instalas no portátil detense no portátil, e todo o que instalas durante un job desaparece co runner. Non existe un espazo de nomes de rede persistente ao que un cliente poida quedar enganchado entre execucións, así que cada execución comeza no mesmo estado inalcanzable. Azure documenta este límite para os axentes aloxados por Microsoft, onde as ligazóns VPN e ExpressRoute cara a redes privadas non son compatibles, e os runners aloxados por GitHub levan esa mesma forma de restrición en lugar dun mecanismo aparte. Léeo como unha propiedade do produto, non como un defecto que haxa que rodear.

Ningún axuste do repositorio o relaxa. A etiqueta do job decide que pool recolle o traballo, e unha etiqueta aloxada significa unha máquina fóra do teu perímetro durante toda a execución.

A accesibilidade privada ten que vir, por tanto, de algo que posúas. Un runner colocado onde a rede xa remata, ou un túnel que vive un só job. O resto do artigo trata de pagar unha desas dúas cousas a propósito en vez de descubrir a factura en produción.

Decide pola frecuencia de execución, non pola familiaridade

Recorre a un runner propio dentro da VPC ou da rede da oficina cando os despregamentos chegan varias veces ao día, cando o job toca máis dun porto privado ou cando un equipo de plataforma xa se encarga do parcheo e da rotación de credenciais. Un job nesa máquina resolve e encamiña como calquera outro host detrás da firewall. O traballo de rede ocorre unha vez, na instalación, en lugar de en cada execución.

Constrúe o túnel dentro do job cando o acceso é ocasional e non queres un axente de longa vida sostendo unha credencial cara á túa rede. O job convértese no seu propio cliente de rede durante uns minutos e non deixa nada atrás, que adoita ser xusto o obxectivo.

Calcula o custo do túnel antes de escollelo. Cada execución paga a materialización do perfil, o arranque do cliente, a espera de dispoñibilidade e a desmontaxe. Un túnel que sobe pero nunca se verifica produce un despregamento que falla a medio camiño e deixa o estado privado nunha forma que ninguén planificara. O reintento posterior raramente é limpo, e o segundo fallo adoita achacarse á versión do cliente.

Escribir o perfil para que o paso non poida abortar

Mantén os perfís e as credenciais fóra do repositorio e materialízaos desde segredos no paso que os necesita. A orde decide se o job sobrevive: un ficheiro creado baixo sudo pertence a root, e a shell sen privilexios do paso xa non pode escribir nel.

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

Os dous detalles dese bloque existen para pasos posteriores. Con umask 077 o perfil queda en modo 0600 e é propiedade do usuario do runner, sudo aínda pode lelo, e o cliente escribe o seu log nun ficheiro que o runner xa posúe, así que o bucle de dispoñibilidade pode ler ese log sen un segundo sudo. --writepid rexistra o PID ao que apuntará a desmontaxe.

Un perfil que se autentica cun certificado de cliente necesita a súa chave escrita do mesmo xeito, e un perfil de desafío estático necesita o modo de autenticación correspondente. Os perfís que empuxan opcións DNS necesitan openvpn-systemd-resolved en Ubuntu e Debian, porque o empuxón por si só non cambia nada nunha imaxe base.

A dispoñibilidade é unha sonda, non unha pausa

Un sleep fixo aguanta xusto ata que o concentrador vai lento, e entón o paso de despregamento execútase antes de que exista a ruta. O fallo aparece despois dentro da ferramenta de despregamento como un timeout contra o host privado, o que non di nada sobre o 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

O bucle observa dúas cousas porque calquera das dúas pode ser a metade rota. getent hosts demostra que o resolver empuxado responde por ese nome, e a apertura do socket demostra que algo escoita no porto que usará o despregamento; un perfil só de rutas necesita o enderezo literal e que se borre a liña getent.

Cando o bucle expira, o log é o único que separa tres fallos distintos: un certificado rexeitado, un modo de autenticación equivocado e unha ruta que nunca chegou. Filtrar antes do tail mantén esa saída curta abondo para lela mentres o job segue fallando. Le o primeiro erro en lugar da última liña, porque un cliente que non pode autenticarse repítese ata que o bucle se rende. ip -4 route show dev tun0 responde á pregunta máis simple de se se empuxou algunha ruta, e custa unha liña dentro do mesmo bucle.

Desmontaxe e a cuestión do artefacto

Pecha o job cun paso protexido por if: always() que execute sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" e nada máis. Nun runner propio compartido, nunca mates polo nome do proceso: outro job pode ter un cliente co mesmo binario e argumentos parecidos. O fallo cae entón sobre o job que correu segundo, e o seu log apunta a un workflow que non fixo nada mal.

A protección importa porque un despregamento fallido deixa igualmente un túnel vivo. Sen always() o paso de limpeza sáltase xusto cando fai falta, e a execución seguinte arranca contra un cliente xa conectado.

Queda unha pregunta antes de adoptar calquera dos dous deseños: a etapa de compilación necesita unha ruta privada? Un axente de despregamento que xa está dentro da rede pode baixar un artefacto aprobado, o que mantén as compilacións normais lonxe do camiño privado. Menos jobs sostendo un túnel significa menos lugares onde esconder unha ruta obsoleta.

Artigos relacionados