Части 3: Настройка HTTPS с самоподписными сертификатами для Traefik и Marimo
В предыдущей части мы настроили балансировку нагрузки через Traefik, но весь трафик ходил по HTTP — незашифрованным. В этой части мы добавим HTTPS с помощью самоподписных сертификатов. Это хороший способ подготовить инфраструктуру к продакшену, где вы сможете заменить их на сертификаты от Let’s Encrypt буквально одной строкой.
Часть 1: HashiCorp Nomad: развертывание интерактивного Python-приложения на macOS
Часть 2: Балансировка нагрузки и сервис-дискавери с Consul и Traefik для Marimo
Зачем нужен HTTPS
- Шифрование: данные между браузером и сервером передаются в зашифрованном виде.
- Подготовка к продакшену: архитектура с двумя точками входа (HTTP и HTTPS) и автоматическим редиректом — это стандартная схема.
- Доверие к сертификатам: локально вы можете использовать самоподписные сертификаты, а в продакшене — Let’s Encrypt, не меняя структуру конфигурации.
Шаг 1: Генерация самоподписного сертификата
Для создания сертификата используем `openssl`, который уже есть в macOS. Важный момент: обязательно указываем Subject Alternative Name (SAN) с именем домена, иначе современные браузеры откажутся считать сертификат валидным.
mkdir -p ~/marimo-app/certs && cd ~/marimo-app/certs
openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout marimo.localhost.key \
-out marimo.localhost.crt \
-subj "/CN=marimo.localhost" \
-addext "subjectAltName=DNS:marimo.localhost"В папке `certs` появятся два файла:
- `marimo.localhost.key` — приватный ключ.
- `marimo.localhost.crt` — сертификат.
Что произойдёт при открытии сайта: браузер покажет предупреждение, что сертификат не является доверенным. Это ожидаемое поведение — вы можете принять исключение для домена `marimo.localhost`. В продакшене предупреждения не будет, так как сертификаты Let’s Encrypt автоматически доверенные.
Шаг 2: Обновление конфигурации Traefik
Traefik поддерживает разделение конфигурации на статическую (точки входа, провайдеры) и динамическую (маршруты, сертификаты). Мы создадим обе через блоки `template` в задании Nomad.
Статическая конфигурация
Добавим точку входа `websecure` на порту 8443 и настроим автоматический редирект с HTTP на HTTPS:
[entryPoints]
[entryPoints.web]
address = ":8080"
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
permanent = true
[entryPoints.websecure]
address = ":8443"
[entryPoints.traefik]
address = ":8081"
[api]
dashboard = true
insecure = true
[providers.consulCatalog]
prefix = "traefik"
exposedByDefault = false
[providers.consulCatalog.endpoint]
address = "127.0.0.1:8500"
scheme = "http"
[providers.file]
filename = "${NOMAD_TASK_DIR}/dynamic.toml"
watch = trueКлючевые моменты:
- `websecure` на порту `8443` — точка входа для HTTPS-трафика.
- `[entryPoints.web.http.redirections.entryPoint]` — HTTP-запросы автоматически перенаправляются на HTTPS.
- `[providers.file]` — подключает динамический провайдер, который читает наш файл с сертификатами.
Динамическая конфигурация
Здесь мы описываем сам сертификат:
[tls]
[tls.stores]
[tls.stores.default]
[tls.stores.default.defaultCertificate]
certFile = "/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.crt"
keyFile = "/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.key"
[[tls.certificates]]
certFile = "/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.crt"
keyFile = "/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.key"Обратите внимание, что здесь используются абсолютные пути. Это работает, потому что мы запускаем Traefik через `raw_exec` — процесс работает прямо на хосте и имеет доступ ко всем файлам.
Шаг 3: Полное задание Traefik
Вот итоговый файл `traefik.nomad` с обеими конфигурациями:
job "traefik" {
region = "global"
datacenters = ["dc1"]
type = "service"
group "traefik" {
count = 1
network {
port "http" {
static = 8080
}
port "api" {
static = 8081
}
}
service {
name = "traefik"
check {
name = "alive"
type = "tcp"
port = "http"
interval = "10s"
timeout = "2s"
}
}
task "traefik" {
driver = "raw_exec"
# Статическая конфигурация Traefik
template {
data = <<EOF
[entryPoints]
[entryPoints.web]
address = ":8080"
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
permanent = true
[entryPoints.websecure]
address = ":8443"
[entryPoints.traefik]
address = ":8081"
[api]
dashboard = true
insecure = true
[providers.consulCatalog]
prefix = "traefik"
exposedByDefault = false
[providers.consulCatalog.endpoint]
address = "127.0.0.1:8500"
scheme = "http"
[providers.file]
filename = "${NOMAD_TASK_DIR}/dynamic.toml"
watch = true
EOF
destination = "local/traefik.toml"
}
# Динамическая конфигурация: сертификаты по абсолютному пути
template {
data = <<EOF
[tls]
[tls.stores]
[tls.stores.default]
[tls.stores.default.defaultCertificate]
certFile = "/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.crt"
keyFile = "/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.key"
[[tls.certificates]]
certFile = "/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.crt"
keyFile = "/Users/yuriygavrilov/marimo-app/certs/marimo.localhost.key"
EOF
destination = "local/dynamic.toml"
}
config {
command = "/usr/local/bin/traefik"
args = [
"--configfile",
"${NOMAD_TASK_DIR}/traefik.toml"
]
}
resources {
cpu = 100
memory = 128
}
}
}
}Шаг 4: Обновление тегов Marimo
В задании `marimo-job.nomad` нужно переключить сервис на точку входа `websecure` и включить TLS:
service {
name = "marimo-app"
port = "http"
tags = [
"traefik.enable=true",
"traefik.http.routers.marimo.rule=Host(`marimo.localhost`)",
"traefik.http.routers.marimo.entrypoints=websecure",
"traefik.http.routers.marimo.tls=true",
"traefik.http.services.marimo-app.loadbalancer.sticky.cookie=true",
]
check {
type = "http"
path = "/"
interval = "2s"
timeout = "2s"
}
}Что изменилось:
- `entrypoints=websecure` — трафик направляется на HTTPS-точку входа.
- `tls=true` — включена TLS-терминация для этого маршрута.
Шаг 5: Запуск и проверка
Перезапустите оба задания:
nomad job stop traefik
nomad job run traefik.nomad
nomad job run marimo-job.nomadОткройте в браузере:
https://marimo.localhost:8443
Браузер покажет предупреждение о недоверенном сертификате. Нажмите «Дополнительно» → «Перейти на marimo.localhost (небезопасно)» или добавьте сертификат в системный раздел доверенных сертов (в macOS это делается через «Связка ключей»).
Если вы попробуете открыть `http://marimo.localhost:8080`, Traefik автоматически перенаправит вас на HTTPS.
Возможные ошибки и их решения
Ошибка: `Unclosed configuration block`
Причина: незакрытые блоки `{ ... }` в HCL-файле.
Решение: внимательно проверьте, что все блоки `job`, `group`, `task`, `config`, `template` закрыты. В HCL отступы не важны, но фигурные скобки — критичны.
Ошибка: `Missing required argument: “command”` при `raw_exec`
Причина: конфигурация скопирована из задания для `docker` (с полями `image`, `volumes`, `network_mode`), но применён драйвер `raw_exec`.
Решение: для `raw_exec` используйте поля `command` и `args`. Файлы конфигурации создавайте через `template`.
Ошибка: `volume_mount` не поддерживается драйвером `raw_exec`
Причина: драйвер `raw_exec` работает напрямую с процессами хоста и не поддерживает монтирование томов.
Решение: используйте абсолютные пути к файлам (как мы сделали в динамической конфигурации). Или переходите на драйвер `docker`/`podman` — там `volume_mount` работает штатно.
Ошибка: `entryPoint “websecure” doesn’t exist`
Причина: имя точки входа в тегах сервиса не совпадает с именем в статической конфигурации Traefik.
Решение: убедитесь, что в конфиге есть блок `[entryPoints.websecure]`, а в тегах указано `traefik.http.routers.marimo.entrypoints=websecure`.
Итог
Мы добавили HTTPS в наш стек. Теперь архитектура выглядит так:
Пользователь (HTTPS)
│
▼
Traefik ──► Marimo 1 / 2 / 3
(8443) │
│ ▼
└─────────► Consul
▲
│
Nomad Cluster- HTTP-трафик на порту 8080 автоматически перенаправляется на HTTPS.
- HTTPS-трафик на порту 8443 расшифровывается Traefik и направляется на реплики Marimo.
- Сертификаты хранятся на хосте и подключаются через абсолютные пути.
В продакшене достаточно заменить пути к сертификатам и указать Traefik получать их автоматически от Let’s Encrypt — структура конфигурации останется той же. Мы разберём это в следующей части.