4 min read

local dns & reverse proxy

После того как я решил начать экспериментировать с докером количество сервисов увеличилось и теперь помимо роутера или NAS появились и другие. Так что мне надоело каждый раз прописывать разные порты и захотелось ходить по хосту, а не вспоминать что на каком порту висит. Для этого нам нужен локальный днс для доменов по которым мы будем заходить и прокси который будет раскидывать запросы по контейнерам.

Запуск контейнера

С докером можно работать как через сам docker так и через docker-compose.
например так выглядит запуск portainer через cli

docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest

А вот так через docker-compose: сначала надо составать docker-compose.yml

services:
  portainer:
    name: portainer
    image: portainer/portainer-ce:latest
    volumes:
      - portainer_data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - "8000:8000"
      - "9443:9443"

а потом запускать

docker compose up -d

Через compose можно запустить сразу несколько контейнеров необходимых для работы сервиса и они все будут видеть друг друга.

Сеть в docker

Немного про сети docker: если в двух словах, то работа сети в docker реализуется за счет сетевых драйверов:
- none: отключение всех сетевых ресурсов.
- bridge: сетевой драйвер по умолчанию. По сути, это мост между контейнером и хостовой машиной.
- host: для автономных контейнеров устраняется сетевая изолированность между контейнером и хостом Docker и напрямую используются сетевые ресурсы хоста.

При создании контейнера мы можем вручную указать к каким сетям подключаться

docker run -d -p 8000:8000 -p 9443:9443 --network=homelab --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest
 # docker container inspect portainer_test | grep -A1 Networks |tail -n 1| cut -d'"' -f2
homelab

сеть homelab

В противном случае докер сам поместит контейнер в bridge сеть

# docker run -d -p 1000:8000 --name portainer_test --restart=always -v /var/run/docker.sock:/var/run/docker.sock  portainer/portainer-ce:latest
# docker container inspect portainer_test | grep -A1 Networks |tail -n 1| cut -d'"' -f2
bridge

сеть bridge

По умолчанию docker-compose создает единую сеть для контейнеров в отдельно взятом сервисе и называет её по имени контейнера, так что есть несколько вариантов:

  1. Засунуть все контейнеры в один docker-compose. Это решает задачу, но имеет большой минус: если надо будет поменять что то в одном контейнере то пересобираться будут все контейнеры и это занимает много времени.
  2. Сделать свою сеть и добавить туда все сервисы. В таком случае каждый отдельный сервис можно будет выключить отдельно, без пересборки всех остальных.
сеть 172.20.0.0/24

Подключается просто:

services:
  test:
    container_name: test
...
    networks:
      - homelab

networks:
  homelab:
    external: true

docker-compose.yml

docker network connect --ip 172.20.0.x mynet container_name

docker cli

Pi-hole

В качестве локального днс был выбран Pi-hole, он не только выполняет функцию днс, но и умеет работать с блоклистами, а значит будет меньше рекламы.

version: '3.8'
services:

  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    restart: always
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "9001:80/tcp"
    environment:
      DNSMASQ_USER: root
      SKIPGRAVITYONBOOT: 1
      TZ: Europe/Moscow
      DNSMASQ_LISTENING: all
    volumes:
      - '/mnt/HD/HD_a2/Public/Docker/Pihole/pihole:/etc/pihole'
      - '/mnt/HD/HD_a2/Public/Docker/Pihole/dnsmasq.d:/mnt'
    networks:
      - homelab

networks:
  homelab:
    external: true

настройки и логи будут доступны на первом диске в каталоге Docker/Pihole

Контейнер при запуске не будет обновлять блоклисты и сразу начнет отвечать на все запросы, вообще по умолчанию dnsmasq отвечает только на локальные запросы, но в таком случае он будет недоступен. Панель управления будет доступна на 9001 порте. По идеи нам остается только добавить домены и направить их на локальные ip.

На этом с настройкой все, по желанию можно подключить различные блоклисты доступные на github. После того как у нас заработает прокси то проброс 80 порта можно убрать т.к. все запросы пойдут через nginx.

Proxy

Вообще в докере для проксирования есть traefik. Traefik это реверс прокси написанный специально для докера, он дискаверит контейнеры и при наличии определенных меток проксирует трафик в нужный контейнер. Работает это так:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pihole.rule=Host(`dns.homelab-notes.duckdns.org`)"
      - "traefik.http.services.pihole.loadbalancer.server.port=80"

Включаем traefik, выдаем хост и указываем на какой порт проксировать

В теории он может проксировать и на внешние хосты, но там надо лезть в конфиги. В сумме с проблемами получения сертификата LE я от него отказался в пользу nginx proxy manager. Фактчески это nginx, certbot и удобный gui в одном флаконе, из минусов это необходимость прописывать прокси вручную.

services:
###
### Nginx proxy manager
###
  nginx:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: always
    container_name: nginx_proxy
    ports:
      - '8080:80'
      - '81:81'
      - '443:443'
    networks:
      - homelab

networks:

  homelab:
    external: true

docker-compose.yml

после запуска идем на 81 порт и видим страницу авторизации

admin@example.com / changeme

После авторизации нам предложат изменить данные и пароль. Идем в Hosts > Proxy Hosts и жмем на кнопку "add proxy host"

жмем кнопку save и идем по хосту, если страница открылась то можно убрать 81 порт
Для Pi-hole можно добавить редирект

Не лишним будет ограничить доступ для локальных ресурсов:

allow 192.168.0.0/16
allow 172.16.0.0/12
allow 10.0.0.8/8
deny all

Бонусом nginx proxy manager является удобная генерация ssl от let's encrypt. Для начала нам нужен домен на который будет выпущен сертификат. Я воспользовался duckdns.org, там можно бесплатно взять домен 3 уровня и добавлять TXT записи через api. Для получения сертификата через dnschallenge нужно будет указать свой ключ api.