add: Зависимости и хелсчеки (6)

This commit is contained in:
Sergei Erjemin 2025-02-14 15:54:33 +03:00
parent 9de84306d8
commit a64615386d

View File

@ -479,4 +479,119 @@ dd0b7a683dde certbot/certbot:latest "/bin/sh -c 'apk add…" 13
**Все!** Теперь у нас полностью контейнеризированное решение, без лишних зависимостей на хосте, и при переносе каталога **Все!** Теперь у нас полностью контейнеризированное решение, без лишних зависимостей на хосте, и при переносе каталога
`~/docker-data` на другой сервер (с Docker + docker-compose) всё должно точно также запуститься. `~/docker-data` на другой сервер (с Docker + docker-compose) всё должно точно также запуститься.
## Зависимости и "проверки здоровья"
В нашем случае контейнеры не особо зависят друг от друга, но если стремиться к идеальной контейнеризации, то можно
рассмотреть "кто на ком стоит", какие зависимости и какой порядок запуска контейнеров.
Самый независимый контейнер — это `portainer`. Он просто запускается и работает. Начали его проксировать через nginx
или нет — ему важно.
Контейнер `letsencrypt-certbot` зависит от контейнера `nginx`, так как он без него не пройдет валидация домена.
Решение установить `depends_on` от контейнера `nginx` не идеально, т.к. старт контейнера `nginx` не означает, что он
готов принимать запросы. Избежать проблем можно сделав **healthcheck** для Nginx.
В docker-compose.yml добавим:
```yaml
nginx:
...
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
...
...
certbot:
...
depends_on:
nginx:
condition: service_healthy
...
...
```
Что тут происходит:
- `healthcheck: ...` — добавляем блок в контейнере `nginx` для проверки его здоровья. В данном случае проверяем
доступность Nginx выполняя команду `curl -f http://localhost`. Если Nginx отвечает, то контейнер считается здоровым;
- `interval: 10s` — проверка состояния каждые 10 секунд;
- `timeout: 3s` — ожиданий проверки 3 секунды (когда curl зависнет дольше 3 секунд, то это считается ошибкой);
- `retries: 3` — количество попыток проверки (если команда завершается с ошибкой (код ≠ 0) — Docker попробует ещё
раз... и так до 3-х раз);
- `start_period: 10s` — время ожидания перед началом проверок (первая проверка будет выполнена через 10 секунд после
старта контейнера);
- `depends_on: ...` — добавляем зависимость контейнера `certbot` от контейнера `nginx`. Но не просто так, а с условием
`service_healthy`. Это означает, что контейнер `certbot` не будет запущен, пока контейнер `nginx` не будет здоров.
Теперь при запуске контейнера `certbot` он будет ждать, пока контейнер `nginx` не станет здоровым (а еще задержку
в 10 секунд будет заметно даже на глаз).
К сожалению, если `nginx` упадет в процессе, то `certbot` не будет перезапущен. Но это уже другая история,
мир не идеален.
### Погасить проверку здоровья после первого успешного запуска (раздел для параноиков)
Бдительный читатель может заметить, что проверка `curl -f http://localhost` будет порождать бесполезную нагрузку на
сервере (ведь кажется, что по locahost будет отвечать `portainer`, а не `nginx`). Но это не так. Прокси на `portainer`
отвечать там не будет (ведь он настроен на домен `portainer.you.domain.name`), а будет отдавать дефолтную страницу
`nginx`. У нас она даже не настроена, и будет 301, а это вообще очень-очень мало не потребляет...
Тем не менее есть способ погасить проверку здоровья, как только контейнер стал `healthy` (допустим, чтобы "не следить"
в логах). У нас есть мапп `/home/web/docker-data/letsencrypt/_cert:/etc/letsencrypt` внутри контейнера `nginx`. Если мы
создадим скрипт в каталоге `/home/web/docker-data/letsencrypt/_cert` он будет доступен внутри контейнера в каталоге
`/etc/letsencrypt`. Создадим скрипт `healthcheck-nginx.sh`:
```bash
nano /home/web/docker-data/letsencrypt/_cert/healthcheck-nginx.sh
```
И поместим в него следующее содержимое:
```bash
#!/bin/sh
HEALTH_FILE_FLAG="/tmp/nginx_healthy"
if [ -f "HEALTH_FILE_FLAG" ]; then
exit 0 # Уже healthy, больше не проверяем
fi
if curl -fs http://localhost > /dev/null; then
touch "HEALTH_FILE_FLAG" # Создаем файл-флаг
exit 0 # Успешная проверка
else
exit 1 # Nginx еще не поднялся, пусть проверяют еще
fi
```
Что будет происходить. При старте контейнера `nginx` через *healthcheck* будем запускать этот скрипт.
При первом запуске он проверит есть ли файл-флаг `/tmp/nginx_healthy` внутри контейнера. Сразу после старта контейнера
этого фал-флага нет (контейнер запускается "чистым"). Затем скрипт попытается выполнить `curl -fs http://localhost`.
Если Nginx еще не поднялся, то **curl** возвращает ошибку и контейнер остается *unhealthy*. Но если Nginx уже работает,
то **touch** создается файл-флаг `/tmp/nginx_healthy` то **exit 0** ответит, что контейнер *healthy*.⃣ При всех
следующих *healthcheck* проверка файл-флага пройдет успешно, сразу будет получен `exit 0` и проверка *healthy* будет
завершена без вызова `curl`.
Осталось добавить этот скрипт в `docker-compose.yml`:
```yaml
nginx:
...
healthcheck:
test: ["CMD", "sh", "/etc/letsencrypt/healthcheck-nginx.sh"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
...
...
```
### Зависимости и healthcheck для `portainer` (или параноики могут сломать бизнес-логику)
А что с `nginx` и `portainer`? Они зависят друг от друга? Кажется нет. Если `nginx` упадет, то `portainer` просто
станет недоступен (ничего не будет доступно вообще). Если же `portainer` упадет, то `nginx` будет отдавать ошибку 502.
Ошибка, в данном случае, это тоже информация. Как минимум мы увидим что nginx работает, а упало приложение.
Тем не менее можно добавить `healthcheck` в `portainer`, проверять, что он отвечает внутри себя по порту 9000,
а затем установить зависимость `nginx` от здоровья `portainer`.
Но, кажется, это уже перебор.