Перейти до вмісту

Модуль 6: Цифровий детектив — Усунення несправностей та пошук

Складність: [СЕРЕДНЯ]

Час на проходження: 90 хвилин

Передумови: Модуль 5 з Глибокого занурення в Git


Що ви зможете зробити

Розділ «Що ви зможете зробити»

Після завершення цього модуля ви зможете:

  • Діагностувати точний коміт, що впровадив непомітну регресію конфігурації Kubernetes, поєднавши git bisect run із детермінованим скриптом перевірки.
  • Оцінити справжню історію походження підозрілих блоків коду крізь зміни, що стосуються лише пробілів, переміщення файлів та рефакторинг, застосовуючи розширені опції git blame.
  • Порівняти історичний пошук у diff, пошук у знімках (snapshot) та фільтровані запити до журналу, щоб ви могли обрати правильний інструмент усунення несправностей під час живого інциденту.
  • Спроєктувати цільові журнали аудиту для маніфестів Kubernetes, які відповідають на запитання: хто змінив, що змінилося, коли це змінилося і чому ця зміна мала значення.
  • Впровадити повторюваний робочий процес усунення несправностей у Git, що переходить від симптомів зламаного кластера до виправлення, підкріпленого доказами.

Гіпотетичний сценарій: о 3:14 ночі святкового вихідного дня у сфері роздрібної торгівлі платіжна платформа, яка була стабільною протягом місяців, почала втрачати помітну частку трафіку на етапі оплати, повертаючи відповіді 503 Service Unavailable. Панель управління Kubernetes була в нормі, вузли були готові, а образи застосунків пройшли свої звичайні перевірки. Проте шлях клієнта стикався з помилками настільки часто, що дашборди доходів перетворилися на докази інциденту. Останнє скасування змін не допомогло, оскільки реліз також містив патчі безпеки, які не можна було легко відкотити, і єдиною видимою зачіпкою було те, що маніфести payment-gateway були змінені кількома командами протягом сотень комітів.

Під час такого інциденту Git більше не є місцем, де зберігається завершена робота. Він стає доступною для запитів хронологією операційних рішень, і команда, яка здатна спокійно досліджувати цю хронологію, переживає збій зовсім інакше, ніж команда, яка в паніці гортає пул-реквести. Один-єдиний рядок у YAML може спрямувати трафік на неправильний порт, видалити readiness gate, послабити NetworkPolicy або приховати зміну лімітів ресурсів за комітом із форматуванням. Практична навичка полягає не в запам’ятовуванні більшої кількості команд; вона полягає в умінні перетворити ненадійний симптом на вузьке історичне запитання, на яке Git може дати відповідь.

Цей модуль навчає такому розслідувальному робочому процесу з використанням тих самих інструментів, які у вас уже встановлені. Ви будете використовувати бінарний пошук, щоб знайти перший поганий коміт, автоматизувати цей пошук, щоб людський фактор не впливав на результат, відстежувати авторство рядків через рефакторинг, шукати у видаленій історії за допомогою Pickaxe-запитів, сканувати поточні та історичні знімки за допомогою git grep і збирати журнал аудиту, якому може довіряти командир інциденту або аудитор із безпеки. Для прикладів із Kubernetes припускайте, що цільовим кластером є Kubernetes 1.35 або новіша версія, і використовуйте повну команду kubectl у прикладах, щоб кожну команду можна було скопіювати у скрипт без залежності від інтерактивних псевдонімів оболонки.

Цей урок також змінює те, як ви сприймаєте час під час діагностики та усунення несправностей. Живий кластер показує сьогодення, дашборди показують недавні симптоми, а Git показує послідовність запланованих змін стану, які до цього призвели. Найкращі розслідувачі свідомо перемикаються між цими ракурсами замість того, щоб дозволяти одному з них домінувати. Якщо кластер каже, що Service не працює, Git може розповісти вам, коли змінилося заплановане визначення сервісу; якщо Git каже, що нічого не змінилося, кластер може виявити відхилення в розгортанні, затримку контролера або редагування поза стандартним процесом. Сприйняття цих хронологій як окремих, але порівнянних записів дозволяє розслідуванню спиратися на факти.

Розділ 1: Перетворення симптомів на простір пошуку за допомогою git bisect

Розділ «Розділ 1: Перетворення симптомів на простір пошуку за допомогою git bisect»

Коли система виходить з ладу, але ви не знаєте, коли саме виникла несправність, перегляд комітів один за одним — це повільний спосіб створити хибну впевненість. Якщо між відомим робочим тегом релізу та поточним несправним станом є п’ятсот комітів, лінійний перегляд вимагає від інженерів тримати в голові крихку ментальну модель багатьох непов’язаних змін. Що терміновішим здається інцидент, то більшою стає спокуса звинуватити останню видиму зміну, хоча насправді регресія могла з’явитися кілька днів тому і виявити себе лише тоді, коли змінилися патерни трафіку.

git bisect існує тому, що ця проблема має математичну форму. Ви надаєте Git дві опорні точки: один коміт, який є точно поганим, і один коміт, який є точно хорошим. Потім Git перемикається на коміт посередині і просить вас класифікувати його. Кожна відповідь відкидає приблизно половину історії, що залишилася, так само як пошук слова у словнику шляхом відкриття десь посередині замість того, щоб читати кожну сторінку з початку. Результат не є магією; це дисципліноване звуження простору пошуку, який інакше перевантажив би людську увагу.

Ключовим операційним рішенням є вибір опорних точок, які чесно описують баг. Хороший коміт має бути станом, у якому конкретна поведінка, що досліджується, дійсно працювала, а не просто датою, коли ніхто не пам’ятає скарг. Поганий коміт має бути станом, у якому точно відтворюється збій, а не загальним відчуттям, що main є нездоровою. Якщо опорні точки обрані недбало, бінарний пошук усе одно працюватиме, але він відповість на неправильне запитання з вражаючою швидкістю.

Хороші опорні точки часто походять з операційних записів, а не з пам’яті. Тег релізу, прив’язаний до успішного розгортання, коміт, зафіксований GitOps-контролером, або артефакт збірки, пов’язаний з успішним димовим тестом, кращі, ніж «десь перед обідом». Погані опорні точки мають бути настільки ж конкретними: коміт, який зараз розгорнутий у несправному середовищі, верхівка гілки, згенеровані маніфести якої не проходять валідацію, або точна ревізія, яка відтворює падіння юніт-тесту. Якщо ви не можете встановити обидві сторони, витратьте час на покращення спостережуваності перед початком бісекції, оскільки точний алгоритм не може виправити неточні докази.

flowchart TD
subgraph S1 [Step 1: Test Midpoint C4]
direction LR
C1[C1: Good] --- C2[C2: ?] --- C3[C3: ?] --- C4{C4: Test} --- C5[C5: ?] --- C6[C6: ?] --- C7[C7: Bad]
end
subgraph S2 [Step 2: C4 is BAD, Test Midpoint C2]
direction LR
C1_2[C1: Good] --- C2_2{C2: Test} --- C3_2[C3: ?] --- C4_2[C4: Bad]
end
subgraph S3 [Step 3: C2 is GOOD, Test Midpoint C3]
direction LR
C2_3[C2: Good] --- C3_3{C3: Test} --- C4_3[C4: Bad]
end
S1 -->|Result: BAD| S2
S2 -->|Result: GOOD| S3
S3 -->|Result: GOOD| R[Conclusion: C4 is the first bad commit]

Уявіть маніфест Deployment, який раптово не проходить валідацію на стороні сервера після низки змін в інфраструктурі як коді. Кластер доступний, API-сервер відповідає, і ваша робоча теорія полягає в тому, що поле схеми, блок відступів або версія API змінилися в якийсь момент після останнього релізу. Перш ніж почати, створіть чистий локальний стан, оскільки бісекція багаторазово перемикається на історичні коміти, і Git повинен мати змогу вільно переміщувати ваше робоче дерево, не руйнуючи незафіксовану роботу.

