У меня работает, у пользователя — нет
Чтение и практика ~75 мин
Анна и Борис открывают одну ссылку. У обоих 200 OK, но записи разные.
Ваша задача — объяснить расхождение, выбрать наблюдение и проверить ремонт у обоих.
Подготовка: новая версия лаборатории для двух клиентов
Скачать лабораторию глав 1–6 · Памятка
В архиве прежних глав нет сценариев ch6 и ch6b. Остановите свой прежний запуск через Ctrl+C,
распакуйте новый архив в отдельную папку и откройте network-diagnostics в двух терминалах.
Нужны Linux/Ubuntu 24.04, Python 3.12, curl, ss, nc и OpenSSL 3.
Системные DNS, hosts и настройки curl менять не нужно.
Первый терминал: python3 lab/netlab.py start ch6. Он напечатает полные пути к anna.conf и boris.conf.
Во втором используйте эти пути: например, задайте P=/tmp/netlab-clients-… с реальным именем папки.
После остановки профили удаляются; для второго варианта запустите ch6b и запишите новые пути.
Порты только loopback, включая 18090 во втором варианте; не запускайте два сценария одновременно.
Что нового
В главе 2 у одного клиента был старый адрес, и вы искали, откуда он взялся. Здесь иначе: у двух людей одинаковая ссылка, оба
получают 200 OK, а видят разное. Ошибки нет, поэтому «не работает» по тексту ответа не определить. Нужно объяснить расхождение
двух наблюдений, и чинить придётся со стороны того клиента, у которого плохо — а не со стороны того, у кого «работает».
Что значит «у меня работает». Это результат одного запроса при условиях автора: его настройки, его путь, его адрес. Чтобы сравнить двух клиентов, условия записывают рядом — каждое с ответом на вопрос «совпадает ли у двоих» и «меняет ли это решение»:
| Условие | Что смотреть | У Анны | У Бориса | Совпадает? | Меняет решение? |
|---|---|---|---|---|---|
| Адрес в URL | что именно запрошено | ||||
| Адрес соединения | куда curl реально подключился | ||||
| Путь / посредник | напрямую или через посредника; кто отвечает | ||||
| Признак нужного приложения | версия, заголовки, содержимое | ||||
| Откуда взяты настройки | файл клиента, окружение, значения по умолчанию |
Откуда у клиента адрес и путь. В этой главе у клиента есть четыре источника условий:
- URL — один и тот же у всех:
http://notes.lab.test:18080/api/notes. - Имя → адрес. Обычно адрес даёт DNS (в лаборатории — учебный DNS, как в
fetch). Но в файле настроек клиента может стоять собственная строка, закрепляющая адрес для пары «имя–порт»; тогда DNS клиент не спрашивает. Такая строка — постоянная запись, а не кэш: у неё нет срока действия. В документации libcurl сказано, что записи, добавленные черезCURLOPT_RESOLVEбез+, не истекают, а с+«истекают как обычные записи кэша DNS». Файл жеcurlчитает заново при каждом запуске. - Путь. Клиент может ходить напрямую или через посредника (в настройках клиента — строка
proxy). Если клиент ходит через HTTP-прокси,curlподключается к прокси, а не к серверу назначения, и отправляет ему URL целиком (RFC 9112, 3.2.2: запрос к прокси содержит цель в абсолютной форме). По документации curl имя назначения разрешает посредник. В нашей модели он берёт адрес из собственной таблицы; закреплённый у клиента адрес не используется. - Окружение. Переменные
http_proxy,no_proxyи файл~/.curlrcтоже могут менять условия. В лаборатории командаclientих не использует (см. ниже), чтобы вы наблюдали только то, что лежит в профиле.
Что настоящее, что модель.
| Что | Статус |
|---|---|
curl, его чтение файла настроек, подключение, заголовки | настоящее (curl 8.5.0) |
Профили anna.conf и boris.conf | настоящие файлы настроек curl в папке netlab-clients-… (права 0700); лаборатория пишет их при запуске и удаляет при остановке |
Служба «Заметки» (два экземпляра), учебный DNS, посредник lab-egress | модель на Python; посредник — только http://, только имена из своей таблицы, только адреса 127.x; CONNECT (https) не поддерживает; настоящий прокси умеет больше |
| «Имя из DNS» у клиента | команда client берёт адрес из учебного DNS и передаёт его curl параметром --resolve до файла профиля — так же, как fetch; системный резолвер не используется |
Строка профиля, закрепляющая адрес, главнее адреса, который передал client | наблюдение запуском (curl 8.5.0); в официальных страницах curl порядок «файл настроек / командная строка» не описан |
Инструменты (порядок выбираете сами; готовой цепочки нет):
python3 lab/netlab.py client --profile ФАЙЛ URL # запрос от имени клиента: настоящий curl с настройками из ФАЙЛА
python3 lab/netlab.py client --profile ФАЙЛ --verbose URL # то же с -v: адрес соединения, строка запроса, заголовки
python3 lab/netlab.py client --profile ФАЙЛ --data '{"title": "…"}' URL # запись (POST)
python3 lab/netlab.py fetch URL # «компьютер автора»: без профиля, учебный DNS
python3 lab/netlab.py dig notes.lab.test # что отвечает учебный DNS
cat ФАЙЛ; diff ФАЙЛ1 ФАЙЛ2 # профили — обычные текстовые файлы
ss -ltnp # кто слушает какой порт
python3 lab/netlab.py config / logs --source app / logs --source egress # владелец службы: конфигурация и журналы
client запускает curl -q -sS -i … -K ФАЙЛ: -q — файл ~/.curlrc не читается; переменные окружения *_proxy из окружения этого
вызова убираются (внешние посредники среды не участвуют); внешняя сеть не нужна. Чтобы объяснить, откуда что взялось, добавьте
--verbose.
Подготовка
Из папки курса, в первом окне: python3 lab/netlab.py start ch6. Лаборатория запускает два экземпляра «Заметок»: основной
127.0.0.1:18080 (версия 3.0) и прежний 127.0.0.2:18080 (версия 2.9), создаёт два файла профилей и печатает их пути.
Остановка — Ctrl+C: профили удаляются. Если порт занят — лаборатория откажет и ничего не изменит. Остальное — во втором окне; удобно
записать папку профилей в переменную: P=/tmp/netlab-clients-….
Профили — учебные файлы: правьте их как свои настройки клиента; ~/.curlrc, переменные окружения, файл hosts, настройки DNS и
браузера лаборатория не затрагивает нигде.
Ситуация
Понедельник, 19 октября, 10:00. Службу «Заметки» перевели на версию 3.0; прежняя 2.9 осталась запущенной «на всякий случай». Адрес один:
http://notes.lab.test:18080/api/notes. Борис пишет в канал поддержки:
У меня в Заметках нет записи №4 «Подготовить отчёт», и ответ приходит от версии 2.9. У Анны версия 3.0 и эта запись есть. Ссылка у нас одна и та же. Мне нужно, чтобы мой запрос
GET /api/notesвернул версию 3.0 и «Подготовить отчёт». Мою запись №4 про парковку не потерять; у Анны всё должно остаться как есть.
Дежурный Пётр:
Сервис жив — у меня на ноутбуке 3.0, у Анны 3.0, у Бориса вообще
200. Значит, это его клиент. Пусть перезапустит, и закрываем.
Здесь закрываем жалобу на путь клиента. Перенос записи про парковку — отдельная задача владельца: до переноса прежний экземпляр должен оставаться доступным.
Воспроизведите симптом: выполните один и тот же запрос GET /api/notes с профилем Анны и с профилем Бориса (пути к двум файлам печатает start) и сравните ответы.
Мини-задача
Задача: объяснить, почему два клиента с одной ссылкой видят разное, и вернуть Борису нужный результат так, чтобы не сломать Анну и не потерять записи. Готовой цепочки команд нет.
До любого наблюдения, кроме повтора симптома, запишите:
- Не меньше двух гипотез, чем условия Бориса могут отличаться от условий Анны (одна из них — слова Петра «его клиент»); для каждой — что подтвердило бы её и что бы исключило.
- Предсказание: что вы увидите в выводе, если верна главная гипотеза.
- Следующее наблюдение, которое различает гипотезы (а не просто «ещё один запрос»). Выполните его и запишите, что оно исключило.
Затем заполните таблицу сравнения (см. «Что нового») по фактам и добавьте две строки: какие условия совпали и какой один факт меняет решение.
Разбор мини-задачи
Откройте, когда записали свои ответы
Гипотезы.
| Гипотеза | Если верна | Что исключает |
|---|---|---|
| Борис получает от DNS другой адрес | dig у Бориса вернёт другой адрес | учебный DNS один для обоих: 127.0.0.1; dig не зависит от клиента |
| Борис ходит другим путём (через посредника) | в ответе Via:, curl -v подключается не к сервису | в заголовках Бориса нет Via, curl -v подключается к 127.0.0.2:18080, посредника нет |
| У Бориса закреплён собственный адрес | curl -v подключается к адресу, которого нет в ответе DNS; в профиле — строка про адрес | ничего не исключает, пока не прочитан профиль или -v |
| Служба сама отдаёт разное при одном адресе | журналы обоих экземпляров покажут запросы на один и тот же | запрос Анны пришёл на 127.0.0.1, запрос Бориса — на 127.0.0.2 |
| «Его клиент, пусть перезапустит» (слова Петра) | перезапуск изменил бы результат | профиль читается заново при каждом запуске — перезапуск ничего не меняет |
Различающее наблюдение — сравнить, куда реально подключается curl у каждого, с тем, что говорит DNS. client --verbose у Анны:
Connected to notes.lab.test (127.0.0.1) port 18080, у Бориса — Connected to notes.lab.test (127.0.0.2) port 18080, а dig говорит
127.0.0.1. Второй путь к тому же выводу — сравнить профили (diff anna.conf boris.conf): кроме комментария с именем клиента отличается одна строка —
resolve = "notes.lab.test:18080:127.0.0.2". Подтверждение со стороны службы: logs --source app показывает запрос Анны на
127.0.0.1:18080, запрос Бориса — на 127.0.0.2:18080.
Сравнение условий.
| Условие | Анна | Борис | Совпадает? | Меняет решение? |
|---|---|---|---|---|
| Адрес в URL | http://notes.lab.test:18080/api/notes | то же | да | нет |
| Адрес соединения | 127.0.0.1 | 127.0.0.2 | нет | да |
| Путь / посредник | напрямую | напрямую | да | нет |
| Признак нужного приложения | X-App-Version: 3.0, «Подготовить отчёт» | 2.9, своя запись №4 | нет | это симптом |
| Откуда настройки | anna.conf | boris.conf (+ строка про адрес) | почти | да: единственное содержательное отличие |
Причина. В профиле Бориса постоянная строка закрепляет адрес 127.0.0.2 — прежний экземпляр службы. DNS правильный (127.0.0.1), но
запись профиля перекрывает адрес, который оболочка получила из учебного DNS. Сам curl использует уже заданный адрес. Прежний экземпляр жив, отвечает 200 — поэтому ошибки нет.
Факт, меняющий решение: причина лежит в файле клиента Бориса, а не в службе и не в DNS; значит, ремонт — в этом файле, а общие правки
(DNS, останов экземпляра) заденут тех, у кого всё в порядке.
Почему «у меня работает» ничего не доказывает. fetch без профиля (компьютер автора) и профиль Анны возвращают 3.0 и до ремонта, и после
него: запрос автора не проходит через условия Бориса. Приёмка — запрос от Бориса.
Развилка: 10:40, что делать
Службой и DNS владеете вы; профиль Бориса — его файл, он разрешает вам его править. Решения (порядок случайный):
- А. Попросить Бориса перезапустить клиент и очистить кэш.
- Б. Остановить прежний экземпляр:
python3 lab/netlab.py set old_app=off. - В. Перенаправить имя на прежний экземпляр, чтобы «у всех одинаково»:
set dns_notes=127.0.0.2. - Г. В профиле Бориса заменить закреплённый адрес на
127.0.0.1. - Д. В профиле Бориса убрать строку с закреплённым адресом.
- Е. Ответить Борису: «у меня работает» — и закрыть заявку.
Для выбранного решения запишите: область действия (кого оно задевает), цену, откат и условие закрытия (когда заявка закрыта по существу, а не «стало тихо»).
Последствия
- А. Бесполезное: профиль читается при каждом запуске, кэша, который можно очистить, нет — Борис по-прежнему на 2.9. Область: никто. Откат не нужен.
- Б. Вредное: у Бориса вместо 2.9 — отказ соединения (код 7); его запись про парковку остаётся в хранилище прежнего экземпляра, но недоступна
никому. Жалоба не снята, данные «потеряны для людей». Откат:
set old_app=on. - В. Вредное: Борису не помогает (у него закреплён тот же адрес), а Анна переезжает на 2.9. Общая правка ради одного клиента задевает всех.
Откат:
set dns_notes=127.0.0.1. - Г. Допустимая временная мера: Борис получает 3.0 и «Подготовить отчёт», Анна не затронута. Цена: в файле остаётся постоянная запись,
которая не следует за DNS: при следующей смене адреса (проверьте:
set dns_notes=127.0.0.2, запрос Бориса и Анны, затем вернитеset dns_notes=127.0.0.1) он опять разойдётся с остальными. Откат: вернуть прежний адрес в файл. Условие закрытия: строка убрана (Д) или за ней закреплён владелец и срок. - Д. Исправление причины: у Бориса
200, 3.0, та же заметка №4, что у Анны; Анна не затронута. Область — один файл одного клиента. Цена: если закрепление было намеренным (например, для проверки старой версии), нужна другая договорённость. Откат: вернуть строку. Условие закрытия — приёмка ниже. - Е. Бесполезное: запрос «автора» идёт без профиля Бориса и вернёт 3.0 и до ремонта, и после.
Про данные. У прежнего экземпляра есть запись «Оплатить парковку», которой нет в основном. Это решение владельца службы (перенос записей); в этой главе вы его не выполняете, но не гасите прежний экземпляр, пока записи не перенесены.
Приёмка
- Гипотезы и предсказание записаны до наблюдений; названо различающее наблюдение и что оно исключило.
- Таблица сравнения заполнена по фактам; в ней названы совпавшие условия и один факт, меняющий решение.
- Названо, кто что разрешает: имя Бориса разрешает не DNS, а строка в его профиле; откуда взяты его настройки.
- Выбранное решение названо временной мерой или исправлением причины, с областью действия, ценой, откатом и условием закрытия.
- Запрос от пострадавшего клиента:
client --profile boris.conf URL—200,X-App-Version: 3.0, в списке «Подготовить отчёт». - Проверка второго клиента: запрос Анны — по-прежнему 3.0, тот же список. Починка одного клиента не заменяет проверки другого.
- Данные на месте: запись от Бориса (
client --profile boris.conf --data '{"title": "…"}' URL) —201, и Анна видит её; запись №4 Анны на месте; прежний экземпляр жив и содержит «Оплатить парковку» (fetch http://127.0.0.2:18080/api/notes). - Не принято: ответ с «компьютера автора» (
fetchбез профиля); одинаковый код200у обоих без сравнения содержимого; останов прежнего экземпляра. - Объяснение своими словами (4–6 предложений): что разное, что общее, почему у автора работает, что вы изменили, чего не трогали и почему.
Подсказки
Подсказка 1 — направление
Код ответа у обоих один. Что у них разное в самих ответах? И что у двух клиентов может быть разным при одной и той же ссылке?
Подсказка 2 — нужное наблюдение
Посмотрите, к какому адресу реально подключается curl у каждого (--verbose), и сравните это с тем, что отвечает dig.
Параллельно можно сравнить сами файлы профилей. Любой из двух путей приводит к одному факту.
Подсказка 3 — разбор
Адрес в URL одинаков, DNS одинаков, посредника нет. Отличается адрес соединения: у Бориса 127.0.0.2 из-за строки в его профиле. Значит, чинить
надо профиль Бориса: Г — временно, Д — причина. Б и В задевают всех или не лечат; А и Е не меняют условий Бориса.
Второй вариант
Вторник, 20 октября, 09:00. В другом офисе тот же симптом: у Бориса 2.9, у Анны 3.0. Пётр: «Вчера лечилось удалением строки с адресом из профиля
— сделайте то же самое». Профиль Бориса здесь другой. Перезапустите лабораторию со сценарием ch6b (python3 lab/netlab.py start ch6b;
профили — новые, пути новые) и действуйте так же, как в основной части.
Условия приёмки те же, плюс три:
- Объясните, почему вчерашний ремонт здесь не помогает — и покажите запуском (что изменится, если «сделать то же самое»).
- Назовите наблюдение, которое отличает этот случай от вчерашнего, и покажите, кто здесь разрешает имя, по журналам, а не по догадке.
- Для выбранного решения укажите, кого оно задевает: по сюжету через тот же исходящий посредник ходят и другие отделы.
Решения (порядок случайный):
- А. Повторить вчерашнее: убрать из профиля Бориса строку с закреплённым адресом.
- Б.
set dns_notes=127.0.0.2— «чтобы у всех было одинаково». - В.
set old_app=off— остановить прежний экземпляр. - Г. В профиле Бориса добавить исключение для имени службы из посредника (
noproxy = "notes.lab.test"). - Д. Исправить таблицу имён исходящего посредника:
set egress_notes=127.0.0.1(правка владельца посредника). - Е. Д; временную меру Г, если её ставили, снять; устаревшую строку с адресом в профиле Бориса убрать отдельной уборкой с записью, зачем.
Подсказка 1 — направление
Вчерашняя строка осталась в профиле? Если да, почему же результат не изменился? Что ещё в профиле Бориса отличается от профиля Анны?
Подсказка 2 — нужное наблюдение
client --verbose у Бориса: к какому адресу и порту подключился curl и как выглядит строка запроса? Что показывают заголовки ответа и
logs --source egress (на какой адрес посредник отправил запрос и по какой таблице)? Сверьте с logs --source app.
Подсказка 3 — разбор
Борис ходит через исходящего посредника: curl подключается к 127.0.0.1:18090 и отправляет URL целиком, а имя разрешает посредник по своей
таблице (там устаревший адрес 127.0.0.2). Закреплённый в профиле адрес посредник не получает и не использует — поэтому вчерашний ремонт
ничего не меняет. Д — исправление причины для всех, кто ходит через посредника. Г — временная мера для одного клиента (цена: Борис выходит из-под правил
посредника; откат — убрать строку), но одной строки noproxy мало: пока в профиле остаётся закреплённый адрес, клиент пойдёт напрямую именно на него и
снова получит 2.9 — проверьте запуском. Е — Д плюс уборка. А бесполезно, Б задевает Анну и не лечит, В превращает 2.9 в 502.
Источники
- RFC 9112, «Request Target», раздел 3.2.2 (absolute-form): запрос к прокси содержит цель в абсолютной форме;
пересылающий прокси создаёт
Hostпо цели запроса. - Everything curl, «Proxies»:
curlподключается к прокси вместо сервера. Раздел «Local or proxy name lookup» объясняет, что HTTP-прокси получает имя назначения и разрешает его сам; собственная таблицаlab-egress— предел нашей модели. - libcurl,
CURLOPT_RESOLVE: записи без+не истекают, с+— как кэш DNS; про прокси там ничего нет. - libcurl,
CURLOPT_PROXYиCURLOPT_NOPROXY: явная настройка прокси главнее переменных окружения;noproxy— список имён, не требующих прокси;*отключает прокси для всех. - Everything curl, «Config file»:
-Kчитает файл настроек, одна опция в строке,#— комментарий;-qотключает файл по умолчанию. Порядок «файл настроек / командная строка» там не описан — в главе он наблюдался запуском (curl 8.5.0). - Everything curl, «Name resolving»:
--resolveподставляет адрес в кэш DNS curl для пары «имя–порт». - Не проверялось: поведение настоящих прокси (Squid и др.) — посредник главы смоделирован; другие версии curl; клиенты, отличные от curl; HTTPS через прокси (
CONNECT).
Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.