Capstone — мини-инфраструктура своими руками
Курс подходит к концу. Вы прошли путь от Ethernet-кадра до TLS 1.3, от DHCP до consistent hashing. Знание разбросано по 14 модулям. Capstone-проект собирает его в одну точку: вы строите работающую мини-инфраструктуру, в которой одновременно живут DNS, TLS, HTTP-серверы и load balancer. И всё это — на своей машине, без облака.
Это не «hello world по сетям» и не учебный пример из RFC. Это масштабная копия того, что крутится в любом продакшене на любом современном сайте. У вас будет nginx, который терминирует TLS и распределяет трафик между двумя backend’ами. У вас будет self-signed сертификат, как в dev-окружении любой команды. Вы научитесь делать то, что в реальной работе обычно скрыто за DevOps и Platform-командой: и поймёте, почему оно устроено именно так, а не иначе.
В этом уроке — постановка задачи. Что мы строим, зачем, какие компоненты, какие сценарии. Код и команды — в следующих трёх уроках и в LAB-03-capstone-mini-stack.
Что строим — общая схема
Capstone-инфраструктура состоит из четырёх компонентов: клиент (curl), reverse proxy (nginx с TLS), два backend-сервера на Python, и DNS-резолюция через /etc/hosts (или локальный resolver). Все компоненты живут на одной машине, на разных портах. Этого достаточно, чтобы увидеть взаимодействие пяти уровней стека одновременно.
Один HTTPS-запрос проходит через всю инфраструктуру за миллисекунды
Что происходит за один запрос curl https://shop.local/:
curlсмотрит в/etc/hosts(или у resolver’а), получает IP127.0.0.1.- Открывает TCP-соединение на
127.0.0.1:443— это nginx. - Делает TLS-handshake: ClientHello с SNI=
shop.local, получает сертификат, проверяет его подпись по--cacert, генерирует session key. - Внутри TLS-туннеля шлёт HTTP/1.1 запрос
GET / HTTP/1.1\r\nHost: shop.local\r\n\r\n. - nginx читает запрос, выбирает один из двух upstream’ов (
localhost:8001илиlocalhost:8002), форвардит plain HTTP туда. - Python-backend отвечает
200 OKс заголовкомX-Backend: 1(или2). - nginx добавляет свои заголовки (
X-Forwarded-For,Via), оборачивает в TLS, отправляет назад curl’у. - curl расшифровывает, печатает body.
И всё это занимает 50-150 миллисекунд на локалхосте. На каждом из этих восьми шагов происходят процессы, которые вы изучили на предыдущих модулях.
Зачем строить это руками
Можно справедливо спросить: зачем самому крутить nginx с самоподписанным сертификатом, если в любой компании есть DevOps, который это делает на проде через terraform и helm-чарты? Ответ — ровно потому что это делает DevOps. Если вы DE или backend-разработчик, вы будете жить в этой инфраструктуре каждый день. Ваш сервис будет за nginx, ваш Kafka-cluster будет за HAProxy, ваш RabbitMQ будет за TLS. Когда что-то ломается, вы будете первым в логах.
Сценарии, которые мы проигрываем в capstone, соответствуют реальной работе:
Каждая ситуация -- это реальная боль из практики DE
Если вы один раз построили это руками и проиграли четыре сценария отказа — вы понимаете, как ведёт себя production-стек. Это не страховка от всех проблем, но это базовый интуитивный фундамент. Когда в продакшене вы увидите аналогичные симптомы, у вас в голове щёлкнет: «А, я это видел в capstone, проблема в nginx upstream»
Компоненты подробнее
Давайте разберём, какую роль играет каждый из четырёх кусков и почему именно так.
nginx как reverse proxy
nginx — де-факто стандарт L7 reverse proxy в индустрии. Альтернативы: HAProxy (L4/L7), Envoy (новее, cloud-native), Traefik (легче конфиг). Для capstone выбираем nginx как самое распространённое решение — если вы научитесь читать его конфиг, понимать access.log и error.log, вы в 80% компаний будете на знакомой территории.
В нашей конфигурации nginx делает три вещи: TLS termination (распаковка TLS на входе, чтобы upstream’ы говорили чистым HTTP), routing (по умолчанию — всё на / идёт в upstream’ы), load balancing (round-robin или least_conn между двумя upstream’ами).
upstream app_backend {
server 127.0.0.1:8001;
server 127.0.0.1:8002;
}
server {
listen 443 ssl;
server_name shop.local;
ssl_certificate /etc/nginx/certs/shop.local.crt;
ssl_certificate_key /etc/nginx/certs/shop.local.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Это минимальный production-grade конфиг. Никакой магии — три блока: upstream (где живут backend’ы), server (как нас слушать), location (что делать с запросом).
Python http.server как backend
Два backend’а — это идентичные Python-процессы, которые слушают порты 8001 и 8002, отвечают коротким JSON-ом и заголовком X-Backend: N. Цель — не реализовать сложную бизнес-логику, а увидеть, что nginx действительно балансирует.
from http.server import BaseHTTPRequestHandler, HTTPServer
import os, json
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = json.dumps({"backend": BACKEND_ID, "path": self.path}).encode()
self.send_response(200)
self.send_header("X-Backend", str(BACKEND_ID))
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
BACKEND_ID = int(os.environ.get("BACKEND_ID", "1"))
PORT = int(os.environ.get("PORT", "8001"))
HTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
Запускаем как два независимых процесса:
BACKEND_ID=1 PORT=8001 python3 backend.py &
BACKEND_ID=2 PORT=8002 python3 backend.py &
Self-signed certificate
В продакшене сертификаты выписывает Let’s Encrypt или внутренняя CA организации. В dev-окружении и в capstone мы делаем self-signed: вы сами становитесь Certificate Authority, подписываете сертификат для shop.local, кладёте его рядом с nginx, говорите curl’у доверять вашему CA.
openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout shop.local.key \
-out shop.local.crt \
-subj "/CN=shop.local" \
-addext "subjectAltName=DNS:shop.local"
Эта команда делает три вещи: генерирует RSA-ключ на 2048 бит, создаёт сертификат с CN=shop.local и SAN=DNS:shop.local, самоподписывает его этим же ключом. Никакой CA, никакой цепочки доверия — мы сами и подписант, и владелец, и доверитель.
curl запускается с флагом --cacert shop.local.crt, который говорит: «доверяй сертификату из этого файла как корневому CA». Без флага curl выдаст SSL certificate problem: self signed certificate.
DNS через /etc/hosts
shop.local — это не настоящий домен. Резолвить его в 127.0.0.1 будем через /etc/hosts:
# /etc/hosts
127.0.0.1 shop.local
В реальности это был бы DNS-сервер организации (BIND, CoreDNS, Cloudflare). Но логика та же: имя -> IP. Через dig shop.local @127.0.0.53 мы бы увидели тот же ответ.
/etc/hosts имеет приоритет над DNS-резолвером по умолчанию. Проверьте порядок в /etc/nsswitch.conf: строка hosts: files dns — значит сначала смотрим в файл, потом в DNS. Это нюанс, который иногда ловит инженеров: «у меня в DNS правильный IP, а curl ходит на старый» — проверь /etc/hosts.
Сценарии, которые мы будем играть
В LAB-03-capstone-mini-stack будут конкретные шаги для четырёх сценариев. Здесь — краткий обзор того, что вы увидите.
Сценарий A. TLS-handshake без —cacert
Запускаем curl https://shop.local/ без --cacert shop.local.crt. Видим:
curl: (60) SSL certificate problem: self signed certificate
More details here: https://curl.se/docs/sslcerts.html
Почему: curl проверяет цепочку сертификатов по системному CA bundle. Наш self-signed сертификат не подписан ни одной из CA в bundle — curl отказывается. Это правильное поведение: в продакшене такой же error означал бы MITM-атаку.
Фикс: либо --cacert shop.local.crt (доверяем явно), либо --insecure (не рекомендуется — маскирует реальные ошибки).
Сценарий B. Round-robin балансировка
Запускаем 10 раз подряд:
for i in $(seq 1 10); do
curl -s --cacert shop.local.crt https://shop.local/ | jq .backend
done
Ожидаем: чередование 1, 2, 1, 2, … — nginx по умолчанию делает round-robin. Если видим 1, 1, 1, … — значит либо backend2 не отвечает (проверяй процесс), либо в nginx upstream указан backup параметр.
Сценарий C. Backend упал — nginx должен заметить
Убиваем backend2: pkill -f "PORT=8002". Запускаем 10 запросов. Без правильной настройки proxy_next_upstream мы получим:
{"backend":1,"path":"/"}
curl: (52) Empty reply from server
{"backend":1,"path":"/"}
curl: (52) Empty reply from server
Половина запросов проваливается. С proxy_next_upstream error timeout http_502; nginx после первой неудачи пометит upstream как недоступный и перестанет слать туда трафик — 100% запросов уходят на backend1.
Сценарий D. DNS не резолвится
Закомментируем строку shop.local в /etc/hosts. Запускаем curl:
curl: (6) Could not resolve host: shop.local
Диагностика:
getent hosts shop.local # пусто
dig +short shop.local @127.0.0.53 # NXDOMAIN
ping shop.local # ping: cannot resolve shop.local
Все три инструмента согласуются: имя не резолвится. В продакшене так же — если ваш сервис не может достучаться до базы, первый шаг: dig db.internal.example.com или nslookup db.internal.example.com — проверить, что DNS вообще работает.
Метрики успеха
Capstone считается пройденным, если:
- Конфигурация воспроизводима — скрипт
setup.shподнимает всю инфраструктуру за один запуск без ручных шагов. - TLS работает корректно —
curl --cacert ... https://shop.local/возвращает 200 без warnings. - Балансировка видна — 10 последовательных запросов дают ~50/50 распределение между backend1 и backend2.
- Отказоустойчивость — при убитом backend nginx продолжает отвечать через оставшийся.
- Логи читаемы —
nginx access.logпоказывает$upstream_addr, можно проследить, куда ушёл каждый запрос.
Это не сложно для опытного SRE — но для junior DE это первый раз, когда «инфраструктура» становится не абстракцией, а конкретными процессами с конкретными конфигами.
Попробуй сам
Прежде чем начать урок 2, разверни в голове картинку: что произойдёт, если ты сделаешь curl, а из четырёх компонентов работают только три?
Возьми ручку и бумагу. Нарисуй схему. Для каждой комбинации напиши, какую ошибку увидит curl:
- nginx работает, backend1 работает, backend2 убит, DNS работает.
- nginx работает, оба backend убиты, DNS работает.
- nginx убит, оба backend работают, DNS работает.
- nginx работает, оба backend работают, DNS убит (закомментировали /etc/hosts).
Подсказка: в каждом случае ошибка curl будет разная. Это упражнение проверяет, понимаешь ли ты, какой компонент за что отвечает. Потом в LAB-03 ты проиграешь все четыре кейса руками и сверишь.
Ссылки на инструменты
В capstone мы используем стандартный набор:
- nginx —
apt install nginx(Ubuntu/Debian),brew install nginx(macOS). Версия 1.18+ для нормальной поддержки TLS 1.3. - openssl — встроен в большинство дистрибутивов. Для генерации сертификатов и s_client диагностики.
- curl — встроен везде. С флагами
-v(verbose),--cacert(custom CA),--resolve(override DNS). - python3 — 3.11+, для backend’ов.
http.serverмодуль — из stdlib, ничего ставить не нужно. - dig / nslookup — для проверки DNS.
apt install dnsutilsесли на Linux dig отсутствует.
Все эти инструменты вы уже видели в модулях 8-12. Capstone — не введение в инструменты, а их совместное применение.
Docker и контейнеризация capstone-стека Kubernetes Ingress — production-версия capstone-стекаИтог
В этом уроке мы зафиксировали:
- Что строим: nginx с TLS на :443, два Python backend’а на :8001 и :8002, self-signed cert, /etc/hosts override.
- Зачем: чтобы увидеть взаимодействие пяти уровней стека одновременно и проиграть типичные сценарии отказа.
- Какие сценарии: TLS-error без —cacert, round-robin балансировка, отказ backend’а, отказ DNS.
- Метрики успеха: воспроизводимый setup, читаемые логи, корректная отказоустойчивость.
В следующем уроке — анализ pcap собственного трафика: захватываем curl https://example.com и разбираем по слоям, что мы видим. Это упражнение даст понимание того, что происходит на проводе, прежде чем мы соберём свою инфраструктуру.