Initial commit: interview prep repo (resume, legend, stack cases, Kubernetes course)

This commit is contained in:
Tot Maxim
2026-07-18 00:37:41 +03:00
commit 9bb7f6263f
47 changed files with 3548 additions and 0 deletions

111
stack/nginx/CASE.md Normal file
View File

@@ -0,0 +1,111 @@
# Кейс: Nginx как reverse proxy и балансировщик нагрузки
Связь с легендой: [../../legend/LEGEND.md](../../legend/LEGEND.md) — раздел «АО ТНИИС», конфигурационные файлы Nginx для маршрутизации и балансировки нагрузки перед сервисами.
## Что нужно реально сделать (домашний стенд)
Поднять два бэкенд-сервиса и один Nginx перед ними, который распределяет нагрузку и отдаёт статус бэкендов, чтобы своими руками увидеть балансировку, health-check-подобное поведение и базовую защиту (rate limiting).
### 1. Структура
```
nginx-lab/
├── docker-compose.yml
├── nginx/
│ └── nginx.conf
└── backend/
└── index.html
```
### 2. docker-compose.yml
```yaml
version: "3.8"
services:
backend1:
image: nginx:alpine
container_name: backend1
volumes:
- ./backend/index.html:/usr/share/nginx/html/index.html
environment:
- INSTANCE=backend1
backend2:
image: nginx:alpine
container_name: backend2
volumes:
- ./backend/index.html:/usr/share/nginx/html/index.html
environment:
- INSTANCE=backend2
proxy:
image: nginx:alpine
container_name: proxy
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- "8080:80"
depends_on:
- backend1
- backend2
```
### 3. nginx/nginx.conf
```nginx
events {
worker_connections 1024;
}
http {
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=5r/s;
upstream backend_pool {
least_conn;
server backend1:80 max_fails=3 fail_timeout=10s;
server backend2:80 max_fails=3 fail_timeout=10s;
}
server {
listen 80;
location / {
limit_req zone=req_limit burst=10 nodelay;
proxy_pass http://backend_pool;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /health {
return 200 'ok';
add_header Content-Type text/plain;
}
}
}
```
### 4. Шаги воспроизведения
1. `docker compose up -d`.
2. `curl http://localhost:8080/` несколько раз подряд — убедиться, что ответы приходят от backend1 и backend2 попеременно (балансировка `least_conn`; чтобы увидеть разницу в теле ответа, положить в `index.html` разные значения через `INSTANCE` или просто проверить по логам `docker compose logs backend1 backend2`, какой контейнер получил запрос).
3. Остановить один бэкенд (`docker compose stop backend1`) и убедиться, что Nginx продолжает отдавать 200, перенаправляя весь трафик на backend2 — практика `max_fails`/`fail_timeout` в деле.
4. Прогнать быстрый цикл запросов (`for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/; done`) и увидеть `503` от `limit_req`, когда запросы превышают `rate=5r/s` — понять, как работает rate limiting на практике.
5. Проверить `curl http://localhost:8080/health` — отдельный location без проксирования, как обычно делают health-check эндпоинт.
### 5. Что это даёт в разговоре с интервьюером
- Понимание `upstream`-блока и алгоритмов балансировки (`round-robin` по умолчанию, `least_conn`, `ip_hash`).
- Понимание, зачем нужны `proxy_set_header` (чтобы бэкенд видел реальный IP и Host клиента, а не адрес самого Nginx).
- Практика rate limiting (`limit_req_zone`/`limit_req`) как базовой защиты от перегрузки/abuse.
- Понимание `max_fails`/`fail_timeout` как механизма пассивного health-check upstream-серверов.
## Нагрузка и цифры
Замерить пропускную способность своего стенда: `hey -n 10000 -c 50 http://localhost:8080/` (или `wrk -t2 -c50 -d30s http://localhost:8080/`) — записать полученный RPS и задержку p50/p99. Сравнение с публичными ориентирами по nginx (десятки тысяч RPS на статике, тысячи на проксировании — сильно зависит от конфигурации) и таблица для собственных замеров — в [../../legend/CAPACITY.md](../../legend/CAPACITY.md).
## Как это ложится в легенду
В реальной работе (АО ТНИИС) — конфиги Nginx для маршрутизации и балансировки перед сервисами, в том числе перед компонентами мониторинга. Домашний кейс воспроизводит тот же принцип на двух простых бэкендах вместо реального внутреннего сервиса.

