Learning Platform
Глоссарий Troubleshooting
Урок 17.01 · 25 мин
Начальный
CapstoneArchitectureDNSTLSLoad BalancingHTTP

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). Все компоненты живут на одной машине, на разных портах. Этого достаточно, чтобы увидеть взаимодействие пяти уровней стека одновременно.

Capstone-стек -- от curl до Python-backend

Один HTTPS-запрос проходит через всю инфраструктуру за миллисекунды

curlКлиент. Делает HTTPS-запрос к https://shop.local/. Проверяет сертификат (через --cacert), пишет результат
resolve
/etc/hostsЛокальный override DNS. shop.local -> 127.0.0.1. В реальности это был бы DNS-сервер с A-record на nginx
nginx :443L7 reverse proxy. Принимает TLS-соединение, дешифрует, читает HTTP-запрос, выбирает backend по round-robin или least-connections
HTTP
backend1 :8001Python http.server. Отдаёт plain HTTP с заголовком X-Backend: 1. Никакой авторизации, никакого TLS
self-signed certСертификат, который мы сами выпускаем через openssl req. CN=shop.local, SAN=DNS:shop.local. Не доверенный системой, но доверенный нашим curl через --cacert
HTTP
backend2 :8002Идентичный backend на другом порту. Отдаёт X-Backend: 2. Нужен, чтобы видеть балансировку

Что происходит за один запрос curl https://shop.local/:

  1. curl смотрит в /etc/hosts (или у resolver’а), получает IP 127.0.0.1.
  2. Открывает TCP-соединение на 127.0.0.1:443 — это nginx.
  3. Делает TLS-handshake: ClientHello с SNI=shop.local, получает сертификат, проверяет его подпись по --cacert, генерирует session key.
  4. Внутри TLS-туннеля шлёт HTTP/1.1 запрос GET / HTTP/1.1\r\nHost: shop.local\r\n\r\n.
  5. nginx читает запрос, выбирает один из двух upstream’ов (localhost:8001 или localhost:8002), форвардит plain HTTP туда.
  6. Python-backend отвечает 200 OK с заголовком X-Backend: 1 (или 2).
  7. nginx добавляет свои заголовки (X-Forwarded-For, Via), оборачивает в TLS, отправляет назад curl’у.
  8. curl расшифровывает, печатает body.

И всё это занимает 50-150 миллисекунд на локалхосте. На каждом из этих восьми шагов происходят процессы, которые вы изучили на предыдущих модулях.


Зачем строить это руками

Можно справедливо спросить: зачем самому крутить nginx с самоподписанным сертификатом, если в любой компании есть DevOps, который это делает на проде через terraform и helm-чарты? Ответ — ровно потому что это делает DevOps. Если вы DE или backend-разработчик, вы будете жить в этой инфраструктуре каждый день. Ваш сервис будет за nginx, ваш Kafka-cluster будет за HAProxy, ваш RabbitMQ будет за TLS. Когда что-то ломается, вы будете первым в логах.

Сценарии, которые мы проигрываем в capstone, соответствуют реальной работе:

Что покрывает capstone -- сценарии из работы

Каждая ситуация -- это реальная боль из практики DE

TLS handshake failСертификат не доверенный, неправильный CN, не совпадает SAN. Видим в curl -v: SSL certificate problem: self signed certificate. Учимся читать ошибки TLS и фиксить cert
LB не баланситЗапросы всегда летят на backend1. В nginx access.log видим, что upstream постоянно один. Учимся читать конфиг upstream, проверять weight, понимать sticky sessions
Backend diedОдин из backend'ов уронили. nginx должен это заметить (proxy_next_upstream) и перестать слать туда трафик. Если не настроили -- 502 каждый второй запрос
DNS not resolvingshop.local не резолвится. curl: Could not resolve host: shop.local. Учимся диагностить через dig, проверять /etc/hosts, понимать DNS resolution order

Если вы один раз построили это руками и проиграли четыре сценария отказа — вы понимаете, как ведёт себя 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 мы бы увидели тот же ответ.

TIP

/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 считается пройденным, если:

  1. Конфигурация воспроизводима — скрипт setup.sh поднимает всю инфраструктуру за один запуск без ручных шагов.
  2. TLS работает корректноcurl --cacert ... https://shop.local/ возвращает 200 без warnings.
  3. Балансировка видна — 10 последовательных запросов дают ~50/50 распределение между backend1 и backend2.
  4. Отказоустойчивость — при убитом backend nginx продолжает отвечать через оставшийся.
  5. Логи читаемыnginx access.log показывает $upstream_addr, можно проследить, куда ушёл каждый запрос.

Это не сложно для опытного SRE — но для junior DE это первый раз, когда «инфраструктура» становится не абстракцией, а конкретными процессами с конкретными конфигами.


Попробуй сам

Прежде чем начать урок 2, разверни в голове картинку: что произойдёт, если ты сделаешь curl, а из четырёх компонентов работают только три?

Возьми ручку и бумагу. Нарисуй схему. Для каждой комбинации напиши, какую ошибку увидит curl:

  1. nginx работает, backend1 работает, backend2 убит, DNS работает.
  2. nginx работает, оба backend убиты, DNS работает.
  3. nginx убит, оба backend работают, DNS работает.
  4. nginx работает, оба backend работают, DNS убит (закомментировали /etc/hosts).

Подсказка: в каждом случае ошибка curl будет разная. Это упражнение проверяет, понимаешь ли ты, какой компонент за что отвечает. Потом в LAB-03 ты проиграешь все четыре кейса руками и сверишь.


Ссылки на инструменты

В capstone мы используем стандартный набор:

  • nginxapt 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-стека
Проверка знанийKnowledge check
Вы запускаете capstone-стек: nginx с self-signed сертификатом на :443, два backend на :8001 и :8002, /etc/hosts с shop.local -> 127.0.0.1. Делаете 'curl https://shop.local/' без флага --cacert и получаете 'SSL certificate problem: self signed certificate'. Один junior говорит «давайте сделаем --insecure, чтобы быстрее проверить». Что не так с этим подходом?
ОтветAnswer

Итог

В этом уроке мы зафиксировали:

  • Что строим: 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 и разбираем по слоям, что мы видим. Это упражнение даст понимание того, что происходит на проводе, прежде чем мы соберём свою инфраструктуру.

Проверьте понимание

Результат: 0 из 0
Аналитический
Вопрос 1 из 6. В чём принципиальная разница между tutorial-проектом (Jaffle Shop, 5 моделей) и capstone production-grade pipeline (30+ моделей)? Какие 5 элементов production-ready пропущены в типичных туториалах?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4