#victorialogs #victoriametrics #grafana #troubleshooting
Самобичевание, часть 2 из 2.
Особенности Grafana, о которых я забыл
Потом всплыли нюансы самой Grafana.
В плагине VictoriaLogs для Grafana (victoriametrics-logs-datasource) есть несколько queryType:
- Raw Logs - для логовой панели (лента логов, tailing)
- Range - для time series и графиков по времени, под капотом ходит в /select/logsql/stats_query_range
- Stats - для агрегатов без временной развёртки (одно число/набор чисел по фильтру)
И вот тут началась жесть с выбором правильного queryType:
- queryType: "range" - для time series графиков
- queryType: "stats" - для агрегаций через | stats count() as total
- queryType: "logs" - для сырых логов
Где-то нужен range-запрос, где-то stats.
Где-то метрика уже доступна как число в отдельном поле, а где-то её нужно извлекать из JSON.
И если ошибёшься с queryType - панель просто пустая, без ошибок.
Сидишь и гадаешь: запрос кривой или тип панели неправильный?😭
Разница между _stream_fields (лейблы потока, быстрая фильтрация) и обычными полями лога оказалась критичной:
- фильтровать по stream field - быстро и дёшево
- по log field - медленнее, особенно если JSON-парсинг выполняется в запросе
Выводы и самоанализ
Когда я осмотрел свои коммиты, увидел десятки итераций:
- сначала делал парсинг в запросах через unpack_json, потом переключился на Grafana transformations, потом понял - лучше хранить поля структурированно при ingestion
- менял queryType с range на stats и обратно, потому что панели то пустые, то не те данные
- возился с мульти-тенантностью: настраивал лейблы типа tenant_id в запросах и флаги изоляции
- оптимизировал запросы со stats count() as total чтобы не гонять по всей базе полнотекстовый поиск
- добавлял фильтры по stream:"stderr" вместо поиска по всем логам
Полная фигня, если честно. 🤡
Это сильно меня опечалило:
- либо я отстал от технологий и мои знания Prometheus/Grafana устарели и надо срочно учить весь VM стек
- либо я переоценил себя при оценке задачи
- либо это действительно сложная задача, которую я недооценил
Скорее всего, правда посередине.
VictoriaLogs - не просто замена Loki, это другой инструмент с другой философией:
- Loki индексирует только лейблы потока, текст сообщений не индексируется - regex по message сканирует содержимое
- VictoriaLogs индексирует все поля логов, так что по ним можно быстро фильтровать и искать. При этом стрим-поля через _stream_fields дополнительно помогают с компрессией и ускоряют типичные фильтры
- LogsQL ≠ LogQL - это разные языки запросов, хоть и похожие
- Высокая кардинальность (trace_id, user_id, ip) в VictoriaLogs гораздо менее болезненна, чем в Loki, если хранить такие вещи как обычные поля, а не stream fields. Loki сильно страдает, если тащить user_id/trace_id в лейблы
А миграция дашбордов в мульти-тенантной среде - это не рутина, а полноценный проект.
Во всяком случае так вышло для меня в этот раз.
И это я ещё пропускаю часть про VMrules и recording rules, которые пришлось добавить для некоторых панелей....😭
Задача - теперь для меня не сложная. Ну я максимум часа 2-3 на подобное потрачу.
Проблема в том, что я переоценил свои силы и знания (которых и не было).
В общем надо учиться и читать документацию, прежде, чем приступать к задаче.
Самобичевание, часть 2 из 2.
Особенности Grafana, о которых я забыл
Потом всплыли нюансы самой Grafana.
В плагине VictoriaLogs для Grafana (victoriametrics-logs-datasource) есть несколько queryType:
- Raw Logs - для логовой панели (лента логов, tailing)
- Range - для time series и графиков по времени, под капотом ходит в /select/logsql/stats_query_range
- Stats - для агрегатов без временной развёртки (одно число/набор чисел по фильтру)
И вот тут началась жесть с выбором правильного queryType:
- queryType: "range" - для time series графиков
- queryType: "stats" - для агрегаций через | stats count() as total
- queryType: "logs" - для сырых логов
Где-то нужен range-запрос, где-то stats.
Где-то метрика уже доступна как число в отдельном поле, а где-то её нужно извлекать из JSON.
И если ошибёшься с queryType - панель просто пустая, без ошибок.
Сидишь и гадаешь: запрос кривой или тип панели неправильный?
Разница между _stream_fields (лейблы потока, быстрая фильтрация) и обычными полями лога оказалась критичной:
- фильтровать по stream field - быстро и дёшево
- по log field - медленнее, особенно если JSON-парсинг выполняется в запросе
Выводы и самоанализ
Когда я осмотрел свои коммиты, увидел десятки итераций:
- сначала делал парсинг в запросах через unpack_json, потом переключился на Grafana transformations, потом понял - лучше хранить поля структурированно при ingestion
- менял queryType с range на stats и обратно, потому что панели то пустые, то не те данные
- возился с мульти-тенантностью: настраивал лейблы типа tenant_id в запросах и флаги изоляции
- оптимизировал запросы со stats count() as total чтобы не гонять по всей базе полнотекстовый поиск
- добавлял фильтры по stream:"stderr" вместо поиска по всем логам
Полная фигня, если честно. 🤡
Это сильно меня опечалило:
- либо я отстал от технологий и мои знания Prometheus/Grafana устарели и надо срочно учить весь VM стек
- либо я переоценил себя при оценке задачи
- либо это действительно сложная задача, которую я недооценил
Скорее всего, правда посередине.
VictoriaLogs - не просто замена Loki, это другой инструмент с другой философией:
- Loki индексирует только лейблы потока, текст сообщений не индексируется - regex по message сканирует содержимое
- VictoriaLogs индексирует все поля логов, так что по ним можно быстро фильтровать и искать. При этом стрим-поля через _stream_fields дополнительно помогают с компрессией и ускоряют типичные фильтры
- LogsQL ≠ LogQL - это разные языки запросов, хоть и похожие
- Высокая кардинальность (trace_id, user_id, ip) в VictoriaLogs гораздо менее болезненна, чем в Loki, если хранить такие вещи как обычные поля, а не stream fields. Loki сильно страдает, если тащить user_id/trace_id в лейблы
А миграция дашбордов в мульти-тенантной среде - это не рутина, а полноценный проект.
Во всяком случае так вышло для меня в этот раз.
И это я ещё пропускаю часть про VMrules и recording rules, которые пришлось добавить для некоторых панелей....
Задача - теперь для меня не сложная. Ну я максимум часа 2-3 на подобное потрачу.
Проблема в том, что я переоценил свои силы и знания (которых и не было).
В общем надо учиться и читать документацию, прежде, чем приступать к задаче.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🔥1
#kubernetes #мысли
Сижу, разбираюсь с
- https://docs.crossplane.io/latest/
- https://marketplace.upbound.io/providers (да, мне понравились upbound)
Надо было кое-что найти, а обычный лист выдаёт сотни ответов без грепа.
Глянул сколько у нас всего кастом кайндов, а там..
Ах этот безумный-безумный мир.
513
498
В наших кластерах CRD станет скоро больше, чем базовых сущностей кубернетиса.
Безумие, абсолютное безумие.
В целом можно на собеседовании/в баре с коллегами по девопс-цеху меряться:
- У нас в кластере 498 CRD, а сколько у вас?😁
Скоро это будет показывать насколько зрелый проект, мощная команда и глубокое погружение в экосистему кубера(надеюсь нет).
Очередной раз не завидую всем молодым специалистам, кому надо будет учить этот кубер.
Сижу, разбираюсь с
Crossplane и провайдерами для Azure.- https://docs.crossplane.io/latest/
- https://marketplace.upbound.io/providers (да, мне понравились upbound)
Надо было кое-что найти, а обычный лист выдаёт сотни ответов без грепа.
Глянул сколько у нас всего кастом кайндов, а там..
Ах этот безумный-безумный мир.
kubectl api-resources --verbs=list | grep -v customresourcedefinition | wc -l
513
kubectl get crd -o name | wc -l
498
В наших кластерах CRD станет скоро больше, чем базовых сущностей кубернетиса.
Безумие, абсолютное безумие.
В целом можно на собеседовании/в баре с коллегами по девопс-цеху меряться:
- У нас в кластере 498 CRD, а сколько у вас?
Скоро это будет показывать насколько зрелый проект, мощная команда и глубокое погружение в экосистему кубера
Очередной раз не завидую всем молодым специалистам, кому надо будет учить этот кубер.
Please open Telegram to view this post
VIEW IN TELEGRAM
💯12😱6😁1
1.png
431.8 KB
#devops #git
Никакого рокетсайнса или, прости господи, лайфхаков, просто делюсь наблюдением.
Последние месяцы для работы со схемами/диаграммами вместо привычных ресурсов (draw.io excalidraw.com diagrams.mingrammer.com) перешёл на связку:
- любой AI ассистент, любая модель
- markdown файл https://www.markdownguide.org/
- mermaid фреймворк https://mermaid.js.org/
Поддержка mermaid в MD файлах уже достаточно давно, а основные инструменты - VSCode, GitHub, GitLab, Cursor умеют это отображать для человека и в IDE, и в веб-браузере.
В некоторых случаях надо ставить плагин, но в некоторых уже работает и так.
Как это работает:
- я прошу ассистента нарисовать некую схему. Схема может быть обычная flowchart, может быть state, sequence или что-то другое
- в основном README.md или CLAUDE.md появляется блок кода, который отлично лежит в md файле и при этом
- - схема видна в IDE через preview режим
- - схема видна в браузере на страницах GitHub/GitLab
- - схема понятна любой нейронке
- самое главное, это прозрачно и для меня и нейронка понимает что хочет, чтобы изменение кода было корректным
- ну и всё это в git репозитории
Рандомный пример промпта:
Рандомный пример кода (в файлах формата
Результат на скриншоте (GitHub + Cursor IDE).
Сами промпт/схема просто рандомно-мусорная, лишь для примера.
Можно рисовать что угодно:
- схема инфры
- как работает хэндшейки бэкенда
- логика распределения подов по нод группам в кубере
- как работает самописный оператор
- sequence diagram, как микросервисы общаются при отказе базы
и так далее.
Связка мне нравится, я думаю многие это используют.
Если ещё не пробовали - начните.
Мы итак многие инженерные вещи переусложнили.
Хочется хотя бы с диаграммами и схемами сделать всё проще.
Никакого рокетсайнса или, прости господи, лайфхаков, просто делюсь наблюдением.
Последние месяцы для работы со схемами/диаграммами вместо привычных ресурсов (draw.io excalidraw.com diagrams.mingrammer.com) перешёл на связку:
- любой AI ассистент, любая модель
- markdown файл https://www.markdownguide.org/
- mermaid фреймворк https://mermaid.js.org/
Поддержка mermaid в MD файлах уже достаточно давно, а основные инструменты - VSCode, GitHub, GitLab, Cursor умеют это отображать для человека и в IDE, и в веб-браузере.
В некоторых случаях надо ставить плагин, но в некоторых уже работает и так.
Как это работает:
- я прошу ассистента нарисовать некую схему. Схема может быть обычная flowchart, может быть state, sequence или что-то другое
- в основном README.md или CLAUDE.md появляется блок кода, который отлично лежит в md файле и при этом
- - схема видна в IDE через preview режим
- - схема видна в браузере на страницах GitHub/GitLab
- - схема понятна любой нейронке
- самое главное, это прозрачно и для меня и нейронка понимает что хочет, чтобы изменение кода было корректным
- ну и всё это в git репозитории
Рандомный пример промпта:
Нарисуй flowchart в mermaid, который показывает, как Pod с toleration scheduling'ится на tainted node в Kubernetes
Рандомный пример кода (в файлах формата
*.md)graph TB
subgraph "Node Pool"
N1[Node 1<br/>No Taint]
N2[Node 2<br/>Taint: dedicated=app:NoSchedule]
N3[Node 3<br/>Taint: dedicated=app:NoSchedule]
end
subgraph "Pods"
P1[Pod A<br/>No Toleration]
P2[Pod B<br/>Toleration: dedicated=app]
P3[Pod C<br/>No Toleration]
end
P1 -->|Can Schedule| N1
P1 -.->|Cannot Schedule| N2
P1 -.->|Cannot Schedule| N3
P2 -->|Can Schedule| N1
P2 -->|Can Schedule| N2
P2 -->|Can Schedule| N3
P3 -->|Can Schedule| N1
P3 -.->|Cannot Schedule| N2
P3 -.->|Cannot Schedule| N3
style N1 fill:#E8F5E9,stroke:#4CAF50,stroke-width:2px,color:#1B5E20
style N2 fill:#FCE4EC,stroke:#E91E63,stroke-width:2px,color:#880E4F
style N3 fill:#FCE4EC,stroke:#E91E63,stroke-width:2px,color:#880E4F
style P1 fill:#E3F2FD,stroke:#2196F3,stroke-width:2px,color:#0D47A1
style P2 fill:#E3F2FD,stroke:#2196F3,stroke-width:2px,color:#0D47A1
style P3 fill:#E3F2FD,stroke:#2196F3,stroke-width:2px,color:#0D47A1
Результат на скриншоте (GitHub + Cursor IDE).
Сами промпт/схема просто рандомно-мусорная, лишь для примера.
Можно рисовать что угодно:
- схема инфры
- как работает хэндшейки бэкенда
- логика распределения подов по нод группам в кубере
- как работает самописный оператор
- sequence diagram, как микросервисы общаются при отказе базы
и так далее.
Связка мне нравится, я думаю многие это используют.
Если ещё не пробовали - начните.
Мы итак многие инженерные вещи переусложнили.
Хочется хотя бы с диаграммами и схемами сделать всё проще.
5👍28❤6
#longread #grafana #kubernetes #troubleshooting #одинденьизжизни
А давненько не было лонгридов.
Три дашборда на границе Grafana
Часть 1 из 3.
У нас был дашборд
Пришло время мигрировать на VictoriaLogs.
Чтобы не ломать продакшен, я сделал всё по уму: создал копию дашборда с новым UID
Пару недель работал над ним параллельно с основным.
Переписывал запросы, проверял, что данные сходятся.
Всё работало.
Старый дашборд на Loki, новый на VictoriaLogs - оба живут рядом, никому не мешают.
Когда draft был готов, убрал из названия "Draft", поменял UID на
Ещё через неделю решили, что суффикс
Меняю в JSON:
на
И UID с
Коммит, пуш, ArgoCD синкается.
Зелёненькое. Красота.
Иду пить чай.
А, нет, не иду, коллеги пишут, что многое не работает, ссылки ведут на старую/поломанную борду.
Иду в Grafana проверить.
А там старый дашборд. С "Draft" в названии.
И рядом ещё какие-то СТАРЫЕ версии.
И переменная
Эээ, а я вообще смеержил в main ветку?
Да, смержил, вижу новый код в git.
Первая мысль - может ArgoCD не синканул?
Всё синхронизировано.
Статус: Healthy, Synced.
Странно.
Может ConfigMap в кубернетисе не обновился?
Смотрю - там новый JSON.
Без "Draft". Всё верно.
Ничего старого.
Может в поде Grafana старый файл закэшировался?
Нет, файл тоже новый. UID правильный. Title без "Draft".
Может другие файлы рядом лежат и он их синкает?
Все файлы внутри пода проверил - не, только новые тут.
Проверил во второй реплике POD Grafana -так же только новые дашборды.
Бред.
Чего имеем:
- в гите есть изменения, старых дашбордов нет
- ConfigMap правильный
- файл в поде правильный
а в UI - старое.
Что-то не сходится.
Что я упускаю?
А давненько не было лонгридов.
Три дашборда на границе Grafana
Часть 1 из 3.
У нас был дашборд
service-metrics-overview, который работал с Loki. Пришло время мигрировать на VictoriaLogs.
Чтобы не ломать продакшен, я сделал всё по уму: создал копию дашборда с новым UID
service-metrics-overview-vl-draft и пометкой "[VictoriaLogs Draft]" в названии.Пару недель работал над ним параллельно с основным.
Переписывал запросы, проверял, что данные сходятся.
Всё работало.
Старый дашборд на Loki, новый на VictoriaLogs - оба живут рядом, никому не мешают.
Когда draft был готов, убрал из названия "Draft", поменял UID на
service-metrics-overview-vl. Ещё через неделю решили, что суффикс
-vl не нужен - пусть будет просто service-metrics-overview, как раньше. Старый Loki-дашборд к тому моменту уже удалили.Меняю в JSON:
"title": "Service Metrics Overview [VictoriaLogs Draft]"
на
"title": "Service Metrics Overview [VictoriaLogs]"
И UID с
service-metrics-overview-vl-draft на service-metrics-overview.Коммит, пуш, ArgoCD синкается.
Зелёненькое. Красота.
Иду пить чай.
А, нет, не иду, коллеги пишут, что многое не работает, ссылки ведут на старую/поломанную борду.
Иду в Grafana проверить.
А там старый дашборд. С "Draft" в названии.
И рядом ещё какие-то СТАРЫЕ версии.
И переменная
loki висит, хотя я её точно убирал.Эээ, а я вообще смеержил в main ветку?
Да, смержил, вижу новый код в git.
Первая мысль - может ArgoCD не синканул?
argocd app get dashboards --refresh
Всё синхронизировано.
Статус: Healthy, Synced.
Странно.
Может ConfigMap в кубернетисе не обновился?
kubectl get configmap -n grafana dashboards-myapp-service-metrics-common -o yaml | tail -20
Смотрю - там новый JSON.
Без "Draft". Всё верно.
Ничего старого.
Может в поде Grafana старый файл закэшировался?
kubectl exec -n grafana deployment/prod-region1-grafana -c grafana -- tail -5 /tmp/dashboards/MyApp/service-metrics-overview.json
Нет, файл тоже новый. UID правильный. Title без "Draft".
Может другие файлы рядом лежат и он их синкает?
Все файлы внутри пода проверил - не, только новые тут.
Проверил во второй реплике POD Grafana -так же только новые дашборды.
Бред.
Чего имеем:
- в гите есть изменения, старых дашбордов нет
- ConfigMap правильный
- файл в поде правильный
а в UI - старое.
Что-то не сходится.
Что я упускаю?
👍6🔥1
#longread #grafana #kubernetes #troubleshooting #одинденьизжизни
Три дашборда на границе Grafana
Часть 2 из 3.
Смотрю логи Grafana:
И вот оно:
Grafana provisioning видит файл, пытается его применить, но не может - в базе уже есть дашборд с таким UID.
Кстати в некоторых версиях Grafana логи могут не содержать сам UID.
В таком случае придётся идти в API или базу данных напрямую.
У нас графана с авторизацией по SSO, у меня права, как у девелопера.
Мне лень писать заявки на админский доступ и ковырять UI интерфейс, думаю мне хватит и curl + localhost для анализа.
Ведь я знаю, что рут пароль лежит в секретах кубера.😁
Я сам себе админ😀
Проверяю через Grafana API:
И вижу:
-
-
-
Сука, да откуда вы берётесь.
Три версии одного дашборда.
А файл у меня один (гит, конфигмап, файл внутри пода).
Тут до меня дошло: каждый раз, когда я менял UID, provisioning создавал новую запись в базе.
А старые записи никуда не девались - они просто висели мёртвым грузом.
Теперь, когда я хочу вернуть оригинальный UID
Grafana хранит дашборды в PostgreSQL. Provisioning работает так:
- видит файл с UID
- проверяет - есть ли в БД запись с таким UID?
- если есть и привязана к этому файлу - обновляет
- если есть, но привязана к другому источнику - ошибка
Это не баг, а задуманное поведение: provisioning не перезаписывает дашборды, созданные из других источников.
Лезу в базу:
Смотрю что там:
Три записи от разных этапов миграции.
Файл один.
Вот и причина.
Варианты:
1. Удалить через UI - не выйдет (я ж попробовал), "provisioned dashboard cannot be deleted"
2. Ждать - не рассосётся само
3. Рестарт пода/подов - не вариант, мне по шапке за это дадут, графана как бы на всех.
4. Принудительный reload provisioning через API:
Не помогло - ошибка та же, записи в БД мешают.
5. Удалить записи из базы напрямую🤡
Выбираю последний вариант. Сначала бэкап:
Затем удаление:
Три дашборда на границе Grafana
Часть 2 из 3.
Смотрю логи Grafana:
kubectl logs -n grafana deployment/prod-region1-grafana -c grafana --since=1h | grep -iE "provision|dashboard"
И вот оно:
{"error":"A dashboard with the same uid already exists","file":"/tmp/dashboards/MyApp/service-metrics-overview.json"}Grafana provisioning видит файл, пытается его применить, но не может - в базе уже есть дашборд с таким UID.
Кстати в некоторых версиях Grafana логи могут не содержать сам UID.
В таком случае придётся идти в API или базу данных напрямую.
У нас графана с авторизацией по SSO, у меня права, как у девелопера.
Мне лень писать заявки на админский доступ и ковырять UI интерфейс, думаю мне хватит и curl + localhost для анализа.
Ведь я знаю, что рут пароль лежит в секретах кубера.
Я сам себе админ
Проверяю через Grafana API:
GRAFANA_PASS=$(kubectl get secret -n grafana creds-grafana-admin -o jsonpath='{.data.admin-password}' | base64 -d)
kubectl exec -n grafana deployment/prod-region1-grafana -c grafana -- curl -s -u "admin:${GRAFANA_PASS}" "http://localhost:3000/api/search?query=service%20metrics"И вижу:
-
service-metrics-overview -
service-metrics-overview-vl-
service-metrics-overview-vl-draftСука, да откуда вы берётесь.
Три версии одного дашборда.
А файл у меня один (гит, конфигмап, файл внутри пода).
Тут до меня дошло: каждый раз, когда я менял UID, provisioning создавал новую запись в базе.
А старые записи никуда не девались - они просто висели мёртвым грузом.
Теперь, когда я хочу вернуть оригинальный UID
service-metrics-overview, он уже занят старой записью.Grafana хранит дашборды в PostgreSQL. Provisioning работает так:
- видит файл с UID
- проверяет - есть ли в БД запись с таким UID?
- если есть и привязана к этому файлу - обновляет
- если есть, но привязана к другому источнику - ошибка
Это не баг, а задуманное поведение: provisioning не перезаписывает дашборды, созданные из других источников.
Лезу в базу:
# ищу primary ноду (рид реплика не даст писать)
kubectl exec -n grafana pg-monitoring-1 -c postgres -- psql -U postgres -d grafana -c "SELECT pg_is_in_recovery();"
# true = replica
kubectl exec -n grafana pg-monitoring-2 -c postgres -- psql -U postgres -d grafana -c "SELECT pg_is_in_recovery();"
# false = primary
Смотрю что там:
kubectl exec -n grafana pg-monitoring-2 -c postgres -- psql -U postgres -d grafana -c "SELECT id, uid, title, version FROM dashboard WHERE uid LIKE '%service-metrics%' ORDER BY uid;"
id | uid | title | version
------+-------------------------------------+--------------------------------------------------+---------
3781 | service-metrics-overview | Service Metrics Overview | 1
3830 | service-metrics-overview-vl | Service Metrics Overview [VictoriaLogs] | 1
3825 | service-metrics-overview-vl-draft | Service Metrics Overview [VictoriaLogs Draft] | 1
Три записи от разных этапов миграции.
Файл один.
Вот и причина.
Варианты:
1. Удалить через UI - не выйдет (я ж попробовал), "provisioned dashboard cannot be deleted"
2. Ждать - не рассосётся само
3. Рестарт пода/подов - не вариант, мне по шапке за это дадут, графана как бы на всех.
4. Принудительный reload provisioning через API:
kubectl exec -n grafana deployment/prod-region1-grafana -c grafana -- curl -s -X POST -u "admin:${GRAFANA_PASS}" "http://localhost:3000/api/admin/provisioning/dashboards/reload"Не помогло - ошибка та же, записи в БД мешают.
5. Удалить записи из базы напрямую
Выбираю последний вариант. Сначала бэкап:
kubectl exec -n grafana pg-monitoring-2 -c postgres -- pg_dump -U postgres grafana > grafana_backup_$(date +%Y%m%d_%H%M%S).sql
Затем удаление:
kubectl exec -n grafana pg-monitoring-2 -c postgres -- psql -U postgres -d grafana -c "DELETE FROM dashboard WHERE uid IN ('service-metrics-overview', 'service-metrics-overview-vl', 'service-metrics-overview-vl-draft');"DELETE 3
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5
#longread #grafana #kubernetes #troubleshooting #одинденьизжизни
Три дашборда на границе Grafana
Часть 3 из 3.
Жду 30 секунд (updateIntervalSeconds в provisioning конфиге).
Проверяю:
Одна запись. Правильный title. Правильный UID.
Иду в UI - работает. Никаких дублей, никаких "Draft", никакого Loki.
Победа! 🎉
Если provisioning не срабатывает автоматически, можно вызвать reload через API (выше был пример курлом) или перезапустить pod Grafana (не мой вариант).
Подробнее про provisioning: https://grafana.com/docs/grafana/latest/administration/provisioning/
На всё ушло часа два.
Могло бы - минут пятнадцать, если бы сразу глянул в логи.
Выводы, а куда без них.
- Grafana provisioning не перезаписывает существующие записи в БД, если они созданы из другого источника. Он кидает ошибку в лог и оставляет старую версию.
- ошибка "A dashboard with the same uid already exists" - это про запись в PostgreSQL, не про файл. Файл может быть идеальным, но если в базе мусор - ничего не заработает.
- логи первым делом. Там обычно всё написано. Но не во всех версиях Grafana логи достаточно информативны.
- удаление из БД работает, но это неофициальный подход. Я не нашёл другого способа. Для provisioned dashboards безопасно - они автоматически пересоздаются из файлов. Главное - делать на primary ноде PostgreSQL и иметь бэкап.
- миграции дашбордов - это не "поменять пару строк". Особенно если UID менялся несколько раз. Особенно, если я баран.
Как избежать этой проблемы (предполагаю):
- не меняйте UID после первого деплоя. Если нужна миграция - создайте новый дашборд с новым UID, а старый удалите полностью (включая запись в БД).
- используйте стабильные UID с префиксами:
- не используйте один UID для дашбордов в разных папках - это создаёт race condition.
- документируйте миграции в changelog или README.
- используйте
Пример конфигурации провижининга
Альтернативный подход, без прямого доступа к БД - когда его нет или страшно (предполагаю, что это правильный путь, но я не проверял):
- временно переименовать ConfigMap (например добавить суффикс
- дождаться, пока Grafana удалит дашборды (если
- вернуть ConfigMap с правильным именем и новым UID
- provisioning создаст дашборды заново
Этот способ медленнее, но безопаснее.
Для зануд:
- - -
- https://github.com/grafana/grafana/issues/12411
- https://github.com/grafana/grafana/issues/41085
- https://github.com/grafana/grafana/issues/73043
Три дашборда на границе Grafana
Часть 3 из 3.
Жду 30 секунд (updateIntervalSeconds в provisioning конфиге).
Проверяю:
kubectl exec -n grafana pg-monitoring-2 -c postgres -- psql -U postgres -d grafana -c "SELECT id, uid, title FROM dashboard WHERE uid LIKE '%service-metrics%';"
id | uid | title
------+-----------------------------+--------------------------------------------
3838 | service-metrics-overview | Service Metrics Overview [VictoriaLogs]
Одна запись. Правильный title. Правильный UID.
Иду в UI - работает. Никаких дублей, никаких "Draft", никакого Loki.
Победа! 🎉
Если provisioning не срабатывает автоматически, можно вызвать reload через API (выше был пример курлом) или перезапустить pod Grafana (не мой вариант).
Подробнее про provisioning: https://grafana.com/docs/grafana/latest/administration/provisioning/
На всё ушло часа два.
Могло бы - минут пятнадцать, если бы сразу глянул в логи.
Выводы, а куда без них.
- Grafana provisioning не перезаписывает существующие записи в БД, если они созданы из другого источника. Он кидает ошибку в лог и оставляет старую версию.
- ошибка "A dashboard with the same uid already exists" - это про запись в PostgreSQL, не про файл. Файл может быть идеальным, но если в базе мусор - ничего не заработает.
- логи первым делом. Там обычно всё написано. Но не во всех версиях Grafana логи достаточно информативны.
- удаление из БД работает, но это неофициальный подход. Я не нашёл другого способа. Для provisioned dashboards безопасно - они автоматически пересоздаются из файлов. Главное - делать на primary ноде PostgreSQL и иметь бэкап.
- миграции дашбордов - это не "поменять пару строк". Особенно если UID менялся несколько раз. Особенно, если я баран.
Как избежать этой проблемы (предполагаю):
- не меняйте UID после первого деплоя. Если нужна миграция - создайте новый дашборд с новым UID, а старый удалите полностью (включая запись в БД).
- используйте стабильные UID с префиксами:
myteam-service-metrics-overview
myteam-app-logs-dashboard
- не используйте один UID для дашбордов в разных папках - это создаёт race condition.
- документируйте миграции в changelog или README.
- используйте
allowUiUpdates: false в provisioning конфиге - это предотвращает дрейф конфигурации.Пример конфигурации провижининга
apiVersion: 1
providers:
- name: 'sidecarProvider'
orgId: 1
type: file
disableDeletion: false
allowUiUpdates: false # запрет редактирования в UI
updateIntervalSeconds: 30 # интервал сканирования файлов
options:
foldersFromFilesStructure: true
path: /tmp/dashboards
Альтернативный подход, без прямого доступа к БД - когда его нет или страшно (предполагаю, что это правильный путь, но я не проверял):
- временно переименовать ConfigMap (например добавить суффикс
-old)- дождаться, пока Grafana удалит дашборды (если
disableDeletion: false)- вернуть ConfigMap с правильным именем и новым UID
- provisioning создаст дашборды заново
Этот способ медленнее, но безопаснее.
Для зануд:
Прямое изменение базы данных Grafana - неофициальная практика!
Это не является моим советом или рекомендацией!
Просто поделился своей историей.
- - -
- https://github.com/grafana/grafana/issues/12411
- https://github.com/grafana/grafana/issues/41085
- https://github.com/grafana/grafana/issues/73043
🔥8❤1
#kubernetes
Пока я спал там взломали все инторнеты и все кубернетисы.
Источник на английском с глубоким разбором.
- https://grahamhelton.com/blog/nodes-proxy-rce
Хорошие ребята, кого не жалко репостнуть:
- https://xn--r1a.website/tech_b0lt_Genona/6101
- https://labs.iximiuz.com/tutorials/nodes-proxy-rce-c9e436a9
Хорошо, что все кубернетисы (кроме моих личных лабораторий) на предыдущих и нынешней работах это приватные сетки (что спасает от внешних негодяев, но не спасает от внутренних, но таких у нас нет).
Как проверить, подвержен ли ваш кластер/SA? А легко!
Копи-паст скрипта в своем кластере:
Да, такие дела. Ну и порт 10250 должен быть доступен.
Пока я спал там взломали все инторнеты и все кубернетисы.
Источник на английском с глубоким разбором.
- https://grahamhelton.com/blog/nodes-proxy-rce
Хорошие ребята, кого не жалко репостнуть:
- https://xn--r1a.website/tech_b0lt_Genona/6101
- https://labs.iximiuz.com/tutorials/nodes-proxy-rce-c9e436a9
Хорошо, что все кубернетисы (кроме моих личных лабораторий) на предыдущих и нынешней работах это приватные сетки (что спасает от внешних негодяев, но не спасает от внутренних, но таких у нас нет).
Как проверить, подвержен ли ваш кластер/SA? А легко!
Копи-паст скрипта в своем кластере:
kubectl get serviceaccounts --all-namespaces -o json | \
jq -r '.items[] | "\(.metadata.namespace)\t\(.metadata.name)"' | \
while IFS=$'\t' read -r ns sa; do
if kubectl auth can-i get nodes/proxy --as="system:serviceaccount:${ns}:${sa}" -n "$ns" >/dev/null 2>&1; then
echo -e "${ns}\t${sa}\tyes"
fi
done
Да, такие дела. Ну и порт 10250 должен быть доступен.
🔥2
#kubernetes #golang #troubleshooting
Снова про операторы.
У ресурса
-
-
Как это работает на самом деле:
Когда ты создаёшь или обновляешь секрет, API-сервер мержит
В etcd хранилке и в ответе API сохраняется только
При чтении секрета из кластера поле
А теперь типичнейшая ошибка, на которую я снова попался.😭
Код оператора с ошибкой:
В реконсайлере проверка "изменился ли секрет?":
Видите проблему?
При создании desired-секрета мы используем
Но в Go-структуре этого объекта поле
Что происходит:
- оператор создаёт секрет с
- при следующем reconcile оператор читает секрет - там
- оператор строит desired-секрет - там
- сравнение видит разницу:
(потому что nil)
- оператор решает обновить секрет и делает
записывая nil в поле данных!
- теперь в кластере секрет с
(0 ключей)
- в следующем reconcile сравнение
оба пустые? Да!
- оператор думает: "секрет актуален", и никогда не восстанавливает данные🤡
Результат: секрет живёт,
Поды падают с:
Фикс очевиден: использовать
Теперь
🎉🎉🎉
Мораль:
-
-
- в client-go структуре
- если используешь
- сравнивай и применяй то, что реально используешь
Потерял на этом баге часа два, пока не дошло. Сука.
Секрет создавался, лейблы были,
Классика - симптомы в одном месте (падающие поды), причина в другом (nil вместо мапы в билдере).
Снова про операторы.
У ресурса
Secret в Kubernetes есть два поля для данных:-
Data map[string][]byte - бинарные данные, в YAML выглядят как base64-
StringData map[string]string - строковые данные, plain text в YAMLКак это работает на самом деле:
StringData - write-only поле для удобства. Когда ты создаёшь или обновляешь секрет, API-сервер мержит
StringData в Data (ключи из StringData перезаписывают Data при совпадении). В etcd хранилке и в ответе API сохраняется только
Data. При чтении секрета из кластера поле
StringData всегда пустое - его там просто нет.Поэтому StringData - для людей и YAML-манифестов, а Data - для кода операторов.
А теперь типичнейшая ошибка, на которую я снова попался.
Код оператора с ошибкой:
func BuildCredentialsSecret(instance *v1alpha1.MyApp) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: instance.Name + "-credentials",
Namespace: instance.Namespace,
},
Type: corev1.SecretTypeOpaque,
StringData: map[string]string{
"API_HOST": instance.Spec.Config.API.Host,
"API_TOKEN": instance.Spec.Config.API.Token,
},
}
}В реконсайлере проверка "изменился ли секрет?":
func secretDataEqual(a, b map[string][]byte) bool {
if len(a) != len(b) {
return false
}
for k, v := range a {
if !bytes.Equal(v, b[k]) {
return false
}
}
return true
}
// в reconcile:
if !secretDataEqual(found.Data, secret.Data) {
// обновляем секрет
found.Data = secret.Data // ВОТ ТУТ ПРОБЛЕМА
return r.Update(ctx, found)
}Видите проблему?
При создании desired-секрета мы используем
StringData. Но в Go-структуре этого объекта поле
Data равно nil, потому что мы его не заполняли!!!Что происходит:
- оператор создаёт секрет с
StringData > API сервер конвертирует в Data, в кластере всё ок- при следующем reconcile оператор читает секрет - там
Data заполнен- оператор строит desired-секрет - там
Data = nil, StringData заполнен- сравнение видит разницу:
len(found.Data) != 0, а len(secret.Data) == 0
(потому что nil)
- оператор решает обновить секрет и делает
found.Data = secret.Data
записывая nil в поле данных!
- теперь в кластере секрет с
data: {}(0 ключей)
- в следующем reconcile сравнение
secretDataEqual(found.Data, nil)
оба пустые? Да!
(len(nil) == 0, len(map{}) == 0)- оператор думает: "секрет актуален", и никогда не восстанавливает данные
Результат: секрет живёт,
ownerReference на месте, лейблы есть, а данных - ноль. Поды падают с:
MountVolume.SetUp failed for volume "credentials":
references non-existent secret key: API_TOKEN
Фикс очевиден: использовать
Data в коде оператора:func BuildCredentialsSecret(instance *v1alpha1.MyApp) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: instance.Name + "-credentials",
Namespace: instance.Namespace,
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"API_HOST": []byte(instance.Spec.Config.API.Host),
"API_TOKEN": []byte(instance.Spec.Config.API.Token),
},
}
}Теперь
secret.Data заполнен, сравнение работает корректно, и ты случайно не затираешь данные nil-ом.🎉🎉🎉
Мораль:
-
stringData - для YAML и kubectl, где удобно писать plain text-
data - для кода операторов на Go- в client-go структуре
StringData и Data - разные поля; API сервер не синхронизирует их при чтении- если используешь
StringData в коде, помни: secret.Data == nil в этой структуре- сравнивай и применяй то, что реально используешь
Потерял на этом баге часа два, пока не дошло. Сука.
Секрет создавался, лейблы были,
ownerReference был, а данных - ноль. Классика - симптомы в одном месте (падающие поды), причина в другом (nil вместо мапы в билдере).
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍6
#kubernetes #aws #eks #helm
Тихо и незаметно вышла мажорная версия AWS Load Balancer Controller - 3.0.0.
https://github.com/kubernetes-sigs/aws-load-balancer-controller
Вместе с ней и Helm-чарт тоже прыгнул на 3.x.
https://github.com/aws/eks-charts/tree/master/stable/aws-load-balancer-controller
Как я понял по заявлению мейнтейнеров, breaking changes в пользовательском API (Ingress, Service, Gateway) они не предполагают.
Но issue на GitHub уже висят, так что посмотрим.
Что важного и интересного (для меня):
- Gateway API объявлен GA (production ready) - теперь можно смело использовать в бою, а не в тестовых проектах 🔥
- Версия Helm-чарта выровнена с версией контроллера - раньше было 2.x контроллер с чартом 1.x, теперь 3.0.0 = 3.0.0. Наконец-то! ❤️
- Новые фиксы NLB target groups, порядка сабнетов, AZ mismatch и webhook-сертификатов
- CRD ListenerRuleConfiguration добавлен, но пока экспериментальный - не использовать в проде
- Минимальная версия Kubernetes - теперь официально требуется 1.24+ (интересно, кто ещё на таком старье сидит😁 )
- IPv6 для internet-facing ALB/NLB - теперь нативно поддерживается без костылей, раньше были ограничения 🎉
Нюансы с апгрейдом:
- перед helm upgrade до 3.0.0 нужно вручную обновить CRD'ы. Issue #4555 прямо про это - люди натыкаются на проблемы, потому что в release notes это не очень явно прописано.
Но как по мне так это и очевидно, так как мы все знаем, что при хелм апгрейде CRD не обновляется никогда и ни у кого, архитектурная особенность самого хелма.
- для Gateway API нужны не только CRD самого контроллера, но и базовые Gateway API CRD (HTTPRoute, GatewayClass и т.д.), если их ещё нет в кластере
Сам, конечно же, я пока обновляться не буду.
Почитаю issues ещё недельку, потом с командой решим - стоит ли сразу или подождать патч-релиз.
Better safe than sorry, особенно с ингресс-контроллерами, которые держат весь трафик.
Тихо и незаметно вышла мажорная версия AWS Load Balancer Controller - 3.0.0.
https://github.com/kubernetes-sigs/aws-load-balancer-controller
Вместе с ней и Helm-чарт тоже прыгнул на 3.x.
https://github.com/aws/eks-charts/tree/master/stable/aws-load-balancer-controller
Как я понял по заявлению мейнтейнеров, breaking changes в пользовательском API (Ingress, Service, Gateway) они не предполагают.
Но issue на GitHub уже висят, так что посмотрим.
Что важного и интересного (для меня):
- Gateway API объявлен GA (production ready) - теперь можно смело использовать в бою, а не в тестовых проектах 🔥
- Версия Helm-чарта выровнена с версией контроллера - раньше было 2.x контроллер с чартом 1.x, теперь 3.0.0 = 3.0.0. Наконец-то! ❤️
- Новые фиксы NLB target groups, порядка сабнетов, AZ mismatch и webhook-сертификатов
- CRD ListenerRuleConfiguration добавлен, но пока экспериментальный - не использовать в проде
- Минимальная версия Kubernetes - теперь официально требуется 1.24+ (интересно, кто ещё на таком старье сидит
- IPv6 для internet-facing ALB/NLB - теперь нативно поддерживается без костылей, раньше были ограничения 🎉
Нюансы с апгрейдом:
- перед helm upgrade до 3.0.0 нужно вручную обновить CRD'ы. Issue #4555 прямо про это - люди натыкаются на проблемы, потому что в release notes это не очень явно прописано.
Но как по мне так это и очевидно, так как мы все знаем, что при хелм апгрейде CRD не обновляется никогда и ни у кого, архитектурная особенность самого хелма.
- для Gateway API нужны не только CRD самого контроллера, но и базовые Gateway API CRD (HTTPRoute, GatewayClass и т.д.), если их ещё нет в кластере
Сам, конечно же, я пока обновляться не буду.
Почитаю issues ещё недельку, потом с командой решим - стоит ли сразу или подождать патч-релиз.
Better safe than sorry, особенно с ингресс-контроллерами, которые держат весь трафик.
Please open Telegram to view this post
VIEW IN TELEGRAM
💯4🎃1
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7🤣4🤯3💔2👍1
Там
Прям с графиками, картиночками, наглядно.
В целом годно, как и многие его другие ресурсы/статьи. Никакого рокетсайнса и кишочков - просто база-дополнение к официальной документации.
Рекомендую как для общего развития начинающим специалистам, так и в качестве подготовки к собеседованиям на тему кубернетиса.
https://learnkube.com/kubernetes-api-explained
Daniele Polencic обновил статью про k8s API.Прям с графиками, картиночками, наглядно.
В целом годно, как и многие его другие ресурсы/статьи. Никакого рокетсайнса и кишочков - просто база-дополнение к официальной документации.
Рекомендую как для общего развития начинающим специалистам, так и в качестве подготовки к собеседованиям на тему кубернетиса.
https://learnkube.com/kubernetes-api-explained
🔥10👍2
#gitlab #troubleshooting #одинденьизжизни
На новом проекте дали задачу: небольшие изменения в гошном операторе Kubernetes.
Секреты, cluster-scoped объекты, ничего космического.
Покрутил код, разобрался в архитектуре, поднял локальный kind-кластер, погонял тесты.
Makefile на месте, всё зелёное. Красота.
Коммичу, пушу MR.
Тесты падают. 🙃
Ну ладно, думаю. Я серьёзно менял логику, наверное поломал что-то. Бывает.
Возвращаюсь, перепроверяю. Локально гоняю - всё проходит.
Коммичу, пушу - снова падает.
Ок, уже интересно.
Открываю
Повторяю ровно то же самое локально - make test, всё зелёное.
В GitLab - красное.
Смотрю логи джобы внимательнее.
Один из тестов падает на скачивании image из container registry соседнего репозитория. Не хватает авторизации. Ага.
Проверяю:
- мой GitLab токен - валидный✅
- доступ в соседние репозитории (сайдкар-образы для тестов) - есть✅
- образы в registry - на месте✅
- пайплайн до моих изменений - зелёный, тесты проходили✅
Всё на месте, а не работает.🤡
Перепроверяю ещё тысячу раз.
Локально - 100% то же самое - работает.
В GitLab - нет.
Тут приходит мысль: а может дело не в коде, а в правах?
Пайплайн-то запускается от моего имени.
А у тех, кто запускал до меня, может быть что-то другое.
Иду смотреть настройки репозитория.
Settings - нет доступа.
Access Tokens - нет доступа.
Deploy Tokens - нет доступа.
У меня Developer, без права заглянуть хоть куда-то в настройки.
Пишу в личку коллеге, который точно Admin:
- Слушай, можешь по этой ссылке просто нажать Retry на джобе тестов?
Он жмёт.
Тесты проходят. ✅Сука.
Я жму Retry.
Тесты падают. ❌Сука.
Пишу главной команде DevOps (да, оксюморон - девопс пишет девопсам "спасити-памагити").
Объясняю ситуацию: так и так, тесты падают только когда пайплайн запускает мой юзер, предполагаю, что
Ну, после некоторых усилий удалось донести мысль. Ребята берут таймаут.
Через несколько дней/часов:
- Ретрай.
Ретраю.
Зелёное. 🎉
- А что было?
- Мы тоже долго дебажили, скормили все токены в Claude, но нашли.
У тебя в профиле GitLab стояла галочка External User.
External. User. Сука.
Галочка. В профиле. Которую я не ставил. Которую даже не видел (у меня нет доступа к админ-панели). Которую кто-то когда-то поставил при создании моего аккаунта. Или не снял. Или поставил по дефолту в LDAP/SAML/SCIM-маппинге. Или просто потому что.
Что делает External User в GitLab:
- https://docs.gitlab.com/administration/external_users/
- внешний пользователь не имеет доступа к internal-репозиториям
- CI_JOB_TOKEN наследует ограничения профиля
- даже если у тебя есть явный Developer-доступ к проекту, некоторые internal-ресурсы (включая container registry соседних проектов) могут быть недоступны
- в логах пайплайна это выглядит как обычная 401/403 при pull image - ни слова про external user
Всё работает. Везде. Кроме одного места.
И это место - галочка в чужой админ-панели, которую ты даже увидеть не можешь.
Потрачено:
- ~6-7 часов моего времени на дебаг
- N часов времени DevOps-команды
- массу нервных клеток
- массу токенов в Claude
Выводы:
- если пайплайн падает только у вашего юзера, а у коллег - нет, проблема не всегда в коде.
Проблема иногда в правах. Не в правах репозитория, а в правах профиля.
- External User в GitLab - это тихий убийца. Никаких предупреждений в UI, никаких баннеров "вы external". Просто
- вы не увидите эту галочку сами - она в Admin Area, доступ только у администраторов GitLab.
- при онбординге на новый проект спросите: "А мой аккаунт точно не external?" Серьёзно. Одна галочка - полдня жизни.
- локальные тесты != CI тесты. Даже если команды идентичны. Контекст авторизации разный.
- и как обычно: во всём виноваты девопсы. Даже когда девопс - это ты сам.😢
Тихий убийца времени.
На новом проекте дали задачу: небольшие изменения в гошном операторе Kubernetes.
Секреты, cluster-scoped объекты, ничего космического.
Покрутил код, разобрался в архитектуре, поднял локальный kind-кластер, погонял тесты.
Makefile на месте, всё зелёное. Красота.
Коммичу, пушу MR.
Тесты падают. 🙃
Ну ладно, думаю. Я серьёзно менял логику, наверное поломал что-то. Бывает.
Возвращаюсь, перепроверяю. Локально гоняю - всё проходит.
Коммичу, пушу - снова падает.
Ок, уже интересно.
Открываю
.gitlab-ci.yml, читаю джобу тестов строчка за строчкой.Повторяю ровно то же самое локально - make test, всё зелёное.
В GitLab - красное.
Смотрю логи джобы внимательнее.
Один из тестов падает на скачивании image из container registry соседнего репозитория. Не хватает авторизации. Ага.
Проверяю:
- мой GitLab токен - валидный✅
- доступ в соседние репозитории (сайдкар-образы для тестов) - есть✅
- образы в registry - на месте✅
- пайплайн до моих изменений - зелёный, тесты проходили✅
Всё на месте, а не работает.
Перепроверяю ещё тысячу раз.
Локально - 100% то же самое - работает.
В GitLab - нет.
Тут приходит мысль: а может дело не в коде, а в правах?
Пайплайн-то запускается от моего имени.
А у тех, кто запускал до меня, может быть что-то другое.
Иду смотреть настройки репозитория.
Settings - нет доступа.
Access Tokens - нет доступа.
Deploy Tokens - нет доступа.
У меня Developer, без права заглянуть хоть куда-то в настройки.
Пишу в личку коллеге, который точно Admin:
- Слушай, можешь по этой ссылке просто нажать Retry на джобе тестов?
Он жмёт.
Тесты проходят. ✅Сука.
Я жму Retry.
Тесты падают. ❌Сука.
Пишу главной команде DevOps (да, оксюморон - девопс пишет девопсам "спасити-памагити").
Объясняю ситуацию: так и так, тесты падают только когда пайплайн запускает мой юзер, предполагаю, что
CI_JOB_TOKEN моего профиля не имеет нужных прав, он передаётся в кубер как image pull secret, а там авторизация не проходит.Ну, после некоторых усилий удалось донести мысль. Ребята берут таймаут.
Через несколько дней/часов:
- Ретрай.
Ретраю.
Зелёное. 🎉
- А что было?
- Мы тоже долго дебажили, скормили все токены в Claude, но нашли.
У тебя в профиле GitLab стояла галочка External User.
External. User. Сука.
Галочка. В профиле. Которую я не ставил. Которую даже не видел (у меня нет доступа к админ-панели). Которую кто-то когда-то поставил при создании моего аккаунта. Или не снял. Или поставил по дефолту в LDAP/SAML/SCIM-маппинге. Или просто потому что.
Что делает External User в GitLab:
- https://docs.gitlab.com/administration/external_users/
- внешний пользователь не имеет доступа к internal-репозиториям
- CI_JOB_TOKEN наследует ограничения профиля
- даже если у тебя есть явный Developer-доступ к проекту, некоторые internal-ресурсы (включая container registry соседних проектов) могут быть недоступны
- в логах пайплайна это выглядит как обычная 401/403 при pull image - ни слова про external user
Всё работает. Везде. Кроме одного места.
И это место - галочка в чужой админ-панели, которую ты даже увидеть не можешь.
Потрачено:
- ~6-7 часов моего времени на дебаг
- N часов времени DevOps-команды
- массу нервных клеток
- массу токенов в Claude
Выводы:
- если пайплайн падает только у вашего юзера, а у коллег - нет, проблема не всегда в коде.
Проблема иногда в правах. Не в правах репозитория, а в правах профиля.
- External User в GitLab - это тихий убийца. Никаких предупреждений в UI, никаких баннеров "вы external". Просто
CI_JOB_TOKEN молча получает урезанные права.- вы не увидите эту галочку сами - она в Admin Area, доступ только у администраторов GitLab.
- при онбординге на новый проект спросите: "А мой аккаунт точно не external?" Серьёзно. Одна галочка - полдня жизни.
- локальные тесты != CI тесты. Даже если команды идентичны. Контекст авторизации разный.
- и как обычно: во всём виноваты девопсы. Даже когда девопс - это ты сам.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18🤡2💔1
#longread #troubleshooting #operators #kubectl #kubernetes #одинденьизжизни
Внезапный и незапланированный лонгрид.
Разбирал по работе задачу, узнал новое про фичу
Теперь делюсь историей и знаниями с вами.
Приятного прочтения.
https://teletype.in/@kruchkov_alexandr/wWa-INvQvJH
Внезапный и незапланированный лонгрид.
Разбирал по работе задачу, узнал новое про фичу
kubectl cache discovery.Теперь делюсь историей и знаниями с вами.
Приятного прочтения.
https://teletype.in/@kruchkov_alexandr/wWa-INvQvJH
Teletype
Когда kubectl врёт
Сегодня мне для нашего кубернетис оператора надо было поменять Namespaced scope на Cluster scope у одного CRD.
15👍23🔥1