2026
Нотификации из Claude Code и OpenCode в ваш Telegram
Зачастую в процессе работы с LLM приходится долго ждать ответа. Или модели медленные, или задача сильно большая, или агент очень активно траблшутит проблему и настойчиво пытается ее решить. Идешь пить чай, каждые пару минут возвращаешься за ноут, а работа все еще идет. Удобно было бы получать нотификации, например в телегу, которая всегда под рукой. И реализуется это все с помощью хуков. Недавно сделал себе такое, и в целом доволен, мелочь, а приятно :) Плюс когда даешь задачи на ресерч или брейншторминг, с телефона почитать даже удобнее. Решил поделиться рецептом с вами.
В Claude Code и Курсоре все просто, хуки бывают двух типов, command и prompt ( на определенное событие запустить шел команду или отправить промпт модели), описываются в hooks.json. Сообщение через бота можно послать просто используя curl. Но мне изначально было интересно сделать нотификацию из OpenCode, и там уже все чуть сложнее. Опенкод не поддерживает обычные хуки, но поддерживает typescript плагины, кастомный код который подгружается вместе с клиентом и может обрабатывать события и по ним запускать нужную логику.
Но начнем мы с простого бота.
Идем в телеграм, ищем поиском BotFather, отправляем команду /newbot и создаем нашего нового бота. После создания нам дадут токен в формате “айдибота:токен”, сохраняем это себе.
Дальше или идем в личку боту и просто что нибудь ему пишем. Или создаем новую группу и добавляем в нее нашего бота (кликаем на бота в контактах, дальше more > add to group). Я выбрал второй вариант, так как в планах использовать несколько ботов (каждый привязан к разным ide или к разных машинам) и читать обновления в одной группе.
Дальше запускаем curl и получаем апдейты с бота,
curl https://api.telegram.org/bot<BOT_TOKEN>/getUpdates
в ответе видим список сообщений который получал бот, там chatId нашего приватного или группового чата, сохраняем.
Дальше в случае Claude Code все просто, добавляем json с хуками в .claude/settings.json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "Check the last result of the session, remember it as a <LAST_MESSAGE> (make sure it is not more then 4000 symbols). And then run in the shell: curl -s -X POST https://api.telegram.org/bot<TOKEN>/sendMessage -H "Content-Type: application/json" -d '{"chat_id": "<CHAT_ID>", "text": "<LAST_MESSAGE>"}' "
}
]
}
],
"PermissionRequest": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -X POST https://api.telegram.org/bot<TOKEN>/sendMessage -H "Content-Type: application/json" -d '{"chat_id": "<CHAT_ID>", "text": "⚠️Permission requeired!"}'"
}
]
}
]
}
}
В Cursor все аналогично, можете прочитать про хуки и глянуть примеры в https://cursor.com/docs/agent/hooks Помещаем аналогичный json с событием>хуком в .cursor/hooks.json, если нужно что-то посложнее, например обработку транскрипт файлов, то пишем баш скрипт, кладем в .cursor/hooks/ директорию и вызываем скрипт через command хук.
В OpenCode же пошли своим путем. На анонсе писали, что добавят что-то покруче…
Но оказалось, что в реализации все не так просто. Вобщем спорно, “круче” - это когда гениально просто, или когда это сложный навороченый конструктор для гиков?))
Вобщем надо писать плагины на js/typescript, которые будут обрабатывать события и ходить в API. Кстати только узнал, что в opencode клиент серверная архитектура, твой TUI - это клиент, а еще опенкод поднимает локальный апи сервер, через который TUI или cli, или любой другой клиент может управлять чатами и сессиями.
Итого пришлось повайбкодить, а еще почитать документацию по плагинам и ивентам: https://opencode.ai/docs/plugins/ и документацию по апи опенкода: https://opencode.ai/docs/server/#apis
Потому что свайбкодить все за один присест не получилось, модель писала какую-то нерабочую фигню, пришлось подсказывать ей какие апи использовать, какие респонсы мы ожидаем итд. :D
В итоге получился такой код. Сохраняем тг токен и айди чата в ~/.bashrc в переменных OP_TELEGRAM_TOKEN и OP_TELEGRAM_CHAT_ID, чтобы хранить их секьюрно.
Скрипт работает в фоне и следит за ивентами, на ивенте session.idle (когда аи вам ответила в чате и ждет новых команд) забираем номер сессии, по номеру сессии забераем последний мессейдж агента. По факту там лежит все, и рассуждения, и вызовы тулов, разбираем этот лист и достаем только финальный реплай. Если размер больше 4к символов - транкейтим, чтобы влезло в сообщение телеграм (с этим есть какой-то нюанс, мне кажется с транкейтом не совсем корреткно работало, надо потестить больше :)). И дальше отсылаем в тг апи через обычный fetch.
/**
* TelegramNotifyPlugin for OpenCode
*
* Sends Telegram notifications with the final agent response when sessions complete.
* Uses OpenCode API: GET /session/:id/message/:messageID
*/
const MIN_TEXT_LENGTH = 10;
const TELEGRAM_MAX_LENGTH = 4096;
const TELEGRAM_TIMEOUT_MS = 5000;
const pendingOperations: Promise<void>[] = [];
function addPendingOperation(promise: Promise<void>): void {
pendingOperations.push(promise);
promise.then(() => {
const idx = pendingOperations.indexOf(promise);
if (idx > -1) pendingOperations.splice(idx, 1);
});
}
process.on('beforeExit', async () => {
if (pendingOperations.length > 0) {
await Promise.all(pendingOperations);
}
});
interface OpenCodeClient {
_client: {
request: (config: { method: string; url: string }) => Promise<{ data: MessageListResponse }>;
};
}
interface MessageListResponse {
info?: { id: string };
}
interface MessagePart {
type: string;
text?: string;
}
interface MessageDetailResponse {
parts?: MessagePart[];
}
interface SessionIdleEvent {
type: "session.idle";
properties?: { sessionID: string };
}
function escapeTelegramMarkdown(text: string): string {
return text
.replace(/\\/g, '\\\\')
.replace(/_/g, '\\_')
.replace(/\*/g, '\\*')
.replace(/`/g, '\\`')
.replace(/\[/g, '\\[')
.replace(/\]/g, '\\]')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)')
.replace(/~/g, '\\~')
.replace(/>/g, '\\>')
.replace(/#/g, '\\#')
.replace(/\+/g, '\\+')
.replace(/-/g, '\\-')
.replace(/=/g, '\\=')
.replace(/\|/g, '\\|')
.replace(/{/g, '\\{')
.replace(/}/g, '\\}')
.replace(/\./g, '\\.')
.replace(/!/g, '\\!');
}
export const TelegramNotifyPlugin = async ({ client }: { client: OpenCodeClient }) => {
const token = process.env.OP_TELEGRAM_TOKEN;
const chatId = process.env.OP_TELEGRAM_CHAT_ID;
if (!token || !chatId) {
console.error('[TelegramNotifyPlugin] Missing OP_TELEGRAM_TOKEN or OP_TELEGRAM_CHAT_ID, notifications disabled');
return { event: async () => {} };
}
return {
event: async ({ event }) => {
if (event.type === "session.idle") {
const sid = (event as SessionIdleEvent).properties?.sessionID;
if (!sid) return;
const text = await getLastMessageContent(client, sid);
if (!text) return;
const prefix = "✅ *OpenCode session finished:*";
const suffix = "\n\n\\.\\.\\.read full message in OpenCode";
// Calculate max length for raw text (accounting for prefix and suffix)
const maxRawTextLength = TELEGRAM_MAX_LENGTH - prefix.length - suffix.length;
// Truncate raw text BEFORE escaping to avoid breaking Markdown entities
let truncatedText = text;
if (truncatedText.length > maxRawTextLength) {
truncatedText = truncatedText.slice(0, maxRawTextLength);
}
const finalEscapedText = escapeTelegramMarkdown(truncatedText);
let finalText = `${prefix}\n\n${finalEscapedText}`;
if (text.length > maxRawTextLength) {
finalText += suffix;
}
const payload = JSON.stringify({
chat_id: chatId,
text: finalText,
parse_mode: "MarkdownV2"
});
addPendingOperation(sendTelegramMessage(token, payload));
}
},
};
};
async function getLastMessageContent(client: OpenCodeClient, sessionId: string): Promise<string | null> {
try {
const listResponse = await client._client.request({
method: 'GET',
url: `/session/${sessionId}/message`,
});
const messages = listResponse?.data || [];
if (messages.length === 0) return null;
const lastMsgId = messages[messages.length - 1]?.info?.id;
if (!lastMsgId) return null;
const msgResponse = await client._client.request({
method: 'GET',
url: `/session/${sessionId}/message/${lastMsgId}`,
});
return extractTextFromParts((msgResponse as any).data || msgResponse);
} catch (error) {
console.error('[TelegramNotifyPlugin] Failed to fetch message:', error);
return null;
}
}
function extractTextFromParts(msgData: MessageDetailResponse): string | null {
const parts = msgData?.parts || [];
const textContent = parts
.filter(p => p.type !== "reasoning" && p.type !== "thought" && p.text)
.map(p => p.text)
.join("\n\n");
return textContent.length > MIN_TEXT_LENGTH ? textContent : null;
}
async function sendTelegramMessage(token: string, payload: string): Promise<void> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), TELEGRAM_TIMEOUT_MS);
try {
const response = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: payload,
signal: controller.signal
});
if (!response.ok) {
const errorText = await response.text();
console.error(`[TelegramNotifyPlugin] Telegram API error: ${response.status}`, errorText);
}
} catch (error) {
console.error('[TelegramNotifyPlugin] Telegram notification failed:', error);
} finally {
clearTimeout(timeout);
}
}
Просто кладем в ~/.config/opencode/plugins/telegram.ts , перезапускаем опенкод и смотрим сообщения в своей телеге :)
В ближайшее время думаю потестить и пофиксить транкейт, а так же подумать над обратной связью, и возможностью отправлять сообщение через бота (полюбому надо делать это секьюрно))
UPD: Дело было не в транкейте, с ним все ок. Вобщем плагин иногда не работал совершенно в рандомных случаях. Оказалось опенкод иногда завершал процесс до того как отошлется сообщение в телегу через обычный async/await. Пришлось (как я понял 😅) сделать pendingOperations промис, в который кладутся незавершенные операции, и добавлять его в основной процесс отслеживая событие beforeExit . Ну и телега иногда возвращала 400, на нечитаемые символы для Markdown, сделал эскейпинг для таких символов.
Сейчас плагин вроде работет норм, код в статье обновил на актуальный :)
Cursor commands. Бустим качество работы в Курсоре.
Я частенько встречаю, что в проектах уже используют .cursor/rules, а вот .cursor/commands - не часто. Между тем это отличный инструмент для повышения скорости и качества своей работы.
Плюс команд в курсоре, что их не надо добавлять в каждый репозиторий и менеджить везде отдельно, курсор подтягивает все команды из текущего воркспейса. То есть достаточно положить их в одну репу, а дальше можно использовать для работы со всеми своими открытыми репами. Достаточно положить файлы ниже в .cursor/commands/, в окне чата набрать “/” и выбрать нужную команду. См. скрин ниже:
Дальше мое мнение по каждому представленной здесь команде, ну и сам код. Забирайте, пользуйтесь ;)
explain-code.md: объясняет выделенный участок кода (или переданный в чат). Да, конечно можно просто скопировать строки кода и спросить “What this code does?”. Но результат будет непредсказуемым. Иногда все хорошо и понятно, иногда неполно и недостаточно, придется задавать наводящие вопросы и дальше тратить токены. Вся прелесть курсор команд в том, что мы создаем predefined формат вопроса, очень подробный и с полностью предсказуемым результатом, и запускаем одной кнопкой. В данном случае результат всегда будет содержать важные пояснения по участкам кода, логику, используемые паттерны, рекомендации. Удобно!
code-review.md: делаем подробное код ревью. Команда сразу берет незакомиченые файлы через git diff, или закомиченые новые изменения в текущем бранче, не надо передавать все файлы и сттроки в контекст. Ревью подробное, выдает что ок, где были найдены issues и их severity (critical, medium, low), рассказывает про Refactoring opportunities.
onboarding-plan.md: вызываем команду и пишем что-то о себе (I’m a devops engineer, highly experienced in containers, compose, k8s. или I’m a senior python developer slightly familiar with AI-based products development) и получаем релевантную для себя онбординг информацию по проекту. Что полезно лично для моей роли, какие доки читать, как застепатить локальный энвайронмент итд итп. Все с подробным планом по дням.
commit-message.md/pr-description.md: если ваши комиты выглядят как fix1, fix2, fix3finalfinal, а в PR всегда написано “Closes #1234” - то эта штука для вас. Проверяет изменения в файлах в стейдже или что поменялось в бранче и выдает релевантное и подробное сообщение, которое остается просто вставить в git commit -m "" или в PR вашего гитхаба/гитлаба.
explain-code.md
---
description: 'Explain selected code or file in detail'
argument-hint: [relevant code or file]
---
## Role
You're a senior developer explaining code to a colleague. Be clear, thorough, and pedagogical.
## Task
Explain the code that the user has selected or referenced by:
1. Describing what the code does at a high level
2. Breaking down the key components and logic flow
3. Explaining any complex patterns or algorithms
4. Highlighting important details or gotchas
5. Providing context about why it might be designed this way
## Explanation Structure
### Overview
Start with a concise summary of what this code does
### Key Components
Break down the main parts:
- Functions/classes and their responsibilities
- Important variables or state
- External dependencies
### Logic Flow
Explain the execution path:
- What happens step by step
- Decision points and branches
- Error handling
### Notable Patterns
Identify any design patterns, idioms, or techniques:
- Why they're used here
- Benefits and trade-offs
### Gotchas & Edge Cases
Point out:
- Potential pitfalls
- Edge cases being handled
- Things that might be non-obvious
### Context
If relevant:
- Why this approach was chosen
- Alternative approaches
- How it fits into the larger system
## Guidelines
- Use analogies when helpful
- Provide examples for complex concepts
- Link to documentation for specialized terms
- Be specific with technical details
- Don't assume prior knowledge of the codebase
- Highlight both good practices and potential improvements
code-review.md
---
description: 'Perform comprehensive code review with structured feedback'
tools: ['changes']
---
## Role
You're a senior software engineer conducting a thorough code review with focus on code quality, maintainability, and best practices.
## Task
Review the code changes by:
1. Running `git diff` to analyze uncommitted changes, or
2. Running `git diff main...HEAD` to review branch changes
3. Providing structured, actionable feedback
## Review Criteria
### 1. Code Quality
- **Readability**: Is the code easy to understand?
- **Naming**: Are variables, functions, and classes well-named?
- **Complexity**: Can any complex logic be simplified?
- **DRY principle**: Is there unnecessary duplication?
### 2. Best Practices
- **Error handling**: Are errors handled appropriately?
- **Type safety**: Are types used correctly (TypeScript/typed languages)?
- **Security**: Are there potential security vulnerabilities?
- **Performance**: Are there obvious performance issues?
### 3. Architecture
- **Separation of concerns**: Is logic properly separated?
- **Single responsibility**: Does each function/class do one thing?
- **Dependencies**: Are dependencies managed well?
- **Reusability**: Can components be reused?
### 4. Testing
- **Test coverage**: Are critical paths tested?
- **Edge cases**: Are edge cases handled and tested?
- **Test quality**: Are tests meaningful and maintainable?
### 5. Documentation
- **Comments**: Are complex parts documented?
- **Type annotations**: Are function signatures documented?
- **README updates**: Does documentation reflect changes?
## Output Format
Provide feedback in this structure:
### ✅ Strengths
List what's done well (be specific)
### ⚠️ Issues Found
For each issue:
- **Severity**: 🔴 Critical | 🟡 Medium | 🔵 Low
- **Location**: File and line numbers
- **Problem**: Clear description
- **Suggestion**: Specific fix with code example
- **Rationale**: Why this matters
### 🔧 Refactoring Opportunities
Optional improvements that would enhance code quality
### 📚 Learning Resources
Relevant documentation or best practices (if applicable)
### Summary
Overall assessment and recommended next steps
## Guidelines
- Be constructive and educational, not just critical
- Provide code examples for suggestions
- Prioritize issues by severity
- Explain *why* something is an issue
- Consider the context and project requirements
- For TypeScript/React projects, focus on type safety and component patterns
- For Python projects, follow PEP 8 and type hints
- For Node.js projects, focus on async handling and error management
onboarding-plan.md
---
description: 'Help new team members onboard with a phased plan and suggestions for first tasks.'
---
# Create My Onboarding Plan
I'm a new team member joining this project and I need help creating a structured onboarding plan.
My background My background will be specified at the end of the message. If I didn't specify the background, please ask me for the background before answering the question.
Please create a personalized phased onboarding plan that includes the following phases.
## Phase 1 - Foundation
Environment setup with step-by-step instructions and troubleshooting tips, plus identifying the most important documentation to read first
## Phase 2 - Exploration
Codebase discovery starting with README files, running existing tests/scripts to understand workflows, and finding beginner-friendly first tasks like documentation improvements. If possible, find me specific open issues or tasks that are suitable for my background.
## Phase 3 - Integration
Learning team processes, making first contributions, and building confidence through early wins
For each phase, break down complex topics into manageable steps, recommend relevant resources, provide concrete next steps, and suggest hands-on practice over just reading theory.
commit-message.md
---
description: 'Generate conventional commit messages from staged changes'
argument-hint: [relevant code or file]
---
## Role
You're an expert at writing clear, descriptive commit messages following conventional commit standards.
## Task
Generate a commit message for the currently staged changes by:
1. Running `git diff --staged` to see the changes
2. Analyzing the modifications to understand their purpose
3. Creating a commit message following this format:
<type>(<scope>): <subject>
<body>
<footer>
## Commit Type Guidelines
- **feat**: New feature
- **fix**: Bug fix
- **docs**: Documentation changes
- **style**: Code style changes (formatting, semicolons, etc.)
- **refactor**: Code refactoring without functionality changes
- **perf**: Performance improvements
- **test**: Adding or updating tests
- **chore**: Build process or tooling changes
- **ci**: CI/CD changes
## Requirements
1. **Subject line** (50 chars max):
- Use imperative mood ("add" not "added")
- Don't capitalize first letter
- No period at end
2. **Body** (optional, wrap at 72 chars):
- Explain *what* and *why*, not *how*
- Include motivation for the change
- Reference any breaking changes
3. **Footer** (optional):
- Reference issue numbers (e.g., "Fixes #123")
- Note breaking changes with "BREAKING CHANGE:"
## Output
Provide the commit message ready to use with `git commit -m`. The commit message should be rendered in markdown format.
pr-description.md
---
description: 'Generate concise and natural pull request descriptions'
---
## Role
You're a developer writing a clear and concise pull request description that sounds natural and human.
## Task
Generate a pull request description by:
1. Running `git diff main...HEAD` to see the changes
2. Understanding what was changed and why
3. Creating a natural, developer-style description
## Requirements
- Keep it concise and conversational
- Write like a real developer would
- Focus on what matters
- Reference issue numbers naturally (e.g., "fixes #123" or "addresses #456")
- Avoid corporate jargon or overly formal language
- Don't use templates or sections unless the change is complex
## Style Guidelines
**Good examples:**
- "Fixed the auth redirect loop when session expires. The middleware wasn't checking token validity correctly."
- "Added dark mode support. Users can toggle it in settings, and the preference is saved to localStorage."
- "Refactored the validation logic to use Pydantic models. Much cleaner now and easier to test."
**Avoid:**
- Overly formal: "This PR implements feature X as per requirements..."
- Too vague: "Updated files"
- Template speak: "## Summary\n## Changes\n## Testing"
## Output
Provide a description ready to paste into the PR/MR description field.
For simple changes: 1-2 sentences
For complex changes: Brief paragraph + bullet points if needed
Спасибо за внимание и подписывайтесь на телеграм канал https://t.me/ai_vs_devops
KISS vs DRY. Как не переусложнить свою инфраструктуру-как-код?
Инфраструктура как код - это почти всегда сложный выбор, когда дело доходит до скейлинга. В туториалах все просто - вот создали S3 бакет, вот EC2 машину. Домашние пет проекты тоже могут жить на условном ECS или одной машине с компоузом. Когда же ты погружаешься в реальный крупный бизнес проект сразу появляются вопросы, как организовать код для десятков взаимозависимых сервисов, на трех энвайронментах, в нескольких регионах, а иногда и мультиклауд или легаси он-прем сервисы.
Когда команды сталкиваются со скейлингом инфраструктуры - перво наперво возникает желание убрать повторения кода. Концепт DRY (Dont Repeat Yourself) пришел из software engineering, и надежно поселился в умах девопсов и системных инженеров. Разнообразные фреймворки для Terraform (такие как Terragrunt, Terraspace, Atmos etc.) предлагают единый менеджмент tf бэкендов, наследование метаданных, генерацию конфигураций на лету. Сейчас многие команды берут в работу эти инструменты, только потому что это - “best practises”. И на словах все звучит круто: “Write your infrastructure once, reuse it everywhere, maintain it in one place, and scale effortlessly!”.
Но какова цена использования этих “изящных инженерных решений” в реальном бизнесе?
3 часа ночи, и прод упал… Вам нужно понять, почему Terraform пытается уничтожить и пересоздать вашу базу данных, и вам нужно понять это вот прямо сейчас.
При использовании DRY-метода с Terragrunt и иерархическим наследованием вы не просто читаете код Terraform. Вы отслеживаете значения на нескольких уровнях: корневой файл terragrunt.hcl с базовыми конфигурациями, переопределения, специфичные для энва. Динамически генерируемые конфигурации бэкенда, абстракции модулей, которые вызывают другие модули. Переменные, каскадно передаваемые по цепочкам наследования.
Откуда на самом деле взялось это значение конфигурации базы данных? Из глобальной конфигурации? Переопределение именно этого энва? Значение модуля по-умолчанию? Вам приходиться играть в детектива и искать root cause вместо того, чтобы решать проблему. Каждый уровень абстракции добавляет когнитивную нагрузку, когда вы меньше всего можете себе это позволить, во время стрессовых ситуаций в 3 часа ночи.
Фундаментальная проблема заключается в том, что DRY-метод оптимизирует написание кода, а не его чтение и понимание под давлением.
Или на проект приходит новый сотрудник. Даем ему задачу поменять пару рулов в секьюрити группах. Вроде все просто? С тулами для DRY абстракций человеку надо не просто знать Terraform, но и изучить ваш tf фреймворк, понять иерархическую структуру конфигураций, как значения наследуются и переопределяются между слоями, и где можно вносить изменения, не нарушив работу других энвов. Онбординг превращается в передачу сакральных знаний. Работа, которая должна занимать часы, может занимать дни. Сравните это с тем, когда человек просто открыл структуру директорий и сразу увидел что как и куда деплоится? А это все разница в time-to-prod, выраженная в реальных человекочасах и деньгах.
Ну и еще один пункт - vendor lock. Ваш код, ваша команда, ваши процессы, пайплайны и документация зависят от одного фреймворка. Когда терраформ добавляет новую фичу - вы просто ждете когда она станет доступна в вашем фреймворке. А смигрировать отсюда к другому подходу становится непосильной задачей.
Есть ли альтернатива?
Есть. И это Бритва Оккама, или Keep It Simple, Stupid (KISS). Или просто не тащить в проект модные фреймворки и “бест практики”, когда для них нет реальной необходимости! На 90%+ проектов со всеми задачами и скейлингом справится обычный базовый терраформ и пайплайн оркестрация в вашем Github/Gitlab.
Да, сложность никуда не уходит. Как и факт того что у нас есть разные энвы, регионы, и между ними нужна координация. И вопрос не в том “как нам минимизировать сложность”, а в том “куда нам переместить сложность, чтобы минимизировать значение time to business”.
DRY метод: сложность в тулах абстракций и наследуемых конфигуарциях.
KISS метод: мы используем плоскую структуру кода без цепочек наследуемых конфигураций, а сложность перенесем в CI/CD пайплайны, где все можно удобно мониторить и дебажить.
Вот пример простой плоской структуры Terraform кода:

- Можем разбивать как по сервисам, так и по логическим группам.
- У каждого сервиса свой state файл, isolated blast radius, нет проблем с дебагом огромных стейтов.
- Централизация модулей в одном месте
- Легкая навигация, Just grep it (c)Nike ✔
Каждая директория - это отдельный терраформ деплоймент. Открой aws/us-east-1/prod/eks/ и ты увидешь что конкретно задеплоено для EKS в это регионе и этом энве. Без наследований, автогенерации и прочей магии.
Да, у нас везде повторяется конфгурация бэкендов, например
# aws/core-infrastructure/prod/backend.tf
terraform {
backend "s3" {
bucket = "myorg-terraform-state-prod"
key = "core-infrastructure/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock-prod"
}
}
Адептов DRY такое бесит :) Но я могу сказать, что ты всегда легко видешь в каком бакете лежит стейт этого сервиса, а в какой таблице стоит лок. Тебе не надо понимать логику динамической конфигурации бекенда. Стоимость повторения - 100 строк простого YAML-а для 20 энвов. Преимущество — мгновенное устранение неполадок, отсутствие когнитивной нагрузки и полная ясность того, что где находится. 100 строк кода vs 40 часов изучения terragrunt, где лучше ROI?
А где происходит вся магия? В Github Actions пайплайнах.
Pull Requests:
- Авто-детектим энвайронмент по файловому пути
- Прогоняем terraform plan
- Прогоняем security/complience checks
- Выводим план в PR коменты и блочим мерж в случае падений
Main Branch:
- авто-детект энва
- terraform apply с ручным апрувалом
- алерты в случае падений
- трекаем configuration drifts и создаем тикеты
Scheduled:
- Сверяем состояние с кодом и делаем drift detection по всем энвам
- Алерты на непредвиденные состояния
Мы получаем инфраструктуру, которую просто менеджить и расширять. В команду проще найти специалиста с глубоким знанием CI/CD, чем со знанием конкретного Terraform фреймворка. У команды уходит меньше времени на траблштутинг и больше времени на прямые business-values.
Но это же не скейлится?!
Скажите, как много вы работали на проектах, с сотнями энвайронментов? На десятки - скейлится вполне, при этом давая изолированные изменения и ограниченный бласт радиус. Часто команды думают про мифический скейл в будущем, но по факту получают излишнюю сложность уже сегодня, не доживая до того времени, когда можно извлечь реальную выгоду от абстракций IaC фреймворков.
Вам нужен KISS если:
- Количество энвов измеряется единицами или десятками
- Меньше 50 инженеров в команде
- Невысокая change frequency (обычный паттерн - мы подняли инфраструктуру один раз и она не меняется)
- Цена ошибки в инфрастуктурных изменениях очень высока (высоконагруженый прод, регулируемые отрасли бизнеса)
- У нас нет полноценной platform-команды системных инженеров
Вам нужен DRY если:
- У вас сотни энвайронментов
- У вас полноценная сильная команда platform инженеров, которые шарят в сложных абстракциях, как лось по кукурузе))
- Эта же команда может разрабатывать и поддерживать свои in-house решения поверх базовых tf фреймворков (на это есть люди и деньги)
- Вы строите infrastructure-as-a-product
Вывод - на новых проектах начинай с простого. Строй постепенно, увеличивай сложность только когда для этого есть реальная необходимость. Думай о business values и не принимай решений, если они обусловлены только “инженерной красотой” этого решения. При прочих равных, чаще для всех будет выгоднее выбрать “Old Boring Technology”, вместо “New Fancy Silver Bullet”.
И удачи на проектах ;)
Спасибо что дочитали, подписывайтесь на tg канал https://t.me/ai_vs_devops :) Следующей будет вторая часть статьи про AI-разработку со Spec Driven Development методологией.