В какой-то момент в нашей команде стало очевидно: пора тащить всю инфраструктуру в 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 — попробуйте, звёздочку тоже можно 🙂
ссылка на оригинал статьи https://habr.com/ru/articles/933148/
Добавить комментарий