В какой-то момент в нашей команде стало очевидно: пора тащить всю инфраструктуру в 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 — попробуйте, звёздочку тоже можно :)

Комментарии (0)