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

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

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

Розділ «Модуль 6: Цифровий детектив — Пошук та усунення несправностей»

Складність: [MEDIUM]
Час на виконання: 90 хвилин
Пререквізити: Модуль 5 Git Deep Dive

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

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

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

  • Діагностувати точний коміт, який вніс приховану помилку в конфігурацію, впровадивши алгоритми автоматизованого бінарного пошуку за допомогою git bisect run.
  • Оцінити справжнє походження блоків коду крізь перейменування файлів, зміни пробілів та рефакторинг, щоб ідентифікувати оригінального автора за допомогою розширених опцій git blame.
  • Порівняти та обрати відповідні стратегії пошуку для аудиту історичних змін, використовуючи пошук Git “Pickaxe” (git log -S/-G) проти пошуку по знімках (snapshots) за допомогою git grep.
  • Спроектувати цільові фільтри історії для реконструкції точних аудиторських слідів для конкретних маніфестів Kubernetes, відповідаючи на запитання відповідності (compliance) про те, хто, що і коли змінив.
  • Впровадити надійні робочі процеси усунення несправностей, які переводять зламаний стан кластера у вимірюване дослідження історичної регресії.

Зараз 3:14 ранку у Чорну п’ятницю. Основний кластер обробки платежів, який ідеально працював місяцями, раптово втрачає 15% усіх вхідних запитів із загадковою помилкою 503 Service Unavailable. Дашборди метрик горять червоним, міст для інцидентів заповнюється запанікованим керівництвом, а єдина зачіпка, яка у вас є — це те, що маніфест Deployment Kubernetes payment-gateway був “підправлений” кимось із команди Platform Engineering протягом останніх 400 комітів за останні три тижні.

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

У такі моменти високого тиску стандартних команд керування версіями недостатньо. Ви більше не розробник, який просто фіксує код; ви — цифровий детектив, що проводить криміналістичне розслідування на місці злочину в реальному часі. Хоча більшість інженерів сприймають Git як простий механізм “збереження”, його справжня сила полягає в здатності аналізувати час. Він зберігає повну геномну послідовність вашої інфраструктури. Коли щось ламається, відповідь уже є в репозиторії, прихована серед тисяч диффів. Різниця між п’ятихвилинним вирішенням проблеми та багатогодинним простоєм полягає у вашій здатності ефективно допитувати цю історію.

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

Розділ 1: Рушій бінарного пошуку: git bisect

Розділ «Розділ 1: Рушій бінарного пошуку: git bisect»

Коли система виходить із ладу, але ви не знаєте, коли була внесена помилка, перегляд комітів один за одним — це марна справа. Якщо у вас є 500 комітів між відомим робочим станом (скажімо, тег релізу минулого місяця) і поточним зламаним станом у main, лінійна перевірка кожного з них займе години нудної роботи, схильної до помилок.

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

Механіка бінарного пошуку

Розділ «Механіка бінарного пошуку»

Уявіть, що ви шукаєте конкретне слово у фізичному словнику на 1000 сторінок. Ви не читаєте сторінку 1, потім сторінку 2, потім сторінку 3. Ви відкриваєте книгу точно посередині (сторінка 500). Якщо ваше слово йде за алфавітом перед словами на сторінці 500, ви миттєво відкидаєте всю праву половину книги (сторінки 501–1000). Потім ви відкриваєте ліву половину на її середині (сторінка 250) і повторюєте процес. З кожним кроком ви відкидаєте 50% залишкового простору пошуку.

git bisect робить саме це з вашим деревом історії комітів Git. Ви надаєте йому “поганий” (bad) коміт (зазвичай ваш поточний зламаний стан, HEAD) і “хороший” (good) коміт (стан у минулому, про який ви точно знаєте, що все працювало). Git автоматично перемикається (checkout) на коміт рівно посередині між ними та зупиняється, по суті запитуючи вас: “Чи зламана система на цьому конкретному коміті?”

На основі вашої відповіді (good або bad), Git відкидає половину комітів і переходить до середини половини, що залишилася.

