Welcome to my personal place for love, peace and happiness 🤖

Части 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 — структура конфигурации останется той же. Мы разберём это в следующей части.

Follow this blog
Send
Share
Tweet
Pin