Доменные имена с валидным SSL для локальных Docker-контейнеров +24



preview


Использование Docker в процессе разработки стало уже стандартом де-факто. Запускать приложение со всеми его зависимостями, используя всего одну команду — становится всё более и более привычным действием. Если приложение предоставляет доступ используя web-интерфейс или какое-либо HTTP API — скорее всего "фронтовой" контейнер пробрасывает свой, уникальный (среди остальных приложений, разработкой которых вы занимаетесь параллельно) порт в хост, постучавшись на который мы можем взаимодействовать с приложением в контейнере.


И это отлично работает, пока у вас не появляется целый зоопарк приложений, переключение между которыми начинает вызывать некоторые неудобства, так как надо помнить и схему, и порт, и где-то фиксировать какие порты для какого приложения вы когда-то выделили, дабы не возникло коллизии со временем.


А потом ещё тебе хочется проверить работу по https — и приходится либо использовать свой корневой сертификат, либо всегда использовать curl --insecure ..., а когда над приложениями работают различные команды — количество запар начинает возрастать в геометрической прогрессии.


Столкнувшись с такой проблемой в очередной раз — в голове промелькнула мысль "Хватит это терпеть!", и результатом работы на паре выходных стал сервис, который решает эту проблему на корню, о чем будет рассказано ниже. Для нетерпеливых, традиционно — ссылочка.


Мир Нас спасёт реверс-прокси


По-хорошему, нам нужна какая-то доменная зона, все под-домены из которой всегда будут резольвить локалхост (127.0.0.1). Беглые поиски навели на домены вида *.localho.st, *.lvh.me, *.vcap.me и другие, но как к ним прикрутить валидный SSL сертификат? Повозившись со своим корневым сертификатом удалось завести curl без ошибок, но не все браузеры корректно принимали его, и продолжали вываливать ошибку. Кроме того — крайне не хотелось "возни" с SSL.


"Чтож, зайдём с другой стороны!" — и тут же был приобретен домен с именем localhost.tools, делегирован на CloudFlare, настроен требуемый резольвинг (все под-домены резольвят 127.0.0.1):


$ dig foo.localhost.tools | grep -v '^;\|^$' 
foo.localhost.tools.    190 IN  A   127.0.0.1

После этого был запущен certbot в контейнере, который на входе получая API KEY от CF при помощи DNS записи подтверждает право владения доменом, и выдаёт валидный SSL сертификат на выходе:


$ docker run   --entrypoint=""   -v "$(pwd)/cf-config.conf:/cf-credentials:ro"   -v "$(pwd)/cert:/out:rw"   -v "/etc/passwd:/etc/passwd:ro"   -v "/etc/group:/etc/group:ro"   certbot/dns-cloudflare:latest sh -c     "certbot certonly     --dns-cloudflare     --dns-cloudflare-credentials '/cf-credentials'     -d '*.localhost.tools'     --non-interactive     --agree-tos     --email '$CF_EMAIL'     --server 'https://acme-v02.api.letsencrypt.org/directory'     && cp -f /etc/letsencrypt/live/localhost.tools/* /out     && chown '$(id -u):$(id -g)' /out/*"

Файл ./cf-config.conf содержит данные авторизации на CF, подробнее можно прочитать в документации по certbot, $CF_EMAIL — переменная окружения с твоим email

Ок, теперь у нас есть и валидный SSL сертификат (пускай и на 3 месяца, и только для под-доменов одного уровня). Остается как-то научиться проксировать все запросы, что приходят на локалхост в нужный контейнер.


И тут нам на помощь приходит Traefik (спойлер — он прекрасен). Запустив его локально, пробросив в его контейнер через volume докер-сокет — он умеет проксировать запросы в тот контейнер, у которого есть необходимый docker label. Таким образом, нам нет необходимости в какой-либо дополнительной конфигурации, кроме как при запуске указать нужный label у контейнера (и docker сеть, но при запуске без docker-compose даже это не обязательно, хотя и очень желательно), к которому мы хотим получить доступ по доменному имени и с валидным SSL!


Проделав весь этот путь свет увидел докер-контейнер с этим самым пред-настроенным Traefik-ом и wildcard SSL сертификатом (да, он публичный).


Приватный ключ от SSL в публичном контейнере?