+--------------------------------------------------------------------+
| Процес Git Bisect (Пошук серед 7 комітів) |
| |
| [G] = Відомо як хороший [B] = Відомо як поганий [?] = Невідомо |
| [*] = Обрана Git середина для тестування (Detached HEAD) |
| |
| Крок 1: Визначення меж. Git обирає середину (Коміт 4). |
| C1[G] --- C2[?] --- C3[?] --- C4[*] --- C5[?] --- C6[?] --- C7[B] |
| |
| Ви тестуєте C4. Він ПОГАНИЙ (BAD). Git відкидає C4, C5, C6, C7. |
| Простір пошуку тепер від C1 до C4. Git обирає нову середину (C2). |
| |
| Крок 2: |
| C1[G] --- C2[*] --- C3[?] --- C4[B] |
| |
| Ви тестуєте C2. Він ХОРОШИЙ (GOOD). Git відкидає C1, C2. |
| Простір пошуку тепер від C2 до C4. Git обирає середину (C3). |
| |
| Крок 3: |
| C2[G] --- C3[*] --- C4[B] |
| |
| Ви тестуєте C3. Він ХОРОШИЙ (GOOD). |
| Висновок: C4 — це саме той коміт, де була внесена помилка. |
+--------------------------------------------------------------------+

Ручний робочий процес Bisect на практиці

Розділ «Ручний робочий процес Bisect на практиці»

Давайте пройдемо через стандартний ручний процес для зламаного маніфесту Kubernetes. Припустимо, ваш deployment.yaml раптово перестав проходити валідацію на API-сервері, але ви не впевнені, яка з нещодавніх змін Infrastructure-as-Code спричинила синтаксичну помилку.

Крок 1: Ініціалізація сесії.

Terminal window
git bisect start

Ця команда активує режим bisect. Git починає відстежувати ваш прогрес.

Крок 2: Визначення Поганого (Bad) стану. Позначте поточний (зламаний) стан як поганий. Якщо ви не вкажете хеш коміту, Git припустиме, що ваш поточний активний коміт (HEAD) є поганим.

Terminal window
git bisect bad

Крок 3: Визначення Хорошого (Good) стану. Ви перевіряєте нотатки до релізу і згадуєте, що версія v1.2.0 розгорталася ідеально минулого тижня. Ви позначаєте цей тег як відомий хороший стан.

Terminal window
git bisect good v1.2.0

Git обчислює відстань між двома точками і негайно відповідає:

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

У цей момент Git фізично переключився на коміт a1b2c3d4. Ваш репозиторій зараз перебуває в стані “detached HEAD”, “подорожуючи в часі” до того самого моменту, коли був зроблений цей коміт.

Зупиніться та подумайте: Якщо ви запустите kubectl apply --dry-run=client -f deployment.yaml просто зараз, і команда виконається успішно, яку команду ви повинні надати Git наступною?

Відповідь: Ви повинні запустити git bisect good. Це повідомить Git, що помилка була внесена після цього перевіреного коміту, дозволяючи алгоритму безпечно відкинути старішу ліву половину простору пошуку.

Крок 4: Цикл тестування. Ви тестуєте маніфест.

  • Якщо dry-run не вдається з помилкою валідації, введіть git bisect bad.
  • Якщо dry-run проходить успішно, введіть git bisect good. Git автоматично переключиться на наступну оптимальну середину і знову запитає вас.

Крок 5: Виявлення. Приблизно після 7 ітерацій 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

Крок 6: Очищення (Важливо!). Після того, як ви ідентифікували винуватця, ви повинні явно наказати Git завершити криміналістичну сесію. Це виведе ваш репозиторій зі стану detached HEAD і поверне на гілку, де ви були до початку.

Terminal window
git bisect reset

Історія з фронту: Прихована регресія Helm

Розділ «Історія з фронту: Прихована регресія Helm»

Команда Platform Engineering, що керує мультитенентним кластером, помітила, що у нових орендарів (tenants), доданих того ранку, були відсутні стандартні NetworkPolicies. Автоматичні сповіщення не спрацювали, пайплайни пройшли успішно, але політики просто не створювалися в кластері. Вони знали, що процес онбордингу працював бездоганно в релізі v3.1.0, але поточна гілка main мовчки видавала помилку.

З понад 200 комітами, що торкалися різних складних шаблонів Helm-чартів у кількох репозиторіях, знайти помилку в синтаксисі вручну через перегляд PR було неможливо. Інженер ініціював ручний git bisect, тестуючи вивід helm template на кожному кроці. Рівно за 8 кроків Git знайшов коміт, що зламав систему. Знадобилося 4 хвилини, щоб знайти пропущений блок відступу YAML, прихований глибоко в допоміжному шаблоні — помилка, на пошук якої людським оком знадобилися б години інтенсивного перегляду коду.

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

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