Terminal window
git bisect start

Ця команда розпочинає сесію, але вона ще не знає ваших меж. Поточний коміт зазвичай є поганою стороною, коли ви досліджуєте актуальну регресію з верхівки вашої гілки. Ви можете вказати це явно і позначити HEAD як поганий, або пропустити хеш, коли ви вже переключилися на несправний стан.

Terminal window
git bisect bad

Інша межа — це останній відомий хороший стан. У зрілому процесі випуску релізів це може бути тег версії, коміт продуктового розгортання або коміт злиття для попереднього релізного циклу. У цьому прикладі v1.2.0 позначає точку, коли маніфест було розгорнуто успішно.

Terminal window
git bisect good v1.2.0

Після встановлення цих опорних точок Git перемикається на середній коміт і повідомляє, скільки приблизно ревізій залишилося. Репозиторій тепер перебуває у стані від’єднаного HEAD (detached HEAD), що є очікуваним під час бісекції. Ви не перебуваєте на своїй звичайній гілці; ви відвідуєте історичний знімок, щоб мати змогу класифікувати його як хороший чи поганий для цієї конкретної поведінки.

Terminal window
Bisecting: 125 revisions left to test after this (roughly 7 steps)
[a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0] Update resource requests

Зупиніться та подумайте: якщо ви запустите валідацію Kubernetes на цьому історичному коміті і вона пройде успішно, що ви повинні сказати Git далі, і що ця відповідь означатиме для решти історії? Правильна відповідь — git bisect good, оскільки успіх доводить, що регресію було додано після поточного середнього коміту. Тоді Git зможе проігнорувати старішу половину діапазону і продовжити пошук лише в новішій половині.

Terminal window
kubectl apply --dry-run=server -f deployment.yaml
git bisect good

Якщо та сама валідація завершується помилкою з точним симптомом, який ви досліджуєте, замість цього позначте середній коміт як поганий. Будьте тут точними: помилка, спричинена відсутністю локальних залежностей, це не те саме, що регресія схеми Kubernetes, яку ви шукаєте. Ручна бісекція працює лише тоді, коли кожна класифікація відображає цільову поведінку, а не будь-яку можливу незручність на тому історичному коміті.

Terminal window
git bisect bad

Після кількох раундів Git виводить перший поганий коміт. Ця фраза має значення: це не просто коміт, де існує баг, а найперший коміт у вибраному діапазоні історії, де погана поведінка з’являється після відомого хорошого попередника. Цей коміт стає вашим головним доказовим артефактом для глибшого розслідування, перегляду або планування відкату.

Terminal window
b9c8d7e6f5g4h3i2j1k0l9m8n7o6p5q4r3s2t1u0 is the first bad commit
commit b9c8d7e6f5g4h3i2j1k0l9m8n7o6p5q4r3s2t1u0
Author: Alex Engineer <alex@example.com>
Date: Tue Oct 24 14:32:11 2025 -0400
chore: update apiVersion for HorizontalPodAutoscaler

Завжди завершуйте сесію свідомо. Під час інциденту інженери часто святкують знайдену причину і забувають, що репозиторій досі перебуває десь у минулому. git bisect reset повертає вас до гілки та коміту, де почалася сесія, що запобігає випадковому виконанню наступних команд на історичному коді.

Terminal window
git bisect reset

Гіпотетичний сценарій: платформна команда, що керує онбордингом орендарів, використовує цей робочий процес, коли об’єкти NetworkPolicy за замовчуванням перестають рендеритися з Helm-чарту. Пайплайн зелений, чарт досі встановлюється, а візуальний diff недавніх пул-реквестів не виявляє пропущеного відступу всередині допоміжного шаблону. Ручна бісекція з використанням helm template як тесту для класифікації може звузити понад двісті комітів до єдиного рефакторингу чарту за вісім перевірок, що дає каналу інциденту конкретний коміт, автора та обґрунтування замість ще однієї години спекулятивного перегляду.

З того інциденту команда засвоїла важливий другорядний урок: бісекція є найбільш корисною, коли тест можна запустити на історичному коді без необхідності використання всієї продуктової системи. Рендеринг чарту, валідація маніфесту, запуск сфокусованого юніт-тесту або старт невеликого локального сервісу дають Git щось повторюване для класифікації. Якщо єдиний спосіб побачити баг — це чекати на продуктовий трафік, першим завданням є створення меншого способу відтворення. Це відтворення стає мостом між операційними симптомами та історичними доказами.

Розділ 2: Автоматизація розслідування за допомогою git bisect run

Розділ «Розділ 2: Автоматизація розслідування за допомогою git bisect run»

Ручний пошук поділом навпіл (bisection) допомагає зрозуміти концепцію, але саме завдяки автоматизації цей інструмент стає надійним під тиском. Люди — повільні класифікатори, особливо коли кожна середня точка (midpoint) вимагає збирання артефакту, рендерингу маніфестів, запуску локального тестового кластера або читання перевантаженого шумом результату валідації. Гірше того, втома спричиняє непослідовність: інженер може позначити коміт як поганий через те, що налаштування було незручним, або позначити як хороший, бо нестабільний (flaky) тест випадково один раз спрацював. git bisect run усуває цю непослідовність, дозволяючи скрипту класифікувати кожен вибраний (checked-out) коміт за допомогою кодів завершення (exit codes).

Контракт кодів завершення простий і суворий. Коли ваша команда завершується з кодом 0, Git позначає коміт як хороший (good). Коли вона завершується з кодом від 1 до 124 або від 126 до 127, Git позначає коміт як поганий (bad). Код 125 має особливе значення: коміт неможливо перевірити для цього питання, тому Git повинен пропустити його і вибрати сусіднього кандидата, не розглядаючи його як доказ наявності багу. Саме це відрізняє надійне розслідування (forensic run) від автоматизованого шляху до хибної відповіді.

Припустімо, маніфест StatefulSet перестав проходити валідацію API-сервером Kubernetes після багатьох змін у шаблонах сховища. Корисний скрипт для bisect має виконувати лише найменший тест, здатний довести або спростувати цільову поведінку на поточному вибраному коміті. Наведений нижче скрипт валідує один маніфест і перетворює результат kubectl на класифікацію, очікувану Git. Розмістіть його поза репозиторієм, наприклад, у /tmp/test-manifest.sh, щоб під час переходу по історії (historical checkouts) скрипт не було видалено чи змінено в процесі роботи bisect.

/tmp/test-manifest.sh
#!/usr/bin/env bash
# We do NOT use 'set -e' because we want to capture the failure exit code manually,
# rather than having the script abort immediately.
echo "Testing commit: $(git rev-parse --short HEAD)"
# Run a dry-run apply against the API server to validate the YAML schema
kubectl apply -f k8s/production/statefulset.yaml --dry-run=server > /dev/null 2>&1
# Capture the exit code of the kubectl command
EXIT_CODE=$?
# Evaluate the exit code and communicate with git bisect
if [ $EXIT_CODE -eq 0 ]; then
echo "Validation passed. Returning GOOD."
exit 0 # Tells Git this commit is Good
else
echo "Validation failed. Returning BAD."
exit 1 # Tells Git this commit is Bad
fi

Зробіть скрипт виконуваним перед початком запуску. Це рутинний крок, але його варто виконати явно, оскільки помилка доступу (permission error) під час пошуку не є доказом того, що цільовий коміт є поганим. Коли ваше тестове середовище не є виконуваним, у вас проблема із середовищем, а не регресія Kubernetes.

Terminal window
chmod +x /tmp/test-manifest.sh

