← Усі статті

Transparent Gateway · Мережі · DevOps

Оновлення Transparent Gateway без повторного налаштування

20 жовтня 2026 р. · 6 хв читання

Прозорий мережевий шлюз зі змінним бірюзовим модулем, сховищем даних і кораловою стрілкою повернення попередньої версії.

У попередній статті пройшов установлення Transparent Gateway на чистому Debian. Ввів параметри проксі, запустив сервіси й отримав відповідь на клієнтський запит. По дорозі навіть обленерго долучилося до тестування 😅

Тепер gateway працює. Коли вийде нова версія, хочу встановити її й продовжити роботу, а не знову вводити параметри мережі та дані доступу до проксі.

Для користувача це має бути звичайне оновлення вже встановленого інструмента. Тому наступним кроком беру саме його: новий код, наявні налаштування й перевірка того, що після заміни версії трафік усе ще проходить.

Спочатку подивився, що вже вміє скрипт

Перший запуск зібрав у scripts/tgctl.py — це Python-скрипт із репозиторію Transparent Gateway. На машині зі встановленим проєктом він готує конфігурацію, перевіряє її, запускає та зупиняє сервіси й показує їхній стан.

Перед змінами подивився, які команди вже були у скрипті:

Bash-код
python3 scripts/tgctl.py --help

На той момент у списку були configure, check, up, down, wait і status. Отримання нової версії залишалося окремою ручною дією.

Тому додав команду update до того самого scripts/tgctl.py.

Важливі перевірки перед git pull

Найкоротший шлях напрошується сам: завантажити свіжий код через git pull, а потім ще раз виконати tgctl.py up.

Для сумісного виправлення цього може вистачити. Наявні файли налаштувань Git ігнорує, тож звичайне отримання нового коду саме по собі не повинне змінити користувацькі налаштування.

Нова версія може потребувати налаштування, якого раніше не було. Тому перевірку конфігурації виконую до зупинки робочого gateway — дізнатися про відсутній параметр краще заздалегідь 😅

Перед заміною коду команда перевіряє через Git, чи змінені файли проєкту. Якщо так — показує їхні назви й зупиняє оновлення. Налаштування мережі та проксі ця перевірка не зачіпає: Git ігнорує ці файли.

Для перевірки локальних змін у Git уже є готова команда:

Bash-код
git status --porcelain --untracked-files=no

Ця команда показує змінені файли, але не виводить їхній вміст. Таку перевірку включив у update, щоб він не затирав чужі правки.

Пароль уже ввів. Другий раз не хочу

Поточне встановлення має кілька речей, які абсолютно точно не належать новій версії програми.

  • .env зберігає параметри мережі та сервісів.
  • inventory/upstreams.jsonl містить список проксі.
  • secrets/proxy-credentials.json містить дані доступу до них.
  • Окремі Docker-томи зберігають дані Redis і локальну базу мережевих операторів.

Під час оновлення команда замінює код і контейнери, зберігаючи налаштування, паролі та накопичені дані.

Нову версію готую до зупинки старої

Якщо образи не зібралися або конфігурація не пройшла перевірку, краще дізнатися про це, поки поточний gateway ще працює.

Нову версію команда отримує з того репозиторію, звідки встановлений проєкт, — у Git він називається origin. Для підготовки створює окрему робочу копію. Поточний код і запущені контейнери в цей час залишаються на місці.

Далі команда перевіряє нову версію з копією наявних налаштувань мережі та проксі. Якщо бракує обов’язкового параметра, зупиняється й пояснює, що додати у файл. Оригінали налаштувань не перезаписує.

Після перевірки запускається docker compose build. Ця команда збирає нові образи — з них Docker потім створить контейнери. Поки триває збирання, старі контейнери працюють далі.

Коли образи готові, команда зупиняє службу gateway та замінює контейнери через docker compose up -d --no-build. Параметр --no-build означає, що образи вже підготовлені й повторно збирати їх не потрібно. Налаштування й томи даних залишаються ті самі.

