В какой-то момент в нашей команде стало очевидно: пора тащить всю инфраструктуру в Git — по-взрослому, через GitOps. Kubernetes у нас уже был, ArgoCD тоже. Осталось «дотащить» туда AWS-ресурсы, которые мы описываем с помощью AWS CDK.
Идея казалась простой: есть CDK-код в Git, запускается ArgoCD, всё красиво деплоится в облако. Но реальность оказалась совсем не такой. CDK — это не YAML и даже не Terraform. Это исполняемый код. GitOps — это про декларативность и kubectl apply
. CDK с этим не дружит.
Ожидалось, что наверняка есть готовый Kubernetes-оператор, который запускает cdk deploy
при изменении кода. Как это уже сделано для Terraform (через ArgoCD Terraform Controller), Pulumi, или хотя бы через ACK. Но после долгого ресерча выяснилось: нет ничего рабочего и production-ready.
Так появилась идея — написать собственный Kubernetes-оператор, который сможет:
раз в какое-то время (или по коммиту в Git) запускать
cdk deploy
;проверять
cdk diff
иcdk drift
для отслеживания изменений и дрифта;удалять CloudFormation-стэк, если ресурс удалили из Git;
интегрироваться с ArgoCD и Prometheus.
Получился полноценный GitOps-воркфлоу для AWS CDK — без пайплайнов, без ручных cdk deploy
, без дрейфующих стэков.
Под катом — расскажу, как мы подошли к проблеме, как устроен Custom Resource CdkTsStack
, какие фишки мы добавили (метрики, хуки, IAM-пользователи), и почему наш подход оказался практичнее, чем существующие альтернативы вроде Terraform Operator или Pulumi.
Какую задачу решаем
CDK Stack — это, например, проект на nodejs который описывает инфраструктуру в облаке AWS. С этим стэком мы можем делать следующее:
cdk deploy
преобразует TypeScript в CloudFormation и деплоит результат в облако. В случае повторного запуска, деплоит разницу
cdk diff
— показывает разницу между зедеплоеным стэком и кодом
cdk drift
— Запускает CloudFormation Drift detection. И показывает дрифты если кто то внес изменения, например, через консоль AWS
cdk destory
— удаляет стэк.
В отличии от Terraform, если кто то внес изменение вручную, то повторный запуск cdk deploy
не перезапишет эти изменения. А просто выдаст предупреждения. Подробней описано в этой статье
Нам нужно решение, которое периодически (в будущем по хуку из гита на каждый коммит) делает cdk deploy и деплоит все изменения. Так же периодическая проверка diff
и drift
Custom Resource Kubernetes
Придумаем примерную структуру нашего манифеста. Очивидно нам нужна ссылка на гит, где живет наш CDK код, а так же что мы с ним хотим делать (actions)
apiVersion: awscdk.dev/v1alpha1
kind: CdkTsStack
metadata:
name: my-first-stack
spec:
stackName: MyFirstS3Stack
source:
git:
repository: https://github.com/your-org/cdk-examples.git
ref: main
actions:
deploy: true
destroy: true
driftDetection: true
autoRedeploy: false
Сам по себе новый ресурс статичен. Это просто запись в базе данных. Нужен некто, кто будем смотреть за появлением и удалением новых объектов и делать соответсвующие действия. Т.е. когда запускается kubectl delete cdk my-first-stack
должна запускать cdk destroy
и удалять объекты из облака
Kubernetes Operator
За обработку наших ресурсов CdkTsStack будет отвечать kubernetes operator. По простому это просток контейнер, который через Kubernetes API понимает, что происходит с объектами kind: CdkTsStack
Небольшое ресеч показал, что самым «правильным» вариантом написания своего оператора является operator-sdk. Но там требуется знания Go, который я знаю на уровне вайб-кодинга. Так же попаплся вариант от Флант. https://github.com/flant/shell-operator Shell Operator превращает ваши шел-скрипты в kubernetes operator
Что в итоге получилось
Метрики
spec.actions.driftDetection: true
— на дрифт выдает prometheus метрики. Можно настроить алертинг в уже сущуствующую систему
Хуки
Одна из самых гибких и полезных возможностей оператора — это поддержка хуков. Это небольшие сценарии, которые можно запускать до и после ключевых операций: cdk deploy
, cdk destroy
, cdk diff
, cdk drift
.
Они позволяют встроить в процесс деплоя любые дополнительные действия, специфичные для вашей команды, окружения или политики безопасности. По сути, это аналог lifecycle-эвентов в CI/CD, только внутри Kubernetes-оператора.
Зачем это нужно?
Хуки позволяют:
отправлять уведомления (например, в Slack или Telegram) после успешного деплоя;
выполнять бэкапы (например, перед удалением базы данных);
автоматически создавать IAM-пользователей и сохранять их ключи в Kubernetes Secret;
проверять лимиты ресурсов до начала деплоя;
валидировать конфигурации или выполнять dry-run проверки;
интегрироваться с внешними системами (например, запускать Jenkins job или REST API вызов).
Хуки особенно полезны при работе с инфраструктурой, к которой предъявляются строгие требования по безопасности и автоматизации — когда важно, чтобы каждый шаг был контролируемым, логируемым и предсказуемым.
Примеры использования на практике:
После
cdk deploy
отправляется сообщение в Slack о том, что стек успешно развернут.Если при
cdk drift
обнаружены отклонения, оператор вызывает хук, который уведомляет об этом команду.Перед удалением стека с базой данных запускается скрипт, который делает автоматический бэкап в S3.
При создании стека с приложением оператор генерирует нового IAM-пользователя, создает ключи доступа и записывает их в Kubernetes Secret.
До начала деплоя хук проверяет, не превышены ли квоты AWS, например по количеству EC2-инстансов.
Почему это удобно?
Хуки полностью изолированы от основного кода оператора.
Их можно легко адаптировать под любые нужды — от самых простых до сложных CI/CD-сценариев.
Поведение можно менять, не пересобирая образ или перезапуская контроллер.
Поддерживается чистый GitOps-подход — все хуки можно держать рядом с манифестами в Git.
Если обобщить: хуки превращают оператор из просто автомата деплоя в полноценный инструмент для гибкого и безопасного управления инфраструктурой, без необходимости городить пайплайны и внешние обвязки.
Большой пример манифеста
apiVersion: awscdk.dev/v1alpha1
kind: CdkTsStack
metadata:
name: lambda-with-hooks
spec:
stackName: MyLambda-With-Hooks-Stack
credentialsSecretName: aws-credentials
awsRegion: us-east-1
source:
git:
repository: https://github.com/your-org/cdk-examples.git
ref: main
path: ./lambda-example
cdkContext:
- "environment=development"
- "functionName=my-demo-function"
actions:
deploy: true
destroy: true
driftDetection: true
autoRedeploy: false
lifecycleHooks:
# Pre-deployment validation and notifications
beforeDeploy: |
echo "? Starting deployment of stack: $CDK_STACK_NAME"
echo "? Region: $CDK_STACK_REGION"
echo "? AWS Account: $AWS_ACCOUNT_ID"
echo "? Timestamp: $(date -u +%Y-%m-%d\ %H:%M:%S\ UTC)"
# Basic environment validation
echo "? Validating deployment environment..."
# Check AWS CLI configuration
if ! aws sts get-caller-identity > /dev/null 2>&1; then
echo "❌ ERROR: AWS credentials not properly configured"
exit 1
fi
echo "✅ AWS credentials validated"
echo "✅ Pre-deployment checks completed successfully"
# Post-deployment testing and verification
afterDeploy: |
echo "? Successfully deployed stack: $CDK_STACK_NAME"
# Get stack outputs for validation
echo "? Retrieving stack outputs..."
STACK_OUTPUTS=$(aws cloudformation describe-stacks \
--stack-name $CDK_STACK_NAME \
--region $CDK_STACK_REGION \
--query 'Stacks[0].Outputs' \
--output json 2>/dev/null || echo "[]")
# Extract Lambda function name if available
FUNCTION_NAME=$(echo "$STACK_OUTPUTS" | jq -r '.[] | select(.OutputKey=="FunctionName") | .OutputValue' 2>/dev/null || echo "")
if [ -n "$FUNCTION_NAME" ] && [ "$FUNCTION_NAME" != "null" ]; then
echo "? Testing Lambda function: $FUNCTION_NAME"
# Simple health check - invoke the function
echo "? Running post-deployment health check..."
INVOKE_RESULT=$(aws lambda invoke \
--function-name $FUNCTION_NAME \
--payload '{"test": true, "source": "cdk-operator-healthcheck"}' \
--region $CDK_STACK_REGION \
/tmp/lambda-response.json 2>&1)
if [ $? -eq 0 ]; then
echo "✅ Lambda function health check passed"
else
echo "⚠️ Lambda function health check failed: $INVOKE_RESULT"
fi
fi
echo "✅ Post-deployment validation completed"
# Pre-destruction backup and safety checks
beforeDestroy: |
echo "⚠️ Preparing to destroy stack: $CDK_STACK_NAME"
# Create backup of important data if needed
echo "? Creating backup before destruction..."
# Log destruction for audit trail
echo "? Logging destruction event for compliance"
echo "✅ Pre-destruction preparations completed"
# Post-destruction cleanup and notifications
afterDestroy: |
echo "?️ Successfully destroyed stack: $CDK_STACK_NAME"
echo "? Performing post-destruction cleanup..."
echo "✅ Destruction completed at $(date -u +%Y-%m-%d\ %H:%M:%S\ UTC)"
# Drift detection notifications
afterDriftDetection: |
if [[ "$DRIFT_DETECTED" == "true" ]]; then
echo "? DRIFT DETECTED in stack: $CDK_STACK_NAME"
echo "? Region: $CDK_STACK_REGION"
echo "⚠️ Manual changes detected - review required"
# Optional: Send alert to Slack/Teams
# curl -X POST -H 'Content-type: application/json' \
# --data "{\"text\":\"? Drift detected in $CDK_STACK_NAME\"}" \
# $SLACK_WEBHOOK_URL
else
echo "✅ No drift detected in stack: $CDK_STACK_NAME"
fi
Вывод
Получилось решение, которое закрывает реальную дыру между CDK и GitOps. Без пайплайнов, без ручного cdk deploy
, без дрейфов и багов в 2 часа ночи.
Мы пока не решаем всё (поддержка только TypeScript, нет webhook на коммиты), но уже сейчас можно:
деплоить CDK-стэки из Git
отслеживать дрифт
оборачивать деплой в хуки и метрики
интегрировать всё с ArgoCD
Проект: github.com/awscdk-operator/cdk-ts-operator
Если вы используете CDK и хотите GitOps — попробуйте, звёздочку тоже можно :)