Коли скрипт готовий, визначте межі і дозвольте Git керувати циклом. Сеанс bisect робить чекаут комітів, запускає скрипт, зчитує код завершення та обирає наступну середню точку, не чекаючи від вас ручного маркування. Ваша роль змінюється з постійного введення good чи bad на перевірку того, що сам тест є точною моделлю симптому в продакшені.

Terminal window
# 1. Initialize
git bisect start
# 2. Define the current broken state
git bisect bad HEAD
# 3. Define the last known working release
git bisect good v2.4.0
# 4. Hand over control to the script
git bisect run /tmp/test-manifest.sh

Під час виконання вивід може здаватися швидким і знеособленим, і в цьому вся суть. Git не формує власної думки про ймовірних авторів, повідомлення комітів або підозрілі diff-и. Він раз у раз ставить єдине запитання: чи проходить поточне дерево (checked-out tree) тест? Саме така дисципліна робить автоматизований bisect надзвичайно корисним під час емоційно напружених інцидентів.

running /tmp/test-manifest.sh
Testing commit: 7a8b9c0
Validation passed. Returning GOOD.
Bisecting: 67 revisions left to test after this (roughly 6 steps)
...
running /tmp/test-manifest.sh
Testing commit: 1d2e3f4
Validation failed. Returning BAD.
Bisecting: 33 revisions left to test after this (roughly 5 steps)
...
f8e7d6c5b4a3c2d1e0f9a8b7c6d5e4f3a2b1c0d9 is the first bad commit

Перш ніж запускати це в реальному репозиторії, запитайте себе, якого виводу ви очікуєте від відомо-хорошого (known-good) тегу та відомо-поганого (known-bad) HEAD. Якщо скрипт не здатен чітко розрізнити ці два якорі до початку пошуку, він точно не стане надійнішим десь посеред історії. Швидка попередня перевірка обох крайніх точок часто виявляє відсутність контексту кластера, застарілі облікові дані або надто широкі тести для регресії, яку ви намагаєтеся ізолювати.

Найпоширеніша причина невдач — трактування будь-якого ненульового результату як цільового багу. Уявіть діапазон, де один історичний коміт тимчасово зламав Makefile, тоді як ваше розслідування стосується маршрутизації вхідного трафіку (ingress routing). Якщо make test завершується помилкою на цьому коміті, а ваш скрипт виходить з кодом 1, Git позначає коміт як поганий у контексті багу маршрутизації, хоча сама маршрутизація навіть не перевірялася. Така хибна класифікація здатна відкинути ту половину історії, яка містить справжню регресію.

/tmp/robust-test.sh
#!/usr/bin/env bash
# Step 1: Attempt to compile the binary
make build
if [ $? -ne 0 ]; then
echo "Compilation failed! This commit is untestable."
# Exit 125 tells git bisect: "Skip this commit and find another midpoint"
exit 125
fi
# Step 2: Run the actual test for the bug
./bin/app-tester --run-integration
if [ $? -eq 0 ]; then
exit 0 # Good
else
exit 1 # Bad
fi

Код завершення 125 — це не спосіб ігнорувати незручні докази; це спосіб зберегти їхнє справжнє значення. Використовуйте його тоді, коли коміт не може дати відповідь на запитання через неможливість запустити тестове оточення. Не використовуйте його, якщо застосунок запускається і відтворює баг, адже це приховає справжній поганий коміт від пошуку.

Якісний скрипт для bisect виробничого рівня (production-grade) зазвичай складається з трьох логічних етапів. По-перше, він готує лише необхідні для тесту залежності, уникаючи масштабних перезбирань там, де достатньо точкового рендерингу маніфесту. По-друге, він перевіряє валідність самого тестового середовища і завершується з кодом 125, якщо щось не так. По-третє, він виконує одну детерміновану перевірку (assertion), яка безпосередньо мапиться на симптом інциденту. Завдяки такій структурі знайдений перший поганий коміт буде набагато легше аргументувати під час огляду після інциденту (post-incident review).

Також варто логувати коміт, що перевіряється, команду, яка виконується, та причину кожного коду завершення. Ці повідомлення можуть здаватися надмірними під час швидкого виконання скрипту, але вони набувають цінності, коли хтось запитає, чому було пропущено певну середню точку або чому коміт позначили як поганий. Робіть лог зрозумілим для людини і не ховайте весь вивід у /dev/null, доки скрипт не стане стабільним. Щойно ви переконаєтеся в правильності класифікації, можна приглушити занадто «галасливі» інструменти, продовжуючи при цьому виводити ключовий рядок із доказами для кожного коміту.

Для валідації Kubernetes надавайте перевагу тестам, що не змінюють спільні середовища. Dry run на стороні сервера може перехопити помилки схеми та admission-контролерів без фактичного збереження об’єктів, тоді як локальний рендеринг у поєднанні зі статичною перевіркою політик може працювати навіть тоді, коли кластер недоступний. Якщо регресія стосується поведінки під час виконання (runtime behavior), подумайте про використання одноразового локального кластера або простору імен, які скрипт створює, а після завершення — знищує. Принцип завжди один: кожен історичний чекаут має згенерувати артефакт для тестування, а тест повинен перевіряти саме цей артефакт, а не якесь застаріле розгортання.

Розділ 3: Читання історії за допомогою git blame замість звинувачення людей

Розділ «Розділ 3: Читання історії за допомогою git blame замість звинувачення людей»

Пошук першого поганого коміту відповідає на питання, коли саме регресія потрапила у вибраний діапазон, але це не завжди пояснює ширший задум. Вам усе ще потрібно зрозуміти, чи був підозрілий рядок звичайною опискою (typo), свідомим компромісом (tradeoff), скопійованим блоком з іншого сервісу чи просто залишком після масового переформатування. Команда git blame є надзвичайно цінною, оскільки вона анотує кожен рядок поточного файлу комітом, який востаннє його змінював, але сама назва команди може спровокувати команди використовувати її як інструмент для персональних звинувачень. Використовуйте її як інструмент для читання історії походження (lineage reader), а не як спосіб присоромити автора.

Базова форма навмисно проста. Вона виводить ревізію, автора, дату та текст для кожного рядка у файлі. Для невеликого маніфесту цього буває достатньо. Але для великого згенерованого чарту (chart) базовий blame перетворюється на стіну шуму, особливо якщо підозрілий блок — це лише кілька рядків у шаблоні із сотнями інших, непов’язаних рядків.

Terminal window
git blame k8s/deployment.yaml

Output:

^e2f3g4h (Alice 2024-01-10 09:00:00 -0400 1) apiVersion: apps/v1
^e2f3g4h (Alice 2024-01-10 09:00:00 -0400 2) kind: Deployment
b9c8d7e6 (Bob 2024-02-15 14:30:00 -0400 3) metadata:
b9c8d7e6 (Bob 2024-02-15 14:30:00 -0400 4) name: payment-gateway
... (800 more lines)

Прицільний blame — це значно практичніший підхід. Якщо readiness probe, блок resources або секція securityContext виглядають підозріло, обмежте команду саме цим діапазоном замість того, щоб вручну розбирати весь файл. Ви можете вказати фіксовані номери рядків, якщо знаєте їх, або використати регулярний вираз і зсув (offset), якщо файл часто змінюється.

Terminal window
git blame -L 45,50 k8s/deployment.yaml

Формат із регулярними виразами особливо корисний для маніфестів Kubernetes, оскільки одні й ті самі концепції постійно повторюються в контейнерах, init-контейнерах, sidecar-ах і шаблонах. Наведена нижче команда знаходить перше входження resources: і анотує його разом із десятьма наступними рядками. Цього зазвичай вистачає, щоб охопити запити (requests) та ліміти (limits) без потреби підраховувати рядки від початку файлу.

Terminal window
git blame -L '/resources:/',+10 k8s/deployment.yaml