Ручна бісекція неймовірно потужна, але люди повільні за своєю природою та схильні до помилок при перемиканні контексту. Якщо тестування одного коміту вимагає від вас компіляції бінарного файлу Go, очікування 45 секунд, перевірки виводу лог-файлу, а потім ручного введення git bisect good/bad, ви швидко втратите терпіння. Крім того, ручне тестування підвладне людському фактору — ви можете випадково ввести good, коли насправді сталася ледь помітна помилка.

Справжня, трансформаційна магія криміналістики Git розкривається, коли ви поєднуєте git bisect із автоматизацією в оболонці (shell).

git bisect run дозволяє надати скрипт для виконання або пряму shell-команду. Git автоматично виконуватиме цю команду на кожному кроці алгоритму бісекції.

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

Створення надійного скрипта для Bisect

Розділ «Створення надійного скрипта для Bisect»

Припустімо, у нас є складний маніфест StatefulSet, який раптово почав не проходити валідацію на Kubernetes API-сервері. Ми хочемо знайти точний коміт, який вніс недійсну схему серед 300 нещодавніх комітів.

Ми можемо написати спеціальний bash-скрипт test-manifest.sh.

Критичне правило: Ваш тест-скрипт в ідеалі повинен бути розташований поза репозиторієм Git, який ви тестуєте (наприклад, у /tmp/), або ви повинні гарантувати, що сам скрипт не змінювався, не перейменовувався і не видалявся протягом історичного періоду, який ви переглядаєте!

/tmp/test-manifest.sh
#!/usr/bin/env bash
# Ми НЕ використовуємо 'set -e', бо хочемо зафіксувати код завершення помилки вручну,
# а не дозволити скрипту перерватися миттєво.
echo "Тестування коміту: $(git rev-parse --short HEAD)"
# Запускаємо dry-run apply проти API-сервера для перевірки схеми YAML
kubectl apply -f k8s/production/statefulset.yaml --dry-run=client > /dev/null 2>&1
# Фіксуємо код завершення команди kubectl
EXIT_CODE=$?
# Оцінюємо код завершення та повідомляємо git bisect
if [ $EXIT_CODE -eq 0 ]; then
echo "Валідація пройшла успішно. Повертаємо GOOD."
exit 0 # Повідомляє Git, що цей коміт Хороший
else
echo "Валідація не вдалася. Повертаємо BAD."
exit 1 # Повідомляє Git, що цей коміт Поганий
fi

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

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

Виконання автоматизованого запуску

Розділ «Виконання автоматизованого запуску»

Коли скрипт готовий, ми визначаємо наші межі та запускаємо автоматизацію.

Terminal window
# 1. Ініціалізація
git bisect start
# 2. Визначення поточного зламаного стану
git bisect bad HEAD
# 3. Визначення останнього відомого робочого релізу
git bisect good v2.4.0
# 4. Передача керування скрипту
git bisect run /tmp/test-manifest.sh

Тепер Git візьме керування вашим терміналом. Він буде швидко перемикати коміти, виконувати скрипт, оцінювати код завершення та обчислювати наступний алгоритмічний стрибок повністю без вашої участі. Ви побачите потік тексту, поки він тестуватиме 10 або 20 комітів за лічені секунди.

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

Те, на що інженеру знадобилося б 30 хвилин нудного ручного налаштування середовища та тестування, цикл автоматизації робить за 4 секунди.

Обробка комітів, які неможливо протестувати (Exit 125)

Розділ «Обробка комітів, які неможливо протестувати (Exit 125)»

Зупиніться та подумайте: Якщо ви використовуєте git bisect run make test для пошуку регресії, а сам Makefile був тимчасово зламаний молодшим розробником посеред вашої історії (компіляція взагалі не проходить), що станеться з вашим алгоритмом бісекції?

Відповідь: Якщо make test не спрацює через грубу помилку компіляції, яка не пов’язана з логічним багом, який ви шукаєте, скрипт завершиться з ненульовим кодом. Git наосліп позначить цей коміт як поганий (bad) для бага, який ви відстежуєте! Цей хибнопозитивний результат повністю зіб’є бінарний пошук, привівши вас до неправильного винуватця.

Щоб створити надійний скрипт для bisect, ви повинні розрізняти виникнення бага та неможливість запустити тест взагалі. Саме тут код завершення 125 рятує ситуацію.