Да, но я считаю что это не страшно, так как он на доменную зону, которая всегда резольвит локалхост. MitM в этом случае не имеет особого смысла в принципе.


Что делать когда сертификат протухнет?


Просто стянуть свежий образ, перезапустив контейнер. У проекта настроен CI, который в автоматическом режиме, раз в месяц (на данный момент) обновляет сертификат и публикует свежий образ.


Хочу попробовать!


Нет ничего проще. Первым делом, убедись что локальные порты 80 и 443 у тебя свободны, и выполни:


# Создаём docker-сеть для нашего реверс-прокси
$ docker network create localhost-tools-network

# Запускаем сам реверс-прокси
$ docker run -d --rm     -v /var/run/docker.sock:/var/run/docker.sock     --network localhost-tools-network     --name localhost.tools     -p 80:80 -p 443:443     tarampampam/localhost

# Запускаем nginx, говоря ему откликаться на "my-nginx.localhost.tools"
$ docker run -d --rm     --network localhost-tools-network     --label "traefik.frontend.rule=Host:my-nginx.localhost.tools"     --label "traefik.port=80"     nginx:latest

И теперь можем потестировать:


$ curl -sS http://my-nginx.localhost.tools | grep Welcome
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>

$ curl -sS https://my-nginx.localhost.tools | grep Welcome
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>

Как видим — работает :)


Где живёт документация, описание?


Всё, как не сложно догадаться, живёт по адресу https://localhost.tools. Более того, морда — отзывчивая, и умеет смотреть запущен ли у тебя локально демон реверс-прокси, и выводить список запущенных и доступных для взаимодействия (если таковые имеются) контейнеров.


Сколько стоит?


Нисколько. Совсем. Сделав данную штуку для себя и своей команды пришло понимание того, что она может пригодиться и другим разработчикам/ops-ам. Более того — денег стоит только доменное имя, всё остальное — юзается без необходимости в оплате.


P.S. Сервис ещё в beta, посему — если будут найдены какие-либо недочёты, опечатки и т.д. — просто черканите в личку. Хабы "Программирование" и "Разработка веб-сайтов" указаны по причине того, что данный подход может быть полезен в первую очередь в этих отраслях.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (12):

  1. kenbo
    /#19741444

    Вы не думали над тем как контейнеры будут общаться между собой и сами с собой используя поддомен в *.localhost.tools?

    • paramtamtam
      /#19741626

      Да, между контейнерами эта штука работать с ходу не будет, увы. Но и предназначение её — из локала попадать в контейнер по домену, а не обеспечивать межконтейнерную связь.

      • kenbo
        /#19741766

        Согласитесь, однобоко получилось. Можно заставить ее работать если сменить локальный IP 127.0.0.1 на IP сетевого моста Docker, конечно при условии что он у вас настроен.

        • paramtamtam
          /#19741896

          Скажу "для решения определенных задач" получилось. Для всего остального — можно собрать свой докер-контейнер со своей-же доменной зоной — всё в ваших руках ;)

  2. 1Demon
    /#19741528

    а mkcert чем не вариант?

    • paramtamtam
      /#19741542

      Он крутой, но зависимость от дополнительной зависимости на тачке разработчика — оттолкнула

  3. kholmukhamedovme
    /#19743566

    Может я не так понял, но вроде же есть готовое решение (с поддержкой SSL) в виде Docker образа – jwilder/nginx-proxy. Поясните, если ошибаюсь.

    • paramtamtam
      /#19743964

      Основная разница в том, что traefik использует docker labels вместо env и имеется dashboard + API из коробки; SSL же всё равно пришлось бы генерировать или ручками, или собирать свой контейнер с уже сгенерированным (было важное условие — ничего не должно дополнительно конфигуряться на тачке разработчика). Да и traefik использую уже давненько, как-то проникся им

  4. atoshin
    /#19743960 / -1

    Imgur


    $ man nss-myhostname

  5. VFedorV
    /#19746780

    Файл ./cf-config.conf содержит данные авторизации на CF, подробнее можно прочитать в документации по certbot, $CF_EMAIL — переменная окружения с твоим email

    вот сложно что ли листинг этого файла привести? перерыть кучу документации надо, чтобы понять как правильно его создать :(

  6. sashaaro
    /#19747792

    Можно установить локальный dns сервер, который работает с контейнерами.
    Допустим github.com/kevinjqiu/coredns-dockerdiscovery