Зміни лише пробілів (whitespace-only) — це те місце, де наївний blame починає активно збивати з пантелику. Через коміт від автоматичного форматера може здатися, ніби кожен рядок написав користувач CI або це результат злиття гілки очищення (cleanup branch). Це приховує справжнє проєктне рішення, яке ви намагаєтеся оцінити. Опція -w наказує Git ігнорувати пробіли під час виконання blame. Завдяки цьому він може проігнорувати зміни відступів і показати той коміт, який змінив самий текст.

Terminal window
git blame -w k8s/deployment.yaml

У великих репозиторіях таку політику часто фіксують на постійній основі за допомогою .git-blame-ignore-revs. Якщо команда провела глобальне переформатування YAML у всьому репозиторії, ви можете додати геш цього коміту до файлу ігнорування та налаштувати Git так, щоб він пропускав його під час виконання blame. Це дозволить під час майбутніх розслідувань фокусуватися на значущому авторстві, а не раз по раз «відкривати» одну й ту саму подію форматування.

Terminal window
git config blame.ignoreRevsFile .git-blame-ignore-revs

Переміщення коду — це друга пастка. Стандартний blame прив’язаний до поточного шляху файлу, тому він може приписати авторство людині, яка просто перенесла блок у новий файл, а не тій, яка його спроєктувала. Ця різниця стає критичною, коли помічник Helm (helper), секція сховища або політика безпеки були скопійовані під час рефакторингу, а пізніше, під час збою, почали викликати підозри.

Terminal window
git blame -C k8s/charts/payment/_ingress.tpl

Опція -C наказує Git відстежувати скопійовані або переміщені рядки. Повторення цієї опції робить пошук більш агресивним серед комітів і файлів. У дуже великих репозиторіях це займає більше часу, але дозволяє відновити справжнє авторство навіть після складних рефакторингів. Використовуйте цю «важку» (expensive) форму, коли відповідь є достатньо важливою з операційної точки зору, щоб виправдати додаткову роботу.

Terminal window
# Standard blame shows the refactoring commit:
c8d7e6f5 (Bob 2024-03-01 10:00:00 -0400 12) {{ include "mychart.labels" . | nindent 4 }}
# Blame with -C -C pierces the veil to find the true author:
a1b2c3d4 (Alice 2023-11-15 09:15:00 -0400 12) {{ include "mychart.labels" . | nindent 4 }}

Зупиніться та подумайте: ви проводите аудит блоку securityContext, який, схоже, дозволяє контейнеру працювати з вищими привілеями, ніж очікувалося. Стандартний blame вказує на користувача Jenkins, а повідомлення коміту каже, що було виконано конвертацію відступів YAML з чотирьох пробілів на два. Який підхід ви б обрали в такій ситуації і чому? Обґрунтована відповідь передбачає поєднання git blame -w та -C (якщо можливе переміщення), оскільки ігнорування пробілів дозволяє оминути зміни форматера, а виявлення копіювання відстежує рух блоку під час рефакторингів.

Найкращий результат, який ви можете отримати від blame — це правильне наступне запитання. Щойно ви знайдете вихідний коміт, прочитайте його повідомлення, перегляньте diff навколо зміни та знайдіть пов’язане issue або pull request. Підозрілий рядок міг бути цілком виправданим екстреним workaround-ом, для якого так і не створили issue на очищення (cleanup), або ж він може вказувати на хибне розуміння системи, яке варто покрити тестом. blame просто дає вам координати в історії; інженерне мислення вирішує, що з ними робити.

blame також стає кориснішим, якщо враховувати шляхи до файлів. Якщо файл перейменували, звичайна команда до поточного шляху може втратити контекст, який існував під старою назвою, тоді як виконання команд log з --follow або опціями виявлення копіювання можуть дати підказки про те, коли відбулося перейменування. Під час обговорень на рев’ю використовуйте вивід blame без зайвої категоричності: твердження «цей рядок востаннє змінився тут» є фактом, але «ця людина спричинила збій» — це набагато серйозніше звинувачення. Ваша мета — відновити логіку проєктування (design intent), а не звести складний системний збій до імені розробника навпроти рядка коду.

Розділ 4: Пошук змін-привидів за допомогою запитів Pickaxe

Розділ «Розділ 4: Пошук змін-привидів за допомогою запитів Pickaxe»

Іноді рядок, який потрібно дослідити, більше не існує. Значення конфігурації могло бути видалено з маніфесту, виняток політики міг зникнути та з’явитися знову, або секретоподібний заповнювач міг бути зафіксований, а потім видалений. git blame не може анотувати рядок, відсутній у поточному файлі, а git grep виконує пошук у знімках, а не в життєвому циклі рядка. Для видаленого або мінливого тексту вам потрібні git log -S та git log -G, які зазвичай називають пошуком Pickaxe.

Параметр -S шукає в історичних diff-ах коміти, у яких змінилася кількість входжень рядка. Ця деталь важлива, оскільки запит не про те, чи містить коміт рядок десь у собі; він перевіряє, чи додав коміт або ж видалив входження цього рядка. Якщо змінна середовища зникла з Deployment, -S може показати коміт додавання та коміт видалення, навіть якщо поточний файл нічого не містить.

Terminal window
git log -S "DB_MAX_CONNECTIONS" --oneline

Вивід:

f9a8b7c Remove legacy database connection limits
a1b2c3d Add explicit connection limits for stability

Цей результат дає вам коротку хронологію, але досліднику інцидентів зазвичай також потрібен патч. Поєднання Pickaxe з --patch або подальше використання git show розкриває точний контекст. Мета полягає не лише в тому, щоб дізнатися, хто видалив DB_MAX_CONNECTIONS, але й зрозуміти, чи було це видалення частиною запланованої зміни пулу з’єднань, випадковим очищенням або ризикованою спробою зменшити навантаження на ресурси.

Параметр -G відповідає на ширше питання. Замість того, щоб підраховувати входження одного точного рядка, він шукає в рядках diff за допомогою регулярного виразу. Це корисно, коли значення з часом змінюється або точний текст невідомий. Ліміти CPU, обсяги пам’яті, номери портів, імена хостів і версії API часто краще шукати за шаблоном, аніж за одним буквальним значенням.

git log -G та git log -S використовують розширені регулярні вирази POSIX (ERE), а не PCRE, тому для пробілів використовуйте [[:space:]], а не \s.

Terminal window
git log -G "cpu:[[:space:]]*[0-9]+m" --oneline -p

Фрагмент виводу:

commit e4d3c2b1
Author: SRE Team <sre@example.com>
Date: Mon Nov 05 11:20:00 2024 -0400
feat: scale up frontend resources for holiday traffic
diff --git a/k8s/frontend-deployment.yaml b/k8s/frontend-deployment.yaml
--- a/k8s/frontend-deployment.yaml
+++ b/k8s/frontend-deployment.yaml
@@ -45,7 +45,7 @@
resources:
requests:
cpu: 100m
limits:
cpu: 200m
cpu: 500m

Зупиніться та подумайте: якщо git grep "DB_MAX_CONNECTIONS" не повертає жодного збігу, чи доводить це, що репозиторій ніколи не містив цієї змінної? Ні, не доводить. Це лише доводить, що в поточному знімку для пошуку відсутній цей рядок. Запит Pickaxe до історії — це правильний інструмент, коли підозрювані докази могли бути додані та видалені до того, як ви почали пошук.

Відмінність між пошуком Pickaxe та пошуком у знімках варто запам’ятати, оскільки це запобігає марній витраті часу під час інцидентів. Пошук у знімку відповідає на питання: “Чи існує цей текст у цьому дереві?” Пошук Pickaxe відповідає на питання: “Які коміти змінили наявність або відповідні рядки diff для цього тексту?” Ці питання звучать схоже, коли ви втомлені, але вони ведуть до різних інструментів і різних доказів.

КомандаЩо вона шукаєВипадок використання
git log -S "password"Шукає в історії змін (diff-ах) серед усіх комітів.”Знайди мені коміт, де цей рядок було додано або видалено.”
git grep "password"Шукає в поточному знімку (файлах) вказаного коміту.”Чи існує цей рядок у кодовій базі прямо зараз?”