/tmp/robust-test.sh
#!/usr/bin/env bash
# Крок 1: Спроба скомпілювати бінарний файл
make build
if [ $? -ne 0 ]; then
echo "Компіляція не вдалася! Цей коміт неможливо протестувати."
# Exit 125 повідомляє git bisect: "Пропусти цей коміт і знайди іншу середину"
exit 125
fi
# Крок 2: Запуск безпосереднього тесту для бага
./bin/app-tester --run-integration
if [ $? -eq 0 ]; then
exit 0 # Good
else
exit 1 # Bad
fi

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

Розділ 3: Археолог коду: git blame поза основами

Розділ «Розділ 3: Археолог коду: git blame поза основами»

Знайти конкретний коміт, який зламав систему — це часто лише половина справи. Після того, як ви знайдете проблемні рядки коду, вам потрібен контекст. Вам потрібно зрозуміти, чому було зроблено зміну. Це була проста друкарська помилка? Фундаментальне нерозуміння архітектури системи? Чи це була свідома, продумана зміна для підтримки нової фічі, яка, на жаль, викликала небажані побічні ефекти?

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

Перевантажений стандартний Blame

Розділ «Перевантажений стандартний Blame»

Запуск звичайного git blame на великому файлі еквівалентний спробі напитися з пожежного гідранта:

Terminal window
git blame k8s/deployment.yaml

Вивід:

^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 рядків)

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

Цільовий Blame: Діапазони рядків (-L)

Розділ «Цільовий Blame: Діапазони рядків (-L)»

Якщо ви знаєте, що підозрілий баг знаходиться в рядках з 45 по 50, агресивно обмежте вивід:

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

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

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

Це каже Git: “Знайди у файлі рядок, що відповідає регулярному виразу /resources:/, і виведи анотації для цього рядка та 10 рядків, що йдуть одразу за ним”.

Ігнорування шуму форматування (-w та .git-blame-ignore-revs)

Розділ «Ігнорування шуму форматування (-w та .git-blame-ignore-revs)»

Часте розчарування викликає запуск git blame лише для того, щоб виявити, що автором кожного рядка у файлі є CI-бот, який запустив форматувальник коду (наприклад, prettier або gofmt), повністю приховуючи інженерів, які насправді писали логіку.

Щоб пробитися крізь зміни, що стосуються лише пробілів, використовуйте прапор -w:

Terminal window
git blame -w k8s/deployment.yaml

Git ігноруватиме коміти, які просто замінювали пробіли на табуляцію, змінювали відступи або видаляли пробіли в кінці рядків, і зазирне далі в історію, щоб знайти коміт, який вніс реальні текстові символи.

Для масових змін форматування в межах усього репозиторію (наприклад, ваша команда вирішила перевести всю кодову базу з 2-х на 4-х пробільні відступи) прапора -w недостатньо. Ви можете явно наказати Git повністю ігнорувати певні коміти під час аналізу blame, використовуючи файл ігнорування:

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

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

Відстеження переміщення коду (-C)

Розділ «Відстеження переміщення коду (-C)»

Ось де git blame перетворюється з базового інструменту на засіб просунутої криміналістики. Часто коміт, на який вказує стандартний git blame, не є тим комітом, де код був насправді написаний. Це може бути просто коміт, де інженер перемістив код з одного файлу в інший або розділив монолітний файл на менші модулі.

Якщо Аліса написала блискучий, складний шаблон Helm у helpers.tpl шість місяців тому, а Боб пізніше перемістив цей самий блок шаблону в новий файл _ingress.tpl під час рефакторингу, стандартний git blame на _ingress.tpl помилково заявить, що Боб написав кожен рядок. Це марно, якщо вам потрібно запитати початкового автора (Алісу), чому існує певна незрозуміла логічна конструкція.

Тут на допомогу приходить прапор -C (Copy/Movement).

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

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

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

Terminal window
# Стандартний blame показує коміт рефакторингу:
c8d7e6f5 (Bob 2024-03-01 10:00:00 -0400 12) {{ include "mychart.labels" . | nindent 4 }}
# Blame з -C -C прориває завісу, щоб знайти справжнього автора:
a1b2c3d4 (Alice 2023-11-15 09:15:00 -0400 12) {{ include "mychart.labels" . | nindent 4 }}

Зверніть увагу, як просунутий blame правильно приписує рядок оригінальному творінню Аліси, дивлячись крізь організаційний коміт Боба.

