Пошук уроків, статей та іншого контенту
Розмістите кілька контейнеризованих сервісів за reverse proxy та налаштуєте маршрутизацію запитів.
Reverse proxy — це сервер, який приймає запити від клієнтів і передає їх потрібному внутрішньому сервісу.
Клієнт взаємодіє лише з reverse proxy:
Клієнт → Nginx → потрібний Docker-контейнерНаприклад:
http://localhost/ — frontend-сервіс;
http://localhost/api/ — API-сервіс.
Клієнту не потрібно знати внутрішні імена контейнерів або порти. Reverse proxy використовує Docker DNS і звертається до сервісів за їхніми іменами, наприклад frontend:5678 або api:5678.
Створимо три контейнери:
frontend — приклад frontend-сервісу;
api — приклад API-сервісу.
Зовнішній доступ матиме лише Nginx:
localhost:8080/
└── frontend
localhost:8080/api/
└── apiКонтейнери frontend і api будуть доступні тільки всередині Docker-мережі.
Створіть таку структуру:
reverse-proxy/
├── compose.yaml
└── nginx/
└── default.confФайл compose.yaml:
services:
proxy:
image: nginx:1.27-alpine
ports:
- "8080:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- frontend
- api
frontend:
image: hashicorp/http-echo:1.0
command: ["-text=Frontend service", "-listen=:5678"]
expose:
- "5678"
api:
image: hashicorp/http-echo:1.0
command: ["-text=API service", "-listen=:5678"]
expose:
- "5678"Сервіс proxy:
ports:
- "8080:80"Порт 80 контейнера Nginx буде доступний на порту 8080 хоста.
Для інших сервісів використано:
expose:
- "5678"expose описує порт, доступний іншим контейнерам у тій самій Docker-мережі. Він не публікує порт на хості.
На відміну від цього, ports створює зовнішню публікацію порту. Для frontend і api вона не потрібна.
Усі сервіси з цього файлу автоматично потраплять до спільної мережі Compose. Тому Nginx зможе звертатися до них за іменами сервісів:
frontend:5678
api:5678Не потрібно використовувати localhost. Усередині контейнера localhost означає поточний контейнер, а не інший сервіс.
Файл nginx/default.conf:
upstream frontend_service {
server frontend:5678;
}
upstream api_service {
server api:5678;
}
server {
listen 80;
location /api/ {
proxy_pass http://api_service/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://frontend_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}upstreamupstream frontend_service {
server frontend:5678;
}upstream описує внутрішній сервер, до якого Nginx передаватиме запити.
У цьому прикладі:
frontend — ім’я сервісу в Docker Compose;
5678 — порт, на якому сервіс слухає запити.
Ім’я frontend автоматично розв’язується Docker DNS у внутрішню IP-адресу контейнера.
Окремий блок upstream зручний тим, що пізніше до нього можна додати кілька серверів для розподілу навантаження.
Правило:
location /api/ {
proxy_pass http://api_service/;
}передає запити з префіксом /api/ до API-сервісу.
Важливо звернути увагу на завершальний символ / у proxy_pass:
proxy_pass http://api_service/;За такого запису Nginx замінює префікс /api/ на /.
Наприклад:
/api/users → /users
/api/health → /healthЯкщо написати proxy_pass http://api_service; без завершального /, початковий шлях буде переданий інакше:
/api/users → /api/usersЦе може бути потрібним, якщо сам API очікує префікс /api, але поведінку потрібно обирати свідомо.
Правило:
location / {
proxy_pass http://frontend_service;
}обробляє всі інші запити. Наприклад:
/ → frontend
/about → frontend
/assets/app.js → frontendNginx обирає найбільш конкретний відповідний location, тому /api/ має пріоритет над загальним /.
За замовчуванням проксі може не передати застосунку всю інформацію про початковий запит. Додаткові заголовки допомагають сервісу дізнатися:
початкове ім’я хоста;
IP-адресу клієнта;
ланцюжок проксі;
початкову схему запиту — http або https.
Наприклад:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;Додаток може використовувати X-Forwarded-For, щоб визначити IP клієнта замість IP reverse proxy.
Перейдіть до каталогу проєкту та запустіть Compose:
docker compose up -dПеревірте стан контейнерів:
docker compose psОчікувано всі три сервіси мають бути запущені.
Перевірте кореневий маршрут:
curl http://localhost:8080/Очікувана відповідь:
Frontend serviceПеревірте API-маршрут:
curl http://localhost:8080/api/healthОчікувана відповідь:
API serviceУ другому випадку Nginx отримує запит /api/health, знаходить правило location /api/ і передає сервісу api шлях /health.
Поведінку можна подати у вигляді правил:
Запит Сервіс Шлях усередині сервісу
/ frontend /
/about frontend /about
/api/health api /health
/api/users api /usersПереглянути журнали Nginx можна командою:
docker compose logs -f proxyЖурнали окремого сервісу:
docker compose logs -f apiЩоб зупинити контейнери:
docker compose downReverse proxy може маршрутизувати запити не лише за шляхом, а й за доменом.
Наприклад, один домен можна спрямувати на frontend, а інший — на API:
server {
listen 80;
server_name app.example.test;
location / {
proxy_pass http://frontend_service;
}
}
server {
listen 80;
server_name api.example.test;
location / {
proxy_pass http://api_service;
}
}У такому разі маршрутизація залежить від заголовка Host:
app.example.test → frontend
api.example.test → apiДля локального тестування такі домени можна додати до файлу hosts, але для цього прикладу достатньо маршрутизації за шляхом.
Якщо змінити default.conf, контейнер Nginx не обов’язково потрібно створювати заново. Можна перевірити конфігурацію:
docker compose exec proxy nginx -tЯкщо перевірка успішна, перезавантажте Nginx:
docker compose exec proxy nginx -s reloadКоманда nginx -t перевіряє синтаксис конфігурації, але не гарантує, що кожен upstream-сервіс працює. Доступність сервісів можна перевірити через журнали та HTTP-запити.
До одного upstream можна додати кілька серверів:
upstream frontend_service {
server frontend_1:5678;
server frontend_2:5678;
}Тоді Nginx розподілятиме запити між ними.
Для такого варіанта кожен контейнер повинен:
перебувати в тій самій Docker-мережі;
слухати вказаний порт;
бути доступним за відповідним іменем.
У базовій конфігурації використовується лише один контейнер на сервіс, але структура upstream уже підготовлена до розширення.
Під час налаштування reverse proxy зручно діяти послідовно:
Запустити внутрішні сервіси без Nginx.
Переконатися, що вони слухають очікувані порти.
Додати сервіси до спільної Docker-мережі.
У конфігурації Nginx використовувати імена Compose-сервісів.
Перевірити конфігурацію командою nginx -t.
Перевірити кожен маршрут через curl.
Переглянути журнали Nginx і цільового сервісу в разі помилки.
localhost для іншого контейнераНеправильно:
proxy_pass http://localhost:5678;У контейнері Nginx localhost посилається на сам Nginx.
Правильно:
proxy_pass http://api_service;або безпосередньо:
proxy_pass http://api:5678;Необов’язково публікувати frontend і api через ports. Якщо доступ до них має відбуватися лише через Nginx, достатньо внутрішньої мережі Docker.
Публічним має бути лише порт reverse proxy:
proxy:
ports:
- "8080:80"proxy_passЦі конфігурації мають різну поведінку:
location /api/ {
proxy_pass http://api_service/;
}і:
location /api/ {
proxy_pass http://api_service;
}Перша видаляє префікс /api/, друга зберігає початковий URI. Якщо API отримує неочікуваний шлях, спочатку перевірте саме це.
locationЗагальне правило:
location / {
proxy_pass http://frontend_service;
}може обробити всі запити, якщо спеціальне правило для API написане неправильно. Переконайтеся, що маршрут має потрібний префікс:
location /api/ {
proxy_pass http://api_service/;
}Перевірте синтаксис:
docker compose run --rm proxy nginx -tТакож перегляньте журнали:
docker compose logs proxyНайчастіші причини:
помилка в дужках або крапках з комою;
неправильне ім’я upstream;
відсутній змонтований файл конфігурації;
неправильний порт внутрішнього сервісу.
Reverse proxy приймає зовнішні запити й передає їх внутрішнім сервісам.
Nginx у Docker Compose може звертатися до контейнерів за іменами сервісів.
Для маршрутизації за шляхом використовують блоки location.
Запит /api/users можна передати API як /users, використовуючи proxy_pass із завершальним /.
Внутрішні сервіси не потрібно публікувати через ports, якщо клієнти мають доступати до них лише через proxy.
Заголовки X-Forwarded-* передають застосунку інформацію про початковий запит.
Після зміни конфігурації перевіряйте її через nginx -t, а потім перезавантажуйте Nginx.