Розслідування інцидентів безпеки роблять цю різницю конкретною. Якщо приклад ключа AWS, як-от AKIAIOSFODNN7EXAMPLE, був зафіксований у навчальному маніфесті, а пізніше видалений, git grep може нічого не повернути з поточного дерева. git log -S "AKIAIOSFODNN7EXAMPLE" усе ще може виявити коміти додавання та видалення, що дозволяє команді оцінити розкриття даних, потреби в ротації облікових даних і те, чи існують подібні патерни деінде в історії. Ставтеся до історичних секретів як до інцидентів, навіть коли поточна гілка чиста.

Запити Pickaxe стають ще потужнішими, коли ви поєднуєте їх з обмеженнями шляхів та фільтрами дат. Якщо застаріла версія API з’являється в старих оверлеях Kubernetes, пошук у всій історії репозиторію може повернути забагато шуму. Додавання -- k8s/prod/ або звуження діапазону дат робить результат ближчим до операційного питання, на яке вам потрібна відповідь. Починайте широко, коли не впевнені, де містяться докази, а потім звужуйте пошук лише після того, як побачите достатньо збігів, щоб зрозуміти форму історії.

Розділ 5: Швидке сканування знімків за допомогою git grep

Розділ «Розділ 5: Швидке сканування знімків за допомогою git grep»

Коли докази існують у поточному дереві, git grep зазвичай є правильним інструментом пошуку. Він розуміє репозиторій, ефективно шукає відстежуваний вміст і уникає багатьох нерелевантних каталогів, у яких може заплутатися звичайна рекурсивна команда grep. В інфраструктурних репозиторіях це має значення, оскільки згенерований вивід, каталоги залежностей, віртуальні середовища та артефакти збірки можуть бути достатньо великими, щоб зробити звичайний пошук повільним і шумним.

git grep також шукає в довільних об’єктах дерева Git без виконання checkout. Ця здатність є цінною, коли колега по команді просить вас переглянути гілку, коли вам потрібно порівняти тег релізу з main, або коли ви хочете виконати пошук у віддаленій гілці відстеження, залишаючи свої локальні зміни недоторканими. Ви шукаєте безпосередньо у збережених знімках Git, замість того щоб вносити зміни до свого робочого каталогу.

Припустімо, колега каже, що додав PodDisruptionBudget у своїй гілці (feature branch), але не може пригадати шлях. Вам не потрібно ховати (stash) свою роботу, перемикати гілки та ризикувати порушенням локального контексту. Шукайте безпосередньо в об’єкті віддаленої гілки.

Terminal window
git grep "kind: PodDisruptionBudget" origin/feature-ha-setup

Вивід:

origin/feature-ha-setup:k8s/infra/pdb-frontend.yaml:kind: PodDisruptionBudget

git grep також підтримує логічні вирази. Якщо ви проводите аудит Service і вам потрібно знайти маніфести, які містять як kind: Service, так і type: LoadBalancer, звичайний пошук будь-якого з цих рядків буде занадто широким. Поєднання виразів дозволяє вам перетворити шумне сканування тексту на практичний інфраструктурний запит.

Terminal window
git grep -e "kind: Service" --and -e "type: LoadBalancer"

Ви можете розвинути цю ідею далі, попросивши Git перевірити кожен коміт і пошукати в цих деревах, хоча у великих репозиторіях це слід робити обережно. Наведена нижче команда шукає в усіх досяжних комітах застарілу версію Kubernetes API, що корисно під час роботи з підготовки до оновлення, коли вам потрібно знайти старі гілки або історичні маніфести, які все ще згадують видалене API.

Terminal window
git grep "apiVersion: policy/v1beta1" $(git rev-list --all)

Який підхід ви б обрали тут і чому: поточну перевірку готовності релізу щодо застарілих API чи історичне розслідування того, коли застарілі API були впроваджені? Для поточного релізу шукайте у відповідній гілці або тегу за допомогою git grep. Для історії впровадження використовуйте git log -S або -G, щоб результат вказував на коміти, а не просто показував сучасні файли.

У роботі з Kubernetes поєднуйте git grep з перевірками кластера, коли вам потрібні як очікуваний, так і фактичний стани. Репозиторій може показати, що має бути розгорнуто, тоді як kubectl get та kubectl describe показують, що наразі відомо API-серверу. Використовуйте повну команду у спільній документації, скриптах та скопійованих прикладах, щоб послідовність збору доказів не залежала від локального псевдоніма (alias), який існує лише в інтерактивній оболонці одного інженера.

Terminal window
kubectl get deploy,svc,pdb -n payments
kubectl describe deploy payment-gateway -n payments

Не плутайте пошук у репозиторії з істиною в кластері. Гілка маніфесту може містити PodDisruptionBudget, тоді як у кластері його немає, оскільки гілка так і не була застосована, GitOps-контролер призупинено, або оверлей виключає цей файл. Ефективне усунення несправностей зазвичай чергує докази з Git та докази з Kubernetes, поки дві хронології не зійдуться.

Справедливим є і зворотне: кластер може містити ресурси, які поточний репозиторій більше не описує. Хтось міг застосувати хотфікс вручну, старий контролер міг залишити сирітські об’єкти, або міграція могла перенести володіння до іншого репозиторію. Коли пошук у Git та перевірка кластера не збігаються, стримуйте бажання негайно оголосити одну зі сторін неправою. Натомість запитайте, який механізм узгодження (reconciliation) має їх з’єднувати, а потім перевірте логи цього механізму, посилання на коміти та метадані застосованої ревізії.

Розділ 6: Створення аудиторських слідів за допомогою відфільтрованої історії

Розділ «Розділ 6: Створення аудиторських слідів за допомогою відфільтрованої історії»

Усунення несправностей часто закінчується ширшим питанням відповідальності: що змінилося в продакшн-маніфестах протягом періоду, коли зріс ризик? Фільтри журналу Git дозволяють відповісти на це питання без попереднього експорту всієї історії репозиторію в електронну таблицю. Ви можете фільтрувати за автором, датою, шляхом і форматом виводу, а потім об’єднати ці фільтри у звіт, достатньо вузький, щоб його міг прочитати аудитор або керівник розслідування інциденту.

Фільтри автора та дати є простими, коли йдеться про особу чи команду. Наведена нижче команда запитує коміти, зроблені обліковим записом підрядника з початку місяця. У реальних середовищах пам’ятайте, що імена авторів самі по собі не є доказом ідентичності; це корисні метадані Git, які слід зіставляти з перевірками pull request-ів, підписаними комітами, логами розгортання або записами контролю доступу, коли ставки високі.

Terminal window
git log --author="contractor.name" --since="2024-10-01" --oneline

Фільтри шляхів зосереджують звіт на файлах, які мають значення. Якщо продакшн-інцидент стосується config/settings.yaml, коміти вихідного коду застосунку лише відволікатимуть, навіть якщо вони відбулися того ж тижня. Подвійне тире відокремлює параметри ревізії від шляхів, що запобігає інтерпретації неоднозначних імен як гілок або прапорців.

Terminal window
git log --oneline -- config/settings.yaml

Для звіту про відповідність (compliance) повідомлень комітів рідко буває достатньо. Часто потрібен список змінених файлів, щоб рецензенти могли бачити, які маніфести були змінені, додані або видалені. --name-status додає цю деталізацію на рівні файлів до кожного відповідного коміту, що перетворює розмиту хронологію на практичний аудиторський слід.

Terminal window
git log \
--since="2023-12-20" \
--until="2024-01-02" \
--name-status \
-- k8s/prod/

Вивід:

commit d4c3b2a1
Author: DevOps Bot <bot@example.com>
Date: Wed Dec 27 03:00:00 2023 -0400
Automated image tag update for frontend
M k8s/prod/frontend-deployment.yaml
A k8s/prod/frontend-configmap.yaml

Якщо звіт має надходити до SIEM, електронної таблиці або бази даних інцидентів, форматуйте його безпосередньо з Git, а не копіюйте вивід термінала вручну. Наведені нижче заповнювачі (placeholders) --pretty=format створюють просте представлення, розділене комами, з коротким хешем, ім’ям автора, датою автора та темою. Для більш просунутої автоматизації віддавайте перевагу роздільнику, який не може з’явитися в темах комітів, але цю форму легко читати під час виконання вправ модуля.

Terminal window
git log --since="2024-01-01" --pretty=format:"%h,%an,%ad,%s" --date=short -- k8s/

Вивід:

a1b2c3d,Alice Engineer,2024-01-15,Update ingress rules
e4f5g6h,Bob Developer,2024-01-12,Fix typo in deployment

Надійний аудиторський слід зазвичай містить запитання, команду, межі та інтерпретацію. Наприклад, “усі коміти, що зачіпають k8s/prod/ під час святкового заморожування змін” є сильнішим за “нещодавні зміни маніфестів”, оскільки воно визначає шлях і діапазон дат. Якщо пізніше команда поставить під сумнів результат, ви можете повторно запустити точну команду та обговорити, чи були межі правильними, замість того, щоб сперечатися по пам’яті.

Гіпотетичний сценарій: після збою платформи, спричиненого відсутністю проби готовності (readiness probe), команда спочатку сперечається про те, чи оминула зміна рев’ю. Відфільтрований журнал для продакшн-оверлея показує, що пробу було видалено в невеликому коміті, позначеному як оновлення тегу образу, а --name-status виявляє, що той самий коміт змінив файл патча оверлея. Ці докази переміщують розмову від особистих підозр до виправлення процесу: автоматизація образів потребує вужчого шляху запису, а правила рев’ю мають позначати видалення полів маніфесту окремо від змін образів.

Відфільтрована історія є найбільш переконливою, коли вона поєднується з межами, які відповідають операційній політиці. Якщо організація має заморожування змін від однієї дати до іншої, використовуйте саме ці дати. Якщо заморожування стосується лише продакшн-оверлеїв, фільтруйте лише ці шляхи. Якщо автоматизації дозволено оновлювати теги образів, але не проби, вказуйте імена файлів і патчі, щоб ця відмінність була видимою. Звіт, який відображає мову політики, рецензентам легше оцінити, аніж великий масив суміжних комітів.

Патерни та антипатерни

Розділ «Патерни та антипатерни»

Найсильніші робочі процеси усунення несправностей у Git поєднують вузький симптом, детермінований тест та історичний запит, який відповідає формі доказів. Команда не повинна починати з кожної відомої їй команди. Вона має почати з того, щоб вирішити, чи існують докази зараз, чи вони існували в минулому, чи їх потрібно довести за допомогою повторюваних історичних перемикань (checkouts).

ПатернКоли використовуватиЧому це працюєМасштабування
Bisect з єдиним хибним твердженнямСистема була справною в одній відомій точці і несправною в іншій.Це перетворює багато комітів на логарифмічний пошук із відтворюваними мітками.Робіть скрипт вузькоспрямованим і виконайте його попередню перевірку на обох якорях перед запуском.
Цільовий blame після знаходження підозрілого рядкаВам потрібен контекст для поточного рядка в маніфесті або шаблоні.Це пов’язує текст із комітом, який востаннє його змінив, а потім дозволяє дослідити намір.Використовуйте -w, -C та файли ignore-revs, коли форматування або переміщення приховує авторство.
Pickaxe для зниклого або змінного текстуЗмінна, виняток із політики, ліміт або рядок, схожий на секрет, тепер зникли.Він шукає в diff-ах, тому видалені докази все одно залишають історичний слід.Віддавайте перевагу -G для патернів і -S для точних змін життєвого циклу.
Відфільтрований log для звітів про інцидентиЗацікавленим сторонам (стейкхолдерам) потрібна хронологія певних шляхів, людей або дат.Це перетворює історію комітів на обмежений звіт, а не на словесні здогадки.Зберігайте точну команду разом зі звітом, щоб рецензенти могли відтворити межу.

Антипатерни зазвичай виникають через використання знайомої команди для неправильного типу доказів. git grep чудово підходить для поточних знімків (snapshots), але це неправильний інструмент для видаленого коду. git blame корисний для поточних рядків, але може вводити в оману після масового переформатування, якщо не враховувати пробіли (whitespace). git bisect run є потужним, але небезпечним, коли скрипт плутає «тест не зміг запуститися» з «баг існує».

АнтипатернЩо йде не такКраща альтернатива
Пошук видалених доказів у поточних файлахРезультат порожній, і команда робить помилковий висновок, що значення ніколи не існувало.Використовуйте git log -S або git log -G для пошуку в історичних diff-ах.
Виконання curl-запиту до живого production всередині скрипта bisectКожен історичний checkout тестує ту саму розгорнуту систему, тому мітки не мають сенсу.Створюйте збірку або рендеріть із вибраного коміту та тестуйте ізольований локальний артефакт.
Сприйняття виводу blame як особистого доказуОчевидним автором може бути форматер, той, хто перемістив файл, або акаунт автоматизації.Читайте коміт, використовуйте параметри переміщення та пробілів, а також зіставляйте з контекстом рев’ю.
Написання широких скриптів валідаціїНестабільні (flaky) або непов’язані збої позначають коміти як погані та псують пошук.Відокремлюйте збої налаштування від цільових збоїв і завершуйте з кодом 125, коли коміт неможливо протестувати.

Фреймворк прийняття рішень

Розділ «Фреймворк прийняття рішень»

Обирайте інструмент, запитуючи, де містяться докази. Якщо симптом відтворюється лише тоді, коли ви робите checkout історичних комітів і запускаєте тест, використовуйте bisection. Якщо підозрілий рядок існує зараз, використовуйте цільовий blame. Якщо текст зараз відсутній, але міг існувати раніше, використовуйте Pickaxe. Якщо питання полягає в тому, хто змінював шлях протягом певного періоду, використовуйте відфільтровані запити log.

+------------------------------+
| What evidence do you have? |
+---------------+--------------+
|
+-------------------+-------------------+
| |
Current line exists? Line/value missing now?
| |
+-------+-------+ +-------+-------+
| git blame -L | | git log -S/-G |
+-------+-------+ +-------+-------+
| |
Need true author through moves? Need exact patch context?
| |
Use -w, -C, ignore revs Add -p or run git show
|
+-------------------+
|
Behavior changed over time?
|
+-------+-------+
| git bisect |
| git bisect run|
+-------+-------+
|
Need stakeholder timeline?
|
+-------+-------+
| git log filters|
| --name-status |
+---------------+
Питання розслідуванняОсновний інструментКрок підтвердження
”Який коміт запровадив цей відтворюваний збій?”git bisect runВивчіть git show для першого поганого коміту та перезапустіть тест навколо його батька.
”Хто останнім змінив цей поточний рядок у маніфесті?”git blame -LВикористовуйте -w або -C, якщо форматування або переміщення може приховувати початкового автора.
”Коли було видалено цю змінну?”git log -SДодайте --patch або вивчіть коміт видалення безпосередньо.
”Які маніфести змінилися під час заморозки (freeze)?”git log --since --until --name-status -- path/Порівняйте хронологію Git із записами про розгортання та рев’ю.
”Чи містить гілка колеги цей ресурс?”git grep pattern branchПідтвердьте, чи була ця гілка розгорнута або злита.

Фреймворк навмисно простий, оскільки реагування на інциденти заохочує повторювані рішення. Не починайте з найбільш вражаючої команди; почніть із питання, яке усуває найбільше невизначеності. Коли відповідь змінює тип доказів, змінюйте інструменти, а не змушуйте одну команду виконувати всю роботу.