Який підхід ви б обрали тут і чому? Ви проводите аудит критичного блоку securityContext у маніфесті Pod. Блок виглядає дуже підозріло та небезпечно. Стандартний git blame каже, що “Jenkins CI User” останнім торкався цих рядків. Ви перевіряєте коміт Jenkins, і це було автоматичне завдання, яке конвертувало всі YAML-файли з 4 пробілів на 2. Як знайти людину, яка насправді написала небезпечний блок?

Відповідь: Ви повинні поєднати прапори. Ви використовуєте git blame -w (який явно ігнорує зміни пробілів) у поєднанні з -C (на випадок, якщо блок також був переміщений). Коміт форматування змінив лише пробіли, тому -w зазирне крізь нього на базову зміну тексту.

Розділ 4: Пошук “Pickaxe”: git log -S та -G

Розділ «Розділ 4: Пошук “Pickaxe”: git log -S та -G»

Іноді у вас немає зламаного файлу для аналізу за допомогою blame. Іноді значення конфігурації просто зникло з кодової бази, і вам потрібно знати точно коли, чому і хто санкціонував видалення.

Уявіть, що вашому застосунку потрібна змінна середовища DB_MAX_CONNECTIONS=100. Ви перевіряєте поточний deployment.yaml, а змінна повністю відсутня. Оскільки рядки видалено, git blame марний (він може анотувати лише рядки, які фізично існують у поточному файлі).

Зупиніться та подумайте: Ви запускаєте git grep для пошуку видаленої змінної середовища і нічого не отримуєте. Чому?

Відповідь: git grep шукає виключно у файлах, як вони існують у вашому поточному робочому каталозі (snapshot). Оскільки змінна була видалена в попередньому коміті, її фізично більше не існує для grep. Вам потрібен інструмент, який шукає в історичних диффах, а не в поточних файлах.

Щоб знайти код-привид, вам потрібен “Pickaxe” (Кайло) від Git.

Прапор -S (Додавання/Видалення рядка)

Розділ «Прапор -S (Додавання/Видалення рядка)»

Команда git log -S не шукає файли, що зараз знаходяться на вашому диску. Вона шукає в історичних диффах (патчах) усіх комітів у репозиторії. Вона шукає саме ті коміти, де змінилася кількість входжень рядка — це означає, що рядок був остаточно доданий до кодової бази або видалений з неї.

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

Вивід:

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

Ви миттєво знайшли коміт (f9a8b7c), який видалив значення. Тепер ви можете переглянути його за допомогою git show f9a8b7c, щоб побачити, хто саме його видалив, прочитати повідомлення до коміту та вивчити контекст Pull Request, щоб зрозуміти причину.

Прапор -G (Пошук за регулярним виразом у диффах)

Розділ «Прапор -G (Пошук за регулярним виразом у диффах)»

Хоча -S суворий і шукає точне додавання або видалення рядка, прапор -G використовує регулярні вирази для пошуку в історичних диффах. Це критично важливо, коли ви шукаєте варіації конфігурації.

Якщо ви хочете дізнатися, коли будь-які limits CPU історично змінювалися у ваших маніфестах, пошук статичного рядка не спрацює, бо значення коливаються (наприклад, 100m, 500m). Ви повинні шукати в диффах за шаблоном регулярного виразу cpu:\s*[0-9]+m:

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

(Додавання прапора -p або --patch — це професійна порада. Це змушує Git не просто вивести хеші комітів, а одразу показати реальний дифф для відповідних комітів, дозволяючи вам візуально перевірити зміни без запуску додаткових команд git show).

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

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
limits:
cpu: 500m

Матриця порівняння: Pickaxe проти Snapshot-пошуку

Розділ «Матриця порівняння: Pickaxe проти Snapshot-пошуку»

Критично важливо засвоїти концептуальну різницю між запитами Pickaxe (git log -S) та запитами по знімках (git grep).

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

Якщо ключ API був жорстко закодований у маніфесті, зафіксований у репозиторії, а потім видалений у наступному коміті через два дні, git grep "API_KEY" нічого не знайде (бо поточний каталог чистий). Однак git log -S "API_KEY" чітко вкаже на коміт, де він був доданий, і на коміт, де його видалили, виявляючи приховане порушення безпеки в постійній пам’яті вашого репозиторію.

Розділ 5: Високошвидкісне сканування: git grep

Розділ «Розділ 5: Високошвидкісне сканування: git grep»

Якщо ви дійсно шукаєте щось, що зараз існує у вашому знімку репозиторію, вам варто повністю відмовитися від стандартної команди Linux grep і використовувати виключно git grep.

Чому git grep перевершує стандартний grep

