Make. Build. Break. Reflect.
1.29K subscribers
144 photos
3 videos
1 file
154 links
Полезные советы, всратые истории, странные шутки и заметки на полях от @kruchkov_alexandr
Download Telegram
#troubleshooting #aws #azure #kubernetes #одинденьизжизни

Пришёл алерт ранним утром. Ровно после начала рабочего дня в 10.01.
Чёткий. Конкретный. С именем сервиса, неймспейсом и метрикой.
Не что-то не то с подами, а прям: HighMemoryUsage, payment-service, namespace production, порог 85%, текущее значение 91%.

Открыл дашборд - всё выровнено, таймлайн читается, spike начался ровно в момент последнего деплоя.
Зашёл в ArgoCD, посмотрел diff последнего релиза.
Разработчик увеличил replicas с 3 до 5, но не тронул resources.limits.memory.
Всё сошлось.

Написал разработчику. Он ответил сразу.
Сказал да, моя ошибка и прислал PR через 10 минут.
В PR было описание: что изменил, почему, как проверял локально, какой эффект ожидается.
Коммиты по делу: fix(payment-service): set memory limits and requests after OOM incident.
Не fix, не wip, не asdfgh.
Поправил лимиты, добавил requests заодно.

Пока ждал PR, попросил Cursor посмотреть helm values на предмет других неоптимальных лимитов.
Нашёл два места. Предложил изменения. Ничего лишнего не тронул.

Я заапрувил, ArgoCD задеплоил. Память упала. Алерт закрылся сам.
CI прошёл с первого раза. Без flaky tests. Без "ой, retry нажмите, оно иногда так".

Разработчик после апрува написал в PR: спасибо за ревью, понял, учту.
Следующий его PR пришёл уже с нормальными лимитами. Без напоминания.
В Slack за всё это время никто ничего не написал.

Кстати, на прошлой неделе коллективно договорились не писать @here и @channel в каналах с людьми, если это не реальный прод-инцидент. Все согласились и все соблюдают.

Параллельно пришло письмо от AWS: они самостоятельно обнаружили, что у нас один из инстансов работает в неоптимальном режиме, и предложили конкретный тип на замену - дешевле на 15%.
Написал в саппорт уточнить детали, ответили за полчаса, классные ребята из лондонского офиса, поболтали за жизнь на английском.

Ещё с утра был запланирован автоапдейт AKS - security patch.
Прошёл сам, пока я занимался инцидентом.
Все ноды обновились по очереди, ни одна не упала в NotReady.
API не дёргался. Поды не пересоздавались хаотично.
kubectl get nodes после апдейта вернул всё Ready. С первого раза.
Странно, что я удивляюсь этому, ведь так было всегда, это стабильный и любимый всеми Ажур.

Коллеги наконец перестали спорить что лучше - Windows или Mac.
Все пересели на MacBook. Тихо. Без срачей. Без "но у меня Visual Studio".

Перед обедом написал PM.
Он ответил, что предлагает отменить ежедневный стендап на этой неделе - нет открытых топиков, нет смысла тратить время.

Ещё PM сказал, что руководство приняло решение: гонка за AI - не наша история.
Главное - люди, процессы и спокойная работа.
Никаких срочных внедрений, никакого "а вы уже используете агентов?", никаких OKR про AI-трансформацию до конца квартала.
Ведь главное, чтобы у всех были рабочие места и зарплата вовремя, а не это ваше ИИ.

Весь день занял 40 минут рабочего времени.
Остальное время занимался самообразованием и гладил кота.

Написал постмортем.
Никто не переспрашивал.
Кто-то из команды сам обновил страницу в Notion по итогам инцидента.
Без задачи. Без напоминания. Просто взял и обновил.

NewRelic прислал автоматический отчёт: data ingestion за месяц снизился на 12%. Сам.
Пообедал в 13:00. Целый час, как положено.
Статус в Slack стоял 🍕 Lunch.
Никто не написал. Никто не позвонил. Никто не "быстрый вопрос".

Хороший день.
Может, когда-нибудь так и будет.😀
Please open Telegram to view this post
VIEW IN TELEGRAM
😁4510😢6🔥3
#guardduty #aws #eks #security #troubleshooting

Guardduty, фишинг. Часть 1 из 2.

Никого не трогаю. Прилетает алёрт от AWS GuardDuty.
Backdoor:EC2/C&CActivity.B!DNS

Продакшн нода в EKS делает DNS-запрос к x****.cn - по базам GuardDuty это известный фишинговый/DGA домен.
Серьёзность - High. В слаке шум, все смотрят на меня.
Блин, почему в мою смену секьюрити инциденты 😬

Окей. Поехали.
Cмотрим что за нода
aws ec2 describe-instances \
--instance-ids i-0xxxxxxxxxxxxxxxxx \
--region us-east-1 \
--query 'Reservations[0].Instances[0].{IP:PrivateIpAddress,State:State.Name,LaunchTime:LaunchTime}'

Нода уже terminated. Karpenter убил её раньше, чем я добрался до расследования.
Живого форензика (digital forensics) нет.
Прекрасное начало.
kubectl get nodes -o wide
# ноды нет

Ладно, живыми данными не поработать, идём в логи.

Cмотрим что вообще крутилось на ноде
Хорошо, что у нас включено всё логирование control plane EKS. Идём в CloudWatch, смотрим логи шедулера - кто и когда был назначен на эту ноду.
aws logs start-query \
--log-group-name /aws/eks/my-cluster/cluster \
--start-time $(date -v-83H +%s) \
--end-time $(date +%s) \
--query-string 'fields @timestamp, @message | filter @logStream like "scheduler" | filter @message like "ip-10-0-25-132" | sort @timestamp asc | limit 50' \
--region us-east-1

Первая попытка падает с MalformedQueryException. Оказывается, ретеншн у лог группы EKS - 84 часа. Нода жила 4 дня. Часть ранних данных уже недоступна. Запоминаем это на потом.

За те данные, что есть, видим волну деплоя примерно за час до алёрта: новые ReplicaSet хеши, несколько сервисов прокатились роллинг апдейтом. Подозрительно по тайминингу. Ухожу копать туда.

