Intro


Не смог придумать подходящее название для поста, поэтому кратко опишу, о чем будет идти речь.


У большинства из нас есть какие-нибудь мелкие личные поделки, которые не выходят за рамки наших домов. Кто-то хостит их на рабочем компьютере, кто-то — на Heroku, кто-то — на VPS, а у кого-то есть домашний сервер. На реддите даже есть сообщество r/homelab, в котором люди обсуждают разные железки и софт для т.н. домашней лаборатории.


Я не настолько увлечен этим вопросом, но у меня дома стоит Intel NUC, который проигрывает музыку с NAS с помощью MPD. Помимо MPD на нем крутятся мои мелкие поделки, которые помогают мне с ним работать: ныне мертвый бот для телеграма, HTTP API на синатре и корявенький фронтенд для него.


В посте я без особых подробностей (многих из которых сам не понимаю) опишу процесс установки DNS-сервера для работы с доменными именами для сервисов, схему одновременной работы нескольких сервисов с помощью Docker и установку Gitlab с CI. Ничего нового вы не узнаете, но вдруг кому-нибудь пригодится этот "гайд". К тому же я бы хотел услышать предложения по поводу того, как можно было бы сделать это проще/элегантнее/правильнее.


Изначально код моих сервисов лежал на битбакете/гитхабе и после создания докер-образов мне нужно было зайти под SSH и запустить пару скриптов, которые создавали/обновляли контейнеры с сервисами. Поймал себя на мысли, что вижу мелкий раздражающий баг в приложении, который я не исправляю только потому, что мне лень производить всю эту процедуру. Очевидно, что пора было автоматизировать все. Тут-то и пришла мысль установки Gitlab + CI.


Локальные домены с помощью DNS


Все контейнеры создавались с флагом --network=host для простоты — достаточно было использовать разные порты в приложениях. Однако с ростом количества сервисов запоминать, какое приложение какой порт использует. Да и вводить каждый раз IP-адрес с портом в браузере не очень красиво, поэтому прежде чем устанавливать гитлаб я решил разобраться с хостингом нескольких приложений на одном сервере.


Идея простая: настраиваем DNS, скармливаем его роутеру, устанавливаем Nginx и с помощью его конфигурации перенаправляем запросы на разные порты в зависимости от домена. Это же позволит не заморачиваться с портами при разработке, т.к. контейнеры начнут использовать --publish вместо --network=host.


При установке использовался этот гайд. В нем настройка производится для Ubuntu 16.04, у меня же Debian.


Дальнейшие действия производятся от пользователя root.


Первым делом устанавливаем bind9 и утилиты:


apt-get install -y bind9 bind9utils bind9-doc dnsutils

Далее нам нужно настроить доменную зону. Для этого добавляем в файл /etc/bind/named.conf.local следующее:


zone "nondv.home" IN { // Желаемое доменное имя
     type master;
     file "/etc/bind/fwd.nondv.home.db"; // Forward lookup file
     allow-update { none; }; // Since this is the primary DNS, it should be none.
};

Также в гайде добавляется конфигурация для reverse lookup, но, честно говоря, я не особо понимаю, для чего это нужно, поэтому не стал этого делать.


Теперь создаем файл /etc/bind/fwd.nondv.home.db:


$TTL    604800
@       IN      SOA     ns1.mydomain.home. root.mydomain.home. (
                             20         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

;Name Server Information
        IN       NS     ns1.nondv.home.

;IP address of Name Server
ns1     IN       A      192.168.0.3

;A - Record HostName To Ip Address
nuc     IN       A      192.168.0.3
gitlab  IN       A      192.168.0.3
mpd     IN       A      192.168.0.3
@       IN       A      192.168.0.3

Далее перезапускаем bind9 и устанавливаем автозапуск:


systemctl restart bind9
systemctl enable bind9

Обратите внимание, что я использовал .home вместо .local. Это было сделано потому, что домен nondv.local не резолвился без поддоменов. Ну, точнее dig распознавал его нормально, но браузеры и curl — нет. Как мне объяснил коллега, это скорее всего связано с разным софтом вроде Bonjour (мой рабочий ноутбук с яблоком на крышке). В общем, с доменом .home подобных проблем быть не должно.


Собственно, это все. Я после этого добавил DNS как первичный в роутер и переподключился к нему (чтобы автоматически обновился файл /etc/resolve.conf).


Nginx


Как я уже сказал, чтобы иметь возможность обращаться по HTTP на 80 порт ко всем сервисам одновременно, нам нужно настроить Nginx так, чтобы он проксировал запросы на разные порты в зависимости от домена.


Документация по образу nginx доступна на сайте Docker Hub.


Подготовим основной файл конфигурации /srv/nginx/nginx.conf:


user  nginx;
worker_processes  1;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;
    server {
      listen 80;
      server_name nondv.home;
      rewrite ^/$ http://mpd.nondv.home redirect; # основной домен, на который будут пеправляться ненастроенные запросы
    }
    include /etc/nginx/conf.d/*.conf;
}

Далее настроим домены. Я покажу только один:


# /srv/nginx/conf.d/gitlab.conf
server {
  listen       80;
  server_name  gitlab.nondv.home;

  location / {
    proxy_pass http://127.0.0.1:3080;
  }
}

Запуск контейнера производится командой:


docker run --detach            --network host            --name nginx            --restart always            --volume /srv/nginx/nginx.conf:/etc/nginx/nginx.conf:ro            --volume /srv/nginx/conf.d:/etc/nginx/conf.d:ro            nginx:alpine

Вот и все, теперь HTTP запросы на 80 порт будут отлавливаться с помощью nginx и перенаправляться на нужный порт.


Gitlab


Здесь все просто по официальному руководству:


docker run --detach            --hostname gitlab.nondv.home            --publish 3080:80 --publish 3022:22            --name gitlab            --restart always            --volume /srv/gitlab/config:/etc/gitlab:Z            --volume /srv/gitlab/logs:/var/log/gitlab:Z            --volume /srv/gitlab/data:/var/opt/gitlab:Z            gitlab/gitlab-ce:latest

Ждем, когда все настроится (поглядываем в docker logs -f gitlab) и после входим в контейнер (docker exec -it gitlab bash) для доп. настройки:


nano /etc/gitlab/gitlab.rb # or vim

# /etc/gitlab/gitlab.rb
external_url 'http://gitlab.nondv.home'
gitlab_rails['gitlab_shell_ssh_port'] = 3022
# /etc/gitlab/gitlab.rb

gitlab-ctl reconfigure

Для надежности можно перезапустить контейнер (docker container restart gitlab).


CI


Gitlab CI уже интегрирован, но ему нужен Gitlab Runner (документация).


Для этого я написал небольшой скрипт:


NAME="gitlab-runner$1"
echo $NAME
docker run -d --name $NAME --restart always            --network=host            -v /srv/gitlab-runner/config:/etc/gitlab-runner            -v /var/run/docker.sock:/var/run/docker.sock            gitlab/gitlab-runner:alpine

После создания раннера, нам нужно его зарегистирировать. Для этого заходим в гитлаб (через браузер), идем в Admin area > Overview > Runners. Там описана установка раннеров. Вкратце, вы просто выполняете:


docker exec -it gitlab-runner register

и отвечаете на вопросы.


Ваши собственные HTTP-сервисы


Они запускаются по аналогии с гитлабом. Публикуете их на каком-нибудь порте и добавляете конфиг в nginx.


Заключение


Теперь вы можете хостить ваши проекты на домашнем сервере и использовать мощь Gitlab CI для автоматизации сборки и публикации ваших проектов. Удобно ведь делать git push и не беспокоиться о запуске, верно?


Еще я бы рекомендовал настроить почту для гитлаба. Лично я использовал почтовый ящик на Яндексе. Документация.

Комментарии (21)


  1. VolCh
    14.07.2018 23:33
    +1

    Для почти идеальной системы не хватает системы «регистрации» доменов и записей в nginx по поднятию контейнера по его имени или, например, метке. Посмотрите проекты nginx-proxy и docker-flow для примера. С GitLab можно вообще на каждую ветку/mr/таг новое окружение поднимать.


    1. gecube
      15.07.2018 00:46
      +1

      Возможно, проще nginx заменить на traefik. Он умеет динамически на базе меток переконфигурироваться


      1. mentatxx
        15.07.2018 16:03

        Поддерживаю traefik — он умеет динамически домены + let's encrypt, docker swarm, поддерживает интеграцию с кучей сервисов, включая логирование. Вдобавок, красивый дашборд. Очень крутая штука


    1. Nondv Автор
      15.07.2018 01:43

      Да, хорошая идея. Но я не в той ситуации, когда это может мне облегчить жизнь (слишком мало всего)


  1. gecube
    15.07.2018 00:45
    +1

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


    1. Nondv Автор
      15.07.2018 01:42

      Интересное замечание. Спасибо.


      Но я дилетант и речь идет о домашней системе, так что...=)


    1. kemko
      16.07.2018 08:58

      Если я не ошибаюсь, без этого нельзя внутри CI делать docker build.


      1. gecube
        16.07.2018 09:17

        Ну, во-первых, это не всем нужно.
        Во-вторых, внезапно (!) оказалось, что докер-образы можно собирать и без docker daemon. Смотрите в сторону libpod/buildah
        В третьих, пес его знает как запускается gitlab-runner, но есть опция использования dind (docker-in-docker)


      1. Nondv Автор
        16.07.2018 09:32

        Привет:)


        Да, проброс сокета нужен для билда образов. Это не единственный способ, но он мне показался самым понятным и простым.


        Документация


  1. shushu
    15.07.2018 04:19
    +1

    Bind9 тут как из пушки по воробьям. Вполне достаточно было бы dnsmasq.По моему мнению, идеально было бы использовать skydns https://github.com/skynetservices/skydns
    Опенсорс тулза, написанная на го, использует etcd для хранения доменов. Т.е очень легко регистрировать новые домены.


    1. Nondv Автор
      15.07.2018 10:46

      Ну, учитывая, что я ничего не понимаю в сабже, я не вижу никакой проблемы в bind9. Установил и настроил за 10 минут, даже не вдаваясь в тех. детали (если не считать проблемы с bonjour). В конечном итоге все сводится к "работает — не трогай"


      1. padlyuck
        15.07.2018 14:23

        В dnsmasq та же задача решается путем добавления таких строк в стандартный конфиг


        address=/nondv.home/192.168.0.3
        address=/3.0.168.192.in-addr.arpa/192.168.0.3

        все поддомены вида *.nondv.home будут откликаться на 192.168.0.3


        1. shushu
          16.07.2018 02:48

          Поидее
          address=/nondv.home/192.168.0.3
          будет уже достаточно.


          1. padlyuck
            16.07.2018 03:46

            обратную зону стоит добавить, чтобы пинги шустрее шли


      1. shushu
        16.07.2018 02:47

        Я и не говорил что это проблема :)
        Просто предложил варианты попроще.
        Если «работает — не трогай», т.е изменятся конфиг не планируется — то всё замечатально и все равно что использовать.
        Я лишь хотел сказать: если же прийдется часто добавлять/удалять домены то есть более удобные средства.


    1. gecube
      15.07.2018 11:51

      За skydns — спасибо, подумаем об использовании в своих проектах.
      Но, к слову, bind или named не так уж сложно настроить...


    1. Scott_Maison
      15.07.2018 15:55

      Из простого еще можно было бы CoreDNS заиспользовать. Он тоже на go, но не использует etcd.


      1. shushu
        16.07.2018 02:50

        Спасибо, тоже гляну что это.


  1. E_STRICT
    15.07.2018 15:12

    Все контейнеры создавались с флагом --network=host для простоты — достаточно было использовать разные порты в приложениях
    Use bridge networks!
    Для таких целей они намного удобней. У каждого контейнера будет свой IP, который можно добавить в /etc/hosts. Т.е. не придётся ставить NGinx, Traefik, DNS и т.д.


    1. gecube
      15.07.2018 15:19

      В общем случае bridges network не отменяет необходимости реверс прокси....


    1. Nondv Автор
      15.07.2018 22:03

      Редактировать hosts удобно, когда есть только один клиентский компьютер.
      У меня же помимо ноутбука еще телефон и планшет. DNS нужен в любом случае.


      по поводу IP — не вижу, каким образом это могло помочь в моей ситуации. Вместо запоминания портов мне бы пришлось запоминать IP-адреса. --network=host просто работает. Никакие вопросы безопасности меня не интересуют, это моя личная сеть.