Розділ «Чому git grep перевершує стандартний grep»
  1. Неперевершена швидкість: git grep експоненціально швидший, бо він шукає лише файли, що відстежуються Git. Стандартний grep -rn витрачатиме величезну кількість ресурсів вводу-виводу на сліпий перегляд об’єктів .git/, глибоких папок node_modules/, середовищ venv/ та скомпільованих бінарних файлів, які вас не цікавлять.
  2. Контекстна обізнаність: Він внутрішньо розуміє структуру вашого репозиторію та поважає .gitignore.
  3. Сканування з “подорожами в часі”: Це його суперсила. Ви можете використовувати git grep для пошуку в усій кодовій базі в тому вигляді, в якому вона існувала в будь-якому історичному коміті, тегу чи віддаленій гілці, без необхідності виконувати повільний git checkout.

Пошук в альтернативних вимірах (гілках)

Розділ «Пошук в альтернативних вимірах (гілках)»

Який інструмент ви б обрали?: Як би ви шукали конкретну конфігурацію в гілці колеги, не перемикаючись на неї і не ховаючи (stash) свою поточну незавершену роботу?

Відповідь: Ви додаєте назву віддаленої гілки до git grep. Оскільки git grep нативно читає внутрішні об’єкти дерева Git, запуск git grep "search-term" origin/their-branch шукає в їхньому знімку прямо з вашого поточного робочого каталогу.

Припустимо, колега пише в Slack: “Я додав маніфест PodDisruptionBudget у свою гілку з фічею, але не пам’ятаю як назвав файл, мені потрібно, щоб ти його переглянув”.

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

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

Вивід:

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

Ви миттєво отримуєте точний шлях до файлу, не торкаючись свого робочого каталогу.

Складні логічні запити

Розділ «Складні логічні запити»

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

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

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

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

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

Розділ 6: Реконструкція аудиторських слідів: Фільтрування історії

Розділ «Розділ 6: Реконструкція аудиторських слідів: Фільтрування історії»

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

Команда git log містить дуже специфічні параметри фільтрації, які діють як SQL-запити до бази даних вашої історії інфраструктури.

Фільтрування за часом та автором

Розділ «Фільтрування за часом та автором»

Щоб знайти всі зміни конфігурації, зроблені конкретним підрядником з початку місяця:

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

Фільтрування за шляхом до файлу

Розділ «Фільтрування за шляхом до файлу»

Якщо конкретний файл config/settings.yaml викликає проблеми, вас не цікавлять 5000 комітів, що змінюють код застосунку на Go. Вас цікавлять лише коміти, які явно змінили цей єдиний YAML-файл.

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

(Подвійне тире -- — це важлива конвенція Git. Воно явно повідомляє Git: “Досить розбирати прапори командного рядка. Все, що йде після цього подвійного тире — це буквальний шлях до файлу, а не назва гілки чи тега”).

Створення аудиторського звіту для керівництва

Розділ «Створення аудиторського звіту для керівництва»

Вас попросили надати повний звіт про всі зміни, внесені до маніфестів Kubernetes для продуктивного середовища (k8s/prod/) під час дії заборони на зміни (change-freeze) на святковий період (з 20 грудня по 2 січня). Вам потрібно знати не лише повідомлення до коміту, а й те, які саме файли були змінені.

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

Прапор --name-status змінює вивід. Замість того, щоб показувати лише повідомлення коміту, він додає детальний список файлів, які були змінені (Modified - M), додані (Added - A) або видалені (Deleted - D) у цьому конкретному коміті.

Вивід:

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

Форматування для CSV або автоматизації

Розділ «Форматування для CSV або автоматизації»

Якщо вам потрібно експортувати ці дані в систему SIEM (Security Information and Event Management) або електронну таблицю CSV для аудитора, використовуйте --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