Cмотрим образы, которые пушились незадолго до инцидента
aws ecr describe-images \
--repository-name my-ecr-repo \
--region us-east-1 \
--query 'sort_by(imageDetails, &imagePushedAt)[-10:].[imagePushedAt,imageTags[0],imageDigest]' \
--output table

Вижу два образа, которые запушились за ~30-60 минут до алёрта GuardDuty. Первая гипотеза: кто-то накатил заражённый образ.

Лезу в git, смотрю коммиты.
Один коммит - это Dependabot, обновил grpc. Проверяю go.sum - стандартные пакеты, нормальные хеши. Второй коммит - двухфайловый вайтспейс-фикс в SQL query builder, strings.TrimSpace в одном файле.

Всё это вообще не то. Red herring, мать ее.

Исключаем DaemonSet
kubectl get daemonsets -A

DaemonSet крутится на всех нодах кластера - их там под 44 штуки. Если бы DaemonSet был заражён, GuardDuty бы полыхал по всем нодам разом. Сработало только на одну. Значит, не DaemonSet.

Базовый образ Dockerfile смотрю - официальный golang bookworm, мультистейдж, всё чисто.

Смотрю дополнительные файндинги GuardDuty
aws guardduty list-findings \
--detector-id $(aws guardduty list-detectors --query 'DetectorIds[0]' --output text) \
--finding-criteria '{"Criterion":{"type":{"Eq":["Backdoor:EC2/C&CActivity.B!DNS","Trojan:EC2/DNSDataExfiltration","DefenseEvasion:EC2/UnusualDoHActivity"]}}}' \
--region us-east-1

Нашлось ещё пара файндингов - не игнорируем, но пока продолжаем копать основное.

Наконец-то заглядываю в логи приложения (когда же я, бл, научусь это смотреть первым 🤡)
Открываю OpenSearch, ищу по домену x********.cn , в индексе EKS-логов.
8 записей. Все - из одного контейнера. Смотрю на таймстемпы:
12:51:19 - PaymentWebhookProcessor: starting to parse note
12:51:19 - PaymentWebhookProcessor: no images found in note
...
13:21:38 - PaymentWebhookProcessor: starting to parse note <- ВОООТ ОНО
13:21:38 - PaymentWebhookProc...

13:21:38 UTC - точь-в-точь время алёрта GuardDuty.

Попался!
Please open Telegram to view this post
VIEW IN TELEGRAM
😁3👍1
#guardduty #aws #eks #security #troubleshooting

Guardduty, фишинг. Часть 2 из 2.


Что произошло на самом деле
Схема простая до слёз:
Пользователь отправил сообщение с URL -> x*****.cn
|
Сервис обработки контента внутри кубера получил вебхук(?)
|
Функция(?) PaymentWebhookProcessor внутри POD-а попыталась резолвить домен
(логика: ищет превью/изображения по URL-ам в тексте)
|
DNS-запрос к x*****.cn с EKS ноды, на которой запущен под
|
GuardDuty: ALARM


Никакой малвари. Нода не скомпрометирована. Credentials не угнаны.
Просто сервис резолвит любой домен из пользовательского контента - а юзер прислал фишинговую ссылку.
Фишинг не удался (дополнительно логи смотрел).

Несколько часов расследования, CloudTrail, CloudWatch, ECR history, git blame - ответ лежал в логах приложения и нашёлся бы за 15 минут, если бы я туда полез первым делом.

Что реально плохо и надо починить
Само расследование закончилось словами "малвари нет", но по ходу вскрылись настоящие проблемы 🚬.

- SSRF
PaymentWebhookProcessor резолвит произвольные URL из пользовательского контента без какой-либо валидации.
Это означает, что любой клиент может прислать:
http://169.254.169.254/latest/meta-data/
И сервис спокойно попробует его резолвнуть - прямо с production ноды, у которой есть IAM-роль со повышенными правами (это вообще, бл, отдельный разговор какого рожна нода имела доступ к некоторым AWS ресурсам).

Фикс очевидный - блокировать приватные диапазоны перед любым исходящим запросом:
169.254.0.0/16  (AWS metadata endpoint)
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
127.0.0.0/8

- GuardDuty будет шуметь вечно
Пока сервис зачем-то резолвит пользовательские URL без фильтрации - каждый раз, когда кто-то пришлёт фишинговую ссылку, GuardDuty будет стрелять алёртом. Это шум, который убивает доверие к алёртингу.

Решение: AWS Route 53 Resolver DNS Firewall с managed threat lists.
- AWSManagedDomainsMalwareDomainList
- AWSManagedDomainsAggregateThreatList
Известные вредоносные домены блокируются до DNS-резолва автоматически, без ручных списков.
https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resolver-dns-firewall-managed-domain-lists.html

- URL-резолвинг в основном процессе
Длиннее задача, но правильная: вынести резолвинг URL в изолированный асинхронный воркер с ограниченным egress.
Без доступа во внутреннюю сеть. Без IAM-роли с правами на всё.

Выводы и итоги:
- в логи приложения надо идти первым. Не после нескольких часов инфра-форензики.☔️
- ретеншн CloudWatch-логов для EKS control plane 84 часа - маловато. Нода жила 4 дня, ранние события уже недоступны. Надо поднять. Не критикал, но надо бы.
- сервисы, которые резолвят URL из пользовательского контента, будут встречать фишинг. Это не вопрос если, это вопрос когда. Надо документировать и обрабатывать проактивно, а не разбираться в панике с High severity алёртом.
- SSRF - это не гипотетическая угроза, это прямая дыра в production при текущей архитектуре.
- кто дает права EKS нодам на доступ к AWS ресурсам, то негодяй, нельзя так
- с одной стороны я переживал тогда, что потратил много времени на расследование, копая изначально не туда, с другой стороны рад, ведь без полной картины происходящего я не смог бы понять были ли скомпроментированы креды или нет, был ли хак или нет, а так вроде и пополнил знания
- используйте сервисы AWS, многие из них хорошо помогают в траблшутинге, пусть и стоят денег

False positive закрыт, но работы добавилось.

- - -
В последствии все косяки были закрыты, все Security/AWS бест практисы внедрены.
Все потенциальные известные "дыры" закрыты, PII защищены, даже инвесторы спали спокойно после детальнейшнего постмортема.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17
#devops #github