На практиці розслідування проходять через цей фреймворк більше одного разу. Пошук за допомогою Pickaxe може виявити коміт видалення, потім blame може пояснити поточний рядок заміни, а відфільтрований log може показати, що обидва коміти відбулися під час заморозки. Цей цикл є здоровим, доки кожен крок звужує або підтверджує історію. Записуйте команду, яка створила кожне твердження, оскільки відтворюваність — це те, що відрізняє криміналістичну хронологію від розповідей у кімнаті реагування на інциденти.

  1. Назва Pickaxe — це стара культура Git: git log -S зазвичай називають Pickaxe (кайло), оскільки він “копає” історію патчів у пошуках додавання та видалення рядків, а не шукає лише в поточному checkout-і.
  2. git bisect visualize може показати ваш поточний стан пошуку: під час складного bisection, git bisect visualize або git bisect view відкриває переглядач, такий як gitk, щоб ви могли оглянути коміти, позначені як good (добрі), bad (погані) та untested (непротестовані).
  3. Зворотний blame (Reverse blame) може шукати в історії видалень: git blame --reverse START..END file може допомогти визначити, де зник рядок у вказаному діапазоні. Це корисно, коли звичайний blame не бачить видаленого тексту.
  4. Режими dry-run у Kubernetes змінили операційні звички: серверні dry runs (доступні приблизно з Kubernetes 1.18, включаючи сучасні кластери 1.35+) дозволяють командам валідувати маніфести через сервер API без збереження об’єктів. Це робить Git bisection безпечнішим, коли доступний досяжний кластер.
ПомилкаЧому це трапляєтьсяЯк це виправити
Невиконання git bisect resetПісля знаходження багу інженери святкують і залишають репозиторій у від’єднаному історичному checkout-і (detached historical checkout).Виконайте git bisect reset одразу після фіксації першого поганого коміту, щоб подальша робота повернулася до початкової гілки.
Тестування брудних робочих дерев (dirty working trees)Незакомічені зміни роблять Git нездатним безпечно переміщатися між історичними комітами.Закомітьте, збережіть у stash або відкиньте непов’язані локальні зміни перед початком сесії bisection.
Використання git grep для пошуку видаленого кодуКоманда шукає у знімках (snapshots), тому видалений текст є невидимим у поточному дереві.Використовуйте git log -S для точних рядків або git log -G для змін регулярних виразів в історичних diff-ах.
Довіра до blame у переформатованих файлахКоміт форматера може виглядати автором кожного рядка, навіть якщо він змінив лише пробіли.Використовуйте git blame -w, налаштуйте .git-blame-ignore-revs та огляньте базовий коміт, перш ніж робити висновки.
Написання скриптів bisect, які завершуються з кодом 1 при помилках компіляціїСкрипт розглядає коміт, який неможливо протестувати, як доказ цільової регресії.Виявляйте збої налаштування або збірки окремо і завершуйте з кодом 125, коли коміт не може дати відповідь на питання розслідування.
Виконання curl-запиту до живого production під час bisectionІсторичні checkout-и не впливають на вже розгорнуту систему production.Створюйте збірку, рендеріть або розгортайте ізольований артефакт із вибраного коміту перед тестуванням поведінки.
Виконання blame на неправильній гілціЛокальний checkout може бути застарілим або вказувати на гілку фічі без відповідної історії.Спочатку підтвердьте цільову гілку або явно передайте потрібну ревізію командам Git.

Контрольні запитання

Розділ «Контрольні запитання»
Запитання 1: Ви запускаєте автоматизований git bisect run ./test.sh, щоб знайти баг маршрутизації в маніфестах Ingress у Kubernetes. Один коміт посередині не може зібратися через непов'язану синтаксичну помилку Makefile. Що має зробити скрипт і чому?

Відповідь: Скрипт повинен виявити, що збій збірки перешкоджає запуску тесту маршрутизації, і завершитися з кодом 125. Цей код повідомляє Git, що коміт неможливо протестувати для цього розслідування, тому Git має пропустити його замість того, щоб позначати як поганий. Якщо скрипт завершується з 1, Git сприймає непов’язану проблему з Makefile як доказ регресії маршрутизації і може відкинути половину історії, яка містить справжнього винуватця. Важливе обґрунтування полягає в тому, що мітки bisection повинні описувати цільову поведінку, а не кожну можливу історичну несправність.

Запитання 2: Сканування безпеки показує, що виняток у `NetworkPolicy` існує зараз, але ви підозрюєте, що його було тимчасово видалено минулого місяця, а потім відновлено. Який пошук Git ви повинні використати, щоб довести або спростувати цей життєвий цикл?

Відповідь: Використовуйте git log -S "specific-policy-string", коли ви можете ідентифікувати точний текст винятку, або git log -G "regex-pattern", якщо форма відома, але значення змінилися. Ці команди шукають в історичних diff-ах, тому вони можуть виявити коміти, де текст було видалено, а пізніше додано знову. git grep лише сказав би вам, чи містить поточний знімок (snapshot) виняток, що не може довести тимчасове видалення. Знайшовши коміти-кандидати, огляньте їхні патчі та контекст рев’ю, перш ніж робити висновки щодо намірів.

Запитання 3: `git blame` показує, що молодший інженер написав небезпечний блок storage вчора, але він стверджує, що лише перемістив маніфест під час реорганізації репозиторію. Як перевірити це твердження?

Відповідь: Запустіть цільовий blame з виявленням копіювання або переміщення, наприклад, git blame -C на поточному файлі та діапазоні підозрілих рядків. Якщо блок було переміщено з іншого файлу, Git може зазирнути за коміт реорганізації і показати попереднього автора та коміт, який запровадив текст. Якщо переміщення відбулося в кілька етапів, повторювані параметри -C можуть зробити пошук більш агресивним. Після цього вам слід оглянути оригінальний коміт, оскільки саме по собі авторство не пояснює, чи був цей блок правильним для його початкового контексту.

Запитання 4: Ваш API зламаний на `HEAD` і стабільний на тегу `v2.0`. Ви автоматизуєте bisection за допомогою скрипта, який робить curl-запит до ендпойнта здоров'я живого production за адресою `https://api.production.com/health`. Чому це не знайде регресію в Git?

Відповідь: Bisection змінює локальний checkout репозиторію, але він не розгортає повторно кожен історичний коміт у production. Скрипт продовжував би тестувати ту саму живу систему незалежно від того, який коміт вибрав Git, тому кожна класифікація описувала б поточний production, а не історичне дерево. Валідний скрипт повинен створити збірку, відрендерити або запустити ізольований артефакт із поточного вибраного коміту і протестувати цей артефакт. Для маніфестів Kubernetes це може означати рендеринг чарта і запуск серверного dry run проти безпечного кластера валідації.

Запитання 5: Вам потрібно дослідити приклад ключа AWS, `AKIAIOSFODNN7EXAMPLE`, який міг бути закомічений, а пізніше видалений. `git grep` нічого не повертає. Що доводить цей результат і якою має бути наступна команда?

Відповідь: Порожній результат git grep доводить лише те, що шуканий знімок наразі не містить цього рядка. Це не доводить, що рядок ніколи не існував в історії репозиторію. Наступною командою має бути git log -S "AKIAIOSFODNN7EXAMPLE", щоб Git шукав diff-и, де змінилася кількість входжень. Якщо з’являються збіги, огляньте коміти додавання та видалення, щоб визначити факт розкриття, час виправлення та наявність подібних прикладів деінде.

Запитання 6: Аудитор запитує всі коміти від `platform-team`, які змінили файли у `k8s/networking/`, включно з тим, чи кожен файл був доданий, змінений або видалений. Яка команда створює цей звіт?

