Ваш крок разгортвання вісіць на ўнутраным імені хоста, хаця тая ж каманда з ноўтбука адказвае імгненна, а зборка кліента на абедзвюх машынах ідэнтычная. Заданне выконваецца на runner, які размяшчае GitHub: віртуальная машына, створаная для гэтага аднаго задання ў воблаку GitHub, без маршруту ў вашу прыватную адрасную прастору, без запісу resolver для ўнутранай зоны і без сертыфіката, які прыме прыватная канчатковая кропка. Гэтаму абмежаванню адпавядаюць толькі дзве схемы, і ні адну нельга ўключыць пазней без перабудовы працоўнага працэсу. Выберыце паміж імі да першага радка YAML, бо міграцыя потым каштуе больш за першую спробу.

Выбар паміж runner, размешчаным там, дзе сетка ўжо сканчаецца, і тунелем, які існуе роўна на працягу аднаго задання. Абодва гадамі неслі прадукцыйныя разгортванні, і яны адмаўляюць па-рознаму.

Дзе насамрэч праходзіць мяжа

Усё, што вы ўсталёўваеце на ноўтбук, застаецца на ноўтбуку, а ўсё, што ўсталёўваецца падчас задання, знікае разам з runner. Пастаяннай сеткавай прасторы імёнаў, да якой кліент мог бы заставацца далучаным паміж запускамі, няма, таму кожны запуск пачынаецца з таго ж недасяжнага стану. Azure дакументуе гэтую мяжу для агентаў, якія размяшчае Microsoft: там злучэнні VPN і ExpressRoute у прыватныя сеткі не падтрымліваюцца, а runner, які размяшчае GitHub, нясе тую ж форму абмежавання, а не асобны механізм. Чытайце гэта як уласцівасць прадукту, а не як дэфект, які трэба абыйсці.

Ніякая налада рэпазіторыя гэтага не паслабляе. Метка задання вырашае, які пул падхопіць працу, а хостная метка азначае машыну па-за вашым перыметрам на ўвесь запуск.

Таму прыватная дасяжнасць павінна прыходзіць ад чаго-небудзь, што належыць вам. Runner, размешчаны там, дзе сетка ўжо сканчаецца, або тунель, які жыве адно заданне. Астатняя частка артыкула пра тое, каб заплаціць за адзін з гэтых варыянтаў свядома, а не выявіць рахунак у прадукцыі.

Вырашайце па частаце запускаў, а не па знаёмасці

Звяртайцеся да runner ва ўласным хостынгу ўнутры VPC або офіснай сеткі, калі разгортванні адбываюцца некалькі разоў на дзень, калі заданне закранае больш за адзін прыватны порт або калі каманда платформы ўжо займаецца абнаўленнямі і ратацыяй уліковых даных. Заданне на гэтай машыне разрознівае і маршрутызуе як любы іншы хост за файрволам. Сеткавая праца адбываецца адзін раз, пры ўсталяванні, а не пры кожным запуску.

Будуйце тунель унутры задання, калі доступ рэдкі і вы не хочаце доўгажывучы агент, які трымае ўліковыя даныя ў вашай сетцы. Заданне становіцца ўласным сеткавым кліентам на некалькі хвілін і нічога пасля сябе не пакідае, што часта і ёсць галоўным сэнсам.

Ацаніце кошт тунеля, перш чым выбіраць яго. Кожны запуск плаціць за матэрыялізацыю профілю, запуск кліента, гатоўнасць і разборку. Тунель, які паднімаецца, але ніколі не правяраецца, дае разгортванне, што зрываецца на паўдарозе і пакідае прыватны стан у форме, якую ніхто не планаваў. Паўторная спроба пасля гэтага рэдка бывае чыстай, а другая няўдача звычайна спісваецца на версію кліента.

Пішыце профіль так, каб крок не мог сарвацца