Так помилка завантаження чи збирання не перетвориться на зайвий простій. Але сама заміна контейнера все одно може перервати відкриті з’єднання.

Перевірку готовності взяв із попередньої роботи над розгортанням: вона лежить у deploy/readiness.py. Вона перевіряє шість сервісів, свіжість пулів проксі й виконує запит через relay — компонент gateway, який встановлює з’єднання через вибраний проксі.

Додав ще одну умову: результати в Redis мають бути отримані після заміни контейнерів. Інакше старий успішний результат міг би приховати те, що нова версія вже не перевіряє проксі.

Якщо на старому встановленні команди update ще немає, спочатку потрібно отримати новий код через Git і запустити його звичним способом. Для робочої копії на гілці main без локальних змін:

Bash-код
git pull --ff-only
sudo python3 scripts/tgctl.py up --timeout 300

Це разовий перехід на версію з підтримкою оновлень. Надалі всі кроки виконує одна команда. Її запускають з каталогу встановленого gateway:

Bash-код
sudo python3 scripts/tgctl.py update main --timeout 300

main — гілка, з якої потрібно отримати нову версію. Замість неї можна вказати тег або коміт. --timeout 300 дає п’ять хвилин на перевірку готовності після запуску. Збирання образів має окремий ліміт; якщо знадобиться відкат, його перевірка отримає свої п’ять хвилин.

Перевірка оновлення на стенді

Перевіряв оновлення на своєму стенді. Перед запуском подивився стан сервісів і активного пулу на gateway:

Bash-код
sudo python3 scripts/tgctl.py status
Стан Transparent Gateway перед оновленням: шість працюючих сервісів, ACTIVE_POOL ready=1 та active_exit_ids.
Початковий стан gateway: сервіси працюють, у пулі є активний проксі.

На стенді використовував робочу гілку feat/transactional-updates, яку видно в команді на скріні. Після перевірок код увійшов у main.

Перша спроба оновлення: перевірка готовності пройшла, створення тегу старого образу завершилося помилкою.
Оновлення зупинилося під час підготовки образів для відкату, до заміни контейнерів.

Перше оновлення спіткнулося на спробі зберегти старі образи для відкату 😅 Скрипт вивів UPDATE FAIL: не вдалося створити тег образу. Docker повідомив, що образу немає, хоча контейнер із нього спокійно працював.

Виявилося, скрипт брав ідентифікатор образу з опису контейнера. На тестовому Docker 26 цього вистачало, а Docker 29 на стенді не прийняв його для створення тегу. Сам образ нікуди не зник — неправильно звернувся до нього.

Виправив пошук без прив’язки до номера версії Docker. Скрипт спочатку пробує знайти образ за ідентифікатором із контейнера. Якщо це не вдається, шукає через назву образу й отримує придатний ідентифікатор за допомогою docker image inspect. Перед збереженням перевіряє, що знайшов саме ту версію, з якої запущений контейнер.

Назва після нового збирання може вже вказувати на інший образ. Тому, якщо він не збігається із запущеним, скрипт шукає потрібний серед інших локальних образів. Якщо точного збігу немає, зупиняє оновлення й повідомляє, для якого сервісу не вдалося підготувати відкат.

До заміни контейнерів команда ще не дійшла, тому весь цей час gateway продовжував працювати. Після виправлення збереження всіх шести образів пройшло успішно. Повторний запуск уже завершився UPDATE PASS:

Повторне оновлення: п’ять образів зібрані, шість контейнерів запущені, READINESS PASS і UPDATE PASS з runtime_preserved=1.
Після виправлення пошуку образів оновлення пройшло перевірку готовності та зберегло налаштування й підключені томи.

Позначка runtime_preserved=1 у фінальному рядку означає, що файли налаштувань і даних доступу не змінилися, а контейнери використовують ті самі томи даних.