GitHub объявил, что с 24 апреля 2026 года данные взаимодействий Copilot (prompts, ответы, сниппеты и контекст) начинают использоваться для обучения моделей по умолчанию, если пользователь явно не отключит это в настройках для тарифов Copilot Free/Pro/Pro+.
https://github.blog/news-insights/company-news/updates-to-github-copilot-interaction-data-usage-policy/

Как и водится в маленьких инди компаниях, даже сроки они не стали соблюдать, залепили раньше, нарушая сроки объявленого дедлайна. Сейчас 7 апреля и у меня уже стоит эта галочка.
Возможно уже и данные коллектят, кто знает, верить уже тяжело.

Кто-то может, конечно, возразить, что "галочка включена != сбор данных", но я не очень согласен с этим.
Галочка стоит, текст честно написан, значит так оно и есть.
Allow GitHub to collect and use my Inputs, Outputs, and associated context to train and improve AI models.


Кто еще боится в век AI утилит лишиться приватности или заставляют условия работы в компаниях, спешите снять галочку:
- Идите по ссылке на свой профиль https://github.com/settings/copilot/features
- Settings -> Copilot -> Features -> Privacy -> Allow GitHub to use my data for AI model training,
9
#opensource #alertmanager #devops

Новости opensource мира.

У продукта Alertmanager есть неприятная недоработка.
Все уведомления в Slack он отправляет двумя сообщениями:
- проблема (firing)🔴
- проблема решена (resolved)🟢

Наверняка видели.

В мире шумных уведомлений даже этого много.
По сути 100 алертов порождают 200 сообщений (после их решения), что не очень удобно.
API самого Slack позволяет изменять сообщения, но алертменеджер не умел в это.

Это происходит прежде всего потому, что алертменеджер не хранит стейт алертов, чтобы "запоминать" куда слать обновление по алерту.

При этом аналоги умеют менять сообщение (меняя красный firing на зелёный resolved):
- grafana oncall / IRM
- pagerduty

С появлением GenAI инструментов я захотел исправить эту досадную оплошность, сделал форк алертменеджера, локально всё потестировал и примерно с полгода гонял у себя - работает идеально. (Март 2025)

Затем осмелился и запилил пулл реквест:
https://github.com/prometheus/alertmanager/pull/4682 (Ноябрь 2025)

Не скрою, пользовался GenAI, но основная идея была моя.
Я на 90% понимаю изменённый код и поведение.

После вялого обсуждения другие, более активные участники, реализовали это в два пулл реквеста:
- https://github.com/prometheus/alertmanager/pull/4899 (Январь 2026)
- https://github.com/prometheus/alertmanager/pull/5007 (Февраль 2026)

Само собой после этого мой PR не имеет смысла, я закрыл его. (Февраль 2026)

Вчера, 8 апреля 2026 выпущен новый релиз v0.32.0.
https://github.com/prometheus/alertmanager/releases/tag/v0.32.0

Теперь алертменеджер умеет хранить стейт алертов для слака и менять сообщения - что очень удобно. 🎉

Всех, кто использует алертменеджер, поздравляю.
Можете обновлять и радоваться меньшему количеству сообщений и меньшему шуму алертов.

---
Но вам же интересно глянуть, как это работает перед обновлением прода?

Нужен Bot Token Slack (xoxb-...) со скоупами chat:write и chat:update.
Важно: работает только с токеном, не с webhook URL.

Положить конфиг в /tmp/am-test/alertmanager.yml
global:
resolve_timeout: 1m

route:
receiver: slack
group_by: [alertname]
group_wait: 10s
group_interval: 10s
repeat_interval: 12h

receivers:
- name: slack
slack_configs:
- send_resolved: true
api_url: https://slack.com/api/chat.postMessage
http_config:
authorization:
credentials: xoxb-YOUR-BOT-TOKEN
channel: '#your-channel'
update_message: true # <-- вот эта опция
title: '{{ .GroupLabels.alertname }} - {{ .Status | toUpper }}'
text: |
{{ if eq .Status "firing" }}🔥 *{{ .Alerts.Firing | len }} alert(s) FIRING*{{ else }} *All alerts RESOLVED*{{ end }}

{{ range .Alerts }}• {{ if .Annotations.summary }}{{ .Annotations.summary }}{{ else }}{{ .Labels.alertname }}{{ end }}
{{ end }}
color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'


Запустить alertmanager v0.32.0 через Docker:
mkdir -p /tmp/am-test

docker run -d \
--name am-test \
-p 9093:9093 \
-v /tmp/am-test/alertmanager.yml:/etc/alertmanager/alertmanager.yml \
-v /tmp/am-test/data:/alertmanager \
prom/alertmanager:v0.32.0


Стрельнуть алертом:
curl -X POST http://localhost:9093/api/v2/alerts \
-H 'Content-Type: application/json' \
-d '[{
"labels": {"alertname": "TestAlert", "severity": "warning"},
"annotations": {"summary": "This is a test alert - will resolve in the same message"}
}]'


Подождать ~10–15 секунд - в Slack появится сообщение с 🔴.

Зарезолвить:
curl -X POST http://localhost:9093/api/v2/alerts \
-H 'Content-Type: application/json' \
-d '[{
"labels": {"alertname": "TestAlert", "severity": "warning"},
"annotations": {"summary": "This is a test alert - will resolve in the same message"},
"endsAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}]'


Подождать ~10 секунд - то же самое сообщение изменится на 🟢.
Нового сообщения не будет!

Поздравляю всех с таким мелким, но приятным обновлением.
🔥462
#devops #ai #mcp #skills #troubleshooting

MCP, скиллы, ИИ, *уи.


Последние месяцы отказываюсь от MCP серверов.
Прям вот по минимуму оставляю и только от безысходности.
Slack, Linear, Jira, Grafana, VM (Victoria metrics), VL (victoria logs) и так далее.
Им замены мало, без них сложно.

Все MCP для Kubernetes, GitHub, GitLab - все это очень плохо работает в боевых условиях.

Распишу основные минусы из моего опыта:

- трата токенов
Вопреки описанию "да просто мсп сервер ничего не жрет в фоне", это лукавство.
Жрет токены и это видно по логу и трате их по обычным запросам.
Постоянно включать/выключать их я пробовал, но это банально неудобно даже если повесить скиллы/алиасы на это.

- они навайбкожены
Давайте быть честными - больше половины этих серверов это вайбкод вайбкода и работает он ровно так же.
Никаких гарантий, что ИИ агент идет четко по REST API/gRPC, из моей практики они просто хаотично по всем ендпойнтам лезут в надежде найти ответ на вопрос/задачу.

- функционал обрезан. Самый главный и основной минус.
Очень часто натыкался на то, что CLI утилита продукта имеет 99.9% функционала UI, а MCP от силы 60-70% по моим наблюдениям и оценке.
Пример на скриншоте.
Контекст скриншота: обучаю скилл/агента трабшутить упавшие пайплайны гитлаба, и mcp тут не подходит, он даже не может смотреть лог джобы, лол. 😬 Пришлось переписать на glab cli.

- происхождение сомнительное
Большинство MCP‑реализаций сейчас - сторонние, не от официальных вендоров, и часто не строго следуют контракту API или gRPC.
Я пробовал делать "доверительные списки", даже обращался к https://archestra.ai/mcp-catalog, но результат все равно для меня сомнительный.

Мне этого хватило, чтобы со временем отключить почти все MCP сервера там, где есть альтернатива - CLI tool + Skills.
Тут нет речи о том, что MCP - говно.
Тут речь лишь о том, что если есть CLI tool, то MCP не нужен.

Есть glab + TOKEN? MCP GitLab не нужен.
Есть gh + TOKEN? MCP GitHub не нужен.
Есть kubectl + auth? MCP Kubernetes не нужен.
И так далее.

Из плюсов подхода Skills +CLI tool:
- трата меньше токенов
- больший функционал, чем у MCP

Я пробовал заменить MCP VM/VL на "cURL + Skills", это работает и работает отлично, но трата токенов выше примерно на 18-22% по моим графикам и данным. Нерационально, поэтому оставил MCP MV/VL.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍143
#ai #devops

Совершенно ожидаемо и неожиданно одновременно Nvidia выпустила бесплатные эндпойнты к популярным LLM моделям.
https://build.nvidia.com/models?filters=nimType%3Anim_type_preview&orderBy=weightPopular%3ADESC

А давайте-ка глянем, так ли оно.

- прошел регистрацию (мой gmail аккаунт)
- прошел верификацию (номер телефона НЕ РФ, другие страны не проверял само собой)
- выбрал рандомную модель
https://build.nvidia.com/moonshotai/kimi-k2-instruct
- нажал view code
- нажал в окошке generate API key
- скопировал код к себе локально в файл 1.py , добавил context простой вопрос про хокку
from openai import OpenAI

client = OpenAI(
base_url = "https://integrate.api.nvidia.com/v1",
api_key = "nvapi-m3_bqO98rY-а-зачем-вам-мой-токен-BAMIGI-tLbz3VQ20MpwyTbU-"
)

completion = client.chat.completions.create(
model="moonshotai/kimi-k2-instruct",
messages=[{"role":"user","content":"Write a haiku about infrastructure as code."}],
temperature=0.6,
top_p=0.9,
max_tokens=4096,
stream=True
)

for chunk in completion:
if not getattr(chunk, "choices", None):
continue
if chunk.choices and chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")

- установил пакет опенаи (если нет)
pip3 install openai

- сохранил код и запустил его
python3 1.py

Lines of code define
bridges spun from pure intent—
infrastructure breathes.


Работает! 🎉

Забирайте себе и тренируйтесь с разными моделями бесплатно. ❤️

- - -
Update для самых быстрых и невнимательных.

Не забывайте, что там:
"Privacy. Your input and output will be recorded to provide you with this trial experience and to improve NVIDIA products and services, including AI models, in accordance with our Privacy Policy.
Do not upload any confidential information or personal data unless expressly permitted. Your use is logged for security, fraud or abuse monitoring and shared with third party service providers for this purpose. If the demo necessarily requires the input of personal data, logging for product development purposes will be turned off."
8
#terraform #AWS #IaC #devops

Все мы живём в привычках.
Как однажды научили - так и делаем, словно котики, повторяющие одно и то же всю жизнь.

Учили поднимать руку, чтобы постучать в дверь.
А можно стучать кистью, не поднимая руку.
Мелочь, но никто не задумывается, потому что "так делается".

Когда у нас с супругой появилась посудомоечная машина, я внимательно читал инструкции - и от самой техники, и от капсул.
Тогда были капсулы со специальным покрытием, которое нужно было срывать перед загрузкой.
Потом она купила новые капсулы с плёнкой, которая растворяется от воды сама, и положила их в старую коробку - удобная была.
Я об этом не знал.
И каждый раз, словно дурачок, аккуратно срывал плёнку перед загрузкой.
Ну скажем сильно больше одного дня я так делал) Сильно больше)
Ведь меня так научили. Да и коробка - та же самая.

Вот так и в нашем айти.

Когда меня учили Terraform и в проекте были накликанные руками ресурсы, объясняли процесс примерно так: пишешь код ресурса, потом идёшь на registry.terraform.io, находишь нужный ресурс, листаешь вниз до секции Import - там написано в каком формате передавать ID (у каждого ресурса свой синтаксис, угадай с первого раза).
Потом идёшь в AWS консоль за нужным ARN или именем ресурса. Возвращаешься, запускаешь команду:
terraform import aws_iam_role.eks_node_role eks-node-role-production

Делаешь план, убираешь дрифт. Следующий ресурс - снова на сайт, снова читаешь синтаксис, снова в консоль за ARN:
terraform import aws_iam_role_policy_attachment.eks_worker_node_policy \
eks-node-role-production/arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy

И так для каждого накликанного ресурса, по одному.

Ну я как обезьянка и повторял.
IAM роль? terraform import.
Политика? terraform import.
Node group? terraform import.
Пять ресурсов - пять итераций, пять заходов на сайт.

