Де закінчується cloud-init і починається gateway ⚙️
Після двох крайнощів межа нарешті стала нормальною:
Секрети надходять окремим захищеним каналом. Шаблон їх не бачить, у Git їх немає, а
Ще одна важлива різниця: запущений контейнер — не обов’язково готовий сервіс. Зелений статус не доводить, що активний список проксі свіжий, relay приймає з’єднання, а клієнт справді виходить через gateway.
Тому остаточний критерій буде жорстким: нова VM з чистого шаблону, повторюване розгортання, перезавантаження і жодної додаткової SSH-команди. Якщо треба "тільки один маршрут руками поправити" — автоматизація ще не завершена 😅
У статті зібрав аудит поточної VM, дві відкинуті крайнощі й архітектуру наступного етапу:
https://devdays.net.ua/articles/chomu-transparent-gateway-ne-hotovyi-do-production/
Наступний матеріал буде практичним: створю чистий шаблон, підготую cloud-init і systemd, а потім перевірю перезавантаження та повне відтворення gateway.
Після двох крайнощів межа нарешті стала нормальною:
шаблон Proxmox
→ cloud-init: ім’я, SSH, мережа
→ код розгортання: версія, конфігурація, systemd
→ Docker Compose
→ перевірка: знімки стану, проксі, весь маршрут
Секрети надходять окремим захищеним каналом. Шаблон їх не бачить, у Git їх немає, а
user-data не перетворюється на музей паролів.Ще одна важлива різниця: запущений контейнер — не обов’язково готовий сервіс. Зелений статус не доводить, що активний список проксі свіжий, relay приймає з’єднання, а клієнт справді виходить через gateway.
Тому остаточний критерій буде жорстким: нова VM з чистого шаблону, повторюване розгортання, перезавантаження і жодної додаткової SSH-команди. Якщо треба "тільки один маршрут руками поправити" — автоматизація ще не завершена 😅
У статті зібрав аудит поточної VM, дві відкинуті крайнощі й архітектуру наступного етапу:
https://devdays.net.ua/articles/chomu-transparent-gateway-ne-hotovyi-do-production/
Наступний матеріал буде практичним: створю чистий шаблон, підготую cloud-init і systemd, а потім перевірю перезавантаження та повне відтворення gateway.
❤1
Видалити VM і не пошкодувати 😅
Transparent Gateway працює. DNS і TCP-трафік проходять через проксі, пул оновлюється, контейнери переживають перезапуск.
Зараз gateway прив’язаний до конкретної VM. Якщо з нею щось станеться, швидко розгорнути таку саму не вийде. Тому хочу перетворити налаштовану вручну машину на відтворюване розгортання: створив нову VM, запустив автоматизацію — отримав готовий gateway.
Мій домашній стенд працює на Proxmox, тому почну з чистого шаблону Debian. Це базова VM без коду gateway, паролів, даних Redis і слідів попередніх експериментів. Proxmox використовуватиме її як основу для створення нових машин.
Початкове налаштування виконає cloud-init. Це стандартний механізм, який готує VM під час першого запуску: задає ім’я машини, створює користувача, додає SSH-ключі, налаштовує мережу та встановлює Docker.
На цьому робота cloud-init закінчується. Він готує операційну систему, але не розгортає сам Transparent Gateway. Код застосунку, його конфігурацію, секрети, запуск контейнерів і перевірку готовності передам окремому сценарію. Так початкове налаштування VM не залежить від конкретної версії gateway, а застосунок можна оновити без створення нової машини.
Після розгортання systemd керуватиме запуском gateway і підніматиме його після перезавантаження.
Transparent Gateway працює. DNS і TCP-трафік проходять через проксі, пул оновлюється, контейнери переживають перезапуск.
Зараз gateway прив’язаний до конкретної VM. Якщо з нею щось станеться, швидко розгорнути таку саму не вийде. Тому хочу перетворити налаштовану вручну машину на відтворюване розгортання: створив нову VM, запустив автоматизацію — отримав готовий gateway.
Мій домашній стенд працює на Proxmox, тому почну з чистого шаблону Debian. Це базова VM без коду gateway, паролів, даних Redis і слідів попередніх експериментів. Proxmox використовуватиме її як основу для створення нових машин.
Початкове налаштування виконає cloud-init. Це стандартний механізм, який готує VM під час першого запуску: задає ім’я машини, створює користувача, додає SSH-ключі, налаштовує мережу та встановлює Docker.
На цьому робота cloud-init закінчується. Він готує операційну систему, але не розгортає сам Transparent Gateway. Код застосунку, його конфігурацію, секрети, запуск контейнерів і перевірку готовності передам окремому сценарію. Так початкове налаштування VM не залежить від конкретної версії gateway, а застосунок можна оновити без створення нової машини.
Після розгортання systemd керуватиме запуском gateway і підніматиме його після перезавантаження.
❤1
Хто тут за що відповідає 🧩
Щоб автоматизація не перетворилася на один великий скрипт із правами root, розділив розгортання на кілька частин:
Шаблон містить лише чистий Debian і підтримку cloud-init. У ньому немає коду Transparent Gateway, секретів, Docker-образів або даних Redis. Це основа, яку можна використовувати для різних машин.
Proxmox створює з шаблону нову VM, підключає мережеві інтерфейси й передає початкові налаштування через cloud-init.
Cloud-init готує операційну систему: створює користувача, додає SSH-ключі, налаштовує мережу та встановлює Docker. Після цього машина готова прийняти застосунок, але самого gateway на ній ще немає.
Далі окремий сценарій доставляє потрібну версію коду, перевіряє конфігурацію та запускає контейнери. Секрети передаються окремо: вони не потрапляють ані в шаблон, ані в Git, ані в дані cloud-init.
Після розгортання керування переходить до systemd. Він запускає Transparent Gateway після Docker і мережі, а також піднімає його знову після перезавантаження VM.
Останній етап — перевірка готовності. Вона дивиться не лише на запущені контейнери, а й на свіжість пулу проксі та проходження клієнтського запиту через gateway.
Тепер кожна частина має одну роботу. А якщо щось зламається, принаймні не доведеться шукати одну потрібну команду посеред YAML на три екрани 😅
Щоб автоматизація не перетворилася на один великий скрипт із правами root, розділив розгортання на кілька частин:
шаблон Debian
↓
Proxmox створює VM
↓
cloud-init готує систему
↓
сценарій розгортає gateway
↓
systemd керує запуском
↓
перевірка проходить увесь маршрут
Шаблон містить лише чистий Debian і підтримку cloud-init. У ньому немає коду Transparent Gateway, секретів, Docker-образів або даних Redis. Це основа, яку можна використовувати для різних машин.
Proxmox створює з шаблону нову VM, підключає мережеві інтерфейси й передає початкові налаштування через cloud-init.
Cloud-init готує операційну систему: створює користувача, додає SSH-ключі, налаштовує мережу та встановлює Docker. Після цього машина готова прийняти застосунок, але самого gateway на ній ще немає.
Далі окремий сценарій доставляє потрібну версію коду, перевіряє конфігурацію та запускає контейнери. Секрети передаються окремо: вони не потрапляють ані в шаблон, ані в Git, ані в дані cloud-init.
Після розгортання керування переходить до systemd. Він запускає Transparent Gateway після Docker і мережі, а також піднімає його знову після перезавантаження VM.
Останній етап — перевірка готовності. Вона дивиться не лише на запущені контейнери, а й на свіжість пулу проксі та проходження клієнтського запиту через gateway.
Тепер кожна частина має одну роботу. А якщо щось зламається, принаймні не доведеться шукати одну потрібну команду посеред YAML на три екрани 😅
❤1
Шаблон, який нічого не знає про gateway 🧼
Розгортання починається не з Docker і навіть не з коду застосунку. Спочатку потрібна чиста основа для майбутніх VM.
У Proxmox такою основою є шаблон. Це підготовлений образ системного диска й конфігурація віртуальної машини, з яких потім створюються окремі VM. Замість повторного встановлення Debian кожна нова машина отримує однакову початкову систему.
За основу взяв офіційний Debian 13 genericcloud. Цей образ уже підготовлений для роботи з cloud-init, тому не потрібно щоразу проходити встановлення Debian вручну.
Створення шаблону автоматизував скриптом
Запускається скрипт від
На цьому місці дуже хочеться запустити підготовлену VM й перевірити, чи Debian узагалі завантажується. Але після першого запуску система вже створить власні SSH-ключі, ідентифікатор машини та стан cloud-init. Якщо потім перетворити її на шаблон, цей багаж можуть отримати всі наступні VM.
Тому образ одразу стає шаблоном, а перший запуск відбувається вже на створеній із нього машині. Саме вона отримує власну системну ідентичність і виконує cloud-init зі своїми налаштуваннями.
Коду Transparent Gateway, паролів, Docker-образів і даних Redis у шаблоні немає. Він знає лише про Debian, віртуальне обладнання та cloud-init.
Першу VM із цього шаблону створив без проблем. Debian завантажився, мережа й SSH запрацювали, а cloud-init завершився з попередженням.
Ну а як інакше. Це ж був перший запуск 😅
Розгортання починається не з Docker і навіть не з коду застосунку. Спочатку потрібна чиста основа для майбутніх VM.
У Proxmox такою основою є шаблон. Це підготовлений образ системного диска й конфігурація віртуальної машини, з яких потім створюються окремі VM. Замість повторного встановлення Debian кожна нова машина отримує однакову початкову систему.
За основу взяв офіційний Debian 13 genericcloud. Цей образ уже підготовлений для роботи з cloud-init, тому не потрібно щоразу проходити встановлення Debian вручну.
Створення шаблону автоматизував скриптом
create-clean-template.sh із репозиторію Transparent Gateway. Він завантажує образ і файл із контрольними сумами, перевіряє завантажений qcow2, створює VM у Proxmox, додає диск cloud-init і перетворює результат на шаблон.Запускається скрипт від
root на хості Proxmox:sudo ./deploy/proxmox/create-clean-template.sh
На цьому місці дуже хочеться запустити підготовлену VM й перевірити, чи Debian узагалі завантажується. Але після першого запуску система вже створить власні SSH-ключі, ідентифікатор машини та стан cloud-init. Якщо потім перетворити її на шаблон, цей багаж можуть отримати всі наступні VM.
Тому образ одразу стає шаблоном, а перший запуск відбувається вже на створеній із нього машині. Саме вона отримує власну системну ідентичність і виконує cloud-init зі своїми налаштуваннями.
Коду Transparent Gateway, паролів, Docker-образів і даних Redis у шаблоні немає. Він знає лише про Debian, віртуальне обладнання та cloud-init.
Першу VM із цього шаблону створив без проблем. Debian завантажився, мережа й SSH запрацювали, а cloud-init завершився з попередженням.
Ну а як інакше. Це ж був перший запуск 😅
❤1
Debian готовий. Gateway ще ні 🧱
Чистий шаблон є. Тепер Proxmox може створити з нього нову VM, але сам Debian ще не знає, яке ім’я отримати, хто матиме доступ через SSH і як налаштувати мережу.
Ці параметри Proxmox передає через cloud-init. Під час першого запуску він створює адміністративного користувача, додає дозволені SSH-ключі, налаштовує мережеві інтерфейси та встановлює Docker.
Після завантаження перевірив результат:
Cloud-init завершився зі статусом
Але контейнерів на машині ще немає. Як і коду Transparent Gateway, його конфігурації та секретів. Cloud-init підготував звичайний Docker-хост і на цьому закінчив свою роботу.
Тепер цю VM можна використовувати не лише для gateway. А нову версію застосунку можна розгорнути без повторного створення машини та нового запуску cloud-init.
Наступний крок — доставити на підготовлений хост конкретну версію Transparent Gateway. Одного
Чистий шаблон є. Тепер Proxmox може створити з нього нову VM, але сам Debian ще не знає, яке ім’я отримати, хто матиме доступ через SSH і як налаштувати мережу.
Ці параметри Proxmox передає через cloud-init. Під час першого запуску він створює адміністративного користувача, додає дозволені SSH-ключі, налаштовує мережеві інтерфейси та встановлює Docker.
Після завантаження перевірив результат:
cloud-init status --wait
ip -br address
docker --version
docker compose version
Cloud-init завершився зі статусом
done, мережеві інтерфейси отримали потрібні адреси, а Docker і Compose були готові до роботи.Але контейнерів на машині ще немає. Як і коду Transparent Gateway, його конфігурації та секретів. Cloud-init підготував звичайний Docker-хост і на цьому закінчив свою роботу.
Тепер цю VM можна використовувати не лише для gateway. А нову версію застосунку можна розгорнути без повторного створення машини та нового запуску cloud-init.
Наступний крок — доставити на підготовлений хост конкретну версію Transparent Gateway. Одного
docker compose up -d недостатньо: ця команда не перевірить, який код лежить на диску, чи на місці конфігурація та секрети й чи готовий gateway приймати трафік. Потрібен окремий сценарій, який перевірить середовище, розгорне потрібну версію, запустить контейнери та дочекається готовності сервісів. Саме ним і займуся далі.❤1
docker compose up — і готово. Так? 😏VM підготовлена, Docker встановлений. Найкоротший шлях далі виглядає очевидно: забрати код і виконати
docker compose up -d.Контейнери справді запустяться. Але Docker не перевірить, яку версію коду отримала машина, чи на місці потрібні файли, чи правильно налаштована мережа та чи може gateway працювати в цьому середовищі.
Тому додав до репозиторію Transparent Gateway окремий сценарій
deploy/deploy.py. Він запускається на новій VM після доставки коду.Для лабораторного стенда команда виглядає так:
sudo python3 deploy/deploy.py \
--source "$PWD" \
--commit "$(git rev-parse HEAD)" \
--lab-fixture \
--timeout 300
Перед запуском контейнерів сценарій перевіряє Debian, Docker, каталоги, мережеві інтерфейси та отриману версію коду. Потім виконує
docker compose config --quiet, щоб зупинитися ще до запуску, якщо конфігурація пошкоджена або в ній бракує обов’язкових значень.Прапорець
--lab-fixture додає локальний проксі з авторизацією та echo-сервіс. Вони потрібні лише для стенда: через них можна перевірити маршрут клієнтського запиту, не залежачи від зовнішнього проксі або доступності інтернету.Лише після всіх перевірок сценарій збирає образи, запускає контейнери та чекає, поки validator додасть придатний проксі до активного пулу.
Успішне завершення виглядає приблизно так:
POOL_COUNTS {"active": 1, "raw": 1, "reserve": 1, "validated": 1}
LOCAL_READY
Тепер є одна команда, яка приводить чистий Docker-хост до готового локального стенда. Залишилося виконати її ще раз і переконатися, що повторне розгортання нічого не ламає. Бо перший успішний запуск — це ще не автоматизація. Це поки що везіння 😅
❤1
Другий запуск має бути нудним 🔁
Перший запуск завершився успішно: контейнери працюють, у Redis є дані, а локальний проксі потрапив до активного пулу. Тепер навмисно запускаю те саме розгортання ще раз.
Якщо сценарій написаний правильно, нічого цікавого статися не повинно. Ті самі вхідні дані мають залишити gateway у тому самому стані: без нових контейнерів, перезаписаних файлів і раптово порожнього Redis.
Для такої перевірки додав до репозиторію
Потім запускає розгортання тієї самої версії й порівнює стан до та після:
Результат вийшов саме таким, яким і мав бути:
Це і є ідемпотентність нормальною мовою: повторний запуск із тими самими вхідними даними не створює побічних змін. Якщо передати нову версію коду або конфігурацію, сценарій їх застосує. Якщо нічого не змінилося — він теж нічого не змінює.
З кодом порядок. Тепер залишилося передати на нову VM секрети й не розкласти їх дорогою по Git, cloud-init і журналах 😅
Перший запуск завершився успішно: контейнери працюють, у Redis є дані, а локальний проксі потрапив до активного пулу. Тепер навмисно запускаю те саме розгортання ще раз.
Якщо сценарій написаний правильно, нічого цікавого статися не повинно. Ті самі вхідні дані мають залишити gateway у тому самому стані: без нових контейнерів, перезаписаних файлів і раптово порожнього Redis.
Для такої перевірки додав до репозиторію
deploy/verify-repeat.py. Перед повторним розгортанням він зберігає ідентифікатори контейнерів, стан Redis, контрольні суми й права конфігураційних файлів, правила iptables та політику маршрутизації.Потім запускає розгортання тієї самої версії й порівнює стан до та після:
sudo python3 deploy/verify-repeat.py \
--source "$PWD" \
--commit "$(git rev-parse HEAD)"
Результат вийшов саме таким, яким і мав бути:
PASS same 6 application and 2 fixture containers
PASS Redis process and data preserved
PASS config bytes/inodes/mtime/permissions unchanged
PASS iptables and policy routing unchanged
IDEMPOTENT_REPEAT PASS
Це і є ідемпотентність нормальною мовою: повторний запуск із тими самими вхідними даними не створює побічних змін. Якщо передати нову версію коду або конфігурацію, сценарій їх застосує. Якщо нічого не змінилося — він теж нічого не змінює.
З кодом порядок. Тепер залишилося передати на нову VM секрети й не розкласти їх дорогою по Git, cloud-init і журналах 😅
❤1
Код у Git. А паролі куди? 🔐
Розгортання вже можна повторити на новій VM. Але gateway потрібні паролі проксі й токен провайдера. Додати їх до репозиторію — означає залишити в історії Git. Передати через cloud-init теж не варіант: отримані дані залишаються на диску VM і можуть потрапити в журнали 🫠
Тому секрети підготував окремо на довіреній машині. Каталог належить
Для передачі додав до репозиторію
Сценарій передає файли через стандартний ввід SSH. Значення не з’являються в аргументах процесів і не друкуються в термінал.
На gateway новий набір спочатку потрапляє в тимчасовий каталог. Приймач перевіряє, чи всі потрібні файли на місці, чи немає зайвих даних і чи конфігурація придатна для запуску. Лише після цього він замінює робочі файли в:
Якщо передача обірветься або перевірка знайде помилку, старий набір залишиться на місці. Невдале оновлення пароля не залишить gateway із половиною нової конфігурації 🛟
Успішний результат не показує жодного секретного значення:
У результаті нову VM вдалося розгорнути без паролів у шаблоні, Git або cloud-init, що не може не радувати 😌
Тепер можна перезавантажити машину й подивитися, чи повернеться gateway до роботи без ручного запуску контейнерів 🔄
Розгортання вже можна повторити на новій VM. Але gateway потрібні паролі проксі й токен провайдера. Додати їх до репозиторію — означає залишити в історії Git. Передати через cloud-init теж не варіант: отримані дані залишаються на диску VM і можуть потрапити в журнали 🫠
Тому секрети підготував окремо на довіреній машині. Каталог належить
root, має права 0700 і не потрапляє в Git.Для передачі додав до репозиторію
deploy/secret-transfer.py 🚚sudo python3 deploy/secret-transfer.py send \
--host tgadmin@<gateway> \
--directory /root/tg-runtime
Сценарій передає файли через стандартний ввід SSH. Значення не з’являються в аргументах процесів і не друкуються в термінал.
На gateway новий набір спочатку потрапляє в тимчасовий каталог. Приймач перевіряє, чи всі потрібні файли на місці, чи немає зайвих даних і чи конфігурація придатна для запуску. Лише після цього він замінює робочі файли в:
/etc/transparent-gateway/runtime
Якщо передача обірветься або перевірка знайде помилку, старий набір залишиться на місці. Невдале оновлення пароля не залишить gateway із половиною нової конфігурації 🛟
Успішний результат не показує жодного секретного значення:
SECRET_DELIVERY PASS files=5 changed=5; validated, root-owned; values hidden
У результаті нову VM вдалося розгорнути без паролів у шаблоні, Git або cloud-init, що не може не радувати 😌
Тепер можна перезавантажити машину й подивитися, чи повернеться gateway до роботи без ручного запуску контейнерів 🔄
❤1
Перезавантажив VM. Gateway повернувся сам 🔄
Код і секрети на місці, контейнери працюють. Але поки що їх запустив сценарій розгортання. Після перезавантаження VM хтось має повторити це без моєї участі.
Можна було залишити цю роботу Docker через
Тому для всіх контейнерів залишив:
А керування всім gateway передав одному systemd-сервісу. Для його встановлення додав
Сценарій перевіряє, що потрібна версія вже розгорнута, конфігурація придатна для запуску, а мережа й Docker готові. Потім установлює
Systemd спочатку чекає мережу та Docker. Лише після цього запускає контейнери й повідомляє про готовність, коли gateway отримав свіжий пул проксі та пройшов локальну перевірку.
Перед перезавантаженням зберіг контрольний стан:
Сценарій записав ідентифікатор поточного завантаження, контейнери, стан захищених файлів і тестове значення в Redis. Після повернення VM перевірив усе ще раз:
Результат:
VM справді перезавантажилася, systemd сам підняв gateway, а конфігурація, секрети й дані Redis залишилися на місці ✅
Тепер можна перевіряти не лише сам gateway, а й повний шлях трафіку з окремої клієнтської машини.
Код і секрети на місці, контейнери працюють. Але поки що їх запустив сценарій розгортання. Після перезавантаження VM хтось має повторити це без моєї участі.
Можна було залишити цю роботу Docker через
restart: unless-stopped. Але тоді запуском одночасно керували б Docker і systemd. Один уже перезапускає контейнери, а другий ще тільки намагається зрозуміти, чому вони впали. Весела компанія, але для нормальної діагностики не підходить 😅Тому для всіх контейнерів залишив:
restart: "no"
А керування всім gateway передав одному systemd-сервісу. Для його встановлення додав
deploy/install-service.py:sudo python3 deploy/install-service.py \
--commit "$(git rev-parse HEAD)" \
--lab-fixture
Сценарій перевіряє, що потрібна версія вже розгорнута, конфігурація придатна для запуску, а мережа й Docker готові. Потім установлює
transparent-gateway.service і вмикає його автоматичний запуск.Systemd спочатку чекає мережу та Docker. Лише після цього запускає контейнери й повідомляє про готовність, коли gateway отримав свіжий пул проксі та пройшов локальну перевірку.
Перед перезавантаженням зберіг контрольний стан:
sudo python3 deploy/verify-boot.py --prepare
sudo systemctl reboot
Сценарій записав ідентифікатор поточного завантаження, контейнери, стан захищених файлів і тестове значення в Redis. Після повернення VM перевірив усе ще раз:
sudo python3 deploy/verify-boot.py
Результат:
PASS ordering: network-online + Docker ready before application
PASS same 6 application + 2 lab containers; Docker auto-restart disabled
PASS Redis marker and protected config/secrets/proxy CA preserved
PASS fresh pools and authenticated local relay echo after reboot
REBOOT_CHECK PASS
VM справді перезавантажилася, systemd сам підняв gateway, а конфігурація, секрети й дані Redis залишилися на місці ✅
Тепер можна перевіряти не лише сам gateway, а й повний шлях трафіку з окремої клієнтської машини.
❤1
Навіщо мені формула Шеннона? 🤨
На «Теорії інформації та кодуванні» дійшли до формули Шеннона:
Дивився на неї й не міг зрозуміти, навіщо вона мені взагалі потрібна. Ймовірності, логарифми, якась абстрактна ентропія — чудовий набір, щоб закрити конспект і піти писати код 😅
Зрозуміліше стало після простого пояснення: формула показує, скільки інформації в середньому несе одне повідомлення. Чим рідше трапляється символ або подія, тим більше інформації дає її поява.
Уявімо чотири символи. Якщо вони трапляються однаково часто, кожен можна записувати двома бітами. Але якщо символ
Частому
Ось навіщо потрібна статистика: вона показує, де дані передбачувані й де можна не витрачати зайві біти. На цьому принципі працює стиснення без втрат. Ентропія не стискає файл сама, але показує нижню межу його середнього розміру. Хаффман і арифметичне кодування вже намагаються підібратися до цієї межі 📦
З контрольними сумами логіка трохи інша. Великий файл перетворюється на коротке значення фіксованої довжини. Наприклад, контрольна сума довжиною
Файлів може бути значно більше ніж 256. Отже, рано чи пізно два різні файли отримають однакову контрольну суму. Це і називається колізією: дані різні, а результат перевірки однаковий 🎭
Контрольна сума довжиною
Що більше бітів, то менша ймовірність пропустити випадкову помилку. Але якщо файлів багато, порівнюється вже величезна кількість пар, тому шанс хоча б однієї колізії зростає.
Формула Шеннона не створює алгоритм стиснення або готову контрольну суму. Вона дає основу для розрахунків: скільки інформації містять дані, скільки бітів потрібно для їх кодування та наскільки надійно коротке значення може їх розрізняти.
Коли це пояснили людською мовою, логарифм нарешті влаштувався на роботу 😄
На «Теорії інформації та кодуванні» дійшли до формули Шеннона:
H(X) = -Σ p(x) log₂ p(x)
Дивився на неї й не міг зрозуміти, навіщо вона мені взагалі потрібна. Ймовірності, логарифми, якась абстрактна ентропія — чудовий набір, щоб закрити конспект і піти писати код 😅
Зрозуміліше стало після простого пояснення: формула показує, скільки інформації в середньому несе одне повідомлення. Чим рідше трапляється символ або подія, тим більше інформації дає її поява.
Уявімо чотири символи. Якщо вони трапляються однаково часто, кожен можна записувати двома бітами. Але якщо символ
A займає 70% тексту, витрачати на нього стільки ж місця, як на рідкісний символ, — марнотратство.Частому
A можна дати короткий код, а рідкісним символам — довші. Завдяки цьому більша частина тексту записуватиметься меншою кількістю бітів. Вихідний текст не зміниться, але середній розмір одного символу зменшиться.Ось навіщо потрібна статистика: вона показує, де дані передбачувані й де можна не витрачати зайві біти. На цьому принципі працює стиснення без втрат. Ентропія не стискає файл сама, але показує нижню межу його середнього розміру. Хаффман і арифметичне кодування вже намагаються підібратися до цієї межі 📦
З контрольними сумами логіка трохи інша. Великий файл перетворюється на коротке значення фіксованої довжини. Наприклад, контрольна сума довжиною
8 біт має лише 256 можливих значень.Файлів може бути значно більше ніж 256. Отже, рано чи пізно два різні файли отримають однакову контрольну суму. Це і називається колізією: дані різні, а результат перевірки однаковий 🎭
Контрольна сума довжиною
b біт має 2ᵇ можливих значень. Для однієї випадково пошкодженої копії шанс збігу з початковою сумою становить приблизно:1 / 2ᵇ
Що більше бітів, то менша ймовірність пропустити випадкову помилку. Але якщо файлів багато, порівнюється вже величезна кількість пар, тому шанс хоча б однієї колізії зростає.
Формула Шеннона не створює алгоритм стиснення або готову контрольну суму. Вона дає основу для розрахунків: скільки інформації містять дані, скільки бітів потрібно для їх кодування та наскільки надійно коротке значення може їх розрізняти.
Коли це пояснили людською мовою, логарифм нарешті влаштувався на роботу 😄
❤2
Як 160 біт перетворилися на 28 📦
Візьмемо повідомлення з двадцяти символів:
У звичайному текстовому файлі кожна латинська літера займає один байт — 8 біт. Отже, все повідомлення потребує 160 біт.
Але в нашому словнику є лише чотири символи:
Символи трапляються з різною частотою:
Тут і з’являється алгоритм Хаффмана. Він не вигадує короткі коди навмання, а щоразу об’єднує два найрідкісніші символи:
Так утворюється дерево. На кожному розгалуженні одному напрямку даємо
Частий
Тут працює важливе правило: жоден код не повинен бути початком іншого. Інакше дані не вдасться однозначно прочитати.
Наприклад, такий словник був би поганим:
Послідовність
У правильному словнику все однозначно:
Рахуємо новий розмір:
Отримав 28 біт замість 40 — на 30% менше. Порівняно зі звичайним текстовим файлом на 160 біт економія становить 82,5%.
Формула Шеннона для такого розподілу дає приблизно
Для короткого повідомлення ще потрібно зберігати словник, і він може з’їсти всю економію. Але на мільярді символів різниця між двома й 1,4 біта на символ становить близько 75 мегабайтів. На трильйоні — вже 75 гігабайтів.
Подібні принципи просто зараз стискають архіви, зображення, резервні копії та мережевий трафік по всьому світу. Так абстрактна формула починає економити цілком реальні терабайти 📉
Візьмемо повідомлення з двадцяти символів:
AAAAAAAAAAAAAABBBBCD
У звичайному текстовому файлі кожна латинська літера займає один байт — 8 біт. Отже, все повідомлення потребує 160 біт.
Але в нашому словнику є лише чотири символи:
A, B, C і D. Нам не потрібна вся таблиця ASCII. Чотири символи можна позначити чотирма комбінаціями з двох бітів:A = 00
B = 01
C = 10
D = 11
20 × 2 = 40 біт
Символи трапляються з різною частотою:
A — 14 разів, 70%
B — 4 рази, 20%
C — 1 раз, 5%
D — 1 раз, 5%
Тут і з’являється алгоритм Хаффмана. Він не вигадує короткі коди навмання, а щоразу об’єднує два найрідкісніші символи:
C 5% + D 5% = 10%
10% + B 20% = 30%
30% + A 70% = 100%
Так утворюється дерево. На кожному розгалуженні одному напрямку даємо
0, іншому — 1. Шлях від вершини дерева до символу стає його кодом:A = 0
B = 10
C = 110
D = 111
Частий
A опинився найближче до вершини й отримав один біт. До рідкісних C і D потрібно пройти три розгалуження, тому їхні коди довші.Тут працює важливе правило: жоден код не повинен бути початком іншого. Інакше дані не вдасться однозначно прочитати.
Наприклад, такий словник був би поганим:
A = 0
B = 01
C = 1
Послідовність
01 можна прочитати як один символ B або як два символи A і C.У правильному словнику все однозначно:
0 | 10 | 110 | 111
A | B | C | D
Рахуємо новий розмір:
A: 14 × 1 = 14 біт
B: 4 × 2 = 8 біт
C: 1 × 3 = 3 біти
D: 1 × 3 = 3 біти
Разом: 28 біт
Отримав 28 біт замість 40 — на 30% менше. Порівняно зі звичайним текстовим файлом на 160 біт економія становить 82,5%.
Формула Шеннона для такого розподілу дає приблизно
1,26 біта на символ, або близько 25 біт для всього повідомлення. Хаффман витратив 28, бо довжина кожного коду має бути цілою.Для короткого повідомлення ще потрібно зберігати словник, і він може з’їсти всю економію. Але на мільярді символів різниця між двома й 1,4 біта на символ становить близько 75 мегабайтів. На трильйоні — вже 75 гігабайтів.
Подібні принципи просто зараз стискають архіви, зображення, резервні копії та мережевий трафік по всьому світу. Так абстрактна формула починає економити цілком реальні терабайти 📉
Чотири біти пережили помилку 🧩
Потрібно передати чотири біти:
Якщо під час передачі один із них зміниться, приймач побачить іншу нормальну комбінацію й не знатиме, де сталася помилка. Початкової інформації вже недостатньо для відновлення.
Тому до чотирьох корисних бітів додамо три перевірочні. Отримаємо код Геммінга
Розкладаємо біти по позиціях:
Перевірочні біти відповідають за різні групи:
Їх підбираємо так, щоб XOR усіх бітів у кожній групі дорівнював нулю. Для
Припустимо, шостий біт пошкодився:
Приймач повторює три перевірки:
Число
На рівні процесора саме виправлення займає кілька команд. Три перевірки вже записали номер пошкодженої позиції в
Позиції в нашому записі рахуються зліва направо від 1 до 7, а зсув у регістрі — справа наліво від 0. Команди
За можливість відновлення заплатили трьома додатковими бітами. Код Геммінга
Саме завдяки їй працюють ECC-пам’ять, QR-коди, супутниковий зв’язок і носії даних. Надлишкові біти здаються зайвими лише доти, доки щось не зламається 😄
Потрібно передати чотири біти:
1011
Якщо під час передачі один із них зміниться, приймач побачить іншу нормальну комбінацію й не знатиме, де сталася помилка. Початкової інформації вже недостатньо для відновлення.
Тому до чотирьох корисних бітів додамо три перевірочні. Отримаємо код Геммінга
7,4: сім бітів передають чотири біти даних і дозволяють виправити одну помилку.Розкладаємо біти по позиціях:
Позиція: 1 2 3 4 5 6 7
Вміст: p1 p2 d1 p4 d2 d3 d4
Значення: p1 p2 1 p4 0 1 1
Перевірочні біти відповідають за різні групи:
p1 перевіряє позиції 1, 3, 5, 7
p2 перевіряє позиції 2, 3, 6, 7
p4 перевіряє позиції 4, 5, 6, 7
Їх підбираємо так, щоб XOR усіх бітів у кожній групі дорівнював нулю. Для
1011 отримуємо:p1 = 0
p2 = 1
p4 = 0
Передаємо: 0110011
Припустимо, шостий біт пошкодився:
Було: 0110011
Прийшло: 0110001
Приймач повторює три перевірки:
s1 = 0
s2 = 1
s4 = 1
s4 s2 s1 = 110₂ = 6
Число
6 прямо вказує на пошкоджену позицію. Змінюємо шостий біт назад, а з позицій 3, 5, 6 і 7 забираємо початкові дані:0110001 → 0110011 → 1011
На рівні процесора саме виправлення займає кілька команд. Три перевірки вже записали номер пошкодженої позиції в
BL, а отримані сім бітів лежать у AL:test bl, bl
jz .done
mov cl, 7
sub cl, bl
mov dl, 1
shl dl, cl
xor al, dl
.done:
test bl, bl перевіряє, чи дорівнює номер помилки нулю. jz .done завершує виправлення, якщо всі перевірки пройшли.Позиції в нашому записі рахуються зліва направо від 1 до 7, а зсув у регістрі — справа наліво від 0. Команди
mov cl, 7 і sub cl, bl переводять номер позиції у потрібну кількість зсувів. Для позиції 6 отримаємо один зсув.mov dl, 1 створює одиницю, а shl dl, cl пересуває її до пошкодженого біта. Для шостої позиції маска виглядає так:00000010
xor al, dl перемикає лише цей біт: 0 стає 1, а 1 — 0. Інші біти не змінюються.За можливість відновлення заплатили трьома додатковими бітами. Код Геммінга
7,4 виправляє одну помилку, але не врятує від довільної кількості пошкоджень. Складніші коди додають більше надлишкової інформації.Саме завдяки їй працюють ECC-пам’ять, QR-коди, супутниковий зв’язок і носії даних. Надлишкові біти здаються зайвими лише доти, доки щось не зламається 😄
Gateway працює. А клієнт про це знає? 🧪
Останні три пости були важливим ліричним відступом. Розібрався, навіщо потрібна формула Шеннона, як із неї виростає стиснення та чому надлишкові біти іноді рятують дані. Тепер повертаюся до Transparent Gateway 🌐
Gateway уже пережив повторне розгортання й перезавантаження. Контейнери запущені, пул проксі свіжий, локальний echo-сервіс відповідає.
Але досі gateway перевіряв переважно сам себе. Такий тест доводить, що його компоненти працюють, але нічого не говорить про трафік іншої машини.
Тому створив окрему клієнтську VM. На ній немає коду Transparent Gateway, паролів проксі або налаштувань proxy в застосунках. Єдиний маршрут IPv4 за замовчуванням веде через gateway, тому непомітно піти напряму клієнт не може.
Перевірку запускаю з довіреної машини:
Сценарій спочатку перевіряє gateway: стан systemd, свіжість пулів і наявність придатного проксі. Потім через SSH передає клієнту невеликий тест. Встановлювати на клієнт код проєкту або копіювати туди SSH-ключі не потрібно.
Клієнт відкриває звичайне TCP-з’єднання — без адреси проксі та його пароля. Запит проходить таким маршрутом:
Echo-сервіс повертає ідентифікатор запиту та адресу, з якої побачив з’єднання. Перевірка звіряє їх зі свіжим активним пулом, тому випадкова відповідь від іншого сервісу за успіх не зарахується.
Результат:
Окрема VM без налаштувань проксі надіслала звичайний TCP-запит. Transparent Gateway перехопив його, передав через локальний проксі та повернув відповідь клієнту ✅
Отже, локальний маршрут працює повністю — від клієнта до echo-сервісу й назад. Але вихід в інтернет, публічний HTTPS і DNS цей тест ще не перевіряє. Для цього знадобляться зовнішній проксі та публічний echo-сервіс.
Останні три пости були важливим ліричним відступом. Розібрався, навіщо потрібна формула Шеннона, як із неї виростає стиснення та чому надлишкові біти іноді рятують дані. Тепер повертаюся до Transparent Gateway 🌐
Gateway уже пережив повторне розгортання й перезавантаження. Контейнери запущені, пул проксі свіжий, локальний echo-сервіс відповідає.
Але досі gateway перевіряв переважно сам себе. Такий тест доводить, що його компоненти працюють, але нічого не говорить про трафік іншої машини.
Тому створив окрему клієнтську VM. На ній немає коду Transparent Gateway, паролів проксі або налаштувань proxy в застосунках. Єдиний маршрут IPv4 за замовчуванням веде через gateway, тому непомітно піти напряму клієнт не може.
Перевірку запускаю з довіреної машини:
python3 deploy/verify-readiness.py \
--gateway tgadmin@<gateway> \
--client tgadmin@<client>
Сценарій спочатку перевіряє gateway: стан systemd, свіжість пулів і наявність придатного проксі. Потім через SSH передає клієнту невеликий тест. Встановлювати на клієнт код проєкту або копіювати туди SSH-ключі не потрібно.
Клієнт відкриває звичайне TCP-з’єднання — без адреси проксі та його пароля. Запит проходить таким маршрутом:
клієнт
→ Transparent Gateway
→ локальний проксі
→ локальний echo-сервіс
→ відповідь клієнту
Echo-сервіс повертає ідентифікатор запиту та адресу, з якої побачив з’єднання. Перевірка звіряє їх зі свіжим активним пулом, тому випадкова відповідь від іншого сервісу за успіх не зарахується.
Результат:
PASS client default via gateway; no alternative default
PASS usable active upstream and local relay CONNECT/echo
PASS transparent client TCP/echo
LAB_READINESS PASS
Окрема VM без налаштувань проксі надіслала звичайний TCP-запит. Transparent Gateway перехопив його, передав через локальний проксі та повернув відповідь клієнту ✅
Отже, локальний маршрут працює повністю — від клієнта до echo-сервісу й назад. Але вихід в інтернет, публічний HTTPS і DNS цей тест ще не перевіряє. Для цього знадобляться зовнішній проксі та публічний echo-сервіс.
Підняв публічний echo для перевірки виходу через проксі 🌍
Коротко для тих, хто щойно приєднався. Transparent Gateway — це окрема VM між клієнтською машиною та рештою мережі. Клієнт користується мережею як звичайно: не потрібно вказувати проксі окремо в браузері чи кожній програмі.
Gateway перехоплює DNS-запити, за допомогою яких програми знаходять IP-адреси сайтів, і TCP-з’єднання, якими передається основний трафік. Потім спрямовує їх через проксі — проміжний сервер, який підключається до потрібного ресурсу від імені клієнта. У результаті сайт бачить адресу проксі, а не клієнтської машини.
Щоб не передавати трафік через першу-ліпшу адресу зі списку, окремі компоненти збирають проксі, перевіряють їхню доступність і залишають справні в активному пулі. Gateway бере маршрути саме з цього пулу. Зараз автоматизую розгортання всієї системи з чистої VM у Proxmox і поетапно перевіряю, чи справді трафік проходить потрібним шляхом.
Попередня перевірка довела, що клієнтський запит проходить через Transparent Gateway і локальний проксі. Але весь маршрут залишився всередині лабораторної мережі.
Щоб перевірити реальний вихід назовні, потрібна публічна точка, яка прийме HTTPS-запит і покаже адресу, з якої він прийшов. Якщо ця адреса збігається з виходом активного проксі, значить запит справді залишив лабораторію потрібним маршрутом.
Готові публічні сервіси для визначення IP існують, але сьогодні вони працюють, завтра змінюють формат відповіді, а післязавтра блокують автоматичні запити. Для повторюваної перевірки хотілося контролювати обидві сторони.
Тому виніс echo-сервіс в окремий проєкт — TransparentGatewayEcho 🔊
На зовнішній машині вже працював Nginx Proxy Manager, тому через нього й опублікував сервіс. NPM приймає HTTPS на порту
Echo-сервіс повертає JSON із випадковим ідентифікатором запиту, адресою клієнта та обмеженим набором діагностичних заголовків. Паролі, cookies й заголовки авторизації у відповідь не потрапляють.
Для перевірки додав до репозиторію
Перший запуск завершився
Cloudflare відхилив стандартний
Додав власний
Також навчив перевірку показувати HTTP-статус і тип відповіді, якщо замість JSON приходить помилка від зовнішнього сервісу.
Після цього запит дістався echo-сервісу:
Тепер є контрольована публічна точка, яка приймає HTTPS, повертає унікальний ідентифікатор запиту та показує адресу, передану Cloudflare ✅
Сам Transparent Gateway до неї ще не ходив. Локальний тестовий проксі бачить лише лабораторний echo-сервіс і не має виходу в інтернет. Наступний крок — додати зовнішній TLS-проксі й провести запит через нього.
Коротко для тих, хто щойно приєднався. Transparent Gateway — це окрема VM між клієнтською машиною та рештою мережі. Клієнт користується мережею як звичайно: не потрібно вказувати проксі окремо в браузері чи кожній програмі.
Gateway перехоплює DNS-запити, за допомогою яких програми знаходять IP-адреси сайтів, і TCP-з’єднання, якими передається основний трафік. Потім спрямовує їх через проксі — проміжний сервер, який підключається до потрібного ресурсу від імені клієнта. У результаті сайт бачить адресу проксі, а не клієнтської машини.
Щоб не передавати трафік через першу-ліпшу адресу зі списку, окремі компоненти збирають проксі, перевіряють їхню доступність і залишають справні в активному пулі. Gateway бере маршрути саме з цього пулу. Зараз автоматизую розгортання всієї системи з чистої VM у Proxmox і поетапно перевіряю, чи справді трафік проходить потрібним шляхом.
Попередня перевірка довела, що клієнтський запит проходить через Transparent Gateway і локальний проксі. Але весь маршрут залишився всередині лабораторної мережі.
Щоб перевірити реальний вихід назовні, потрібна публічна точка, яка прийме HTTPS-запит і покаже адресу, з якої він прийшов. Якщо ця адреса збігається з виходом активного проксі, значить запит справді залишив лабораторію потрібним маршрутом.
Готові публічні сервіси для визначення IP існують, але сьогодні вони працюють, завтра змінюють формат відповіді, а післязавтра блокують автоматичні запити. Для повторюваної перевірки хотілося контролювати обидві сторони.
Тому виніс echo-сервіс в окремий проєкт — TransparentGatewayEcho 🔊
На зовнішній машині вже працював Nginx Proxy Manager, тому через нього й опублікував сервіс. NPM приймає HTTPS на порту
443 і передає запит контейнеру echo у внутрішній мережі Docker. Сам порт контейнера назовні не відкритий.Echo-сервіс повертає JSON із випадковим ідентифікатором запиту, адресою клієнта та обмеженим набором діагностичних заголовків. Паролі, cookies й заголовки авторизації у відповідь не потрапляють.
Для перевірки додав до репозиторію
verify.py:python3 verify.py https://echo.devdays.net.ua/
Перший запуск завершився
JSONDecodeError. Спочатку здавалося, що echo-сервіс повернув неправильну відповідь. Насправді запит до нього взагалі не дістався.Cloudflare відхилив стандартний
User-Agent бібліотеки Python і повернув власну HTML-сторінку з HTTP 403 та кодом 1010. Скрипт очікував JSON і закономірно подавився HTML 😅Додав власний
User-Agent:TG-Echo-Verify/1.0
Також навчив перевірку показувати HTTP-статус і тип відповіді, якщо замість JSON приходить помилка від зовнішнього сервісу.
Після цього запит дістався echo-сервісу:
PASS HTTPS certificate and hostname verification; HTTP 200
PASS source IP=[hidden]; spoofed peer rejected
PASS diagnostic headers and fresh request ID
PASS Authorization/Cookie/Proxy-Authorization excluded
PUBLIC_ECHO_CHECK PASS
Тепер є контрольована публічна точка, яка приймає HTTPS, повертає унікальний ідентифікатор запиту та показує адресу, передану Cloudflare ✅
Сам Transparent Gateway до неї ще не ходив. Локальний тестовий проксі бачить лише лабораторний echo-сервіс і не має виходу в інтернет. Наступний крок — додати зовнішній TLS-проксі й провести запит через нього.
Підняв зовнішній TLS-проксі для контрольованого виходу 🔐
Публічний echo-сервіс готовий. Тепер Transparent Gateway потрібен проксі, який справді може дістатися до нього з інтернету.
Випадковий відкритий проксі для такого тесту не підходить. Сьогодні він працює, через годину зникає, а його вихідна адреса й правила доступу залишаються загадкою. Якщо перевірка впаде, незрозуміло, зламався gateway чи просто помер черговий безкоштовний проксі.
Тому підняв власний тестовий TLS-проксі й виніс його в окремий проєкт — TransparentGatewayProxy 🌐
До нього gateway підключається напряму через TLS на порту
Під час налаштування задаю адресу сервера та єдину зовнішню адресу, з якої дозволено підключення:
Сценарій створює власний CA, сертифікат сервера та випадкові дані авторизації. Пароль передається всередині TLS-з’єднання, тому Basic authentication тут не їде інтернетом відкритим текстом.
Проксі також перевіряє TCP-адресу клієнта. Підроблений
Після підготовки запускаю контейнер і локальну перевірку:
Проксі не дозволяє відкривати довільні адреси. Для стенда йому доступні лише публічний echo-сервіс і визначені DNS-цілі. Спроба підключитися до
Результат локальної перевірки:
Приватний ключ CA та ключ сервера залишаються на машині з проксі. На gateway передаю лише публічний CA-сертифікат, адресу підключення та дані авторизації.
Після передачі запускаю перевірку вже з gateway:
Вона перевіряє TLS-сертифікат проксі, правильну й неправильну авторизацію, заборонені адреси, HTTPS-запит до публічного echo та DNS-over-HTTPS.
Отже, gateway може встановити захищене з’єднання із зовнішнім проксі, пройти авторизацію та дістатися через нього до публічного echo-сервісу й DNS ✅
Окрема перевірка довела, що з хоста gateway зовнішній проксі доступний і справно працює. Але клієнтський трафік через нього ще не піде: Transparent Gateway використовує лише ті проксі, які пройшли validator і потрапили до активного пулу.
Додав новий проксі до конфігурації та запустив звичайний цикл перевірки. Локальний проксі залишився активним, а зовнішній validator відхилив — хоча хвилину тому той успішно пройшов TLS, авторизацію, HTTPS і DNS.
Отже, наступним кроком довелося з’ясовувати, чому ручна перевірка бачить справний проксі, а validator — ні 😅
Публічний echo-сервіс готовий. Тепер Transparent Gateway потрібен проксі, який справді може дістатися до нього з інтернету.
Випадковий відкритий проксі для такого тесту не підходить. Сьогодні він працює, через годину зникає, а його вихідна адреса й правила доступу залишаються загадкою. Якщо перевірка впаде, незрозуміло, зламався gateway чи просто помер черговий безкоштовний проксі.
Тому підняв власний тестовий TLS-проксі й виніс його в окремий проєкт — TransparentGatewayProxy 🌐
До нього gateway підключається напряму через TLS на порту
3129. Nginx Proxy Manager і Cloudflare в цьому маршруті не беруть участі.Під час налаштування задаю адресу сервера та єдину зовнішню адресу, з якої дозволено підключення:
sudo python3 setup.py \
--server-ip "$PROXY_IP" \
--allow-source "$GATEWAY_IP/32"
Сценарій створює власний CA, сертифікат сервера та випадкові дані авторизації. Пароль передається всередині TLS-з’єднання, тому Basic authentication тут не їде інтернетом відкритим текстом.
Проксі також перевіряє TCP-адресу клієнта. Підроблений
X-Forwarded-For не допоможе, бо рішення приймається за реальною адресою з’єднання.Після підготовки запускаю контейнер і локальну перевірку:
docker compose up -d --build --wait
docker compose exec -T proxy python /app/verify.py
Проксі не дозволяє відкривати довільні адреси. Для стенда йому доступні лише публічний echo-сервіс і визначені DNS-цілі. Спроба підключитися до
127.0.0.1:22, службової адреси хмарного сервера або стороннього порту завершується відмовою.Результат локальної перевірки:
PASS TLS certificate/identity
PASS missing and wrong credentials 407
PASS forbidden targets 403
PASS nested HTTPS certificate/hostname
PASS fresh echoed request ID; proxy credentials absent
PROXY_LOCAL_CHECK PASS
Приватний ключ CA та ключ сервера залишаються на машині з проксі. На gateway передаю лише публічний CA-сертифікат, адресу підключення та дані авторизації.
Після передачі запускаю перевірку вже з gateway:
sudo python3 verify.py \
--directory /etc/transparent-gateway/external-upstream \
--remote \
--dns
Вона перевіряє TLS-сертифікат проксі, правильну й неправильну авторизацію, заборонені адреси, HTTPS-запит до публічного echo та DNS-over-HTTPS.
PASS TLS certificate/identity
PASS nested HTTPS certificate/hostname
PASS DNS-over-HTTPS through authenticated proxy
UPSTREAM_REMOTE_CHECK PASS
Отже, gateway може встановити захищене з’єднання із зовнішнім проксі, пройти авторизацію та дістатися через нього до публічного echo-сервісу й DNS ✅
Окрема перевірка довела, що з хоста gateway зовнішній проксі доступний і справно працює. Але клієнтський трафік через нього ще не піде: Transparent Gateway використовує лише ті проксі, які пройшли validator і потрапили до активного пулу.
Додав новий проксі до конфігурації та запустив звичайний цикл перевірки. Локальний проксі залишився активним, а зовнішній validator відхилив — хоча хвилину тому той успішно пройшов TLS, авторизацію, HTTPS і DNS.
Отже, наступним кроком довелося з’ясовувати, чому ручна перевірка бачить справний проксі, а validator — ні 😅
Для CONNECT порт не опція 🧩
Зовнішній проксі працював. Окрема перевірка успішно проходила TLS, авторизацію, публічний HTTPS і DNS.
Але Transparent Gateway не додавав його до активного пулу.
Перед додаванням кожного проксі validator перевіряє, чи може той передавати реальні запити. Для HTTPS він просить проксі відкрити тунель до потрібного сервера за допомогою команди
Validator надсилав такий запит:
Тут указаний домен, але немає порту.
Домен
Правильний запит:
Окрема перевірка проксі додавала
Виправлення звелося до простого правила: під час створення
Після виправлення validator надіслав правильну адресу:
Зовнішній проксі пройшов перевірку й потрапив до активного пулу:
Тепер у gateway є два перевірені маршрути: локальний проксі для лабораторного echo-сервісу та зовнішній — для публічного HTTPS і DNS ✅
Наступним кроком перевірю обидва маршрути з окремої клієнтської VM.
Зовнішній проксі працював. Окрема перевірка успішно проходила TLS, авторизацію, публічний HTTPS і DNS.
Але Transparent Gateway не додавав його до активного пулу.
Перед додаванням кожного проксі validator перевіряє, чи може той передавати реальні запити. Для HTTPS він просить проксі відкрити тунель до потрібного сервера за допомогою команди
CONNECT.Validator надсилав такий запит:
CONNECT echo.devdays.net.ua HTTP/1.1
Тут указаний домен, але немає порту.
Домен
echo.devdays.net.ua визначає сервер, а порт 443 — HTTPS-сервіс на ньому. Без порту проксі отримує неповну адресу й не знає, куди саме відкривати тунель.Правильний запит:
CONNECT echo.devdays.net.ua:443 HTTP/1.1
Окрема перевірка проксі додавала
:443, тому працювала. Validator цього не робив, тому проксі правильно відхиляв його запит.Виправлення звелося до простого правила: під час створення
CONNECT завжди додавати порт, навіть якщо він стандартний для HTTPS.Після виправлення validator надіслав правильну адресу:
echo.devdays.net.ua:443
Зовнішній проксі пройшов перевірку й потрапив до активного пулу:
active: 2
validated: 2
Тепер у gateway є два перевірені маршрути: локальний проксі для лабораторного echo-сервісу та зовнішній — для публічного HTTPS і DNS ✅
Наступним кроком перевірю обидва маршрути з окремої клієнтської VM.
На кілька постів зверну в алгоритми 🧠
На кілька постів знову відійду від Transparent Gateway. Цього разу — в алгоритми та структури даних.
Зараз готуюся до олімпіади з програмування. І ця підготовка потроху змінює моє ставлення до речей, які раніше здавалися необов’язковими.
Програмую вже близько десяти років. Глибокого знання алгоритмів у мене немає, і для задач, із якими стикався в роботі, воно досі не було необхідним.
Це не історія про те, що колись усе вивчив, а потім просто не користувався. Багатьох алгоритмів я не знав і досі не знаю. Нічого страшного в цьому не бачу: до своєї мрії стати кращим програмістом іду поступово й кидати цей шлях не збираюся.
Алгоритми є майже в кожному плані навчання програмуванню, тому свого часу взявся за Кормена. Спроба була чесною, але недовгою: від книги хотілося спати значно сильніше, ніж розбирати чергове доведення 😴
Після цього прочитав «Грокаємо алгоритми». Вона виявилася набагато доступнішою і дала загальне уявлення про пошук, сортування, графи та складність обчислень.
Але тоді враження залишилося приблизно таким: алгоритми — цікава штука для людей з особливими інтересами, яким від усієї цієї теорії чомусь не хочеться спати.
Водночас саме програмування мене драйвило й продовжує драйвити. Тому особливої суперечності тут не бачив: код пишеться, проєкти працюють, а Кормен спокійно стоїть на полиці.
Підготовка до олімпіади показала інший бік програмування. Тут недостатньо знайти будь-яке правильне рішення. Потрібно побачити структуру задачі й вибрати спосіб, який не з’їсть увесь час або пам’ять.
Тому наступні кілька постів будуть про алгоритми. Трохи відпочинемо від серверів, проксі та розгортання — і подивимося, навіщо всі ці дерева взагалі вигадали 🌲
На кілька постів знову відійду від Transparent Gateway. Цього разу — в алгоритми та структури даних.
Зараз готуюся до олімпіади з програмування. І ця підготовка потроху змінює моє ставлення до речей, які раніше здавалися необов’язковими.
Програмую вже близько десяти років. Глибокого знання алгоритмів у мене немає, і для задач, із якими стикався в роботі, воно досі не було необхідним.
Це не історія про те, що колись усе вивчив, а потім просто не користувався. Багатьох алгоритмів я не знав і досі не знаю. Нічого страшного в цьому не бачу: до своєї мрії стати кращим програмістом іду поступово й кидати цей шлях не збираюся.
Алгоритми є майже в кожному плані навчання програмуванню, тому свого часу взявся за Кормена. Спроба була чесною, але недовгою: від книги хотілося спати значно сильніше, ніж розбирати чергове доведення 😴
Після цього прочитав «Грокаємо алгоритми». Вона виявилася набагато доступнішою і дала загальне уявлення про пошук, сортування, графи та складність обчислень.
Але тоді враження залишилося приблизно таким: алгоритми — цікава штука для людей з особливими інтересами, яким від усієї цієї теорії чомусь не хочеться спати.
Водночас саме програмування мене драйвило й продовжує драйвити. Тому особливої суперечності тут не бачив: код пишеться, проєкти працюють, а Кормен спокійно стоїть на полиці.
Підготовка до олімпіади показала інший бік програмування. Тут недостатньо знайти будь-яке правильне рішення. Потрібно побачити структуру задачі й вибрати спосіб, який не з’їсть увесь час або пам’ять.
Тому наступні кілька постів будуть про алгоритми. Трохи відпочинемо від серверів, проксі та розгортання — і подивимося, навіщо всі ці дерева взагалі вигадали 🌲
👀1
Правильного результату для олімпіади недостатньо ⏱️
Першим бажанням в олімпіадній задачі часто стає звичайний перебір.
Потрібно знайти пару елементів — переберу всі пари. Потрібно перевірити відрізки масиву — перевірю кожен відрізок. На прикладах з умови все працює, відповідь правильна, можна відправляти.
А потім тести повідомляють, що чекати до завтра вони не збираються 😅
Наприклад, у масиві 200 000 елементів подвійний цикл може виконати близько 40 мільярдів операцій:
Логічно код може бути бездоганним. Але за відведену секунду він просто не встигне отримати результат.
Рішення зі складністю
Відповідь та сама, але різниця у швидкості — у тисячі разів.
Те саме стосується пам’яті. Можна створити таблицю для всіх можливих пар і навіть правильно її заповнити. Але якщо вона не поміститься в доступну пам’ять, правильність уже нікого не врятує.
Тому обмеження в умові — це частина задачі. Вони підказують, чи можна використовувати перебір, чи доведеться шукати інший підхід.
Поступово звикаю перед написанням коду ставити додаткові запитання:
• скільки буде даних;
• які операції повторюються;
• що доводиться рахувати знову;
• чи можна один раз зберегти частину результату;
• чи існує структура даних саме для таких операцій.
Одне з таких запитань і привело мене до дерева Фенвіка.
Першим бажанням в олімпіадній задачі часто стає звичайний перебір.
Потрібно знайти пару елементів — переберу всі пари. Потрібно перевірити відрізки масиву — перевірю кожен відрізок. На прикладах з умови все працює, відповідь правильна, можна відправляти.
А потім тести повідомляють, що чекати до завтра вони не збираються 😅
Наприклад, у масиві 200 000 елементів подвійний цикл може виконати близько 40 мільярдів операцій:
200 000 × 200 000 = 40 000 000 000
Логічно код може бути бездоганним. Але за відведену секунду він просто не встигне отримати результат.
Рішення зі складністю
O(n log n) для того самого обсягу даних потребуватиме приблизно 3,6 мільйона кроків:200 000 × log₂(200 000) ≈ 3 600 000
Відповідь та сама, але різниця у швидкості — у тисячі разів.
Те саме стосується пам’яті. Можна створити таблицю для всіх можливих пар і навіть правильно її заповнити. Але якщо вона не поміститься в доступну пам’ять, правильність уже нікого не врятує.
Тому обмеження в умові — це частина задачі. Вони підказують, чи можна використовувати перебір, чи доведеться шукати інший підхід.
Поступово звикаю перед написанням коду ставити додаткові запитання:
• скільки буде даних;
• які операції повторюються;
• що доводиться рахувати знову;
• чи можна один раз зберегти частину результату;
• чи існує структура даних саме для таких операцій.
Одне з таких запитань і привело мене до дерева Фенвіка.
🤯1
Нуль задач і значно більше мотивації 🔥
Вчора взяв участь в олімпіаді з програмування у складі команди
Очікування були скромними, але завдання все одно виявилися значно складнішими, ніж очікував. У підсумку команда не розв’язала жодної задачі.
У таблиці команди з таким результатом опинилися в діапазоні 426–605. Окремого місця нам навіть не виділили — настільки впевнено розташувалися у хвості 😅
Для порівняння: минулого року розв’язували по три-чотири задачі в кожному турі й дійшли щонайменше до другого. Можливо, навіть до третього — точно вже не пам’ятаю, тому вигадувати не буду.
Вітаю команду
Окремий респект команді
Минулорічна олімпіада залишила змішані почуття й багато роздратування. Цього разу результат у таблиці значно гірший, але реакція зовсім інша. Замість бажання посперечатися із задачами з’явився інтерес: як вони насправді розв’язуються і яких знань мені не вистачило.
Олімпіада добре показала слабкі місця. Недостатньо знати, що графи, дерева й динамічне програмування десь існують. Потрібно за обмежений час упізнати потрібну ідею, оцінити її складність і перетворити на робочий код.
Саме над цим і будемо працювати. Розберемо задачі, підтягнемо алгоритми й наступного разу прийдемо підготовленішими.
Сьогоднішню таблицю вже не змінимо. А от те, з чим прийдемо на наступну олімпіаду, — цілком 💪
Вчора взяв участь в олімпіаді з програмування у складі команди
PDAU_RootAccess.Очікування були скромними, але завдання все одно виявилися значно складнішими, ніж очікував. У підсумку команда не розв’язала жодної задачі.
У таблиці команди з таким результатом опинилися в діапазоні 426–605. Окремого місця нам навіть не виділили — настільки впевнено розташувалися у хвості 😅
Для порівняння: минулого року розв’язували по три-чотири задачі в кожному турі й дійшли щонайменше до другого. Можливо, навіть до третього — точно вже не пам’ятаю, тому вигадувати не буду.
Вітаю команду
cViVCNUFT_ComputerWorms — Haidei, Hrom і CHEREMUKHIN — із перемогою та чудовим результатом 🏆Окремий респект команді
sPolit_vozduhbenders — Yabchenko, Sadovnikov і Khalin. Команда політеху посіла восьме місце. Дуже крутий результат 👏Минулорічна олімпіада залишила змішані почуття й багато роздратування. Цього разу результат у таблиці значно гірший, але реакція зовсім інша. Замість бажання посперечатися із задачами з’явився інтерес: як вони насправді розв’язуються і яких знань мені не вистачило.
Олімпіада добре показала слабкі місця. Недостатньо знати, що графи, дерева й динамічне програмування десь існують. Потрібно за обмежений час упізнати потрібну ідею, оцінити її складність і перетворити на робочий код.
Саме над цим і будемо працювати. Розберемо задачі, підтягнемо алгоритми й наступного разу прийдемо підготовленішими.
Сьогоднішню таблицю вже не змінимо. А от те, з чим прийдемо на наступну олімпіаду, — цілком 💪
❤2🥰1
Як дерево Фенвіка зберігає суми 🌲
Дерево Фенвіка корисне в задачах, де числа в масиві змінюються, а суми потрібно рахувати багато разів.
Візьмемо вісім чисел:
Нехай потрібно виконувати дві операції:
• додати число до одного елемента;
• знайти суму від початку масиву до вибраної позиції.
Звичайний цикл легко знайде суму. Але для сьомої позиції він прочитає сім елементів. Якщо масив містить сотні тисяч чисел і таких запитів багато, постійний перебір стає дорогим.
Можна заздалегідь порахувати всі накопичені суми:
Тоді сума перших семи чисел одразу дорівнює
Але після зміни третього числа доведеться виправити суми для третьої, четвертої, п’ятої та всіх наступних позицій. Швидке читання отримали, зате оновлення стало повільним.
Дерево Фенвіка зберігає не всі накопичені суми, а суми окремих блоків. Для восьми позицій вони виглядають так:
Щоб знайти суму перших семи чисел, не потрібно читати всі сім:
У дереві це три комірки:
Вони не перетинаються й разом точно покривають позиції від першої до сьомої.
Якщо змінюється третє число, потрібно оновити лише блоки, до яких воно належить:
Тобто комірки:
Ось і вся основна ідея: дерево Фенвіка зберігає масив як набір блоків різного розміру. Великий потрібний відрізок складається з кількох готових блоків, а одна зміна торкається лише тих блоків, які містять змінений елемент.
Залишається зрозуміти, як програма знаходить ці блоки без окремої таблиці переходів.
Для цього й використовується дивний вираз
Дерево Фенвіка корисне в задачах, де числа в масиві змінюються, а суми потрібно рахувати багато разів.
Візьмемо вісім чисел:
3 1 4 1 5 9 2 6
Нехай потрібно виконувати дві операції:
• додати число до одного елемента;
• знайти суму від початку масиву до вибраної позиції.
Звичайний цикл легко знайде суму. Але для сьомої позиції він прочитає сім елементів. Якщо масив містить сотні тисяч чисел і таких запитів багато, постійний перебір стає дорогим.
Можна заздалегідь порахувати всі накопичені суми:
3 4 8 9 14 23 25 31
Тоді сума перших семи чисел одразу дорівнює
25.Але після зміни третього числа доведеться виправити суми для третьої, четвертої, п’ятої та всіх наступних позицій. Швидке читання отримали, зате оновлення стало повільним.
Дерево Фенвіка зберігає не всі накопичені суми, а суми окремих блоків. Для восьми позицій вони виглядають так:
позиція 1 зберігає суму [1]
позиція 2 зберігає суму [1–2]
позиція 3 зберігає суму [3]
позиція 4 зберігає суму [1–4]
позиція 5 зберігає суму [5]
позиція 6 зберігає суму [5–6]
позиція 7 зберігає суму [7]
позиція 8 зберігає суму [1–8]
Щоб знайти суму перших семи чисел, не потрібно читати всі сім:
[1–4] + [5–6] + [7]
У дереві це три комірки:
tree[4] + tree[6] + tree[7]
Вони не перетинаються й разом точно покривають позиції від першої до сьомої.
Якщо змінюється третє число, потрібно оновити лише блоки, до яких воно належить:
[3]
[1–4]
[1–8]
Тобто комірки:
tree[3]
tree[4]
tree[8]
Ось і вся основна ідея: дерево Фенвіка зберігає масив як набір блоків різного розміру. Великий потрібний відрізок складається з кількох готових блоків, а одна зміна торкається лише тих блоків, які містять змінений елемент.
Залишається зрозуміти, як програма знаходить ці блоки без окремої таблиці переходів.
Для цього й використовується дивний вираз
i & -i.Навіщо дереву Фенвіка
У дереві Фенвіка кожна позиція зберігає суму блоку. Розмір цього блоку можна отримати так:
Першого разу цей вираз виглядає як чаклунство. Насправді він просто залишає наймолодший установлений біт числа — крайню праву одиницю в його двійковому записі.
Розберемо число
Щоб отримати
Тепер виконуємо побітове
Отримали
Отже:
Саме два елементи входять до блоку, який закінчується на шостій позиції:
Інший приклад — позиція
Тому четверта комірка зберігає блок із чотирьох чисел:
Для позиції
Отже, сьома комірка відповідає лише за себе:
Тепер можна знайти суму перших семи чисел.
Починаємо з позиції
Шоста позиція зберігає блок із двох чисел. Додаємо
Четверта зберігає чотири числа:
Дійшли до нуля — сума готова:
У коді це виглядає так:
Під час оновлення рухаємося в інший бік. Якщо змінилася третя позиція, потрібно оновити всі більші блоки, які її містять:
Для цього розмір блоку не віднімаємо, а додаємо:
Замість проходу по всьому масиву кожна операція відвідує лише кілька комірок. Для мільйона елементів це приблизно двадцять переходів.
Концепція справді проста: зберігаємо суми правильно підібраних блоків і пересуваємося між ними. Але щоб побачити цю простоту за двійковими числами та
На цьому короткий відступ в алгоритми завершую. Дерево Фенвіка не зробило з мене олімпіадного чемпіона, але добре показало, скільки зайвої роботи можна прибрати правильною структурою даних.
У наступному пості повернуся до Transparent Gateway. Зовнішній проксі вже потрапив до активного пулу — залишилося провести через нього запит із клієнтської VM і перевірити весь маршрут, а не лише окремі його частини.
i & -i 🧩У дереві Фенвіка кожна позиція зберігає суму блоку. Розмір цього блоку можна отримати так:
i & -i
Першого разу цей вираз виглядає як чаклунство. Насправді він просто залишає наймолодший установлений біт числа — крайню праву одиницю в його двійковому записі.
Розберемо число
6:6 = 00000110
Щоб отримати
-6, комп’ютер інвертує біти й додає одиницю:00000110
↓ інвертуємо
11111001
↓ додаємо 1
11111010
Тепер виконуємо побітове
AND:00000110
& 11111010
----------
00000010
Отримали
2.Отже:
6 & -6 = 2
Саме два елементи входять до блоку, який закінчується на шостій позиції:
[5–6]
Інший приклад — позиція
4:4 = 00000100
4 & -4 = 4
Тому четверта комірка зберігає блок із чотирьох чисел:
[1–4]
Для позиції
7 результат дорівнює одиниці:7 = 00000111
7 & -7 = 1
Отже, сьома комірка відповідає лише за себе:
[7]
Тепер можна знайти суму перших семи чисел.
Починаємо з позиції
7. Її блок має розмір 1, тому додаємо tree[7] і переходимо до позиції 6:7 - 1 = 6
Шоста позиція зберігає блок із двох чисел. Додаємо
tree[6] і переходимо до четвертої:6 - 2 = 4
Четверта зберігає чотири числа:
4 - 4 = 0
Дійшли до нуля — сума готова:
tree[7] + tree[6] + tree[4]
У коді це виглядає так:
def prefix_sum(i):
result = 0
while i > 0:
result += tree[i]
i -= i & -i
return result
Під час оновлення рухаємося в інший бік. Якщо змінилася третя позиція, потрібно оновити всі більші блоки, які її містять:
3 → 4 → 8
Для цього розмір блоку не віднімаємо, а додаємо:
def add(i, value):
while i <= n:
tree[i] += value
i += i & -i
Замість проходу по всьому масиву кожна операція відвідує лише кілька комірок. Для мільйона елементів це приблизно двадцять переходів.
Концепція справді проста: зберігаємо суми правильно підібраних блоків і пересуваємося між ними. Але щоб побачити цю простоту за двійковими числами та
i & -i, довелося добряче посидіти.На цьому короткий відступ в алгоритми завершую. Дерево Фенвіка не зробило з мене олімпіадного чемпіона, але добре показало, скільки зайвої роботи можна прибрати правильною структурою даних.
У наступному пості повернуся до Transparent Gateway. Зовнішній проксі вже потрапив до активного пулу — залишилося провести через нього запит із клієнтської VM і перевірити весь маршрут, а не лише окремі його частини.