Перейти к содержимому
RootPoligon

У меня работает, у пользователя — нет

Чтение и практика ~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 реально подключился
Путь / посредникнапрямую или через посредника; кто отвечает
Признак нужного приложенияверсия, заголовки, содержимое
Откуда взяты настройкифайл клиента, окружение, значения по умолчанию

Откуда у клиента адрес и путь. В этой главе у клиента есть четыре источника условий:

  1. URL — один и тот же у всех: http://notes.lab.test:18080/api/notes.
  2. Имя → адрес. Обычно адрес даёт DNS (в лаборатории — учебный DNS, как в fetch). Но в файле настроек клиента может стоять собственная строка, закрепляющая адрес для пары «имя–порт»; тогда DNS клиент не спрашивает. Такая строка — постоянная запись, а не кэш: у неё нет срока действия. В документации libcurl сказано, что записи, добавленные через CURLOPT_RESOLVE без +, не истекают, а с + «истекают как обычные записи кэша DNS». Файл же curl читает заново при каждом запуске.
  3. Путь. Клиент может ходить напрямую или через посредника (в настройках клиента — строка proxy). Если клиент ходит через HTTP-прокси, curl подключается к прокси, а не к серверу назначения, и отправляет ему URL целиком (RFC 9112, 3.2.2: запрос к прокси содержит цель в абсолютной форме). По документации curl имя назначения разрешает посредник. В нашей модели он берёт адрес из собственной таблицы; закреплённый у клиента адрес не используется.
  4. Окружение. Переменные 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) и сравните ответы.

Мини-задача

Задача: объяснить, почему два клиента с одной ссылкой видят разное, и вернуть Борису нужный результат так, чтобы не сломать Анну и не потерять записи. Готовой цепочки команд нет.

До любого наблюдения, кроме повтора симптома, запишите:

  1. Не меньше двух гипотез, чем условия Бориса могут отличаться от условий Анны (одна из них — слова Петра «его клиент»); для каждой — что подтвердило бы её и что бы исключило.
  2. Предсказание: что вы увидите в выводе, если верна главная гипотеза.
  3. Следующее наблюдение, которое различает гипотезы (а не просто «ещё один запрос»). Выполните его и запишите, что оно исключило.

Затем заполните таблицу сравнения (см. «Что нового») по фактам и добавьте две строки: какие условия совпали и какой один факт меняет решение.

Разбор мини-задачи

Откройте, когда записали свои ответы

Гипотезы.

ГипотезаЕсли вернаЧто исключает
Борис получает от 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.

Сравнение условий.

УсловиеАннаБорисСовпадает?Меняет решение?
Адрес в URLhttp://notes.lab.test:18080/api/notesто жеданет
Адрес соединения127.0.0.1127.0.0.2нетда
Путь / посредникнапрямуюнапрямуюданет
Признак нужного приложенияX-App-Version: 3.0, «Подготовить отчёт»2.9, своя запись №4нетэто симптом
Откуда настройкиanna.confboris.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; профили — новые, пути новые) и действуйте так же, как в основной части.

Условия приёмки те же, плюс три:

  1. Объясните, почему вчерашний ремонт здесь не помогает — и покажите запуском (что изменится, если «сделать то же самое»).
  2. Назовите наблюдение, которое отличает этот случай от вчерашнего, и покажите, кто здесь разрешает имя, по журналам, а не по догадке.
  3. Для выбранного решения укажите, кого оно задевает: по сюжету через тот же исходящий посредник ходят и другие отделы.

Решения (порядок случайный):

  • А. Повторить вчерашнее: убрать из профиля Бориса строку с закреплённым адресом.
  • Б. 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).

Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.