Оказывается, с Terraform 1.5 (лето 2023, и да, я снова не знал 🚬) есть блок import прямо в коде.
Без отдельных команд.
Без итераций.
Без "а я точно не забыл что-то запустить".
import {
to = aws_iam_role.eks_node_role
id = "eks-node-role-production"
}
resource "aws_iam_role" "eks_node_role" {
name = "eks-node-role-production"
...
}

import {
to = aws_iam_role_policy_attachment.eks_worker_node_policy
id = "eks-node-role-production/arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
}
resource "aws_iam_role_policy_attachment" "eks_worker_node_policy" {
role = aws_iam_role.eks_node_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
}


Запускаешь обычный terraform apply - он сам всё импортирует.
Это в коде, видно в PR, и при потере стейта не нужно вспоминать, что и как импортировать.
Документация прямо внутри конфига.

Import block не избавляет от необходимости знать ID формат, он просто убирает CLI-танцы.
Важно: import block выполняется только если ресурса нет в state.
После успешного импорта его можно удалить - он больше не нужен.

А если совсем не хочется писать конфиг руками - можно ещё и сгенерировать его через
terraform plan -generate-config-out=generated.tf


Так и живём.
Срываем саморастворяющуюся плёнку с капсул, хотя давно этого делать не нужно.
Please open Telegram to view this post
VIEW IN TELEGRAM
18🫡4👍2👏2
Media is too big
VIEW IN TELEGRAM
#aws #всратость

Спасибо тебе, маленькая инди компания Амазон!

Случайная сортировка аккаунтов при каждом входе на страницу SSO это очень удобно!
😁23🤣6🍌1
#пятница #байки #всратость

Давно не было баек.

Когда-то давно я работал в банке синего цвета.
Или красного. Или жёлтого. А уже и не важно, может и не работал вовсе.

В банках всё строго регламентировано: отдельный контур для дева, отдельный для стейджа, препрод и прод. Свои отдельные аккаунты, свои отдельные CI/CD системы, свои раннеры, только под проект. Те, кто работал в банках, наверняка знают, как всё урезано и изолировано.

Однажды сложилась ситуация, когда надо было шарить между командами и библиотеки, и раннеры.
Это был настоящий прорыв: общие раннеры конвейера позволяли получать пакеты из общего реджистри, и больше не надо было страдать с изоляцией на каждый чих.
Спасибо команде поддержки конвейера ❤️.

И вот тут выяснилось кое-что неприятное.
Не все инженеры из продуктовых команд после работы на шаренном раннере чистили за собой кеш и конфиг.
А oc login, как это принято, пишет конфиг сессии прямо на диск.
И этот конфиг никуда не девается, если явно не выйти.

То есть следующий инженер, который запускал свою джобу на том же раннере, мог просто подобрать чужой опеншифт конфиг и зайти в чужой неймспейс.
А оттуда все секреты кубера, пароли к БД и так далее. Дев, конечно, но тем не менее.

Я писал команде поддержки конвейера - разводили руками. Писал команде поддержки опеншифтов - тоже ничего. Хотя решений было полно: запретить запись этого файла на раннере, чистить конфиг после каждого старта, да хоть просто добавить logout в шаблон джобы. Сотни вариантов. Ни один не взлетел.

Тогда я решил действовать сам. Решение получилось, скажем так, воспитательное.
Я создал пайплайн, который раз в пять минут по крону проверял незавершённые сессии. Если находил, то скейлил все деплойменты и деплойментконфиги в ноль, поднимал десять реплик фейкового пода с говорящим именем и чистил конфиг за нерадивым инженером.
Добавил, так сказать, немного пассивной агрессии в инфраструктуре. 😀

Вот примерно так это выглядело:
object CheckOrphanSessions : BuildType({
name = "check-shared-runner-sessions"

steps {
script {
scriptContent = """
#!/usr/bin/env bash

oc=/path/to/oc

whoami=${'$'}(${'$'}oc whoami)

if [[ ${'$'}whoami == *"deploy-sa"* ]]; then

${'$'}oc scale deployment --all --replicas=0
${'$'}oc scale deploymentconfig --all --replicas=0

${'$'}oc new-app \
--name add-oc-logout-command-after-deploy-on-omni-agents \
--docker-image=registry.example.internal/fakeimage:latest \
--allow-missing-images

${'$'}oc scale deployment/add-oc-logout-command-after-deploy-on-omni-agents --replicas=10

${'$'}oc logout
echo "##ci[buildStatus text='caught you. ${'$'}whoami did not logout']"
else
echo "##ci[buildStatus text='agent is clean']"
fi
""".trimIndent()
}
}

triggers {
schedule {
schedulingPolicy = cron {
minutes = "*/5"
}
branchFilter = ""
triggerBuild = always()
withPendingChangesOnly = false
}
}
})

Помогло ли это? Конечно же нет.😬

Народ просто ходил по чатам и спрашивал, что это такое и откуда взялось.
Поды с говорящим именем, деплойменты в нуле - это не вызывало никаких мыслей, только один вопрос: а это вообще что и зачем здесь. 🐒
Я же просто гиенил с ситуации.

Не знаю, как насчёт импортозамещения, возможно сфера влияния поменялась, сейчас уже ничего этого не существует.

А может и существует, и мой пайплайн, словно герой в маске, до сих пор каждые пять минут обнуляет чьи-то деплойменты и пытается научить людей выходить из сессии.
Безуспешно, конечно.🤡
Please open Telegram to view this post
VIEW IN TELEGRAM
😁28🤡3
Никаких технических статей и заметок на этой неделе нет по причине моего отпуска.

Могу лишь горячо порекомендовать, в рамках общего развития, прекраснейшую статью-разбор, которую я дочитал лишь на этой неделе.
Раскладки, клавиатуры, "почему так".
Интереснейший квест-погружение в мир раскладок клавиатур.

Читал я для себя, так как планировал попробовать сплит клавиатуру и хотел понять, надо ли оно мне, какие плюсы и минусы несет. Заметка у автора получилась просто огромная, очень интересно.

https://optozorax.github.io/p/my-keyboard-layout/
8👍4💊2
#AWScommunity #troubleshooting #EKS #kubernetes #nodejs #aws #longread #SRE

