add: Nginx и Certboot в Docker (3)
This commit is contained in:
parent
a149cab8c5
commit
6470803172
@ -2,11 +2,21 @@
|
|||||||
|
|
||||||
Для удобного переноса сайтов или веб-приложений между серверами, а также для упрощения обновления и обслуживания
|
Для удобного переноса сайтов или веб-приложений между серверами, а также для упрощения обновления и обслуживания
|
||||||
веб-сервера nginx, удобно держать его в контейнере Docker. В данной инструкции рассмотрено развертывание веб-сервера
|
веб-сервера nginx, удобно держать его в контейнере Docker. В данной инструкции рассмотрено развертывание веб-сервера
|
||||||
Nginx с SSL-сертификатами Let's Encrypt в контейнерах Docker. В качестве примера используется контейнер Portainer --
|
Nginx с SSL-сертификатами Let's Encrypt в контейнерах Docker. В качестве примера сайта используется контейнер Portainer --
|
||||||
отличный инструмент для управления Docker-контейнерами через веб-интерфейс.
|
отличный инструмент для управления 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):
|
И так, для начала создадим каталог для хранения данных Portainer (можно опустить если вам не нужен Portainer):
|
||||||
```bash
|
```bash
|
||||||
@ -43,14 +53,14 @@ server {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Что происходит в этом файле конфигурации:
|
Что происходит в этом файле конфигурации:
|
||||||
- `listen 80;` -- слушаем порт 80 (обычный HTTP-трафик);
|
- `listen 80;` — слушаем порт 80 (обычный HTTP-трафик);
|
||||||
- `server_name portainer.you.domain.name;` -- имя хоста, по которому будет доступен Portainer (замените `you.domain.name`
|
- `server_name portainer.you.domain.name;` — имя хоста, по которому будет доступен Portainer (замените `you.domain.name`
|
||||||
на ваш домен);
|
на ваш домен);
|
||||||
- `proxy_pass http://portainer:9000;` -- проксируем запросы в контейнер Portainer, который будет доступен по хосту (имени
|
- `proxy_pass http://portainer:9000;` — проксируем запросы в контейнер Portainer, который будет доступен по хосту (имени
|
||||||
контейнера `portainer`) и порту 9000;
|
контейнера `portainer`) и порту 9000;
|
||||||
- `proxy_set_header Host $host;` -- передаем заголовок `Host` в запросе;
|
- `proxy_set_header Host $host;` — передаем заголовок `Host` в запросе;
|
||||||
- `proxy_set_header X-Real-IP $remote_addr;` -- передаем заголовок `X-Real-IP` в запросе;
|
- `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 X-Forwarded-For $proxy_add_x_forwarded_for;` — передаем заголовок `X-Forwarded-For` в запросе;
|
||||||
|
|
||||||
Сохраните файл и выйдите из редактора (`Ctrl + X`, затем `Y` для подтверждения).
|
Сохраните файл и выйдите из редактора (`Ctrl + X`, затем `Y` для подтверждения).
|
||||||
|
|
||||||
@ -101,24 +111,24 @@ networks:
|
|||||||
|
|
||||||
Что у нас настроено в этом `docker-compose.yml`:
|
Что у нас настроено в этом `docker-compose.yml`:
|
||||||
- `portainer` (у вас его может и не быть, или быть какой-то другой сервис, который вы будете производить):
|
- `portainer` (у вас его может и не быть, или быть какой-то другой сервис, который вы будете производить):
|
||||||
- `image: portainer/portainer-ce:latest` -- используем образ Portainer Community Edition;
|
- `image: portainer/portainer-ce:latest` — используем образ Portainer Community Edition;
|
||||||
- `container_name: portainer` -- имя контейнера `portainer`;
|
- `container_name: portainer` — имя контейнера `portainer`;
|
||||||
- `volumes: ...` -- монтируем файлы или тома изнутри контейнера Portainer на хост. В данном случае монтируем: сокет --
|
- `volumes: ...` — монтируем файлы или тома изнутри контейнера Portainer на хост. В данном случае монтируем: сокет --
|
||||||
чтобы изнутри контейнера Portainer можно было управлять Docker, в котором сам же и работает (вот так хитро) и каталог
|
чтобы изнутри контейнера Portainer можно было управлять Docker, в котором сам же и работает (вот так хитро) и каталог
|
||||||
`/home/web/docker-data/portainer` -- чтобы сохранять данные Portainer между перезапусками;
|
`/home/web/docker-data/portainer` — чтобы сохранять данные Portainer между перезапусками;
|
||||||
- `restart: always` -- автоматически перезапускаем контейнер при его остановке;
|
- `restart: always` — автоматически перезапускаем контейнер при его остановке;
|
||||||
- `networks: ...` -- подключаем контейнер к пользовательской (внутри-контейнерной) сети `web`.
|
- `networks: ...` — подключаем контейнер к пользовательской (внутри-контейнерной) сети `web`.
|
||||||
- `nginx`:
|
- `nginx`:
|
||||||
- `image: nginx:latest` -- используем образ Nginx;
|
- `image: nginx:latest` — используем образ Nginx;
|
||||||
- `container_name: nginx` -- имя контейнера `nginx`;
|
- `container_name: nginx` — имя контейнера `nginx`;
|
||||||
- `ports: ...` -- пробрасываем порты 80 и 443 на хост;
|
- `ports: ...` — пробрасываем порты 80 и 443 на хост;
|
||||||
- `volumes: ...` -- монтируем каталог с конфигурационными файлами Nginx;
|
- `volumes: ...` — монтируем каталог с конфигурационными файлами Nginx;
|
||||||
- `restart: always` -- автоматически перезапускаем контейнер при его остановке;
|
- `restart: always` — автоматически перезапускаем контейнер при его остановке;
|
||||||
- `networks: ...` -- подключаем контейнер к пользовательской (внутри-контейнерной) сети `web`.
|
- `networks: ...` — подключаем контейнер к пользовательской (внутри-контейнерной) сети `web`.
|
||||||
- `networks: ...`:
|
- `networks: ...`:
|
||||||
- `web`:
|
- `web`:
|
||||||
- `driver: bridge` -- используем драйвер сети `bridge` (по умолчанию);
|
- `driver: bridge` — используем драйвер сети `bridge` (по умолчанию);
|
||||||
- `ipam: ...` -- настраиваем IP-адреса для контейнеров внутри сети `web`. В данном случае используем подсеть
|
- `ipam: ...` — настраиваем IP-адреса для контейнеров внутри сети `web`. В данном случае используем подсеть
|
||||||
|
|
||||||
Сохраняем файл `docker-compose.yml` и выходим из редактора (`Ctrl + X`, затем `Y` для подтверждения).
|
Сохраняем файл `docker-compose.yml` и выходим из редактора (`Ctrl + X`, затем `Y` для подтверждения).
|
||||||
|
|
||||||
@ -132,7 +142,7 @@ docker-compose up -d
|
|||||||
|
|
||||||
## Let's Encrypt в контейнере Docker
|
## Let's Encrypt в контейнере Docker
|
||||||
|
|
||||||
Создадим каталог для хранения ключей сертификатов Let's Encrypt:
|
Создадим каталог для хранения ключей сертификатов Let's Encrypt и временных файлов для проверки владения доменом:
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /home/web/docker-data/letsencrypt
|
mkdir -p /home/web/docker-data/letsencrypt
|
||||||
mkdir -p /home/web/docker-data/letsencrypt/_cert
|
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, то тот требует подтверждения владения доменом. При работе
|
1. Когда certbot запрашивает сертификат у Let's Encrypt, то тот требует подтверждения владения доменом. При работе
|
||||||
certbot в контейнере самый популярный (и лучший при работе в контейнере) способ подтверждения -- это HTTP-проверка.
|
certbot в контейнере самый популярный (и лучший при работе в контейнере) способ подтверждения — это HTTP-проверка.
|
||||||
Certbot создает временные файлы в каталоге `/var/www/html/letsencrypt` (мы будем указывать этот каталог при инициализации
|
Certbot создает временные файлы в каталоге `/var/www/html/letsencrypt` (мы будем указывать этот каталог при инициализации
|
||||||
сертификата). Сервер же Let's Encrypt перед выдачей сертификата делает HTTP-запрос к этому временному файлу по URL
|
сертификата). Сервер же Let's Encrypt перед выдачей сертификата делает HTTP-запрос к этому временному файлу по URL
|
||||||
(например, в нашем случае по `http://portainer.you.domain.namey/.well-known/acme-challenge/`) и если файл доступен,
|
(например, в нашем случае по `http://portainer.you.domain.namey/.well-known/acme-challenge/`) и если файл доступен,
|
||||||
@ -178,10 +188,10 @@ mkdir -p /home/web/docker-data/letsencrypt/_ownership_check
|
|||||||
можно не делать, и обойтись хуками certbot, но это сложнее.
|
можно не делать, и обойтись хуками certbot, но это сложнее.
|
||||||
|
|
||||||
4. `entrypoint: ...`:
|
4. `entrypoint: ...`:
|
||||||
- `apk add --no-cache curl` -- устанавливаем пакет `curl`, который потребуется для работы с Docker API через сокет;
|
- `apk add --no-cache curl` — устанавливаем пакет `curl`, который потребуется для работы с Docker API через сокет;
|
||||||
- `trap exit TERM;` -- устанавливаем обработчик сигнала `TERM`, чтобы контейнер certbot корректно завершал работу;
|
- `trap exit TERM;` — устанавливаем обработчик сигнала `TERM`, чтобы контейнер certbot корректно завершал работу;
|
||||||
- `while :; do sleep 12h & wait $${!}; certbot renew --deploy-hook /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh; done`
|
- `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 часов.
|
Таким образом, сертификаты будут автоматически обновляться каждые 12 часов.
|
||||||
|
|
||||||
|
|
||||||
@ -207,9 +217,9 @@ mkdir -p /home/web/docker-data/letsencrypt/_ownership_check
|
|||||||
владения, но теперь для контейнера nginx*:
|
владения, но теперь для контейнера nginx*:
|
||||||
|
|
||||||
- `volumes: ...`:
|
- `volumes: ...`:
|
||||||
- `/home/web/docker-data/letsencrypt/_cert:/etc/letsencrypt` -- маппинг тома для сертификатов Let's Encrypt, чтобы их
|
- `/home/web/docker-data/letsencrypt/_cert:/etc/letsencrypt` — маппинг тома для сертификатов Let's Encrypt, чтобы их
|
||||||
можно было использовать в контейнере nginx;
|
можно было использовать в контейнере nginx;
|
||||||
- `/home/web/docker-data/letsencrypt/_ownership_check:/var/www/letsencrypt` -- маппинг тома временных файлов для
|
- `/home/web/docker-data/letsencrypt/_ownership_check:/var/www/letsencrypt` — маппинг тома временных файлов для
|
||||||
проверки владения доменом со стороны Let's Encrypt.
|
проверки владения доменом со стороны Let's Encrypt.
|
||||||
|
|
||||||
Сохраняем файл `docker-compose.yml`.
|
Сохраняем файл `docker-compose.yml`.
|
||||||
@ -332,15 +342,15 @@ server {
|
|||||||
|
|
||||||
Что изменилось в конфигурации:
|
Что изменилось в конфигурации:
|
||||||
- Добавлены директивы для SSL для сервера в котором живет прокси на Portainer:
|
- Добавлены директивы для SSL для сервера в котором живет прокси на Portainer:
|
||||||
- `listen 443 ssl;` -- теперь сервер слушает порт 443 (HTTPS) и использует SSL;
|
- `listen 443 ssl;` — теперь сервер слушает порт 443 (HTTPS) и использует SSL;
|
||||||
- `ssl_certificate ...` и `ssl_certificate_key ...` -- указываем пути к сертификату и ключу сертификата Let's Encrypt;
|
- `ssl_certificate ...` и `ssl_certificate_key ...` — указываем пути к сертификату и ключу сертификата Let's Encrypt;
|
||||||
- `ssl_protocols TLSv1.2 TLSv1.3;` -- указываем протоколы SSL/TLS, которые будут использоваться;
|
- `ssl_protocols TLSv1.2 TLSv1.3;` — указываем протоколы SSL/TLS, которые будут использоваться;
|
||||||
- `ssl_ciphers HIGH:!aNULL:!MD5;` -- указываем шифры, которые будут использоваться (`HIGH` → Разрешает только сильные
|
- `ssl_ciphers HIGH:!aNULL:!MD5;` — указываем шифры, которые будут использоваться (`HIGH` → Разрешает только сильные
|
||||||
шифры, например, AES256, `!aNULL` → Запрещает анонимные шифры, которые не используют аутентификацию и уязвимы
|
шифры, например, AES256, `!aNULL` → Запрещает анонимные шифры, которые не используют аутентификацию и уязвимы
|
||||||
к MITM-атакам, `!MD5` → Запрещает использование хэша MD5, так как он давно признан небезопасным;
|
к MITM-атакам, `!MD5` → Запрещает использование хэша MD5, так как он давно признан небезопасным;
|
||||||
- `ssl_prefer_server_ciphers on;` -- указываем, что сервер предпочтет использовать свои шифры, а не клиентские.
|
- `ssl_prefer_server_ciphers on;` — указываем, что сервер предпочтет использовать свои шифры, а не клиентские.
|
||||||
- Добавлен блок `server` для перенаправления с HTTP на HTTPS:
|
- Добавлен блок `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` мы не можем управлять контейнерами Docker (и даже ничего не знаем о них),
|
||||||
но можно добавить **хук** в `letsencrypt-certbot`, который будет перезапускать контейнер `nginx` сразу после успешного
|
но можно добавить **хук** в `letsencrypt-certbot`, который будет перезапускать контейнер `nginx` сразу после успешного
|
||||||
обновления сертификатов (и только если обновление прошло успешно)!
|
обновления сертификатов (и только если обновление прошло успешно)!
|
||||||
|
Loading…
Reference in New Issue
Block a user