Трымайце профілі і ўліковыя даныя па-за рэпазіторыем і матэрыялізуйце іх з сакрэтаў у тым кроку, дзе яны патрэбны. Парадак вырашае, ці выжыве заданне: файл, створаны пад sudo, належыць root, і непрывілеяваная абалонка кроку ўжо не можа ў яго пісаць.

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

Абедзве дэталі ў гэтым блоку існуюць для наступных крокаў. З umask 077 профіль трапляе ў рэжым 0600, які належыць карыстальніку runner, sudo ўсё яшчэ можа яго чытаць, а кліент піша свой журнал у файл, якім runner ужо валодае, таму цыкл гатоўнасці можа чытаць гэты журнал без другога sudo. --writepid запісвае PID, на які будзе нацэлена разборка.

Профіль, які аўтэнтыфікуецца кліенцкім сертыфікатам, патрабуе, каб яго ключ быў запісаны гэтак жа, а профіль са статычным выклікам патрабуе адпаведнага рэжыму аўтэнтыфікацыі. Профілі, якія штурхаюць параметры DNS, патрабуюць openvpn-systemd-resolved на Ubuntu і Debian, бо штуршок сам па сабе нічога не мяняе на базавым вобразе.

Гатоўнасць — гэта проба, а не паўза

Фіксаваны сон трымаецца роўна да таго часу, пакуль канцэнтратар не стане павольным, і тады крок разгортвання выконваецца раней, чым з'явіцца маршрут. Пасля гэтага збой усплывае ўнутры інструмента разгортвання як тайма-аўт супраць прыватнага хоста, што нічога не кажа пра тунель.

- 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

Цыкл сочыць за двума назіраннямі, бо любое з іх можа быць зламанай паловай. getent hosts даказвае, што праштурханы resolver адказвае на гэтае імя, а адкрыццё сокета даказвае, што нешта слухае на порце, які выкарыстае разгортванне; профілю толькі з маршрутам патрэбны літаральны адрас, а getent радок трэба выдаліць.

Калі цыкл сканчаецца, журнал — адзінае, што аддзяляе тры розныя збоі: адхілены сертыфікат, няправільны рэжым аўтэнтыфікацыі і маршрут, які так і не прыйшоў. Фільтрацыя перад хвастом трымае гэты вывад дастаткова кароткім, каб прачытаць яго, пакуль заданне яшчэ зрываецца. Чытайце першую памылку, а не апошні радок, бо кліент, які не можа аўтэнтыфікавацца, паўтарае сябе, пакуль цыкл не здасца. ip -4 route show dev tun0 адказвае на прасцейшае пытанне, ці быў маршрут наогул праштурханы, і каштуе адзін радок унутры таго ж цыклу.

Разборка і пытанне артэфакта

Завяршыце заданне крокам, ахоўваемым if: always(), які выконвае sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" і больш нічога. На агульным runner ва ўласным хостынгу ніколі не забівайце па назве працэсу: іншае заданне можа валодаць кліентам з тым жа бінарнікам і падобнымі аргументамі. Тады збой трапляе на тое заданне, якое выканалася другім, і яго журнал паказвае на працоўны працэс, які нічога не зрабіў няправільна.

Ахова важная, бо няўдалае разгортванне ўсё яшчэ пакідае жывы тунель. Без always() крок ачысткі прапускаецца менавіта тады, калі ён патрэбны, і наступны запуск пачынаецца супраць ужо падлучанага кліента.

Застаецца адно пытанне, перш чым прыняць любую са схем: ці патрэбны этапу зборкі прыватны маршрут наогул? Агент разгортвання, які ўжо знаходзіцца ўнутры сеткі, можа забраць зацверджаны артэфакт, і гэта трымае звычайныя зборкі далей ад прыватнага шляху. Менш заданняў, якія трымаюць тунель, азначае менш месцаў, дзе можа схавацца састарэлы маршрут.

Падобныя артыкулы