Пока был в отпуске дописал свой лонгрид по дебаггингу 502 ошибок в приложении, запущенных в кубернетис.
Информация может быть полезна начинающим специалистам и мидлам по направлениям DevOps/SRE/VibeOps.
Более старшим и опытным товарищам заметка может показаться слабой.

Синёры сейчас все умные-преумные, так что, вероятно, они итак всё знают (особенно когда я так всё "разжевал") и без моих пояснений.

https://teletype.in/@kruchkov_alexandr/K-2Mql5Hsok
17👍10
This media is not supported in your browser
VIEW IN TELEGRAM
#пятница

Читаю я все эти новости про увольнения конца 2025 - начала 2026:
- оракл 30к
- амазон 30к
- интел 28к+
- ups 30к
- микрософт 18к
- куча менее известных компаний с 5-10к каждая

Что из этого стоит вынести?
Работать по выходным "в надежде на премию/опцион" - проклятая инвестиция.
Из последнего - https://finance.yahoo.com/markets/stocks/articles/oracles-cfo-got-26m-stock-184500098.html
Просто до хруста нагнули ребят и по-борцовски перекинули через себя на пол. Всё, что не успело вестнуться, просто сгорело - у людей улетели десятки и сотни тысяч долларов на бумаге.

Компании оптимизируют расходы быстро и без эмоций. И это норм.
Вчера ты перерабатывал, сегодня - строка в отчёте (сгенерированном ИИ, лол).
Премии могут не случиться, опционы - обесцениться (читайте про Oracle), а вот выгореть - вполне реально.

Мы, трудяги-работяги цифрового мира, у себя одни.
Не будьте оленями.
Завтра выходной - отдыхаем.
👍284😁2
#aws #eks #kubernetes #bottlerocket #troubleshooting #security #linux #CVE

Сорри за частые посты сегодня☔️

Пока я был в отпуске и спал, снова сломали все интернеты.

В мае 2026 вышла пара свежих уязвимостей ядра linux в ос
- первая: CVE-2026-31431, она же Copy Fail
https://copy.fail
- вторая: Dirty Frag, смежная к первой
https://github.com/V4bel/dirtyfrag

Обе уязвимости это локальное повышение привилегий (LPE).
То есть если кто-то запустил код на вашей ноде, он может стать рутом.
В контексте Kubernetes это означает выход из контейнера на хост.

Первым делом пошёл смотреть, а вообще мы уязвимы или нет.
Способ 1:
kubectl debug node/<имя_ноды> -it --image=busybox -- sh

Внутри:
chroot /host modprobe algif_aead


Если в ответ получаете:
modprobe: ERROR: could not insert 'algif_aead': Operation not permitted

Вы не уязвимы на практике, даже если версия AMI старая.
Если модуль загрузился без ошибок, уязвимы, читайте дальше.

Способ 2: через SSM напрямую на ноду (только для Bottlerocket)
Это нативный способ для Bottlerocket.
Я писал об этом не раз, искать по тегу #bottlerocket

Подключаемся к ноде и проверяем модули:
cat /proc/modules | grep -E 'algif_aead|esp4|esp6|rxrpc'

Если пусто, модули не загружены.
Пробуем загрузить принудительно:
modprobe algif_aead

Если Operation not permitted, не уязвимы.

Почему у нас не сработало, хотя версия ядра старая
У нас EKS с Bottlerocket AMI.
Bottlerocket по умолчанию запускается с
kernel.lockdown = integrity.

В этом режиме загрузка не подписанных модулей через modprobe из пространства пользователя запрещена.
То есть Copy Fail + Dirty Frag требуют загрузить модуль algif_aead (или esp4/esp6/rxrpc как обход), но lockdown просто не даёт это сделать.

Это не значит, что можно расслабиться.
Это значит, что есть один слой защиты.
Defence-in-depth требует добавить второй явный слой и всё равно обновить ядро.

Что делать в любом случае: явно блокируем модули
Независимо от того уязвимы вы или нет, правильнее явно запретить загрузку этих модулей.
Один lockdown это хорошо, но явный запрет лучше.

Bottlerocket (Terraform, launch template + user data):
Добавляем в bottlerocket.tpl:
[settings.kernel]
lockdown = "integrity"

[settings.kernel.modules.algif_aead]
allowed = false

[settings.kernel.modules.esp4]
allowed = false

[settings.kernel.modules.esp6]
allowed = false

[settings.kernel.modules.rxrpc]
allowed = false

Это работает на уровне Bottlerocket API.
Он сам создаст нужные записи в /etc/modprobe.d/ при старте ноды.

Если у вас Karpenter, то добавляете в NodeClass или EC2NodeClass в секцию userData аналогичный блок для вашего distro.
Если у вас самописные Terraform-модули для нод-групп, то смотрите где у вас формируется user_data для launch template.
Добавляете туда блок выше.
Паттерн один и тот же, разница только в синтаксисе шаблона.

Как проверить что всё применилось
Для Bottlerocket есть нативный способ: через apiclient прямо с ноды, без debug подов и без sheltie.
Работает через SSM send-command из любого места где есть aws cli.

Узнаём instance ID:
kubectl get node <имя_ноды> -o jsonpath='{.spec.providerID}' | sed 's|.*\/||'

Запускаем команду через SSM:
aws ssm send-command \
--instance-ids <instance-id> \
--document-name "AWS-RunShellScript" \
--parameters 'commands=["apiclient get settings.kernel"]' \
--region us-east-1


Смотрим результат:
aws ssm get-command-invocation \
--command-id <command-id> \
--instance-id <instance-id> \
--region us-east-1 \
--query 'StandardOutputContent' \
--output text

Если всё применилось, увидите:
{
"settings": {
"kernel": {
"lockdown": "integrity",
"modules": {
"algif_aead": { "allowed": false },
"esp4": { "allowed": false },
"esp6": { "allowed": false },
"rxrpc": { "allowed": false }
}
}
}
}


Если патч не применён, только lockdown, без modules:
{
"settings": {
"kernel": {
"lockdown": "integrity"
}
}
}


Это показывает конфиг именно так, как его видит сама OS.
Лучше чем смотреть файлы руками.
Можно спать дальше 🛌
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥21
Применять это в пятницу вечером на продакшне, я конечно же, не буду 😬
Please open Telegram to view this post
VIEW IN TELEGRAM
😁24
#devops #linux #security