Відповідь: Використовуйте git log --author="platform-team" --name-status -- k8s/networking/. Фільтр автора звужує результат до вказаної особи, тоді як pathspec після -- обмежує звіт директорією мережі. --name-status додає тип зміни на рівні файлу, що і є деталлю, яку запитував аудитор. Для реального пакету відповідності (compliance packet) запишіть точну команду і часове вікно, щоб звіт можна було відтворити.

Запитання 7: Колега каже, що їхня віддалена гілка містить новий `PodDisruptionBudget`, але у вас є локальна робота, яку ви не хочете ховати у stash. Як ви можете знайти шлях до файлу без checkout-у їхньої гілки?

Відповідь: Запустіть git grep "kind: PodDisruptionBudget" origin/their-branch, замінивши назву гілки на фактичну віддалену гілку відстеження. git grep може шукати об’єкти дерева Git безпосередньо, тому йому не потрібно змінювати вашу робочу директорію. Результат включає гілку, шлях до файлу і рядок зі збігом, чого достатньо для початку рев’ю. Вам все одно слід підтвердити, чи була ця гілка розгорнута, перш ніж розглядати її як істину кластера.

Практична вправа: Справа про зламаний маніфест

Розділ «Практична вправа: Справа про зламаний маніфест»

У цій вправі ви створите новий репозиторій зі згенерованою історією, додасте непомітний баг у стилі Kubernetes, сховаєте його під рутинними комітами та використаєте автоматизовану бісекцію разом із цілеспрямованим blame, щоб знайти докази. Лабораторна робота навмисно виконується локально та є безпечною: вона не вимагає наявності кластера, а скрипт валідації перевіряє текст маніфесту, щоб ви могли зосередитися на механіці розслідування в Git, перш ніж застосовувати цей самий патерн для валідації Kubernetes на боці сервера.

Крок 1: Налаштування сценарію

Розділ «Крок 1: Налаштування сценарію»

Спочатку згенеруйте репозиторій із достатньо глибокою історією, щоб зробити ручний огляд непривабливим. Початковий маніфест використовує containerPort: 80, що відповідає стандартній поведінці контейнера Nginx у цьому спрощеному сценарії. Згодом один коміт змінює порт на 8080, а безліч рутинних комітів приховують цю зміну від побіжного огляду.

Terminal window
mkdir k8s-forensics-lab && cd k8s-forensics-lab
git init
# Create the initial, perfectly working manifest
cat << 'EOF' > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27.0
ports:
- containerPort: 80
EOF
git add deployment.yaml
git commit -m "Initial commit: working deployment"
git tag v1.0
# Simulate 20 good, benign commits
for i in {1..20}; do
echo "# Comment $i" >> deployment.yaml
git commit -am "chore: minor update $i"
done
# INTRODUCE THE BUG (Typo in containerPort: 80 -> 8080)
sed -i.bak 's/containerPort: 80/containerPort: 8080/' deployment.yaml
rm deployment.yaml.bak
git commit -am "fix: adjust port configuration for new ingress"
# Simulate 30 more commits completely burying the bug
for i in {21..50}; do
echo "# Comment $i" >> deployment.yaml
git commit -am "chore: minor update $i"
done

Крок 2: Перевірка проблеми

Розділ «Крок 2: Перевірка проблеми»

Огляньте поточний файл, щоб підтвердити, що зламаний стан існує на HEAD. Це ваша «погана» точка відліку. У реальному робочому процесі Kubernetes цим кроком міг би бути невдалий kubectl apply --dry-run=server або провалений інтеграційний тест, але тут пряма перевірка тексту робить вправу портативною.

Terminal window
cat deployment.yaml | grep containerPort

Очікуваний вивід:

containerPort: 8080

Крок 3: Створення скрипта валідації

Розділ «Крок 3: Створення скрипта валідації»

Створіть скрипт, який відрізняє хороший стан від поганого. Якір $ має значення, оскільки він запобігає прийняттю 8080 так, ніби він містить правильне значення. Цей скрипт навмисно невеликий, що дозволяє легко аналізувати поведінку його коду виходу під час бісекції.

/tmp/test-port.sh
#!/usr/bin/env bash
# Check if the deployment contains the correct exact string 'containerPort: 80'
# The '$' anchors the search to the end of the line, ensuring '8080' fails.
grep "containerPort: 80$" deployment.yaml > /dev/null
# grep exits 0 if the string is found (Good commit), 1 if not found (Bad commit)
exit $?

Зробіть тестовий скрипт виконуваним:

Terminal window
chmod +x /tmp/test-port.sh

Крок 4: Виконання автоматизованої бісекції

Розділ «Крок 4: Виконання автоматизованої бісекції»

Тепер дайте вказівку Git знайти баг. Прочитайте кожну команду перед її запуском і пов’яжіть її з моделлю бісекції: почніть сесію, позначте погану кінцеву точку, позначте хорошу кінцеву точку, а потім передайте класифікацію скрипту.

  • Ініціалізувати процес бісекції: git bisect start
  • Позначити поточний зламаний стан: git bisect bad HEAD
  • Позначити відомий хороший релізний тег: git bisect good v1.0
  • Передати виконання скрипту автоматизації: git bisect run /tmp/test-port.sh
Переглянути очікуваний вивід
Terminal window
running /tmp/test-port.sh
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[some-hash] chore: minor update 26
...
running /tmp/test-port.sh
[hash] is the first bad commit
commit [hash]
Author: Your Name <your.email@example.com>
Date: [Date]
fix: adjust port configuration for new ingress

Git успішно знайшов точний коміт, який змінив порт, пройшовши 50 комітів і виконавши 5 тестів за частку секунди.

Крок 5: Очищення та дослідження коду

Розділ «Крок 5: Очищення та дослідження коду»

Перший поганий коміт вказує вам на те, коли регресія потрапила в історію, але цілеспрямований blame допомагає пов’язати поточний рядок з історичними доказами. Спочатку скиньте сесію бісекції, а потім застосуйте blame лише до рядка з портом, замість того щоб просити Git анотувати весь маніфест.

  • Завершити криміналістичну сесію та повернутися до реальності: git bisect reset
  • Використати цілеспрямований blame для рядка з портом: git blame -L '/containerPort/',+1 deployment.yaml
  • Дослідити перший поганий коміт за допомогою git show --stat --patch <hash> і підтвердити, що diff змінив лише очікуваний рядок.
  • Зафіксувати послідовність команд і хеш першого поганого коміту як докази інциденту.
Terminal window
git blame -L '/containerPort/',+1 deployment.yaml
Пояснення рішення

Команда git blame націлена на регулярний вираз /containerPort/ і відображає лише цей рядок. Ви побачите конкретний хеш коміту, особу, яка його зробила, та мітку часу, пов’язані зі зміною порту. Поєднуючи автоматизовану бісекцію з цілеспрямованим blame рядків, ви переходите від «маніфест неправильний» до «цей коміт змінив цей рядок, і ось докази». Це практичний стандарт для розслідування інцидентів.

  • Ви створили локальний репозиторій з відомим хорошим тегом v1.0 та поганим HEAD.
  • Ваш скрипт /tmp/test-port.sh завершується з кодом 0 для правильного порту та ненульовим кодом для неправильного порту.
  • Команда git bisect run /tmp/test-port.sh ідентифікувала повідомлення коміту fix: adjust port configuration for new ingress.
  • Ви виконали git bisect reset після розслідування.
  • Ви використали цілеспрямований git blame, щоб анотувати лише рядок containerPort.

Тепер, коли ви вмієте розбирати історію для пошуку регресій і створювати криміналістичні аудиторські сліди, зверніть увагу на робочі процеси співпраці. Дізнайтеся, як синхронізувати локальну роботу із зовнішніми серверами, вирішувати складні конфлікти злиття та безпечно керувати змінами в upstream у Модулі 7: Професійна співпраця — Remotes та PRs.