Опанувавши ці цільові фільтри та опції форматування, ви перетворюєте Git з пасивного сховища коду на активну базу даних стану інфраструктури, що піддається ретельному аудиту.

  1. Походження назви “Pickaxe”: Прапор -S розмовно відомий як пошук “Pickaxe” (Кайло), тому що розробники Git спочатку задумали його як інструмент для “видобутку” глибоко в осадових шарах історії репозиторію, щоб знайти саме те місце, де конкретний блок коду був похований або витягнутий на світ.
  2. git bisect має графічний вигляд: Якщо ви перебуваєте в середині складної бісекції, втратили нитку роздумів і заплуталися у своїх межах, запуск git bisect visualize (або git bisect view) миттєво відкриє графічний переглядач Git (наприклад, gitk), що показує, які саме коміти позначені як хороші, погані та які залишилося протестувати.
  3. Ви можете шукати винуватця (blame) назад у часі: Хоча стандартний git blame знаходить походження (додавання) рядка, ви можете використовувати git blame --reverse START..END file, щоб знайти точний коміт, де рядок був остаточно видалений або замінений у межах певного діапазону ревізій.
  4. Git інтелектуально пропускає порожні коміти: Якщо коміт у вашому автоматизованому діапазоні git bisect призвів до порожнього диффа (можливо, коміт злиття, який не вирішив жодних конфліктів), git bisect математично достатньо розумний, щоб взагалі пропустити його тестування, оскільки порожній коміт не може внести нову логічну помилку в кодову базу.
ПомилкаЧому це трапляєтьсяЯк виправити
Забування про git bisect resetПісля знаходження бага інженери святкують і забувають, що вони залишилися в стані “detached HEAD” глибоко в минулому.Завжди виконуйте git bisect reset одразу після завершення розслідування, щоб повернутися на свою початкову гілку.
Тестування в “брудному” робочому деревіСпроба розпочати git bisect start, маючи незафіксовані (uncommitted) зміни у робочому каталозі.Git відмовиться перемикати середини, щоб захистити вашу незбережену роботу. Запустіть git stash, щоб зберегти зміни перед початком бісекції.
Використання git grep для пошуку видаленого кодуНерозуміння того, що git grep шукає лише в поточному стані дерева. Він не може знайти те, чого вже немає.Якщо ви шукаєте код, який був видалений історично, ви повинні змінити інструмент і використовувати Pickaxe (git log -S або -G).
Довіра до blame у переформатованих файлахЗапуск таких інструментів, як prettier або gofmt, перезаписує кожен рядок, роблячи автоматичний форматувальник “автором” усього файлу.Використовуйте git blame -w, щоб агресивно ігнорувати зміни пробілів, і поєднуйте це з -C, щоб відстежувати рух коду поза простими правками.
Написання скриптів bisect, які завершуються з кодом 1 при помилках компіляціїТест-скрипт не спрацьовує через непов’язану помилку інфраструктури або відсутній пакет, позначаючи коміт як “поганий” для конкретного бага, який ви шукаєте.Переконайтеся, що ваш скрипт розрізняє конкретний баг (вихід 1) та середовище, яке неможливо протестувати (вихід 125).
Аналіз (blame) не тієї гілкиЗапуск git blame під час перебування на застарілій гілці з фічею, де відсутні нещодавні виправлення.Завжди переконуйтеся, що ви перебуваєте на правильній цільовій гілці (наприклад, main), або явно вказуйте гілку: git blame main -- file.txt.

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

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

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

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

Відповідь: Використовуйте пошук Pickaxe: git log -S "рядок-вашого-конкретного-патчу". Оскільки git log -S шукає в диффах коміти, де рядок був або доданий, або видалений, він розкриє всю хронологію. Якщо вивід показує три різні коміти (оригінальне додавання, коміт видалення та нещодавній коміт повторного додавання), у вас є незаперечні докази того, що патч був тимчасово скасований.

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

Відповідь: Виконайте git blame -C k8s/statefulset.yaml. Прапор -C явно вказує Git на необхідність виявлення руху коду та копіювання між файлами в межах одного коміту. Git зазирне крізь коміт Сема з переміщенням файлу і анотує рядки справжнім автором, який написав конфігурацію сховища в каталозі infra/ місяці тому.

Запитання 4: Ваш продуктивний API повертає 500 помилок. Ви знаєте, що він працював стабільно на тегу v2.0 і зламаний на HEAD. Між ними 800 комітів. Ви хочете автоматизувати пошук бага. Ви пишете скрипт check-api.sh, який робить curl до ендпоінту https://api.production.com/health. Ви запускаєте git bisect start HEAD v2.0, а потім git bisect run ./check-api.sh. Чому цей підхід фундаментально провалиться?

Відповідь: Бісекція покладається на перевірку історичного коду локально. Скрипт check-api.sh робить curl до працюючого продуктивного API, на який абсолютно не впливає те, що ваш локальний Git-репозиторій перемикається на старі коміти. Ваш локальний стан Git не розгортається автоматично і миттєво в продакшн на кожному кроці бісекції. Щоб автоматизувати це, ваш скрипт повинен збирати і запускати застосунок локально (або розгортати його в локальний ефемерний кластер, наприклад Kind), виходячи з поточного перевіреного коміту, а потім робити curl до цього локального ендпоінту.

