Интернет задумывался как глобальная сеть, в которой любой компьютер может связаться с любым другим компьютером. На практике всё давно работает немного иначе. Где-то провайдер режет определённые сервисы, где-то появляются ограничения на отдельные протоколы, где-то плохо работает международная маршрутизация, а иногда нужный ресурс оказывается недоступен просто потому, что находится за пределами привычного маршрута вашего трафика. В результате даже опытный инженер периодически сталкивается с ситуацией, когда нужная документация не открывается, очередной релиз на GitHub скачивается со скоростью почтового голубя, пакеты PyPi становятся недоступными, Telemost начинает заикаться в голосовых каналах, а какой-нибудь зарубежный сервис внезапно оказывается недоступен из-за особенностей маршрутизации или региональной политики доступа.
⚠️ Дисклеймер
Данная статья носит исключительно технический и образовательный характер. Материал посвящён автоматизации развёртывания VPN-инфраструктуры с использованием Hysteria2 и Ansible, а также практикам Infrastructure as Code при сопровождении сетевых сервисов.
Автор не призывает нарушать законодательство какой-либо страны, обходить установленные законом ограничения или использовать описанные технологии в противоправных целях. Ответственность за соблюдение требований законодательства и правил использования сетевых ресурсов полностью лежит на пользователе.
На практике подобные решения нередко используются для обеспечения доступа к корпоративным ресурсам, удалённым площадкам, внутренним сервисам компаний, документации производителей оборудования, репозиториям программного обеспечения, драйверам, техническим форумам, системам совместной разработки и другим легитимным интернет-ресурсам, доступ к которым может быть ограничен региональными особенностями маршрутизации или политикой отдельных сервисов.
Основная цель статьи - показать, как превратить установку VPN из набора ручных действий в воспроизводимый инфраструктурный процесс с использованием Git, Ansible, шаблонов конфигурации и автоматического управления пользователями.
Автор не предоставляет готовые сервисы доступа, не распространяет средства обхода ограничений и не оказывает услуг по организации доступа к запрещённым ресурсам. Материал посвящён вопросам автоматизации развёртывания сетевой инфраструктуры и может применяться исключительно в законных целях, включая удалённый доступ к собственным ресурсам, корпоративным сетям и информационным системам.
Первое решение, которое обычно приходит в голову - купить VPN. Второе решение - купить ещё один VPN после того, как первый перестал работать. Третье решение - купить третий VPN и начать коллекционировать подписки как покемонов. Через некоторое время выясняется, что один провайдер работает только вечером, второй показывает отличную скорость исключительно по праздникам, а третий вообще решил внезапно закрыться вместе с вашими деньгами. В этот момент многие инженеры приходят к простой мысли: если инфраструктуру можно построить самостоятельно, то почему бы не построить собственный VPN-сервис и не контролировать его полностью?
Сам по себе VPS сегодня стоит дешевле похода в хороший бар. За несколько евро в месяц можно получить сервер в Европе, Азии, Америке или практически любой другой точке мира. Проблема заключается вовсе не в аренде сервера. Проблема начинается после покупки. Нужно установить VPN-сервер, выпустить сертификаты, настроить сетевой экран, создать пользователей, сформировать клиентские конфигурации, собрать QR-коды для мобильных устройств и не забыть всё это задокументировать. Пока сервер один, задача выглядит простой. Когда их становится пять, начинается хаос. Когда десять - появляется желание написать автоматизацию. Когда двадцать - автоматизация становится вопросом выживания.
Именно поэтому появился этот проект. Его задача заключается не просто в установке Hysteria2. Таких установщиков в интернете десятки. Задача проекта состоит в том, чтобы превратить Hysteria2 в полноценный инфраструктурный сервис, который разворачивается через Ansible, хранится в Git, воспроизводится на любом количестве серверов и автоматически генерирует всё необходимое для пользователей. В результате вместо набора магических команд в терминале получается нормальный инженерный процесс, который можно сопровождать спустя месяцы и годы без археологических раскопок в истории Bash-команд.
Почему я выбрал Hysteria2, хотя существуют WireGuard, OpenVPN, AmneziaVPN и десятки других решений
Когда речь заходит о собственном VPN-сервере, большинство администраторов автоматически вспоминают WireGuard. И это вполне объяснимо. За последние годы WireGuard фактически стал новым стандартом индустрии. Он простой, быстрый, компактный, легко настраивается и обеспечивает очень хорошую производительность практически на любом оборудовании. Многие мои собственные проекты долгие годы работали именно на WireGuard, и я до сих пор считаю его одним из лучших VPN-протоколов, которые когда-либо появлялись в мире Linux. Однако у любой технологии есть область применения, и именно здесь начинаются интересные нюансы.
Проблема заключается в том, что современный интернет давно перестал быть средой, где достаточно просто установить VPN и забыть о его существовании. Сетевое оборудование провайдеров становится всё умнее, сетевые среды становятся сложнее, а требования к устойчивости сетевых сервисов продолжают расти. Некоторые протоколы лучше работают в нестабильных сетях, другие хуже переносят особенности отдельных каналов связи. В результате некоторые протоколы начинают привлекать к себе слишком много внимания. WireGuard остаётся великолепным решением для корпоративных сетей, объединения офисов и подключения администраторов к инфраструктуре, но когда речь заходит о прохождении через агрессивные системы анализа трафика, начинают проявляться его ограничения.
OpenVPN находится на другом конце спектра. Когда-то именно он считался золотым стандартом VPN-индустрии. Многие крупные компании до сих пор используют его в своих инфраструктурах, а количество готовых инструкций исчисляется тысячами. Проблема OpenVPN заключается в его возрасте. За годы развития он оброс огромным количеством настроек, режимов работы и исторического наследия. Современный сервер OpenVPN напоминает старый корпоративный монолит: он умеет практически всё, но за универсальность приходится платить производительностью и сложностью сопровождения. Особенно хорошо это заметно на мобильных сетях, где дополнительные накладные расходы начинают ощущаться буквально физически.
В последние годы появились и более современные проекты. Например, AmneziaVPN, который получил большую популярность благодаря дополнительным механизмам режим работы протокола. Это действительно интересный продукт, который помогает обходить многие ограничения. Однако у него есть одна особенность, которая лично мне не очень нравится как инженеру. AmneziaVPN является скорее готовой экосистемой поверх существующих технологий. А мне хотелось получить инструмент, который можно полностью контролировать на уровне инфраструктуры, конфигураций, автоматизации и процессов сопровождения.
Именно в этот момент я серьёзно заинтересовался Hysteria2. На первый взгляд проект выглядит достаточно необычно. В отличие от большинства классических VPN он построен поверх протокола QUIC, который изначально разрабатывался для ускорения веб-трафика и в дальнейшем стал основой HTTP/3. Для обычного пользователя это может звучать как очередная техническая деталь, но для инфраструктурного инженера здесь скрывается очень важное преимущество. Вместо попытки передать данные через традиционные механизмы TCP Hysteria2 использует современную транспортную модель, которая значительно лучше переносит нестабильные каналы связи, задержки и потери пакетов.
На практике это ощущается очень быстро. Несколько лет назад мне пришлось заниматься инфраструктурой для пользователей, работающих через мобильные сети разных операторов. С точки зрения сетевой теории мобильный интернет выглядит довольно страшно. Постоянно меняется маршрут, скачет задержка, появляются потери пакетов, происходят переключения между базовыми станциями. В таких условиях многие классические VPN начинают вести себя нервно. Скорость скачет, появляются подвисания, а некоторые приложения начинают периодически терять соединение. После перехода на решения, использующие QUIC, ситуация заметно улучшилась. Соединение стало намного более устойчивым даже при неидеальном качестве канала.
Отдельно стоит поговорить про производительность. Многие пользователи до сих пор оценивают VPN исключительно по результатам Speedtest. На самом деле скорость канала является лишь одной из характеристик. Намного важнее то, как система ведёт себя под реальной нагрузкой. Например, во время видеоконференций, голосовой связи, работы через удалённый рабочий стол или скачивания больших объёмов данных. Именно в таких сценариях преимущества QUIC становятся особенно заметными. Вместо постоянных повторных передач и сложных механизмов восстановления соединений протокол гораздо эффективнее адаптируется к проблемам сети.
Но производительность была не единственной причиной выбора Hysteria2. Не менее важным фактором стала архитектура сетевого режима трафика. Современные системы анализа сетевого трафика всё чаще ориентируются не только на порты или IP-адреса, но и на характер поведения соединений. Некоторые VPN-протоколы имеют вполне узнаваемый сетевой почерк. Hysteria2 изначально проектировался с учётом подобных сценариев и предлагает несколько различных вариантов сетевого режима трафика. Именно поэтому в моём проекте появились две отдельные ветки - main и salamander. Каждая из них реализует собственную стратегию взаимодействия с внешним миром и подходит для разных сценариев эксплуатации.
При этом я совершенно не считаю Hysteria2 универсальной заменой WireGuard. Это важный момент, который часто упускают авторы технических статей. Если мне нужно соединить между собой два дата-центра, организовать доступ администраторов к Kubernetes-кластеру или построить внутреннюю корпоративную сеть, я с высокой вероятностью выберу именно WireGuard. Он проще, понятнее и отлично справляется со своей задачей. Но если основной задачей становится стабильный доступ к удалённым сервисам через нестабильные каналы связи, преимущества Hysteria2 начинают выглядеть очень убедительно.
Хороший инженер выбирает не лучший инструмент "в общем". Хороший инженер выбирает лучший инструмент для конкретной задачи.
Именно поэтому этот проект появился вокруг Hysteria2. Не потому, что остальные решения плохие. И не потому, что существует какая-то магическая технология, способная решить все проблемы интернета. Просто в определённом наборе сценариев Hysteria2 сегодня выглядит одним из самых интересных и перспективных инструментов, а значит заслуживает нормальной инфраструктурной автоматизации, а не очередного Bash-скрипта на триста строк, который никто не рискнёт запускать второй раз.
От одноразового VPS к полноценной инфраструктуре: как устроен проект внутри
Большинство VPN-проектов в интернете развиваются по одному и тому же сценарию. Сначала появляется небольшой Bash-скрипт для личного использования. Затем появляется второй сервер. Потом третий. Затем возникает необходимость добавить нового пользователя, обновить сертификат, поменять порт или перенести всё на другой VPS. В этот момент скрипт начинает обрастать новыми функциями, условными операторами и комментариями в стиле «не трогать, иначе сломается». Через полгода такой проект уже напоминает археологический памятник, который страшно открывать даже его автору.
Я сознательно не хотел идти по этому пути. Поэтому с самого начала проект строился вокруг Ansible. Не вокруг Bash. Не вокруг веб-панели. Не вокруг очередного install.sh на тысячу строк. Именно вокруг подхода Infrastructure as Code, который давно стал стандартом для нормальной эксплуатации инфраструктуры.
На первый взгляд структура репозитория выглядит достаточно привычно:
.
├── ansible.cfg
├── inventory
├── group_vars
├── playbooks
├── roles
│ └── hysteria2
├── output
├── Makefile
└── README.md
Но за этой простой структурой скрывается важная идея. Весь жизненный цикл VPN-сервера описан в коде. Не в голове администратора. Не в заметках на телефоне. Не в истории терминала. А именно в Git-репозитории. Если через полгода понадобится поднять ещё десять серверов, для этого не придётся вспоминать последовательность команд. Достаточно выполнить playbook.
На практике это выглядит значительно важнее, чем кажется на первый взгляд. В одном из старых проектов мне пришлось принимать инфраструктуру, которую несколько лет сопровождали разные администраторы. Каждый новый инженер добавлял что-то своё. Где-то использовался certbot. Где-то acme.sh. Где-то сертификаты лежали в одном каталоге, где-то в другом. Документации не существовало. После недели исследований выяснилось, что некоторые сервисы обновлялись вручную по SSH примерно раз в полгода. Именно после таких историй начинаешь по-настоящему ценить воспроизводимость.
Inventory как единый источник правды
Одной из сильных сторон проекта является то, что все серверы описываются через обычный inventory-файл Ansible.
Например:
all:
hosts:
vpn-amsterdam:
ansible_host: 10.10.10.10
vpn-frankfurt:
ansible_host: 20.20.20.20
vpn-singapore:
ansible_host: 30.30.30.30
Фактически inventory становится картой всей инфраструктуры. Именно здесь описываются серверы, их роли и параметры подключения. Когда появляется новый VPS, его не нужно вручную настраивать по SSH. Достаточно добавить запись в inventory и выполнить playbook.
Многие начинающие инженеры недооценивают этот подход. Пока инфраструктура состоит из одного сервера, разница действительно почти незаметна. Но когда количество узлов начинает расти, inventory превращается в единый источник правды для всей системы. Именно поэтому практически все крупные инфраструктурные команды используют похожие подходы независимо от того, работают они с Ansible, Terraform или Kubernetes.
Секреты должны жить отдельно
Вторая проблема большинства самодельных VPN-проектов - хранение секретов.
Если внимательно посмотреть на типичный GitHub-репозиторий очередного VPN-скрипта, очень часто можно обнаружить пароли прямо в конфигурационных файлах. Иногда встречаются даже приватные ключи. До первого случайного push в публичный репозиторий всё выглядит вполне безопасно. После этого начинается веселье.
Поэтому в проекте изначально используется Ansible Vault.
Создание защищённого хранилища выполняется стандартной командой:
make vault-create
Редактирование происходит так:
make vault-edit
После шифрования содержимое файла превращается примерно в такой набор символов:
$ANSIBLE_VAULT;1.1;AES256
643564333763613336313537313235...
Человек ничего прочитать уже не сможет. Для Ansible это по-прежнему обычный набор переменных.
В результате пароли пользователей, секреты обфускации, ключи и другие чувствительные данные не оказываются в открытом виде внутри репозитория. Для небольших домашних проектов это может показаться избыточным, но любой инженер, который хотя бы раз расследовал утечку секретов из Git, очень быстро начинает относиться к таким вещам серьёзно.
Инфраструктура становится взрослой не тогда, когда появляется Kubernetes. Она становится взрослой тогда, когда секреты перестают лежать в открытом виде.
Почему роль Ansible лучше установочного скрипта
Самая интересная часть проекта находится внутри роли hysteria2.
Именно здесь реализована вся логика установки, настройки и сопровождения сервера.
Вместо одного огромного скрипта проект разделён на несколько логических этапов:
- install.yml
- configure.yml
- certificates.yml
- export.yml
- cleanup.yml
Такой подход может показаться более сложным по сравнению с Bash-скриптом, но на практике он даёт огромное преимущество. Каждая задача отвечает только за свою область ответственности. Если возникает ошибка при генерации сертификатов, не нужно заново выполнять весь процесс установки. Если меняется формат экспорта клиентских конфигураций, это никак не влияет на установку пакетов или настройку сервиса.
Фактически роль превращается в набор независимых строительных блоков, которые можно переиспользовать и сопровождать отдельно друг от друга.
Особенно хорошо это ощущается спустя несколько месяцев эксплуатации. Когда открываешь роль и видишь файл configure.yml, ты сразу понимаешь, где искать генерацию конфигурации. Когда нужен экспорт пользователей - открываешь export.yml. Не нужно читать тысячу строк Bash-кода в поисках нужной функции.
Автоматическая генерация конфигурации сервера
Одной из самых полезных возможностей проекта стала автоматическая генерация конфигурационного файла Hysteria2 через шаблоны Jinja2.
Вместо ручного редактирования config.yaml используется шаблон:
listen: :{{ hysteria2_port }}
tls:
cert: {{ hysteria2_cert }}
key: {{ hysteria2_key }}
auth:
type: password
password: {{ hysteria2_password }}
Во время выполнения playbook значения автоматически подставляются из inventory и group_vars.
На первый взгляд это выглядит как мелочь. Но именно такие мелочи позволяют поддерживать десятки серверов без ручной работы. Если завтра понадобится изменить порт на всех площадках, достаточно поменять одну переменную. Если понадобится изменить параметры TLS, изменения автоматически попадут на все узлы.
Это и есть тот самый подход, который используется в больших инфраструктурных проектах. Разница только в масштабе. Принципы остаются абсолютно одинаковыми.
Разворачиваем сервер: от чистого VPS до готового VPN за несколько минут
Теперь перейдём к самой интересной части проекта. Теория - это прекрасно, красивые разговоры про Infrastructure as Code тоже выглядят замечательно на конференциях, но любого инженера в конечном итоге интересует простой вопрос: сколько времени пройдёт от покупки VPS до появления рабочего VPN-сервера.
Когда я только начинал заниматься инфраструктурой, установка любого сетевого сервиса выглядела примерно одинаково. Подключаемся по SSH. Обновляем систему. Устанавливаем пакеты. Создаём конфигурационные файлы. Исправляем ошибки в конфигурации. Перезапускаем сервис. Снова исправляем ошибки. Затем начинаем искать, какой именно сетевой экран заблокировал нужный порт. Через пару часов всё начинает работать, после чего остаётся только надеяться, что через месяц получится вспомнить последовательность действий.
В случае с этим проектом весь процесс выглядит значительно проще. После клонирования репозитория достаточно подготовить inventory, указать параметры будущего сервера и выполнить несколько команд. Дальше большую часть работы берёт на себя Ansible.
Первым делом создаём рабочее окружение:
git clone https://git.antropoff.ru/DevOpsTools/hysteria2.git
cd hysteria2
make init
Команда инициализации подготавливает окружение Ansible, устанавливает необходимые зависимости и приводит рабочую директорию в состояние, пригодное для дальнейшей эксплуатации. На первый взгляд ничего необычного не происходит, но именно такие мелкие шаги избавляют от огромного количества проблем в будущем. Когда каждый инженер запускает один и тот же процесс подготовки окружения, исчезает знаменитая категория ошибок под названием «у меня работает».
Следующим шагом необходимо описать серверы в inventory. Именно здесь определяется будущая инфраструктура.
Например:
all:
hosts:
vpn-nl:
ansible_host: 192.168.10.10
domain: vpn.example.com
В реальной жизни серверов обычно больше одного. Один узел может находиться в Нидерландах, второй в Германии, третий в Сингапуре, четвёртый в США. Самое приятное заключается в том, что для Ansible количество площадок практически не имеет значения. Все они будут обрабатываться одинаковым образом.
После подготовки inventory можно проверить доступность узлов:
make ping
Фактически эта команда выполняет стандартную проверку подключения Ansible.
Пример результата:
vpn-nl | SUCCESS => {
"changed": false,
"ping": "pong"
}
Если на этом этапе всё работает, значит SSH-доступ настроен правильно и можно переходить к установке.
Один запуск playbook вместо сотни ручных действий
Самая важная команда проекта выглядит предельно просто:
make install
С точки зрения пользователя происходит запуск playbook. С точки зрения инфраструктуры в этот момент выполняется огромный объём работы.
- Сервер получает необходимые пакеты.
- Создаются каталоги.
- Генерируются конфигурационные файлы.
- Настраивается Hysteria2.
- Создаются системные службы.
- Подготавливаются сертификаты.
- Генерируются клиентские параметры.
- Формируются QR-коды.
- Создаются HTML-файлы для подключения пользователей.
Особенно приятно наблюдать за этим процессом на совершенно чистом VPS. Вместо нескольких десятков ручных операций появляется один воспроизводимый сценарий, который можно запускать снова и снова.
Когда впервые смотришь на такой процесс после многих лет ручного администрирования, возникает ощущение небольшой инженерной магии. На самом деле никакой магии нет. Есть только дисциплина автоматизации.
Автоматический выпуск сертификатов
Одна из самых неприятных частей настройки любого современного VPN-сервера связана с сертификатами.
Когда-то всё было относительно просто. Можно было использовать самоподписанные сертификаты и не особенно переживать по этому поводу. Сегодня подобный подход быстро превращается в источник проблем. Клиенты начинают ругаться на безопасность соединения, браузеры показывают предупреждения, а некоторые приложения вообще отказываются работать.
Поэтому в ветке main используется полноценный TLS-сертификат для доменного имени сервера.
Конфигурация выглядит примерно следующим образом:
acme:
domains:
- vpn.example.com
email: admin@example.com
Во время установки сертификат выпускается автоматически.
Администратору не нужно отдельно устанавливать certbot, настраивать cron-задачи или писать собственные скрипты продления. Это особенно важно при сопровождении большого количества узлов. Когда серверов становится больше десяти, ручное управление сертификатами начинает напоминать работу садовника, который пытается подстричь лес маникюрными ножницами.
Генерация пользователей без ручного копирования ссылок
Следующая проблема, которую часто недооценивают авторы VPN-проектов, появляется после настройки сервера.
Сам сервер работает.
Порт открыт.
Сертификат выпущен.
Но теперь необходимо подключить пользователей.
В большинстве самодельных решений начинается обмен сообщениями примерно такого содержания:
Сейчас, подожди, я тебе ссылку соберу.
Через пять минут:
Кажется пароль забыл добавить.
Через десять минут:
Отправил не тот сервер.
Через пятнадцать минут:
Сейчас новый QR-код сделаю.
Подобный сценарий знаком практически каждому администратору.
В этом проекте пользователи описываются заранее:
hysteria2_users:
- iphone
- macbook
- windows
- tablet
После запуска playbook необходимые данные генерируются автоматически.
Каждый пользователь получает собственный набор файлов.
Например:
output/
├── iphone.url
├── iphone.png
├── iphone.txt
├── macbook.url
├── macbook.png
├── macbook.txt
└── index.html
В результате исчезает ручная работа по сборке URI-ссылок и созданию QR-кодов.
Особенно удобно это оказывается для семейных серверов или небольших команд. Один раз добавил пользователя в inventory, выполнил playbook и получил готовый комплект файлов для подключения.
Что находится внутри output
Каталог output заслуживает отдельного внимания.
На первый взгляд он выглядит как обычная директория с файлами. На практике именно здесь заканчивается вся работа автоматизации и начинается пользовательский опыт.
После завершения установки появляются:
-
текстовые конфигурации;
-
URI-ссылки;
-
QR-коды;
-
HTML-каталог пользователей;
-
вспомогательные данные подключения.
Фактически проект автоматически создаёт небольшой портал выдачи доступов.
Вместо длинных инструкций вида «установи приложение, открой настройки, вставь эту строку, затем отсканируй вот этот QR-код» пользователь получает готовый набор файлов, который можно сразу импортировать в клиентское приложение.
Именно такие детали обычно отличают инженерный проект от набора скриптов. Настроить сервер способен практически любой опытный администратор. Сделать так, чтобы его было удобно сопровождать через год и удобно использовать конечным пользователям, значительно сложнее.
Почему здесь появился Makefile
Отдельно хочется сказать несколько слов про Makefile.
Некоторые инженеры считают использование Makefile в инфраструктурных проектах избыточным. Формально они правы. Все команды можно запускать напрямую через ansible-playbook.
Например:
ansible-playbook \
-i inventory \
playbooks/install.yml
Но проблема заключается в человеческом факторе.
Через несколько месяцев никто не помнит полный набор параметров запуска.
Зато команды вида:
make ping
make install
make export
запоминаются практически мгновенно.
Поэтому Makefile в данном проекте выполняет роль удобной точки входа для повседневной эксплуатации. Новому инженеру не нужно изучать внутреннее устройство Ansible с первого дня. Достаточно понять несколько базовых команд и можно начинать работать.
Хорошая автоматизация не только выполняет работу за администратора. Она ещё и уменьшает количество информации, которую нужно держать в голове.
Main против Salamander: две стратегии эксплуатации Hysteria2
Когда люди впервые открывают репозиторий и видят две отдельные ветки - main и salamander, - первый вопрос обычно звучит одинаково:
Почему просто не сделать переключатель через переменную?
Вопрос абсолютно логичный. Более того, если смотреть исключительно глазами разработчика, такое решение действительно кажется более правильным. Можно было добавить переменную obfuscation_mode, написать несколько условий в шаблонах и получить один универсальный playbook.
Но чем больше я работал с различными VPN-решениями, тем больше приходил к выводу, что здесь речь идёт не о разных настройках одного и того же сервиса. Здесь речь идёт о двух разных философиях эксплуатации.
Ветка main предназначена для ситуаций, когда сервер должен выглядеть максимально естественно и не привлекать к себе лишнего внимания. Ветка salamander, наоборот, создавалась для сценариев, где требуется дополнительная обработка транспортного потока и альтернативный режим работы протокола. Формально обе ветки разворачивают Hysteria2. Практически же это два разных подхода к взаимодействию с внешним миром.
Именно поэтому они существуют отдельно. Когда через полгода открываешь репозиторий, гораздо проще понимать, что перед тобой полноценный режим работы со своей логикой, а не набор условных операторов внутри шаблонов, которые уже никто не помнит.
Ветка Main: сервер притворяется обычным сайтом
Начнём с варианта, который я считаю базовым для большинства пользователей.
После установки сервер выглядит как самый обычный веб-сайт. Если открыть доменное имя через браузер, пользователь увидит стандартную страницу nginx или подготовленную статическую заглушку. Если выполнить проверку сертификата, всё тоже будет выглядеть абсолютно легитимно.
Конфигурация содержит следующий блок:
masquerade:
type: file
file:
dir: /var/www/html
Смысл этой настройки довольно простой. Когда кто-то обращается к серверу как к обычному веб-ресурсу, он получает содержимое каталога с веб-страницей. Никаких ошибок. Никаких подозрительных ответов. Никаких признаков того, что перед нами VPN-шлюз.
Для внешнего наблюдателя такой сервер выглядит примерно так же, как тысячи других веб-сайтов в интернете.
Можно выполнить обычную проверку:
curl https://vpn.example.com
И получить вполне ожидаемый результат:
<html>
<head>
<title>Welcome</title>
</head>
<body>
It works
</body>
</html>
На первый взгляд кажется, что здесь вообще нет ничего интересного. Но именно это и является главной целью. Такой подход позволяет использовать один сервер одновременно как обычный веб-ресурс и как точку подключения Hysteria2. Для внешних проверок сервер выглядит как стандартный HTTPS-узел, что соответствует его фактической конфигурации.
Особенно хорошо такой подход работает для домашних серверов, небольших команд и личного использования. В большинстве случаев этого оказывается более чем достаточно.
Автоматический Let's Encrypt вместо самоподписанных сертификатов
Ещё одной важной особенностью ветки main является автоматическое получение настоящих TLS-сертификатов.
В интернете до сих пор можно встретить огромное количество инструкций, предлагающих использовать самоподписанные сертификаты.
Технически это работает.
Практически это превращается в бесконечную борьбу с предупреждениями безопасности.
Поэтому сервер автоматически выпускает сертификат для указанного домена:
acme:
domains:
- vpn.example.com
email: admin@example.com
После завершения установки можно проверить сертификат обычной командой:
openssl s_client \
-connect vpn.example.com:443
В ответ будет показан полностью валидный сертификат.
Это создаёт очень важный эффект. Сервер становится неотличим от огромного количества легитимных HTTPS-сайтов, работающих по всему миру.
Именно поэтому ветка main остаётся вариантом по умолчанию.
Но есть проблема
К сожалению, современные сетевые среды используют всё более сложные механизмы анализа соединений.
Если раньше было достаточно звернуть данные в HTTPS, сегодня некоторые сети применяют дополнительные механизмы классификации трафика, поведение клиентов, особенности протокола и множество других параметров.
Именно здесь начинаются сценарии, ради которых появилась ветка salamander.
Однажды мне пришлось переносить пользователей между несколькими площадками после очередных изменений в сетевой инфраструктуре одного из провайдеров. Самое забавное заключалось в том, что обычные сайты на сервере продолжали прекрасно открываться, сертификаты выглядели идеально, а вот VPN-трафик периодически начинал испытывать странные проблемы. В такие моменты очень быстро понимаешь, что транспортный режим сайта и транспортный режим самого сетевого поведения - это две разные задачи.
Salamander: когда трафик начинает притворяться шумом
Ветка salamander использует совершенно другой подход.
Здесь основной акцент делается уже не на веб-сайте и не на сертификате, а непосредственно на преобразовании сетевого трафика.
В конфигурации появляется новый блок:
obfs:
type: salamander
salamander:
password: my-secret-password
На первый взгляд это выглядит как обычный пароль.
На практике всё гораздо интереснее.
Этот секрет используется для преобразования передаваемых данных таким образом, чтобы сетевой поток перестал иметь характерные признаки Hysteria2.
Проще говоря, трафик начинает выглядеть значительно менее узнаваемым.
Если в режиме main основной задачей является создание максимально естественного сервера, то в режиме salamander основная задача заключается в том, чтобы изменить характеристики транспортного потока по сравнению со стандартным режимом работы.
Поэтому клиентская ссылка тоже становится немного другой.
Например:
hysteria2://password@vpn.example.com:443
?sni=vpn.example.com
&obfs=salamander
&obfs-password=my-secret-password
Появляется дополнительный параметр транспортного режима, который должен знать клиент.
Это небольшая цена за дополнительный уровень обработки трафика.
Почему Salamander не стал режимом по умолчанию
После знакомства с Salamander многие задают следующий вопрос:
Если он такой хороший, почему не использовать его всегда?
Причина довольно простая. Любая дополнительная технология увеличивает сложность системы. Появляется дополнительный пароль. Появляется дополнительный параметр в конфигурации клиента. Усложняется диагностика проблем. Усложняется поддержка пользователей.
Для обычного домашнего сервера это зачастую совершенно не нужно.
Если инфраструктура работает стабильно в режиме main, нет никакого смысла усложнять её только ради того, чтобы использовать более стабильную версию сетевого протокола.
Это примерно как покупать бронеавтомобиль для поездок в соседний супермаркет. Технически возможно. Практически пользы немного.
Что я использую сам
Если говорить о личных предпочтениях, то в большинстве случаев я начинаю именно с ветки main.
Она проще.
Она понятнее.
Она удобнее в сопровождении.
Она выглядит как абсолютно нормальный HTTPS-сервер.
Для большинства пользователей на этом история и заканчивается.
Но когда появляются специфические условия эксплуатации, нестандартные сети или признаки того, что обычного режима работы протокола уже недостаточно, именно тогда появляется смысл смотреть в сторону salamander.
Самое приятное заключается в том, что обе стратегии уже реализованы внутри проекта. Не нужно переписывать конфигурации вручную. Не нужно экспериментировать на боевых серверах. Достаточно выбрать подходящую ветку и развернуть инфраструктуру тем способом, который лучше соответствует вашим условиям эксплуатации.
Хорошая инфраструктура - это не та, которая использует самые модные технологии. Хорошая инфраструктура - это та, которая позволяет выбирать нужный инструмент под конкретную ситуацию.
Разбор роли Hysteria2 по файлам: install.yml, configure.yml, export.yml и генерация QR-кодов
Когда смотришь на большинство проектов по автоматизации VPN, довольно быстро замечаешь характерную особенность. Обычно вся логика находится в одном огромном сценарии. В начале устанавливаются пакеты, затем создаются каталоги, потом генерируются конфигурации, после этого выпускаются сертификаты, а в самом конце где-нибудь между шестисотой и семисотой строкой внезапно появляется генерация пользовательских ссылок. Такой подход может работать для небольшого скрипта, но очень плохо масштабируется с точки зрения сопровождения.
В этом проекте логика разделена на независимые части. На первый взгляд это может показаться незначительной архитектурной деталью, но именно такие решения позволяют сопровождать инфраструктуру спустя месяцы и даже годы после первоначальной разработки. Когда каждая задача находится в собственном файле и отвечает только за одну область ответственности, становится намного проще понимать происходящее и вносить изменения без риска сломать половину системы.
Если посмотреть на структуру роли, можно увидеть классическую организацию Ansible-проекта:
roles/
└── hysteria2
├── defaults
├── files
├── handlers
├── tasks
├── templates
└── vars
На самом деле именно внутри этих каталогов и живёт вся магия проекта. Причём магия вполне инженерная, без шаманства и заклинаний.
Install.yml: превращаем чистый VPS в готовую платформу
Первый этап работы начинается с установки необходимых компонентов.
Многие администраторы привыкли выполнять подобные действия вручную:
apt update
apt install nginx
apt install curl
apt install qrencode
На одном сервере такой подход ещё терпим. На десяти серверах он начинает раздражать. На пятидесяти становится катастрофой.
Поэтому роль автоматически приводит систему к нужному состоянию. Ansible проверяет наличие пакетов, устанавливает отсутствующие зависимости и гарантирует одинаковый результат независимо от того, сколько раз выполняется playbook.
Это один из фундаментальных принципов Ansible, который многие новички недооценивают.
Если Bash-скрипт отвечает на вопрос:
Что нужно сделать?
то Ansible отвечает на другой вопрос:
В каком состоянии должна находиться система?
Разница кажется незначительной, пока не начинаешь сопровождать инфраструктуру годами.
Например:
- name: Install required packages
apt:
name:
- nginx
- qrencode
- curl
state: present
Такое описание не пытается установить пакет любой ценой. Оно просто говорит системе, что пакет должен присутствовать. Каким именно способом это будет достигнуто, Ansible решит самостоятельно.
Configure.yml: самая важная часть всей системы
После подготовки сервера начинается формирование конфигурации Hysteria2.
Именно здесь проект превращается из набора установочных сценариев в полноценную систему управления VPN-инфраструктурой.
Вместо ручного редактирования конфигурационных файлов используется шаблонизация через Jinja2.
Например:
listen: :{{ hysteria2_port }}
tls:
cert: {{ hysteria2_cert }}
key: {{ hysteria2_key }}
auth:
type: password
masquerade:
type: file
На первый взгляд это выглядит как обычная подстановка переменных.
Но именно здесь появляется одна из главных сильных сторон проекта.
Представим ситуацию, что через полгода вы решили перенести все серверы на другой порт.
В традиционной схеме пришлось бы подключаться к каждому узлу и вручную редактировать конфигурацию.
В данном проекте достаточно изменить одну переменную:
hysteria2_port: 8443
После следующего запуска playbook изменение автоматически распространится на всю инфраструктуру.
Такая мелочь кажется незначительной ровно до того момента, пока количество серверов не перевалит за десяток.
Переменные как язык описания инфраструктуры
Одна из вещей, которая мне особенно понравилась в проекте, заключается в том, что почти все параметры вынесены в переменные.
Например:
hysteria2_domain: vpn.example.com
hysteria2_port: 443
hysteria2_users:
- iphone
- macbook
- windows
Фактически inventory начинает превращаться в декларативное описание инфраструктуры.
Инженер больше не думает о том, какие команды необходимо выполнить.
Он описывает желаемое состояние системы.
Именно по такому принципу работают Kubernetes, Terraform, ArgoCD и большинство современных платформ автоматизации.
На самом деле между описанием Hysteria2-сервера и описанием Kubernetes-кластера намного больше общего, чем может показаться на первый взгляд.
Export.yml: самая недооценённая часть проекта
Если спросить администратора, какая часть VPN-сервера является самой важной, большинство назовёт конфигурацию или сертификаты.
На практике огромное количество времени тратится совершенно на другую задачу - передачу параметров подключения пользователям.
Именно поэтому в проекте появился отдельный механизм экспорта.
После завершения установки автоматически формируются:
output/
├── user.url
├── user.txt
├── user.png
├── user.qr.txt
└── index.html
Каждый файл решает собственную задачу.
Текстовый файл содержит параметры подключения.
Файл URL содержит готовую ссылку импорта.
PNG-файл представляет собой QR-код.
HTML позволяет открыть каталог пользователей прямо через браузер.
На первый взгляд это выглядит как косметическое улучшение.
На практике именно экспорт экономит больше всего времени при сопровождении.
Однажды мне пришлось раздавать конфигурации нескольким десяткам пользователей после миграции инфраструктуры между площадками. Если бы пришлось вручную генерировать ссылки и QR-коды, работа заняла бы несколько часов. Автоматизация выполнила всё примерно за минуту.
Как формируются URI-ссылки
Внутри проекта автоматически создаются ссылки вида:
hysteria2://PASSWORD@vpn.example.com:443
?sni=vpn.example.com
#iphone
Для пользователя это выглядит как обычная строка.
Для клиента Hysteria2 это полноценная конфигурация подключения.
Фактически одна такая ссылка содержит:
-
адрес сервера;
-
порт;
-
параметры аутентификации;
-
настройки TLS;
-
дополнительные параметры подключения.
В результате пользователь может импортировать конфигурацию буквально одним нажатием кнопки.
Это значительно снижает количество ошибок при подключении.
Любой инженер поддержки знает простую истину: чем меньше действий требуется выполнить пользователю, тем меньше тикетов попадёт в очередь поддержки.
Генерация QR-кодов
Отдельного упоминания заслуживает автоматическая генерация QR-кодов.
Многие современные клиенты Hysteria2 умеют импортировать конфигурацию напрямую через камеру смартфона.
Проект автоматически формирует изображение для каждого пользователя.
Условно это выглядит так:
qrencode \
-o iphone.png \
"$(cat iphone.url)"
Разумеется, внутри роли всё организовано значительно аккуратнее, но логика остаётся именно такой.
После завершения установки пользователь получает готовый PNG-файл.
- Открываем клиент.
- Нажимаем импорт.
- Сканируем QR-код.
- Подключение готово.
Никакого копирования длинных строк. Никаких ошибок в паролях. Никаких случайно пропущенных символов.
Генерация HTML-каталога
Пожалуй, самой неожиданной находкой для меня оказался HTML-каталог пользователей. Обычно проекты заканчиваются генерацией конфигурационных файлов. Здесь же дополнительно формируется полноценная страница со всеми подключениями.
Примерно так:
<html>
<body>
<h2>iphone</h2>
<img src="iphone.png">
<a href="iphone.url">
Download
</a>
</body>
</html>
Казалось бы, мелочь. Но именно такие мелочи превращают инфраструктурный инструмент в законченный продукт.
Когда открываешь каталог и сразу видишь всех пользователей, QR-коды и ссылки подключения, необходимость в отдельной панели управления практически исчезает.
Для небольших команд, домашних серверов и личного использования этого более чем достаточно.
Почему всё это важно
Если посмотреть на проект целиком, становится понятно, что основная ценность заключается вовсе не в установке Hysteria2. Установить бинарник способен любой инженер за несколько минут. Настоящая сложность начинается позже. Когда появляются пользователи. Когда нужно выпускать новые конфигурации. Когда приходится сопровождать несколько площадок. Когда инфраструктура начинает жить собственной жизнью.
Именно поэтому роль получилась значительно больше обычного установочного сценария. Она автоматизирует не только развёртывание сервера, но и весь последующий жизненный цикл эксплуатации. А это уже совсем другой уровень зрелости проекта.
Хороший VPN-сервер - это не тот, который удалось установить. Хороший VPN-сервер - это тот, который через год всё ещё удобно сопровождать.
Почему этот проект лучше популярных one-click установщиков из GitHub и Telegram
Сейчас я скажу вещь, которая многим может не понравиться. Большинство популярных VPN-установщиков из GitHub являются плохим инженерным решением. Не потому что они написаны плохо и не потому что их авторы не понимают, что делают. Более того, многие из этих проектов действительно помогают тысячам людей быстро получить рабочий VPN-сервер буквально за несколько минут. Проблема находится совершенно в другой плоскости. Практически все подобные решения ориентированы на момент установки, а не на последующие годы эксплуатации. Они прекрасно отвечают на вопрос «как быстро запустить сервис», но практически никогда не отвечают на вопрос «как этот сервис сопровождать через год, когда его создатель уже забыл половину настроек».
Достаточно открыть практически любой популярный репозиторий с VPN-автоматизацией. Чаще всего пользователя встречает команда вида:
curl -fsSL https://example.com/install.sh | bash
После её выполнения происходит маленькое чудо. Скрипт устанавливает пакеты, открывает порты, создаёт пользователей, генерирует сертификаты, настраивает службы и выводит красивое сообщение о том, что сервер готов к работе. Пользователь доволен, автор репозитория доволен, звёздочки на GitHub продолжают расти. На этом этапе всё действительно выглядит замечательно. Проблемы появляются позже, когда возникает необходимость что-то изменить. Нужно обновить сервер до новой версии. Нужно перенести его на другой VPS. Нужно поднять ещё один узел в другой стране. Нужно разобраться, какие параметры были изменены вручную несколько месяцев назад. Нужно восстановить инфраструктуру после аварии. И вот в этот момент внезапно выясняется, что единственным источником правды является сам работающий сервер.
Я много лет работаю с инфраструктурой и за это время несколько раз принимал системы, которые строились именно таким образом. Картина практически всегда повторялась. Есть сервер. Есть набор конфигурационных файлов. Есть какой-нибудь install.sh, который запускался когда-то очень давно. Иногда есть README, который уже не соответствует реальности. При этом никто точно не знает, какие параметры были изменены после установки, какие файлы редактировались вручную и какие изменения были внесены непосредственно на сервере через SSH. Самое неприятное начинается во время миграции. Вместо воспроизводимого процесса появляется настоящий квест с чтением конфигураций, сравнением файлов и попытками восстановить историческую последовательность действий администратора.
Именно поэтому мне никогда не нравились магические установщики. Они очень удобны в момент запуска, но создают технический долг, который приходится оплачивать позже. По сути они превращают сервер в главный источник знаний об инфраструктуре. Чтобы узнать настройки, нужно идти на сервер. Чтобы узнать пользователей, нужно идти на сервер. Чтобы понять историю изменений, снова нужно идти на сервер. Если такой VPS внезапно погибнет вместе с диском, часть информации может исчезнуть вместе с ним. В современном инфраструктурном мире это считается плохой практикой. Источник правды должен находиться не на сервере, а в системе контроля версий.
В моём проекте ситуация устроена ровно наоборот. Сервер не является чем-то особенным. Он представляет собой всего лишь результат выполнения кода. Все настройки находятся в inventory. Все параметры описаны в переменных. Секреты лежат в Ansible Vault. История изменений хранится в Git. Если завтра VPS исчезнет, мне не придётся заниматься археологией. Я просто арендую новый сервер, выполняю playbook и получаю идентичную систему. Именно по такому принципу давно работают Kubernetes-кластеры, облачная инфраструктура, платформы непрерывной доставки и большинство крупных инженерных команд. Честно говоря, я вообще не вижу причин, почему VPN должен управляться каким-то другим способом.
Отдельно стоит поговорить о популярных веб-панелях вроде 3x-ui, Marzban и многочисленных производных проектах. Я прекрасно понимаю их популярность. Они удобны. Они позволяют создавать пользователей через браузер. Они показывают статистику. Они избавляют администратора от необходимости разбираться в конфигурационных файлах. Для многих сценариев это действительно хороший выбор. Но здесь возникает интересный парадокс. Для управления VPN появляется ещё один серверный сервис, который сам требует сопровождения. У него есть собственная база данных, собственный веб-интерфейс, собственные механизмы резервного копирования, собственные обновления и собственные потенциальные уязвимости. В какой-то момент начинаешь понимать, что поддерживаешь уже не VPN-сервер, а инфраструктуру для управления VPN-сервером.
Именно поэтому мне ближе подход через Git и Ansible. Когда нужно понять, кто изменил конфигурацию, я открываю историю коммитов. Когда нужно откатить неудачное изменение, использую стандартный механизм Git. Когда нужно сравнить две версии конфигурации, выполняю обычный diff. Я получаю все преимущества системы контроля версий без дополнительной базы данных, без панели управления и без отдельного слоя инфраструктуры. Более того, такой подход прекрасно масштабируется. Разница между одним сервером и десятью серверами практически исчезает, потому что управление осуществляется одинаковым способом.
Здесь у меня есть одно довольно непопулярное мнение. На мой взгляд, большинство современных VPN-панелей решают неправильную задачу. Они помогают быстро установить сервис, но почти не помогают сопровождать инфраструктуру годами. А ведь именно эксплуатация занимает подавляющую часть жизненного цикла любой системы. Установка выполняется один раз. Миграция может происходить раз в несколько месяцев. А сопровождение продолжается ежедневно. Поэтому я всегда оцениваю инфраструктурные проекты не по скорости первоначального запуска, а по тому, насколько удобно с ними работать спустя полгода. И именно здесь подход Infrastructure as Code начинает показывать свои настоящие преимущества.
Когда меня спрашивают, зачем использовать Ansible для одного-единственного VPS, я обычно отвечаю очень просто. Сегодня у вас один сервер. Через несколько месяцев появляется второй. Потом третий. Затем возникают резервные площадки, тестовые стенды, новые пользователи и дополнительные домены. Инфраструктура почти всегда растёт быстрее, чем ожидает её владелец. Поэтому хорошие практики выгоднее внедрять заранее, пока система ещё маленькая и управляемая. Парадокс заключается в том, что автоматизация нужна не тогда, когда серверов стало сто. Автоматизация нужна тогда, когда серверов ещё мало и есть возможность построить всё правильно с самого начала.
Именно поэтому главная ценность этого проекта заключается даже не в Hysteria2. Сам Hysteria2 можно установить за несколько минут. Настоящая ценность заключается в воспроизводимости. Любой сервер можно пересоздать. Любую конфигурацию можно восстановить. Любое изменение можно отследить через Git. Любую ошибку можно откатить. Любую площадку можно перенести на новый VPS без ручной магии и без надежды на то, что кто-то ещё помнит историю первоначальной настройки. А это уже принципиально другой уровень зрелости инфраструктуры.
Реальный сценарий эксплуатации: три VPS, несколько стран и единая VPN-инфраструктура
До этого момента мы в основном говорили про архитектуру, автоматизацию и преимущества Infrastructure as Code. Всё это звучит красиво, но любой инженер прекрасно знает старую истину: настоящая проверка начинается не на тестовом стенде, а в реальной эксплуатации. Именно поэтому давайте рассмотрим вполне жизненный сценарий, ради которого подобный проект и создавался.
Представим, что у нас есть три VPS в разных странах. Например, один сервер расположен в Нидерландах, второй в Германии, а третий в Сингапуре. Такая схема встречается значительно чаще, чем может показаться. Один узел используется как основной, второй служит резервной площадкой, а третий нужен для работы с ресурсами, расположенными в азиатском регионе. Разумеется, никто не хочет вручную подключаться к каждому серверу и выполнять одинаковые действия. Именно здесь начинает раскрываться главный смысл автоматизации.
В inventory такая инфраструктура выглядит предельно просто:
all:
hosts:
vpn-nl:
ansible_host: 10.10.10.10
hysteria2_domain: nl.example.com
vpn-de:
ansible_host: 20.20.20.20
hysteria2_domain: de.example.com
vpn-sg:
ansible_host: 30.30.30.30
hysteria2_domain: sg.example.com
Обратите внимание на важную деталь. Независимо от количества серверов логика управления остаётся одинаковой. Для Ansible нет принципиальной разницы между одним узлом и несколькими десятками. Именно поэтому подобный подход так хорошо масштабируется. Когда инфраструктура начинает расти, администратор не меняет процесс работы. Он просто добавляет новые описания в inventory.
Следующим шагом определяются пользователи. Причём речь может идти не только о людях. Очень часто отдельные конфигурации создаются для ноутбуков, телефонов, планшетов, домашних маршрутизаторов или даже виртуальных машин. Например:
hysteria2_users:
- iphone
- macbook
- ipad
- home-router
- work-laptop
После этого остаётся выполнить установку:
make install
На первый взгляд команда выглядит слишком простой для такого объёма работы. Однако именно за этой простотой скрывается главное преимущество автоматизации. Пока администратор пьёт кофе, Ansible подключается ко всем серверам, устанавливает Hysteria2, создаёт конфигурации, выпускает сертификаты, настраивает службы, генерирует пользовательские ссылки и подготавливает QR-коды. В результате через несколько минут появляется полностью готовая распределённая инфраструктура.
Особенно хорошо преимущества такого подхода становятся заметны во время масштабирования. Допустим, через несколько месяцев появляется необходимость добавить ещё одну площадку. В традиционной схеме пришлось бы повторять весь процесс вручную. Новый VPS, новые сертификаты, новые настройки, новые пользователи. В случае с этим проектом всё сводится к добавлению ещё одной записи в inventory. Фактически новый сервер становится просто ещё одной строкой в репозитории. Это очень похоже на работу с Kubernetes, где добавление нового узла в кластер является обычной рутинной операцией, а не отдельным проектом на несколько дней.
После завершения установки формируется каталог output. Именно здесь начинается самая приятная часть всей истории. Вместо набора непонятных конфигурационных файлов появляется готовый комплект данных для пользователей:
output/
├── iphone.url
├── iphone.png
├── iphone.txt
├── macbook.url
├── macbook.png
├── macbook.txt
├── ipad.url
├── ipad.png
├── ipad.txt
└── index.html
Если честно, именно этот каталог мне нравится больше всего. В инфраструктурном мире огромное количество времени уходит на передачу параметров доступа конечным пользователям. Настроить сервер относительно легко. Намного сложнее организовать процесс выдачи конфигураций так, чтобы люди не задавали одинаковые вопросы каждые пять минут. Здесь же после завершения установки администратор получает полностью готовый набор файлов, который можно просто отправить пользователю.
Отдельно стоит отметить HTML-каталог. Многие подобные проекты ограничиваются генерацией конфигураций и QR-кодов. Здесь же автоматически формируется небольшая веб-страница, которая позволяет быстро получить доступ ко всем подготовленным подключениям. На первый взгляд это может показаться косметической функцией, но на практике именно такие мелочи экономят огромное количество времени. Когда пользователей становится много, удобство начинает играть не меньшую роль, чем технические характеристики самого VPN.
Однажды мне пришлось сопровождать инфраструктуру, где доступы к VPN выдавались через обычные текстовые инструкции в корпоративном мессенджере. Каждый новый сотрудник получал длинное сообщение с конфигурацией, ссылками, пояснениями и скриншотами. Через несколько месяцев чат превратился в музей старых конфигураций, в котором никто уже не мог понять, какая версия является актуальной. После перехода на автоматическую генерацию конфигураций проблема исчезла практически полностью. Пользователь просто открывал нужную страницу, скачивал файл или сканировал QR-код и сразу подключался к серверу.
Именно в таких сценариях становится понятно, что этот проект решает значительно более широкую задачу, чем простая установка Hysteria2. По сути он создаёт законченный жизненный цикл эксплуатации VPN-инфраструктуры. Серверы разворачиваются автоматически. Конфигурации генерируются автоматически. Пользователи получают готовые данные для подключения автоматически. А администратор вместо постоянной ручной работы занимается тем, чем и должен заниматься инженер - развитием системы, а не бесконечным копированием строк между терминалом и мессенджером.
Хорошая автоматизация начинается не тогда, когда сервер устанавливается одной командой. Она начинается тогда, когда через год эксплуатации количество ручных действий стремится к нулю.
Что бы я добавил в следующей версии проекта: кластеризация, Telegram-бот, API и GitOps
Любой инфраструктурный проект проходит примерно одинаковый путь развития. Сначала появляется решение конкретной задачи. Затем выясняется, что задачу приходится решать регулярно. После этого появляется автоматизация. А затем неожиданно приходит понимание, что автоматизация сама превращается в отдельный продукт со своими требованиями, пользователями и сценариями эксплуатации. Именно в этой точке, как мне кажется, сейчас находится данный проект.
Если смотреть на него как на инструмент для личного использования, то текущей функциональности уже более чем достаточно. Серверы устанавливаются автоматически, сертификаты выпускаются автоматически, пользователи генерируются автоматически, QR-коды и ссылки формируются без участия администратора. Для большинства домашних и небольших командных сценариев этого хватит на годы вперёд. Но если посмотреть на проект глазами инженера, который привык мыслить инфраструктурой, сразу начинают появляться идеи дальнейшего развития.
Первое направление, которое напрашивается практически само собой, связано с управлением несколькими площадками. Сейчас роль отлично умеет разворачивать множество серверов одновременно, но выбор конкретного узла остаётся задачей пользователя. Например, сегодня человек подключился к серверу в Нидерландах, завтра решил использовать Германию, а через неделю переключился на Сингапур. Для небольшого количества узлов это не проблема. Но если площадок становится десять, двадцать или тридцать, появляется желание централизованно управлять маршрутизацией и распределением пользователей между серверами.
В подобной архитектуре можно было бы реализовать автоматическое формирование профилей для нескольких площадок одновременно. Пользователь импортирует один QR-код и получает сразу набор серверов. Если один узел оказывается недоступен, клиент автоматически переключается на резервный. Такой подход давно используется в корпоративных системах, но довольно редко встречается в самодельных VPN-инфраструктурах. Между тем именно резервирование обычно становится первой серьёзной задачей после успешного запуска проекта.
Не менее интересным направлением выглядит интеграция с Telegram. Большинство домашних VPN-сервисов сопровождаются через обычные сообщения. Кто-то просит добавить нового пользователя. Кто-то потерял QR-код. Кто-то купил новый телефон и хочет получить конфигурацию заново. Сейчас для этого требуется изменить inventory и повторно выполнить playbook. Это совершенно нормально с точки зрения инфраструктурного подхода, но некоторые операции вполне можно автоматизировать.
Например, Telegram-бот мог бы работать поверх существующей роли и выполнять строго определённые действия. Создание пользователя, отзыв доступа, повторная генерация QR-кода, получение списка площадок или просмотр состояния серверов. Причём вся логика по-прежнему оставалась бы внутри Git и Ansible. Бот становился бы лишь дополнительным интерфейсом управления, а не отдельной системой со своей жизнью. Такой подход выглядит значительно привлекательнее большинства существующих веб-панелей, которые постепенно начинают дублировать функциональность самой инфраструктуры.
Ещё одно направление развития связано с полноценным API. Сейчас проект прекрасно подходит для ручной эксплуатации, но если представить себе небольшую организацию или команду из нескольких десятков человек, появляется потребность интеграции с другими системами. Например, пользователь появляется в корпоративном каталоге - автоматически создаётся VPN-профиль. Пользователь увольняется - доступ автоматически отзывается. Появляется новый ноутбук - система самостоятельно генерирует конфигурацию и публикует её в защищённом хранилище. Всё это вполне укладывается в современный подход к автоматизации инфраструктуры.
Особенно интересной мне кажется идея перехода к полноценной модели GitOps. Если задуматься, проект уже находится очень близко к этой концепции. Практически все изменения выполняются через Git. Все параметры описываются декларативно. Инфраструктура хранится в репозитории. Не хватает буквально одного шага - автоматической синхронизации изменений. Представьте себе ситуацию, когда добавление нового пользователя выглядит как обычный pull request. Инженер создаёт ветку, добавляет запись в inventory, проходит проверку изменений и после слияния конфигурация автоматически разворачивается на всех площадках. По сути это тот же подход, который сегодня используется для Kubernetes-кластеров через ArgoCD и FluxCD.
Отдельного внимания заслуживает вопрос мониторинга. Сейчас проект намеренно остаётся максимально простым и сосредоточенным на своей основной задаче. Но если инфраструктура начинает расти, рано или поздно появляется желание видеть состояние серверов централизованно. Сколько пользователей подключено. Какие узлы наиболее загружены. Где заканчивается место на диске. Когда истекают сертификаты. Какие площадки испытывают проблемы с доступностью. Подобная информация могла бы автоматически экспортироваться в Prometheus, а затем визуализироваться через Grafana. В результате VPN-инфраструктура стала бы полноценным компонентом общей системы наблюдаемости.
Здесь многие читатели могут возразить, что подобные идеи постепенно превращают относительно простой проект в полноценную платформу. И они будут правы. Именно поэтому я считаю важным сохранять баланс между функциональностью и сложностью. Одна из причин, по которой мне нравится текущая архитектура, заключается в её прозрачности. Любой инженер может открыть репозиторий и буквально за несколько минут понять, как работает система. Это качество очень легко потерять, если бесконтрольно добавлять новые функции. Поэтому любые дальнейшие улучшения должны оставаться продолжением основной идеи проекта, а не превращать его в очередной монолитный комбайн.
На мой взгляд, самая сильная сторона текущего решения заключается именно в сочетании простоты и зрелых инфраструктурных практик. Здесь нет тяжёлой панели управления, нет базы данных на сотни таблиц, нет отдельного сервера авторизации и нет бесконечного количества зависимостей. Есть Git, Ansible, Hysteria2 и понятный воспроизводимый процесс. И если проект будет развиваться дальше, мне хотелось бы сохранить именно эту философию. Потому что хороший инфраструктурный инструмент должен решать задачи инженера, а не создавать новые.
Самая опасная стадия развития любого проекта наступает тогда, когда он начинает нравиться своему автору больше, чем пользователям. В этот момент появляется соблазн добавить ещё десять функций, которые никто не просил. Хорошая инженерия заключается не только в том, чтобы создавать возможности, но и в том, чтобы вовремя остановиться.
Итоги: почему я больше не устанавливаю VPN вручную
Если попытаться сформулировать главную мысль этой статьи одной фразой, то она будет звучать довольно просто: проблема давно перестала заключаться в установке VPN. За последние годы появилось огромное количество отличных технологий. WireGuard, OpenVPN, Hysteria2, AmneziaVPN и десятки других проектов позволяют буквально за несколько минут получить рабочий туннель и начать пользоваться интернетом через удалённую площадку. Сам по себе запуск сервиса сегодня уже не является сложной инженерной задачей. Настоящие сложности начинаются позже, когда появляется второй сервер, затем третий, затем новые пользователи, новые устройства, новые площадки и необходимость сопровождать всё это хозяйство без ежедневных походов по SSH.
Именно поэтому при создании этого проекта я изначально смотрел не на процесс установки, а на весь жизненный цикл эксплуатации. Мне хотелось получить систему, которую можно хранить в Git, разворачивать на новых VPS за считанные минуты, переносить между площадками, документировать через код и сопровождать спустя годы без археологических экспедиций по конфигурационным файлам. В результате Hysteria2 оказался лишь одним из компонентов решения. Безусловно, очень важным компонентом. Но всё же только компонентом. Настоящая ценность появилась благодаря сочетанию Hysteria2, Ansible, шаблонов Jinja2, Vault, автоматической генерации конфигураций и воспроизводимого процесса управления инфраструктурой.
Отдельно мне нравится тот факт, что проект не навязывает единственный сценарий эксплуатации. Именно поэтому появились две полноценные ветки. Ветка main отлично подходит для большинства повседневных задач. Она позволяет развернуть сервер, который выглядит как обычный HTTPS-сайт, автоматически получает сертификаты и практически не требует дополнительного внимания. Ветка salamander ориентирована на ситуации, когда хочется получить дополнительный уровень сетевого протокола трафика и использовать возможности обфускации Hysteria2. При этом обе стратегии разворачиваются одинаковым способом и используют одну и ту же инфраструктурную модель управления. По сути пользователь выбирает не набор настроек, а философию эксплуатации сервера.
Во время работы над проектом я неожиданно пришёл ещё к одному выводу. Очень многие инструменты в мире VPN развиваются вокруг конечного пользователя. Авторы стараются сделать установку максимально простой, добавляют новые кнопки, панели управления и мастера настройки. В этом нет ничего плохого. Но зачастую при таком подходе забывают про администратора, которому предстоит жить с этой системой следующие несколько лет. Именно поэтому в проекте так много внимания уделяется воспроизводимости, Git, автоматизации и управлению изменениями. Красивый интерфейс можно добавить в любой момент. А вот возможность полностью пересоздать инфраструктуру из репозитория спустя два года - это уже значительно более ценное качество.
Однажды мне довелось восстанавливать VPN-инфраструктуру после весьма неприятной аварии у одного из провайдеров. Часть серверов пришлось переносить на новые площадки практически в авральном режиме. В тот момент особенно хорошо понимаешь разницу между системой, которая существует только внутри конкретного VPS, и системой, которая описана в коде. В первом случае начинается стресс, поиск старых инструкций и попытки вспомнить исторические настройки. Во втором случае открывается репозиторий, меняются IP-адреса, запускается playbook и инфраструктура возвращается к жизни. Именно такие ситуации лучше всего показывают настоящую ценность Infrastructure as Code.
Наверное, самое приятное в этом проекте заключается в том, что он остаётся достаточно простым. Здесь нет тяжёлых панелей управления, сложных баз данных и десятков взаимозависимых сервисов. Есть Ansible, есть Hysteria2, есть понятная структура репозитория и есть набор сценариев, которые выполняют свою работу предсказуемо и воспроизводимо. В мире, где многие инструменты постепенно превращаются в огромные монолиты, такая простота начинает восприниматься как отдельное преимущество.
Если несколько лет назад для меня VPN был просто сервисом, который нужно установить на сервер, то сегодня я воспринимаю его как полноценный инфраструктурный компонент. Примерно такой же, как Kubernetes-кластер, база данных, объектное хранилище или система мониторинга. А инфраструктурные компоненты должны жить по одним и тем же правилам: храниться в Git, разворачиваться автоматически, документироваться через код и не зависеть от памяти конкретного администратора. Именно вокруг этой идеи и был построен весь проект.
И если после прочтения статьи кто-то решит отказаться от очередного загадочного curl | bash, перенесёт конфигурации в Git и начнёт относиться к VPN как к нормальной инфраструктуре, значит вся эта работа была проделана не зря.
Хорошая инфраструктура позволяет сохранить доступность необходимых сервисов независимо от того, где развёрнута система и как меняется окружающая сеть.
Для тех, кто дочитал до конца, готовый проект находится в моем гите https://git.antropoff.ru/DevOpsTools/hysteria2