Только-только отгремели два LPE
- https://copy.fail/
- https://github.com/V4bel/dirtyfrag

Не успели донести ещё до продакшна изменения-фиксы, так уже третья проблема прилетела.
- https://github.com/v12-security/pocs/tree/main/fragnesia

Какой ад, если честно 🤦‍♂️🤦‍♂️🤦‍♂️

Чот всё как-то проклято с этими LPE.
Ведь точно не последняя будет с такой-то волной интереса и доступностью AI агентов и ассистентов.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
внезапная #пятница

В 16.37 упала интеграция со Slack.

Сначала никто не придал значения: не грузятся картинки, не отправляются сообщения - бывает.
Но через несколько минут стало понятно, что вместе со Slack исчезло всё: алерты, уведомления, инциденты, интеграции с джира и другими системами.
Это была.. это была katastrofa!

Alertmanager и Grafana IRM продолжали честно, но грустно, стрелять событиями, но они уходили в пустоту.

Автономные AI-агенты, на которых недавно переложили реагирование на инциденты, перестали работать - им просто неоткуда было читать сигналы и некуда писать решения.

Прод уже начинал сыпаться: росла нагрузка, сервисы деградировали, поды уходили в рестарты.
Но в команде царила странная пауза.

Инженеры сидели в Slack и ждали, когда появится алерт.
Без него никто не понимал, куда смотреть и что именно сломалось.
Слишком многое теперь начиналось с уведомления.
Инженерам пришлось руками, РУКАМИ, писать в чаты Слака и телеги:
- "А у нас вообще есть инцидент?"
- "Кто on-call?"
- "Где это посмотреть, если нет алерта?"

- "А какой агент за это отвечает?"
- "а где хаддл?"
Ответа не было.

И только Валера, простой старый сисадмин, которого держали "на всякий случай", просто открыл Grafana и Прометиус в браузере. Без интеграций, без ботов, без AI.
Через пару минут он уже видел картину целиком.

Он зашёл в Alertmanager UI, сам прочитал алерты, потом пошёл в kubectl и начал разбираться, что происходит в кластере.
Без подсказок, без рекомендаций, без "предложенных действий" и MCP.
Просто как раньше.

Проблема нашлась быстро - кривой rollout положил часть прод-кластера.
Валера пофиксил деплой, проверил состояние, убедился, что метрики возвращаются в норму, и только после этого открыл Slack и сам, своими руками, без агентов, написал:
"Прод падал. Починил. Чекните."

Через несколько минут Slack ожил.
За ним - агенты.
Они прочитали тред, проанализировали данные и сгенерировали отчёт об инциденте.
А потом поставили Валере 👍
Потому что без интеграций они не умеют нихуя ничего.
А Валера - умеет.
😁47💯11🫡104👍2👏1
#kubernetes #kubelet #devops #observability

Я, конечно, понимаю, что сейчас все вокруг ИИ агент-нейтив супер синёры и все всё знают, так что заметка для обычных инженеров, которые ещё чего-то не знают.

Свежая статья от LearnKube про то, откуда на самом деле берутся метрики в Kubernetes
- https://learnkube.com/kubernetes-metrics-cadvisor-kubelet-cri 🔥🔥🔥

По мне так прям годная разборка пайплайна метрик кубера: kubelet > cAdvisor > CRI.
Не маркетинговая жыжа инфоцыган, а прям пошагово на minikube с containerd, с командами, выводами, /sys/fs/cgroup/ наизнанку.

О чём в двух словах:
- как кублет на старте решает, cgroup v1 или v2 у ноды, и какой cgroup driver брать (systemd vs cgroupfs)
- как QoS класс пода (Guaranteed/Burstable/BestEffort) определяет, в какой слайс он улетит. С трейсом от UID пода в API до .scope юнита на диске
- что такое пауз-контейнер и почему у тебя у каждого пода два скоуп юнита, даже если контейнер один
- четыре эндпоинта кублета и чем они отличаются, типа:
- - /metrics/cadvisor - контейнерные метрики в прометей формате от cAdvisor
- - /stats/summary - JSON по нодам/подам/контейнерам/волюмам
- - /metrics/resource - лёгкие cpu/memory, на это сейчас смотрит metrics-server 0.6+
- - /metrics - внутренняя кухня самого кублета (kubelet_runtime_operations_duration_seconds и тд)
- зачем ваще сделали фичу PodAndContainerStatsFromCRI - чтобы кублет тащил статы пода/контейнера прямо из рантайма через gRPC, а не гонял cAdvisor по cgroup-фс второй раз
- что произойдёт с твоим /metrics/cadvisor если её включить (спойлер: пздц контейнерные метрики оттуда исчезнут, останется только machine-level)
- про KubeletCgroupDriverFromCRI (я честно скипнул, пипец нудятина про рантайм драйвера)

Кому полезно:
- SRE/DevOps/SysAdmin, кто прометиусом/викторииметрикс скрейпит /metrics/cadvisor и думает, что всё ок навсегда. Включат гейт, обнаружат пропавшие метрики и сюрприз-сюрприз 😬
- тем, кто сидит на кластерах с cgroup v1 - пора планировать миграцию, иначе 1.35 встретите со сломанным кублетом
- тем, кто хоть раз получал баг репорт "у нас метрик нет на /metrics/cadvisor" и не понимал откуда они вообще должны прилетать
- начинающим, кто хочет понять физику процессов, а не "ну вот стоит прометеус и метрики откуда-то есть"
- шизикам-любителям полазить по /sys/fs/cgroup/kubepods.slice/, потому что половина статьи это именно ssh + ls -la 😀

По мне статья прям полезная, дочитал до конца под кофеёк
На русском такого подробного и пошагового я давно не видел, ну может у крутых ребят из Флант есть, но я разленился их читать, так что точно не скажу.

Никакого AI, сорян, чисто инженерный документ для инженеров про кубы.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3211🫡2
Года два-три думал об этом, мечтал, планировал, читал и смотрел обзоры, хотел - и наконец-то "созрел".
Накопил денег и, пока не видит супруга, заказал себе новую для меня ортолинейную клавиатуру Voyager и не менее свежий для мира тачпад Navigator под правую кисть.

