Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mitrofun/eaf88ac35e65da4c0e29d2edb246a1e2 to your computer and use it in GitHub Desktop.
Save mitrofun/eaf88ac35e65da4c0e29d2edb246a1e2 to your computer and use it in GitHub Desktop.

Revisions

  1. @dancheskus dancheskus revised this gist Feb 5, 2019. 1 changed file with 6 additions and 11 deletions.
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,4 @@

    # Certbot и nginx, как обратный прокси в Docker (пример с 2 react проектами)

    >В результате будет 2 react проекта на 1 сервере доступных по разным ссылкам
    @@ -206,16 +207,12 @@ networks:
    ```
    ### Меняем `docker-compose` файл в `react` проекте
    В папке `proj1` в файле `docker-compose`
    В папке `proj1` в файле `docker-compose` удаляем
    ```yml
    ports:
    - 8080:5000
    ```
    меняем на
    ```yml
    ports:
    - 5000
    ```

    >Контейнер по прежнему слушает на порту `5000`, но уже не открывает его для локального компьютера. Этот порт теперь доступен только контейнерам в той-же сети.

    Под блоком `services` пишем
    @@ -244,8 +241,6 @@ services:
    nginx_net:
    environment:
    - NODE_ENV=production
    ports:
    - 5000
    networks:
    nginx_net:
    @@ -411,8 +406,6 @@ server {
    }
    ```

    >**Важно**. Скрипт, который мы запустим позже, сохраняет все сертификаты в папку, которая называется как первое доменное имя, которое мы там указываем. Соответственно в любом `server` блоке, независимо от домена, пути `ssl_certificate` и `ssl_certificate_key` должны быть одинаковыми. В нашем случае они содержат путь к папке `proj1`.

    ## Этап 4. Завершаем настройку `docker-compose.yml` для `nginx` и `certbot`

    В `services -> nginx` добавляем
    @@ -487,4 +480,6 @@ curl -L https://raw.githubusercontent.com/dancheskus/nginx-docker-ssl/master/ini
    Выполнив последнюю команду в папке `nginx -> data` появятся новые папки с сертификатами необходимые для работы `nginx` и `certbot`
    Если в тестовом режиме все получилось, то ставим `staging=0` и повторяем процедуру.
    > `docker-compose run --rm --entrypoint "certbot certificates" certbot` - проверить зарегистрированные сертификаты и узнать их срок годности.
    **Готово**
    > `docker-compose run --rm --entrypoint "certbot certificates" certbot` - позволяет проверить зарегистрированные сертификаты и узнать их срок годности.
  2. @dancheskus dancheskus revised this gist Feb 5, 2019. 1 changed file with 1 addition and 1 deletion.
    Original file line number Diff line number Diff line change
    @@ -472,7 +472,7 @@ networks:
    Находясь в папке `nginx` вводим

    ```
    curl -L https://github.com/dancheskus/nginx-docker-ssl/blob/master/init-letsencrypt.sh > init-letsencrypt.sh
    curl -L https://raw.githubusercontent.com/dancheskus/nginx-docker-ssl/master/init-letsencrypt.sh > init-letsencrypt.sh
    ```
    Затем открываем полученный `init-letsencrypt.sh` и вписываем:
    - вместо `example.com` свои домены через пробел
  3. @dancheskus dancheskus revised this gist Feb 5, 2019. 1 changed file with 1 addition and 1 deletion.
    Original file line number Diff line number Diff line change
    @@ -472,7 +472,7 @@ networks:
    Находясь в папке `nginx` вводим

    ```
    curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh
    curl -L https://github.com/dancheskus/nginx-docker-ssl/blob/master/init-letsencrypt.sh > init-letsencrypt.sh
    ```
    Затем открываем полученный `init-letsencrypt.sh` и вписываем:
    - вместо `example.com` свои домены через пробел
  4. @dancheskus dancheskus revised this gist Feb 4, 2019. 1 changed file with 3 additions and 1 deletion.
    Original file line number Diff line number Diff line change
    @@ -485,4 +485,6 @@ curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsen
    `sudo ./init-letsencrypt.sh`
    Выполнив последнюю команду в папке `nginx -> data` появятся новые папки с сертификатами необходимые для работы `nginx` и `certbot`
    Если в тестовом режиме все получилось, то ставим `staging=0` и повторяем процедуру.
    Если в тестовом режиме все получилось, то ставим `staging=0` и повторяем процедуру.
    > `docker-compose run --rm --entrypoint "certbot certificates" certbot` - проверить зарегистрированные сертификаты и узнать их срок годности.
  5. @dancheskus dancheskus revised this gist Feb 2, 2019. 1 changed file with 1 addition and 1 deletion.
    Original file line number Diff line number Diff line change
    @@ -411,7 +411,7 @@ server {
    }
    ```

    >**Важно**. Скрипт, который мы запустим позже, сохраняет все сертификаты в папку, которая называется как первое доменное имя, которое мы там указываем. Соответственно в любом `server` блоке, независимо от домена пути `ssl_certificate` и `ssl_certificate_key` должны быть одинаковыми. В нашем случае они содержат путь к папке `proj1`.
    >**Важно**. Скрипт, который мы запустим позже, сохраняет все сертификаты в папку, которая называется как первое доменное имя, которое мы там указываем. Соответственно в любом `server` блоке, независимо от домена, пути `ssl_certificate` и `ssl_certificate_key` должны быть одинаковыми. В нашем случае они содержат путь к папке `proj1`.

    ## Этап 4. Завершаем настройку `docker-compose.yml` для `nginx` и `certbot`

  6. @dancheskus dancheskus revised this gist Feb 1, 2019. 1 changed file with 1 addition and 1 deletion.
    Original file line number Diff line number Diff line change
    @@ -54,7 +54,7 @@
    FROM mhart/alpine-node:11
    # Качаем node контейнер с docker hub
    RUN yarn global add serve
    # Устанавливаем serve для серверования нашего проекта
    # Устанавливаем serve для сервирования нашего проекта
    WORKDIR /app
    # Указываем главную папку в контейнере, в которой будет наш проект
    COPY package.json yarn.lock ./
  7. @dancheskus dancheskus created this gist Feb 1, 2019.
    488 changes: 488 additions & 0 deletions Certbot и nginx, как обратный прокси в Docker.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,488 @@
    # Certbot и nginx, как обратный прокси в Docker (пример с 2 react проектами)

    >В результате будет 2 react проекта на 1 сервере доступных по разным ссылкам
    ## Цели
    - Запустить **nginx** в одном контейнере
    - Запустить **другие проекты** в других контейнерах
    - Научить **nginx** перенаправлять запросы с разных доменов на разные проекты
    - Получить **ssl** сертификаты для всех проектов
    - Убрать **www** из названия сайтов с помощью **nginx**

    ## Перед началом
    >Перед тем как начать, должно быть следующее
    - Ubuntu сервер с открытыми портами `80` и `443`.
    - Установленный `docker` и `docker-compose` на локальном компьютере и сервере.
    - Зарегистрированные доменные имена. В этом руководстве я буду использовать `proj1.com` и `proj2.com` для двух проектов.
    - Запись `A` для `proj1.com` и `proj2.com`, указывает на публичный IP адрес нашего сервера.
    - Запись `A` для `www.proj1.com` и `www.proj2.com`, указывает на публичный IP адрес нашего сервера.

    ## В этом руководстве будет использоваться
    - nodejs
    - create-react-app
    - yarn
    - docker
    - docker-compose

    ## В любой момент может понадобится
    - `docker ps` - список запущенных контейнеров
    - `docker network ls` - список всех активных и неактивных сетей созданных docker'ом
    - `docker system prune` - очистка всех **остановленных контейнеров** и **неиспользуемых сетей**
    - `docker-compose down` - выполняется из корня проекта. Останавливает запущенный контейнер.

    # Настройка на локальном компьютере

    ## Этап 1. Создаем 2 `react` проекта
    >В результате будет 2 простых проекта
    В любом удобном месте на локальном компьютере создаем `react` проект `proj1` и `proj2`

    `yarn create react-app proj1`
    `yarn create react-app proj2`

    По очереди запускаем каждый из проектов `yarn start` и вносим маленькие изменения, чтобы видеть отличия проектов.
    >Например в `src -> App.js` каждого проекта вместо `Edit <code>src/App.js</code> and save to reload.` (create-react-app версия 2.1.3) напишем `proj1` и `proj2` соответственно.
    ## Этап 2. Упаковываем каждый проект в контейнер
    >В результате каждый проект будет в отдельном контейнере. Все действия далее описываю только для одного проекта. Их нужно сделать для двух проектов.
    ### Пишем инструкции для запуска контейнера
    В корне проекта создаем `Dockerfile`
    >**Dockerfile** - это инструкция по созданию контейнера
    ```dockerfile
    FROM mhart/alpine-node:11
    # Качаем node контейнер с docker hub
    RUN yarn global add serve
    # Устанавливаем serve для серверования нашего проекта
    WORKDIR /app
    # Указываем главную папку в контейнере, в которой будет наш проект
    COPY package.json yarn.lock ./
    # Копируем файлы package.json и yarn.lock в папку, указанную выше (не забываем ./)
    RUN yarn
    # Устанавливаем зависимости
    COPY . .
    # Копируем остальные файлы проекта в главную папку
    RUN yarn build
    # Компилируем проект
    CMD serve -s /app/build
    # Серверуем проект из новой папки build (по умолчанию порт 5000)
    ```

    Далее в корне проекта создаем `docker-compose.yml`
    >**docker-compose** - это инструкция по запуску контейнер(а/ов)
    ```yml
    version: '3.7'
    # Смотреть актуальную версию docker-compose синтаксиса на оф. сайте

    services:
    # Объявляем список сервисов
    proj1:
    # Указываем название сервиса
    container_name: proj1
    # Указываем название контейнера (не обязательно)
    build: .
    # Запускаем Dockerfile из корня проекта
    environment:
    - NODE_ENV=production
    # ENV переменная production сообщит yarn, что не нужно качать зависимости для разработки, если такие имеются.
    ports:
    # Слева порт, который будет доступен на локальной машине. Справа - тот, к которому нужен доступ в контейнере.
    - 8080:5000
    # 5000 - порт который по умолчанию открывает serve в Dockerfile
    ```

    В корне проекта создаем `.dockerignore`
    >**.dockerignore** - это список исключаемых из копирования `COPY . .` файлов и папок в `Dockerfile`
    ```
    .git
    node_modules
    build
    ```

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

    Из корня проекта выполняем
    `docker-compose up`
    >При первом запуске будут выполняться все инструкции `Dockerfile`, что займет какое-то время. Дальнейшие запуски, при отсутствии изменений в проекте, будут проходить быстрее.
    Когда видим строку вида `proj1 | INFO: Accepting connections at http://localhost:5000`, можем проверить в браузере `localhost:8080`. Результатом должен быть `react` проект с надписью `proj1`.

    В командной строке нажимаем `ctrl+c`, чтобы выйти из контейнера и проверяем проект `proj2`

    ## Этап 3. Создаем 2 фейковых url для теста
    >В результате будет 2 адреса, каждый из которых будет вести на `localhost`. Нужно для дальнейшей настройки `nginx`. **Этот шаг можно пропустить и продолжать действия на сервере**
    В `hosts` файле на компьютере добавляем запись
    ```
    127.0.0.1 proj1.com
    127.0.0.1 proj2.com
    ```

    ## Этап 4. Создаем `nginx` контейнер
    >В результате будет `nginx` контейнер, который будет работать на `80` порту
    ### Создаем `docker-compose` для `nginx`

    В удобном месте создаем папку `nginx`.

    Затем в ней создаем файл `docker-compose.yml`
    ```yml
    version: '3.7'

    services:
    nginx:
    container_name: nginx
    image: nginx:latest
    # Вместо Dockerfile берем готовый nginx (:latest - последняя версия) с docker hub
    volumes:
    - ./data/nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
    - 80:80
    # Порт 80 будет доступен как в контейнере, так и снаружи.
    ```

    >**volumes** - позволяет использовать файлы из локального компьютера не копируя их в контейнер.
    >В данном случае мы берем файл `nginx.conf` из папки `data`, который мы создадим дальше, в папке `nginx` на локальном компьютере и "говорим" контейнеру, что это файл который находится здесь `/etc/nginx/conf.d/default.conf`
    ### Создаем `nginx.conf` для `nginx`

    В папке `nginx` создаем папку `data` в которой создаем файл `nginx.conf` (название и путь должны соответствовать указанным в `docker-compose`)

    ```nginx
    server {
    return 301 http://google.com;
    }
    ```
    >Самый простой `conf`, для проверки работы контейнера.
    ### Запускаем `nginx` контейнер
    В папке `nginx` выполняем `docker-compose up`. В браузере при переходе на `localhost` мы должны попадать на сайт google.

    >Останавливаем контейнер и переходим далее
    ## Этап 5. Связываем контейнеры между собой
    >В результате `nginx` контейнер сможет перенаправлять запросы в другие контейнеры.
    >Действия описанные далее для `proj1` нужно выполнять для каждого проекта.
    ### Меняем `docker-compose` файл в `nginx` проекте
    В папке `nginx` в файле `docker-compose` под блоком `services` пишем новый блок `networks`
    ```yml
    networks:
    # Блок для объявления внутренних docker сетей, которые запустятся с этим файлом
    nginx_net:
    # Название сети для этого контейнера
    name: nginx_net
    # Название сети для внешних контейнеров
    ```

    В блоке `services` в подблок `nginx` добавляем
    ```yml
    networks:
    nginx_net:
    # Подключаемся к новой сети в этом контейнере
    ```
    ---
    **Результат:**
    ```yml
    version: '3.7'

    services:
    nginx:
    container_name: nginx
    image: nginx:latest
    networks:
    nginx_net:
    volumes:
    - ./data/nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
    - 80:80

    networks:
    nginx_net:
    name: nginx_net
    ```
    ### Меняем `docker-compose` файл в `react` проекте
    В папке `proj1` в файле `docker-compose`
    ```yml
    ports:
    - 8080:5000
    ```
    меняем на
    ```yml
    ports:
    - 5000
    ```
    >Контейнер по прежнему слушает на порту `5000`, но уже не открывает его для локального компьютера. Этот порт теперь доступен только контейнерам в той-же сети.

    Под блоком `services` пишем
    ```yml
    networks:
    nginx_net:
    external: true
    # Сообщает о том, что сеть nginx_net находится не в этом контейнере.
    ```

    В блоке `services` в подблок `proj1` добавляем
    ```yml
    networks:
    nginx_net:
    ```
    ---
    **Результат:**
    ```yml
    version: '3.7'
    services:
    proj1:
    container_name: proj1
    build: .
    networks:
    nginx_net:
    environment:
    - NODE_ENV=production
    ports:
    - 5000
    networks:
    nginx_net:
    external: true
    ```

    ### Меняем `nginx.conf` файл
    Удаляем все, что писали для примера и пишем

    ```nginx
    server {
    server_name proj1.com;
    location / {
    resolver 127.0.0.11;
    set $project http://proj1:5000;
    proxy_pass $project;
    }
    }
    server {
    server_name proj2.com;
    location / {
    resolver 127.0.0.11;
    set $project http://proj2:5000;
    proxy_pass $project;
    }
    }
    ```
    > Каждый блок `server` отвечает за отдельные задачи.
    >
    > `server_name` определяет с какого именно домена пришел запрос.
    >
    > `location /` указывает на то, что нас устраивает как `proj1.com`, так и что угодно после `/`.
    >
    > `proxy_pass` перенаправляет запрос на наш контейнер. **proj1** в `http://proj1:5000` доступен благодаря названию контейнера в `docker-compose -> services`.
    >
    > `resolver 127.0.0.11;` и `set $project http://proj1:5000;` нужны для того, чтобы контейнер мог запустится даже при нерабочем контейнере `proj1`. Можно было написать и `proxy_pass http://proj2:5000;` без переменных, но в этом случае, если не запущен контейнер `proj1`, `nginx` не запустится и выдаст ошибку.

    Запускаем все контейнеры `proj1`, `proj2` и `nginx`. В результате, вводя в браузере `proj1.com` и `proj2.com` мы попадаем на наши 2 проекта.

    ## Этап 6. Переносим контейнеры на сервер

    Если, все описанное выше работает, можно переносить наши проекты на сервер заменив все названия проектов, доменов в `nginx.conf` `proj1` и `proj2` на необходимые названия и домены.
    Для удобства я продолжу использовать `proj1` и `proj2`.

    В `nginx.conf` в `server_name` добавляем `www` записи. В итоге строка будет выглядеть так
    ```nginx
    server_name proj1.com www.proj1.com;
    ```
    Убедившись, что на сервере все работает, можно приступать к следующей части.

    # Настройка на сервере
    >Далее, много информации берется [от сюда](https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71)

    ## Этап 1. Подключаем `certbot` контейнер для запуска с `nginx`
    >В результате в папке `nginx`, `docker-compose up` будет запускать 2 контейнера. `nginx` и `certbot`

    В `services -> nginx -> ports` добавляем
    ```yml
    - 443:443
    # Открываем порты для ssl соединения
    ```

    В `services` добавляем
    ```yml
    certbot:
    # Новый контейнер, который запуститься вместе с nginx
    container_name: certbot
    image: certbot/certbot
    # Образ берется с docker hub
    networks:
    nginx_net:
    # Подключаем к той-же сети, что и остальные контейнеры
    ```
    >Если мы запустим контейнер сейчас, будет ошибка, так как `certbot `не может провести первичную настроку в docker контейнере.

    ## Этап 2. Добавляем пути к сертификатам
    >В конце мы запустим скрипт, который сгенерирует, сначала, фейковые сертификаты, для запуска `nginx`, а затем и настоящие. В этой части мы прописываем пути по которым `nginx` и `certbot` смогут их увидеть.

    В `services -> nginx` добавляем

    ```yml
    - ./data/certbot/conf:/etc/letsencrypt
    - ./data/certbot/www:/var/www/certbot
    ```
    В `services -> certbot` добавляем
    ```yml
    volumes:
    - ./data/certbot/conf:/etc/letsencrypt
    - ./data/certbot/www:/var/www/certbot
    ```
    ---
    **Результат**
    ```yml
    version: '3.7'
    services:
    nginx:
    container_name: nginx
    image: nginx:latest
    networks:
    nginx_net:
    volumes:
    - ./data/nginx.conf:/etc/nginx/conf.d/default.conf
    - ./data/certbot/conf:/etc/letsencrypt
    - ./data/certbot/www:/var/www/certbot
    ports:
    - 80:80
    - 443:443
    certbot:
    container_name: certbot
    image: certbot/certbot
    networks:
    nginx_net:
    volumes:
    - ./data/certbot/conf:/etc/letsencrypt
    - ./data/certbot/www:/var/www/certbot
    networks:
    nginx_net:
    name: nginx_net
    ```

    ## Этап 3. Редактируем `nginx`
    >Здесь мы создаем финальную конфигурацию `nginx`.
    >В примере будет 1 `server` блок отвечающий за `proj1`. Соответственно, сколько проектов, столько и `server` блоков.

    Существует множество конфигураций `nginx`. Здесь приведена одна из них.

    ```nginx
    server {
    listen 80;
    listen 443 ssl;
    # Слушаем на портах 80 и 443
    server_name proj1.com www.proj1.com;
    # Этот сервер блок выполняется при этих доменных именах
    ssl_certificate /etc/letsencrypt/live/proj1.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/proj1.com/privkey.pem;
    # ssl_certificate и ssl_certificate_key - необходимые сертификаты
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    # include и ssl_dhparam - дополнительные, рекомендуемые Let's Encrypt, параметры
    # Определяем, нужен ли редирект с www на без www'шную версию
    if ($server_port = 80) { set $https_redirect 1; }
    if ($host ~ '^www\.') { set $https_redirect 1; }
    if ($https_redirect = 1) { return 301 https://proj1.com$request_uri; }
    location /.well-known/acme-challenge/ { root /var/www/certbot; }
    # Путь по которому certbot сможет проверить сервер на подлинность
    location / {
    resolver 127.0.0.11;
    set $project http://proj1:5000;
    proxy_pass $project;
    }
    }
    ```

    >**Важно**. Скрипт, который мы запустим позже, сохраняет все сертификаты в папку, которая называется как первое доменное имя, которое мы там указываем. Соответственно в любом `server` блоке, независимо от домена пути `ssl_certificate` и `ssl_certificate_key` должны быть одинаковыми. В нашем случае они содержат путь к папке `proj1`.

    ## Этап 4. Завершаем настройку `docker-compose.yml` для `nginx` и `certbot`

    В `services -> nginx` добавляем
    ```yml
    restart: unless-stopped
    # Перезапустит контейнер в непредвиденных ситуациях
    command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
    # Перезапустит nginx контейнер каждые 6 часов и подгрузит новые сертификаты, если есть
    ```

    В `services -> certbot` добавляем

    ```yml
    restart: unless-stopped
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    # Проверяет каждые 12 часов, нужны ли новые сертификаты
    ```

    ---
    **Результат**
    ```yml
    version: '3.7'
    services:
    nginx:
    container_name: nginx
    image: nginx:latest
    restart: unless-stopped
    networks:
    nginx_net:
    volumes:
    - ./data/nginx.conf:/etc/nginx/conf.d/default.conf
    - ./data/certbot/conf:/etc/letsencrypt
    - ./data/certbot/www:/var/www/certbot
    ports:
    - 80:80
    - 443:443
    command: '/bin/sh -c ''while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g "daemon off;"'''
    certbot:
    container_name: certbot
    image: certbot/certbot
    restart: unless-stopped #+++
    networks:
    nginx_net:
    volumes:
    - ./data/certbot/conf:/etc/letsencrypt
    - ./data/certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    networks:
    nginx_net:
    name: nginx_net
    ```

    ## Этап 5. Получаем сертификаты
    Находясь в папке `nginx` вводим

    ```
    curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh
    ```
    Затем открываем полученный `init-letsencrypt.sh` и вписываем:
    - вместо `example.com` свои домены через пробел
    - email
    - меняем пути, если меняли их в папке
    - `staging` временно ставим 1 (для теста)
    `chmod +x init-letsencrypt.sh`
    `sudo ./init-letsencrypt.sh`
    Выполнив последнюю команду в папке `nginx -> data` появятся новые папки с сертификатами необходимые для работы `nginx` и `certbot`
    Если в тестовом режиме все получилось, то ставим `staging=0` и повторяем процедуру.