39
stack/nginx/QUESTIONS.md Normal file
View File

@@ -0,0 +1,39 @@
# Вопросы: Nginx
Опираются на кейс: [CASE.md](CASE.md).
### Чем reverse proxy отличается от обычного proxy?
Обычный (forward) proxy работает на стороне клиента — скрывает клиента от сервера. Reverse proxy стоит перед сервером(ами) и скрывает их от клиента — клиент обращается к одному адресу, а Nginx уже сам решает, на какой бэкенд отправить запрос. В своём кейсе я как раз настроил Nginx как reverse proxy перед двумя бэкендами через `upstream` + `proxy_pass`.
### Какие алгоритмы балансировки нагрузки есть в Nginx и в чём разница?
По умолчанию — round-robin (запросы по очереди). `least_conn` — на сервер с наименьшим числом активных соединений (я использовал именно его в лабе, чтобы не заливать один инстанс, если он медленнее отвечает). `ip_hash` — привязывает клиента к одному и тому же серверу по хешу IP, что полезно для sticky-сессий, если приложение не умеет в shared-сессии.
### Зачем нужны proxy_set_header в конфиге?
По умолчанию бэкенд видит IP и Host самого Nginx, а не реального клиента, потому что запрос проксируется. `proxy_set_header X-Real-IP $remote_addr` и `X-Forwarded-For` передают реальный IP клиента дальше, а `Host $host` сохраняет исходный заголовок Host — это важно, если бэкенд как-то зависит от домена или логирует IP клиентов.
### Как Nginx понимает, что бэкенд «упал», и что делает в этом случае?
Через пассивные health-check параметры в `upstream`: `max_fails` — сколько неудачных попыток допустимо, `fail_timeout` — на сколько сервер помечается недоступным после превышения max_fails. В моём кейсе я гасил один из бэкендов и видел, что Nginx перестаёт направлять на него трафик и весь поток идёт на оставшийся, без ошибок для клиента.
### Что такое rate limiting и как он настроен в Nginx?
`limit_req_zone` объявляет зону в разделяемой памяти, ключ (обычно `$binary_remote_addr`, то есть IP клиента) и допустимую скорость запросов (`rate=5r/s` в моём случае). `limit_req` в конкретном location применяет эту зону, а `burst` разрешает кратковременный всплеск сверх лимита (запросы ставятся в очередь) — `nodelay` убирает искусственную задержку для burst-запросов, но всё равно возвращает 503 при превышении burst. Я специально прогонял 20 запросов подряд и видел 503 после исчерпания лимита.
### В чём разница между location / и отдельным location /health?
`location` определяет, как обрабатывается конкретный путь URL. Я вынес `/health` в отдельный блок, который отвечает статикой (`return 200`) без проксирования на бэкенд — так делают health-check эндпоинты, чтобы проверка живости самого Nginx/прокси-слоя не зависела от состояния бэкендов.
### Чем отличается $host от $http_host и от server_name?
`server_name` — значение в конфиге, по которому Nginx выбирает нужный `server`-блок для запроса. `$host` — переменная времени выполнения: значение из заголовка Host запроса (без порта), либо server_name, если заголовка нет. `$http_host` — заголовок Host как есть, включая порт, если он был передан клиентом. На практике для проксирования обычно используют `$host`, чтобы не тащить порт туда, где он не нужен.
### Как бы вы отдавали статику через Nginx, а не проксировали на бэкенд?
Через `location` с директивой `root`/`alias`, указывающей на директорию со статикой, вместо `proxy_pass` — тогда Nginx сам отдаёт файлы, не дёргая бэкенд, что быстрее и снимает нагрузку с приложения.
### Что произойдёт, если в upstream все сервера окажутся недоступны?
Nginx вернёт ошибку (обычно 502 Bad Gateway или 504 Gateway Timeout, в зависимости от того, где именно произошёл сбой — на этапе соединения или ожидания ответа) — в моём кейсе, если бы я остановил оба backend1 и backend2, curl к proxy начал бы получать 502.