Після оновлення перевірив доступ із тестової клієнтської машини, яка виходить в інтернет через gateway. Для цього в репозиторії є scripts/client-check.py: він перевіряє маршрут, роботу DNS та HTTPS-запит до публічного echo-сервісу. Цей сервіс повертає адресу, з якої побачив запит.

На моєму клієнті скрипт збережений як /tmp/tg-client-check.py. Запустив його з адресою gateway у цьому стенді:

Bash-код
python3 /tmp/tg-client-check.py --gateway 192.168.91.1

Адреса 192.168.91.1 — це шлюз у клієнтській мережі мого стенда. Сам проксі клієнту вказувати не потрібно.

Клієнтська перевірка після оновлення: маршрут через 192.168.91.1, CLIENT_DNS PASS, CLIENT_HTTPS PASS та PUBLIC_EXIT id=0649753926f0.
Після оновлення клієнтська машина успішно перевірила маршрут, DNS і HTTPS-запит до публічного echo-сервісу.

Порівняв PUBLIC_EXIT id=0649753926f0 у клієнтському виводі з active_exit_ids на скріні стану gateway. Значення однакові: після оновлення клієнтський запит вийшов через той самий проксі.

Якщо оновлення не запрацювало

Якщо нова версія не проходить перевірку готовності, команда автоматично повертає попередню версію й перевіряє, що gateway знову працює. Не хочеться після невдалого оновлення ще й вручну повертати gateway до робочого стану 😅

Для цього перед заміною зберігаю попередні образи та версію коду. Якщо знадобиться відкат, команда використає їх разом із наявними налаштуваннями й томами даних.

Після успішного відкату команда виводить ROLLBACK PASS: попередня версія знову працює. Саме оновлення при цьому завершується з помилкою, адже нову версію встановити не вдалося. Якщо відновити роботу теж не вдалося, команда повідомляє про це окремо й залишає журнал для повторної спроби.

Щоб перевірити відкат на VM, підготував окрему тестову гілку test/rollback-failure. У ній навмисно змінив запуск контейнера gateway: він одразу завершується з помилкою. Так отримав версію, яка успішно збирається, але не може працювати. На тестовому gateway запустив:

Bash-код
sudo python3 scripts/tgctl.py update test/rollback-failure --timeout 120

Перевірка готовності побачила, що gateway не працює. Команда сама повернула попередній код та образи, запустила сервіси й повторила перевірку. Цього разу отримав READINESS PASS і ROLLBACK PASS previous_version_running=1:

Невдалий запуск тестової версії gateway, автоматичне відновлення попередньої, READINESS PASS та ROLLBACK PASS previous_version_running=1.
Відкат відновив робочу версію. Фінальний UPDATE FAIL повідомляє, що нова версія не пройшла перевірку.

Оновлення й відкат також перевірив автоматичними тестами. Поточний набір проходить без помилок — 70 тестів.

Якщо оновлення обірвалося через завершення процесу або вимкнення машини, відновлення можна повторити зі збереженого журналу:

Bash-код
sudo python3 scripts/tgctl.py update --recover --timeout 300

Відкат повертає код та образи, але не попередній вміст даних. Тому цей варіант оновлення відхиляє зміни конфігурації Redis та підключення томів: для зміни формату збережених даних потрібне окреме рішення.

Хочеться просто встановити виправлення

Тепер для оновлення достатньо однієї команди. Запустив, дочекався результату й користуюся далі. Якщо нова версія не запрацює — попередня повернеться автоматично. Тоді можна спокійно розібратися з причиною,
поки gateway працює.

З оновленням розібрався. Але якщо gateway працює, а сайт на клієнті не відкривається — доведеться самому шукати, де застряг запит. У наступній статті додам команду діагностики: вона перевірятиме DNS, наявність робочих проксі й доступ через них та підказуватиме, на якому етапі виникла проблема. Хочеться витрачати час на виправлення причини, а не на її пошуки по всіх контейнерах 😅

Код Transparent Gateway на GitHub