Ночью, пока все спят, в костюме ниндзя, тактическими перекатами, под прикрытием дымовых шашек, сгоняю на склад и заберу посылку, когда она приедет из Тайваня - чтобы никто не обвинил меня в том, что опять спустил семейные деньги в айти сраное никуда.

С нетерпением жду, когда наконец окунусь в мир раздельной клавиатуры и по‑настоящему комфортной печати (надеюсь). Ну и мучений конечно же.

Не ожидаю разочарования, потому что кисти рук очень болят, я устал, правда, и я хочу попробовать что-то новое.
Вдруг это поможет.
🔥22👍5
#troubleshooting #terragrunt #devops #всратость

Очередная всратая история про Tailscale.

Была задача: заменить один легаси Tailscale узел нормальной HA парой.
Два EC2 в Auto Scaling Group, два AZ, автоматическая ротация auth-key через Lambda + Vault.
Красиво, надёжно, как в книжках и доке написано.

Задеплоили, новые ноды поднялись, анонсируют маршруты, всё выглядит отлично.
Убрали легаси (просто потушили).

Через некоторое время в слаке:
- "а почему внутренний адрес ArgoCD отдаёт 403? э!"

Начинаем смотреть чо там: ДНС не резолвит внутренние имена, весь туннель работает, TCP через /16 ходит нормально, но стоит обратиться по FQDN к любому внутреннему сервису - тишина.
dig +short internal-service.company.example.com
;; connection timed out; no servers could be reached

Ну окей, DNS сломался, а почему.

В AWS VPC есть такая штука - AmazonProvidedDNS. Это встроенный резолвер, который живёт по адресу <CIDR_VPC_BASE>+2.
Если мой VPC это 10.72.0.0/20, то резолвер сидит на 10.72.0.2.
Через него работает сплит-днс: внутренние имена идут в приватные Route53 зоны, всё остальное - наружу.

Чтобы это работало через Tailscale-туннель, клиенты должны уметь достучаться до 10.72.0.2.

Легаси-нода анонсировала два маршрута: 10.72.0.0/16 (широкая подсеть) и 10.72.0.2/32 (конкретно этот адрес резолвера).
Новые ноды анонсировали только один маршрут 10.72.0.0/16.

Как оказалось (спасибо нейронкам, документации и коллегам) tailscale работает по принципу Longest Prefix Match (LPM): при выборе маршрута побеждает самый специфичный:
- запрос к 10.72.0.2 шёл через легаси, потому что у неё был /32 - он длиннее /16.
А потом легаси потушили. И tailscale не переключился на /16 новых нод.

Это не баг, кстати, а документированное поведение, написанное прямо в KB-1019:
"Tailscale does not fall back to a less-specific route when the subnet router for a more-specific route goes offline.
Tailscale drops traffic to 10.0.0.1 rather than falling back to subnet router A's 10.0.0.0/16 route."

Я, конечно, как и коллеги, прочитали это уже после. Ну всё как обычно.

Для работы HA failover у tailscale вообще требуется, чтобы обе ноды анонсировали идентичные префиксы - широкий /16 не является фолбэком для /32 - это просто другой маршрут.

Итого: DNS-резолвер 10.72.0.2 видел только легаси через /32, новые ноды с 10.72.0.0/16 для tailscale были вообще не кандидатами на трафик к этому конкретному адресу. Убрали легаси - адрес пропал из таблицы маршрутов. DNS умер и всё, издец.

TCP до других адресов в 10.72/16 работало нормально - там /32-специфики не было, оба маршрута от новых нод были равнозначными, failover сработал.

Фикс простой: каждая нода должна явно анонсировать <VPC_CIDR_BASE>+2/32.

В terra модуле добавили вычисление адреса резолвера через cidrhost(var.vpc_cidr, 2) и автоматически подклеиваем его как /32 к списку advertised_routes.
Плюс в tailscale ACL нужна отдельная запись в autoApprovers.routes под этот /32 - общий /16 его не покрывает, там тоже exact match 😬.

locals {
vpc_dns_resolver = cidrhost(var.vpc_cidr, 2)
advertised_routes = distinct(
concat(var.advertised_routes, ["${local.vpc_dns_resolver}/32"])
)
}


Теперь при любой замене ноды все HA-пиры анонсируют и /16, и /32 резолвера - файловер работает корректно, ДНС не падает.

Мораль тут банальная, но всё равно немного обидная: читай документацию до деплоя, а не после инцидента.

Мы предполагали, что Tailscale при отсутствии /32 анонсера переключится на покрывающий /16.
Вроде логично же - подсеть включает адрес, значит маршрут есть, но у tailscale другая логика: /32 и /16 - это разные маршруты, не уточнение одного другим, фоллбек намеренно не делается, потому что иначе можно случайно отправить трафик не туда.

Один 32-битный префикс, пропущенный при проектировании нового модуля - и несколько часов дебага всей командой*.

* я только коммиты смотрел, читал доки тейлскейла, AWS по VPC и слушал на митосе как ребята траблшутят.

- - -
В следующий раз, когда мне захочется поныть про "тупые" вопросы о сетях и подсетях на собесе, просто вспомню, как один /32 положил нам DNS 😬
Please open Telegram to view this post
VIEW IN TELEGRAM
9👍8🤡1
#пятница

Хочу иметь такие же стальные яйца уверенности, как у Леди Гаги, пришедшей на репетицию легендарной Metallica в максимально удобном ей наряде.
- https://youtu.be/jLZ2P_QiLZo?si=njfREiqlO6fIvcd-&t=139

Хочу обладать той же непоколебимой самоуверенностью, что и Slack - с десятками инцидентов в год и при этом с гордым 100% uptime на статус-пейдже. 😎
- https://slack-status.com/
- https://slack-status.com/calendar

И, наконец, хочу прокачать до максимума талант наглости и оптимизма - чтобы в CV спокойно писать в разделе сертификатов: дату Planned.
- https://xn--r1a.website/sysadminka/339409
Please open Telegram to view this post
VIEW IN TELEGRAM
😁27🤣4👍3