Запитання 5: Вам потрібно знайти забутий AWS API-ключ (AKIAIOSFODNN7EXAMPLE), який хтось випадково зафіксував у репозиторії місяці тому і пізніше видалив, щоб приховати сліди. Ви запускаєте git grep "AKIAIOSFODNN7EXAMPLE". Команда нічого не повертає. Чому, і яка правильна команда для пошуку витоку?

Відповідь: git grep шукає лише у файлах, як вони існують у поточному знімку (робочому каталозі). Оскільки ключ був видалений у пізнішому коміті, його фізично немає в поточних файлах. Ви повинні використовувати git log -S "AKIAIOSFODNN7EXAMPLE", щоб здійснити пошук в історичних диффах усього репозиторію, аби знайти точний коміт, де ключ був спочатку доданий.

Запитання 6: Ви проводите аудит заплутаної історії комітів. Ви хочете витягнути всі коміти, зроблені командою "platform-team", які спеціально змінювали файли всередині каталогу k8s/networking/, і вам обов'язково потрібно бачити, які саме файли були змінені (додані, змінені, видалені) у кожному коміті. Яка точна команда дозволить це зробити?

Відповідь: Ви повинні виконати git log --author="platform-team" --name-status -- k8s/networking/. Прапор --author фільтрує за ім’ям автора коміту, прапор --name-status змінює вивід для відображення списку змінених файлів у кожному коміті, а подвійне тире --, за яким слідує шлях до каталогу, суворо обмежує пошук лише комітами, що змінили цю конкретну папку з мережевими налаштуваннями.

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

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

У цій вправі ви створите новий репозиторій зі змодельованою історією, навмисно внесете ледь помітну помилку, сховаєте її під десятками нерелевантних комітів, а потім використаєте git bisect та git blame, щоб розкрити цю справу.

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

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

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

Terminal window
mkdir k8s-forensics-lab && cd k8s-forensics-lab
git init
# Створюємо початковий маніфест, що ідеально працює
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.21
ports:
- containerPort: 80
EOF
git add deployment.yaml
git commit -m "Initial commit: working deployment"
git tag v1.0
# Симулюємо 20 хороших, нешкідливих комітів
for i in {1..20}; do
echo "# Comment $i" >> deployment.yaml
git commit -am "chore: minor update $i"
done
# ВНОСИМО БАГ (Помилка в 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"
# Симулюємо ще 30 комітів, що повністю ховають баг
for i in {21..50}; do
echo "# Comment $i" >> deployment.yaml
git commit -am "chore: minor update $i"
done

Ваш репозиторій тепер містить понад 50 комітів. Поточний deployment.yaml має критичну помилку (containerPort: 8080 замість 80, що ламає стандартну внутрішню маршрутизацію Nginx), але вона прихована глибоко в історії Git.

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

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

Перевірте поточний файл, щоб підтвердити наявність зламаного стану в HEAD.

Terminal window
cat deployment.yaml | grep containerPort

Очікуваний результат: containerPort: 8080 (це неправильно, має бути 80).

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

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

Ми автоматизуємо пошук. Ми точно знаємо, що containerPort: 80 — це правильний стан. Ми напишемо скрипт, який явно перевіряє правильну конфігурацію порту.

Створіть файл із назвою /tmp/test-port.sh:

/tmp/test-port.sh
#!/usr/bin/env bash
# Перевіряємо, чи містить розгортання точний рядок 'containerPort: 80'
# Символ '$' прив'язує пошук до кінця рядка, забезпечуючи відмову для '8080'.
grep "containerPort: 80$" deployment.yaml > /dev/null
# grep виходить з кодом 0, якщо рядок знайдено (Good commit), 1 - якщо ні (Bad commit)
exit $?

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

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

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

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

Тепер накажіть Git алгоритмічно вистежити помилку.

  • Ініціалізуйте процес bisect: 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: Очищення та дослідження коду»
  • Завершіть криміналістичну сесію та поверніться до реальності: git bisect reset
  • Тепер у вас є повідомлення коміту: "fix: adjust port configuration for new ingress". Припустимо, ви хочете побачити, хто саме вніс цю зміну в самому рядку. Використовуйте цільовий git blame для рядка з портом.
Terminal window
git blame -L '/containerPort/',+1 deployment.yaml
Пояснення рішення

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

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