add: Nginx и Certboot в Docker (3)
This commit is contained in:
parent
a149cab8c5
commit
6470803172
@ -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` сразу после успешного
|
||||
обновления сертификатов (и только если обновление прошло успешно)!
|
||||
|
Loading…
Reference in New Issue
Block a user