Перейти до вмісту
EuroVPSEuroVPS

Одні вхідні двері для кожного внутрішнього інструмента: шаблон self-hosting

Як запускати дошки завдань, дашборди, сховище паролів тощо за одним reverse proxy: wildcard DNS, контейнери під systemd, багаторівневий доступ, безпечні перейменування.

Автор EuroVPS Engineering

5 хв читання
Одні вхідні двері для кожного внутрішнього інструмента: шаблон self-hosting

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

Форма стека

Шаблон має чотири шари, і кожен має одну роботу:

  • DNS: wildcard-ім’я, наприклад *.tools.example.com, що вказує на один хост.
  • Reverse proxy: Traefik на цьому хості, який термінує TLS і маршрутизує кожне ім’я хоста до правильного контейнера.
  • Контейнери: кожен інструмент у власному контейнері Podman у приватній мережі контейнерів, без портів, опублікованих на хості, крім портів proxy.
  • Нагляд: systemd запускає, перезапускає і впорядковує все, тож стек переживає перезавантаження без участі людини.

Коли це є, додавання інструмента стає коротким контрольним списком, а не проєктом: запустити контейнер у мережі, додати маршрут, готово. Жодної нової діри у firewall, жодного нового запису DNS і жодного нового запиту на сертифікат.

DNS: один wildcard, і забудьте про нього

Wildcard-запис означає, що tasks.tools.example.com, grafana.tools.example.com і vault.tools.example.com усі резолвляться на хост proxy без того, щоб хтось знову торкався DNS. Поєднайте його з wildcard-сертифікатом на те саме ім’я, і кожен новий інструмент народжується з дійсним TLS.

Тримайте wildcard на окремому піддомені. Ви не хочете, щоб anything.example.com потрапляло на ваш хост з інструментами.

Proxy: маршрути як файли, які можна прочитати

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

http:
  routers:
    tasks:
      rule: "Host(`tasks.tools.example.com`)"
      entryPoints: [websecure]
      service: tasks
      middlewares: [mgmt-only]
      tls: {}
  services:
    tasks:
      loadBalancer:
        servers: [{ url: "http://tasks:3456" }]
  middlewares:
    mgmt-only:
      ipAllowList:
        sourceRange: ["192.0.2.0/24"]

Важлива частина тут middleware. Proxy є єдиним місцем, де ви вирішуєте, хто може дістатися до інструмента, тож і рішення кладіть туди.

Доступ: два замки, а не один

Внутрішні інструменти мають власні логіни, і їхня якість різна. Не покладайтеся лише на них. Поставте попереду два рівні контролю:

  • Список дозволених джерел на proxy, обмежений мережами, з яких ваша команда справді працює. Розгортайте кожен CIDR перед тим, як його записати, і перечитуйте після. Помилка в довжині префікса може перетворити один офісний діапазон на велику частину інтернету:
python3 -c "import ipaddress,sys; n=ipaddress.ip_network(sys.argv[1],strict=False); print(n, n[0], '->', n[-1], n.num_addresses)" 192.0.2.0/24
  • Middleware автентифікації (щонайменше basic auth поверх TLS, або forward authentication до вашого постачальника ідентичності) для інструментів, які не мають бути доступні навіть зсередини без другого облікового даного.

Сховище паролів заслуговує окремої думки. Bitwarden-сумісний сервер, такий як Vaultwarden, зберігає вміст сховища зашифрованим клієнтами, тож сервер ніколи не тримає його в читабельному вигляді. Це сильна властивість, але вона не скасовує потреби обмежувати, хто може дістатися до сервера.

Контейнери під systemd

Запуск контейнерів вручну працює до першого перезавантаження. Podman інтегрується із systemd через Quadlet: ви описуєте контейнер у невеликому unit-файлі, і systemd ставиться до нього як до будь-якого іншого сервісу.

# /etc/containers/systemd/tasks.container
[Unit]
Description=Task manager
After=network-online.target

[Container]
Image=docker.io/vikunja/vikunja:<pinned-tag>
Network=tools.network
Volume=/srv/tools/tasks:/app/vikunja/files:Z

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

Після systemctl daemon-reload команда systemctl start tasks запускає його, і відтоді він стартує під час завантаження. Фіксуйте теги образів замість того, щоб слідувати за latest, щоб оновлення було рішенням, яке ухвалюєте ви, з готовим dump і rollback, а не чимось, що трапляється з вами під час наступного перезапуску.

Тримайте SELinux у режимі enforcing. Позначайте кожен том для контейнера, якому він належить (:Z для приватного тому, :z для того, який мають ділити кілька контейнерів).

Перейменування без поломок

Рано чи пізно інструмент переїжджає: новий хост, новий домен, охайніше ім’я. Тримайте старе ім’я робочим, змусивши proxy відповідати на нього постійним перенаправленням на нове. Використовуйте 308 Permanent Redirect, а не 301, бо 308 каже клієнтам повторити той самий метод і тіло за новою адресою.

Є пастка, яка кусає скрипти, а не браузери. Браузери йдуть за перенаправленнями. Багато клієнтів командного рядка і бібліотек не йдуть, якщо їм не сказати. Скрипт, що надсилає POST на старе ім’я, отримує у відповідь 308, не йде за ним і рухається далі. Нічого не створено, нічого гучно не впало, і якщо скрипт перевіряє лише те, що щось відповіло, він звітує про успіх. З curl використовуйте -L, щоб іти за перенаправленням, і завжди перевіряйте код стану і тіло:

curl -s -o out.json -w '%{http_code}\n' -X POST https://tasks.tools.example.com/api/v1/...

Тіло відповіді зі словами «Permanent Redirect» там, де ви очікували JSON, є ознакою. Коли перейменовуєте, пошукайте старе ім’я у своїх скриптах та інтеграціях і оновіть їх, а перенаправлення тримайте лише як запобіжник.

Резервні копії, моніторинг і оновлення

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

Контрольний список для ваших власних інструментів

  • Чи можете ви перелічити кожен внутрішній інструмент, його ім’я хоста і хто може до нього дістатися?
  • Чи публікує якийсь інструмент власний порт на хост або в інтернет в обхід proxy?
  • Чи всі ваші діапазони у списку дозволених такі вузькі, як ви думаєте? Розгорніть їх і подивіться.
  • Чи повертаються ваші контейнери самі після перезавантаження?
  • Чи перевіряють ваші скрипти коди стану, чи лише те, що щось відповіло?
  • Коли ви востаннє відновлювали своє сховище паролів?

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

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

Related

  • /what-we-do/platforms/operations-tooling/
  • /what-we-do/operations/hardening-baseline/

Опубліковано 24 серпня 2026 р..

Усі нотатки з практики