From 6470803172f9c886db942b322c6a63be5990a617 Mon Sep 17 00:00:00 2001 From: erjemin Date: Fri, 14 Feb 2025 02:16:18 +0300 Subject: [PATCH] =?UTF-8?q?add:=20Nginx=20=D0=B8=20Certboot=20=D0=B2=20Doc?= =?UTF-8?q?ker=20(3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker/docker-nginx-w-certbot.md | 84 ++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/docker/docker-nginx-w-certbot.md b/docker/docker-nginx-w-certbot.md index c7065ac..4398095 100644 --- a/docker/docker-nginx-w-certbot.md +++ b/docker/docker-nginx-w-certbot.md @@ -2,11 +2,21 @@ Для удобного переноса сайтов или веб-приложений между серверами, а также для упрощения обновления и обслуживания веб-сервера nginx, удобно держать его в контейнере Docker. В данной инструкции рассмотрено развертывание веб-сервера -Nginx с SSL-сертификатами Let's Encrypt в контейнерах Docker. В качестве примера используется контейнер Portainer -- +Nginx с SSL-сертификатами Let's Encrypt в контейнерах Docker. В качестве примера сайта используется контейнер Portainer -- отличный инструмент для управления Docker-контейнерами через веб-интерфейс. -Соглашения: пусть наш пользователь от имени которого мы работаем -- `web`. Таким образом, домашний каталог -- `/home/web`. -Каталог для хранения данных Docker-контейнеров (место куда монтируют тома контейнера) -- `/home/web/docker-data`. +### Соглашения: +| | | +|-----------------------------|----------------------------------------------------------------------------------------| +| `web` | Пользователь от имени которого мы работаем | +| `/home/web` | Домашний каталог. | +| `/home/web/docker-data` | Каталог для хранения данных Docker-контейнеров (место куда монтируют тома контейнеров) | +| `portainer.you.domain.name` | Домен, по которому будет доступен Portainer | +| `email@you.domain.name` | Email для сертификатов Let's Encrypt | | + +Чтобы не случилось путаницы во время копи-паста рекомендуется сразу произвести все замены в тексте. + +## Portainer + nginx в контейнерах Docker И так, для начала создадим каталог для хранения данных Portainer (можно опустить если вам не нужен Portainer): ```bash @@ -43,14 +53,14 @@ server { ``` Что происходит в этом файле конфигурации: -- `listen 80;` -- слушаем порт 80 (обычный HTTP-трафик); -- `server_name portainer.you.domain.name;` -- имя хоста, по которому будет доступен Portainer (замените `you.domain.name` +- `listen 80;` — слушаем порт 80 (обычный HTTP-трафик); +- `server_name portainer.you.domain.name;` — имя хоста, по которому будет доступен Portainer (замените `you.domain.name` на ваш домен); -- `proxy_pass http://portainer:9000;` -- проксируем запросы в контейнер Portainer, который будет доступен по хосту (имени +- `proxy_pass http://portainer:9000;` — проксируем запросы в контейнер Portainer, который будет доступен по хосту (имени контейнера `portainer`) и порту 9000; -- `proxy_set_header Host $host;` -- передаем заголовок `Host` в запросе; -- `proxy_set_header X-Real-IP $remote_addr;` -- передаем заголовок `X-Real-IP` в запросе; -- `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;` -- передаем заголовок `X-Forwarded-For` в запросе; +- `proxy_set_header Host $host;` — передаем заголовок `Host` в запросе; +- `proxy_set_header X-Real-IP $remote_addr;` — передаем заголовок `X-Real-IP` в запросе; +- `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;` — передаем заголовок `X-Forwarded-For` в запросе; Сохраните файл и выйдите из редактора (`Ctrl + X`, затем `Y` для подтверждения). @@ -101,24 +111,24 @@ networks: Что у нас настроено в этом `docker-compose.yml`: - `portainer` (у вас его может и не быть, или быть какой-то другой сервис, который вы будете производить): - - `image: portainer/portainer-ce:latest` -- используем образ Portainer Community Edition; - - `container_name: portainer` -- имя контейнера `portainer`; - - `volumes: ...` -- монтируем файлы или тома изнутри контейнера Portainer на хост. В данном случае монтируем: сокет -- + - `image: portainer/portainer-ce:latest` — используем образ Portainer Community Edition; + - `container_name: portainer` — имя контейнера `portainer`; + - `volumes: ...` — монтируем файлы или тома изнутри контейнера Portainer на хост. В данном случае монтируем: сокет -- чтобы изнутри контейнера Portainer можно было управлять Docker, в котором сам же и работает (вот так хитро) и каталог - `/home/web/docker-data/portainer` -- чтобы сохранять данные Portainer между перезапусками; - - `restart: always` -- автоматически перезапускаем контейнер при его остановке; - - `networks: ...` -- подключаем контейнер к пользовательской (внутри-контейнерной) сети `web`. + `/home/web/docker-data/portainer` — чтобы сохранять данные Portainer между перезапусками; + - `restart: always` — автоматически перезапускаем контейнер при его остановке; + - `networks: ...` — подключаем контейнер к пользовательской (внутри-контейнерной) сети `web`. - `nginx`: - - `image: nginx:latest` -- используем образ Nginx; - - `container_name: nginx` -- имя контейнера `nginx`; - - `ports: ...` -- пробрасываем порты 80 и 443 на хост; - - `volumes: ...` -- монтируем каталог с конфигурационными файлами Nginx; - - `restart: always` -- автоматически перезапускаем контейнер при его остановке; - - `networks: ...` -- подключаем контейнер к пользовательской (внутри-контейнерной) сети `web`. + - `image: nginx:latest` — используем образ Nginx; + - `container_name: nginx` — имя контейнера `nginx`; + - `ports: ...` — пробрасываем порты 80 и 443 на хост; + - `volumes: ...` — монтируем каталог с конфигурационными файлами Nginx; + - `restart: always` — автоматически перезапускаем контейнер при его остановке; + - `networks: ...` — подключаем контейнер к пользовательской (внутри-контейнерной) сети `web`. - `networks: ...`: - `web`: - - `driver: bridge` -- используем драйвер сети `bridge` (по умолчанию); - - `ipam: ...` -- настраиваем IP-адреса для контейнеров внутри сети `web`. В данном случае используем подсеть + - `driver: bridge` — используем драйвер сети `bridge` (по умолчанию); + - `ipam: ...` — настраиваем IP-адреса для контейнеров внутри сети `web`. В данном случае используем подсеть Сохраняем файл `docker-compose.yml` и выходим из редактора (`Ctrl + X`, затем `Y` для подтверждения). @@ -132,7 +142,7 @@ docker-compose up -d ## Let's Encrypt в контейнере Docker -Создадим каталог для хранения ключей сертификатов Let's Encrypt: +Создадим каталог для хранения ключей сертификатов Let's Encrypt и временных файлов для проверки владения доменом: ```bash mkdir -p /home/web/docker-data/letsencrypt mkdir -p /home/web/docker-data/letsencrypt/_cert @@ -157,7 +167,7 @@ mkdir -p /home/web/docker-data/letsencrypt/_ownership_check *Что тут происходит и зачем нам такие мапинги томов?* 1. Когда certbot запрашивает сертификат у Let's Encrypt, то тот требует подтверждения владения доменом. При работе - certbot в контейнере самый популярный (и лучший при работе в контейнере) способ подтверждения -- это HTTP-проверка. + certbot в контейнере самый популярный (и лучший при работе в контейнере) способ подтверждения — это HTTP-проверка. Certbot создает временные файлы в каталоге `/var/www/html/letsencrypt` (мы будем указывать этот каталог при инициализации сертификата). Сервер же Let's Encrypt перед выдачей сертификата делает HTTP-запрос к этому временному файлу по URL (например, в нашем случае по `http://portainer.you.domain.namey/.well-known/acme-challenge/`) и если файл доступен, @@ -178,10 +188,10 @@ mkdir -p /home/web/docker-data/letsencrypt/_ownership_check можно не делать, и обойтись хуками certbot, но это сложнее. 4. `entrypoint: ...`: - - `apk add --no-cache curl` -- устанавливаем пакет `curl`, который потребуется для работы с Docker API через сокет; - - `trap exit TERM;` -- устанавливаем обработчик сигнала `TERM`, чтобы контейнер certbot корректно завершал работу; + - `apk add --no-cache curl` — устанавливаем пакет `curl`, который потребуется для работы с Docker API через сокет; + - `trap exit TERM;` — устанавливаем обработчик сигнала `TERM`, чтобы контейнер certbot корректно завершал работу; - `while :; do sleep 12h & wait $${!}; certbot renew --deploy-hook /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh; done` - -- это скрипт, который запускается при старте контейнера certbot. Он запускает certbot в режиме `renew` каждые 12 часов. + — это скрипт, который запускается при старте контейнера certbot. Он запускает certbot в режиме `renew` каждые 12 часов. Таким образом, сертификаты будут автоматически обновляться каждые 12 часов. @@ -207,9 +217,9 @@ mkdir -p /home/web/docker-data/letsencrypt/_ownership_check владения, но теперь для контейнера nginx*: - `volumes: ...`: - - `/home/web/docker-data/letsencrypt/_cert:/etc/letsencrypt` -- маппинг тома для сертификатов Let's Encrypt, чтобы их + - `/home/web/docker-data/letsencrypt/_cert:/etc/letsencrypt` — маппинг тома для сертификатов Let's Encrypt, чтобы их можно было использовать в контейнере nginx; - - `/home/web/docker-data/letsencrypt/_ownership_check:/var/www/letsencrypt` -- маппинг тома временных файлов для + - `/home/web/docker-data/letsencrypt/_ownership_check:/var/www/letsencrypt` — маппинг тома временных файлов для проверки владения доменом со стороны Let's Encrypt. Сохраняем файл `docker-compose.yml`. @@ -332,15 +342,15 @@ server { Что изменилось в конфигурации: - Добавлены директивы для SSL для сервера в котором живет прокси на Portainer: - - `listen 443 ssl;` -- теперь сервер слушает порт 443 (HTTPS) и использует SSL; - - `ssl_certificate ...` и `ssl_certificate_key ...` -- указываем пути к сертификату и ключу сертификата Let's Encrypt; - - `ssl_protocols TLSv1.2 TLSv1.3;` -- указываем протоколы SSL/TLS, которые будут использоваться; - - `ssl_ciphers HIGH:!aNULL:!MD5;` -- указываем шифры, которые будут использоваться (`HIGH` → Разрешает только сильные + - `listen 443 ssl;` — теперь сервер слушает порт 443 (HTTPS) и использует SSL; + - `ssl_certificate ...` и `ssl_certificate_key ...` — указываем пути к сертификату и ключу сертификата Let's Encrypt; + - `ssl_protocols TLSv1.2 TLSv1.3;` — указываем протоколы SSL/TLS, которые будут использоваться; + - `ssl_ciphers HIGH:!aNULL:!MD5;` — указываем шифры, которые будут использоваться (`HIGH` → Разрешает только сильные шифры, например, AES256, `!aNULL` → Запрещает анонимные шифры, которые не используют аутентификацию и уязвимы к MITM-атакам, `!MD5` → Запрещает использование хэша MD5, так как он давно признан небезопасным; - - `ssl_prefer_server_ciphers on;` -- указываем, что сервер предпочтет использовать свои шифры, а не клиентские. + - `ssl_prefer_server_ciphers on;` — указываем, что сервер предпочтет использовать свои шифры, а не клиентские. - Добавлен блок `server` для перенаправления с HTTP на HTTPS: - - `return 301 https://$host$request_uri;` -- перенаправляем все запросы с порта 80 на порт 443 c кодом 301 (перемещено + - `return 301 https://$host$request_uri;` — перенаправляем все запросы с порта 80 на порт 443 c кодом 301 (перемещено навсегда). Сохраним файл и, наконец запустим все контейнеры: @@ -380,7 +390,7 @@ Congratulations, all simulated renewals succeeded: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ``` -И наконец, при обновлении сертификатов нужно перезапускать контейнер nginx чтобы он перподключил эти сертификаты. +И наконец, при обновлении сертификатов нужно перезапускать контейнер `nginx` чтобы он перподключил новые сертификаты. Изнутри контейнера `letsencrypt-certbot` мы не можем управлять контейнерами Docker (и даже ничего не знаем о них), но можно добавить **хук** в `letsencrypt-certbot`, который будет перезапускать контейнер `nginx` сразу после успешного обновления сертификатов (и только если обновление прошло успешно)!