CDK Pipelines — миграция базы данных в конвейере

У меня есть конвейер CDK Pipelines, который обрабатывает самоизменение и развертывание моего приложения в ECS, и мне трудно понять, как реализовать миграцию базы данных.

Мои файлы миграции, а также команда миграции находятся внутри док-контейнера, который создается и развертывается в конвейере. Ниже приведены две вещи, которые я пробовал до сих пор:


Моей первой мыслью было просто создать шаг pre на сцене, но я считаю, что есть ситуация с курицей и яйцом. Поскольку команда миграции требует существования базы данных (а также наличия конечной точки и учетных данных), а шаг миграции — pre, стек не существует, когда эта команда запускается...

    const pipeline = new CodePipeline(this, "CdkCodePipeline", {
      // ...
      // ...
    }

    pipeline.addStage(applicationStage).addPre(new CodeBuildStep("MigrateDatabase", {
      input: pipeline.cloudAssemblyFileSet,
      buildEnvironment: {
        environmentVariables: {
          DB_HOST: { value: databaseProxyEndpoint },
          // ...
          // ...
        },
        privileged: true,
        buildImage: LinuxBuildImage.fromAsset(this, 'Image', {
          directory: path.join(__dirname, '../../docker/php'),
        }),
      },
      commands: [
        'cd /var/www/html',
        'php artisan migrate --force',
      ],
    }))

В приведенном выше коде databaseProxyEndpoint было всем, от параметра CfnOutput, SSM до простой старой ссылки на машинописный текст, но все не удалось из-за того, что значение было пустым, отсутствующим или еще не сгенерированным.

Я чувствовал, что это было близко, так как он отлично работает, пока я не попытаюсь сослаться на databaseProxyEndpoint.


Моей второй попыткой было создать контейнер инициализации в ECS.

   const migrationContainer = webApplicationLoadBalancer.taskDefinition.addContainer('init', {
      image: ecs.ContainerImage.fromDockerImageAsset(webPhpDockerImageAsset),
      essential: false,
      logging: logger,
      environment: {
        DB_HOST: databaseProxy.endpoint,
        // ...
        // ...
      },
      secrets: {
        DB_PASSWORD: ecs.Secret.fromSecretsManager(databaseSecret, 'password')
      },
      command: [
        "sh",
        "-c",
        [
          "php artisan migrate --force",
        ].join(" && "),
      ]
    });

    // Make sure migrations run and our init container return success
    serviceContainer.addContainerDependencies({
      container: migrationContainer,
      condition: ecs.ContainerDependencyCondition.SUCCESS,
    });

Это сработало, но я совсем не фанат. Команда миграции должна выполняться один раз в конвейере ci/cd при развертывании, а не при запуске/перезапуске или масштабировании службы ECS... Однажды мои миграции завершились неудачно, и это заблокировало облачное формирование, поскольку проверка работоспособности не удалась как при развертывании, так и естественным образом на откате, а также вызывает полностью разорванную петлю боли.

Любые идеи или предложения о том, как это сделать, спасут меня от потери оставшихся волос!

База данных также развернута с помощью того же конвейера? Вы хотите, чтобы миграции выполнялись каждый раз, когда выполняется конвейер? Или запускать только при создании ресурса БД?

fedonev 14.02.2023 14:44

@fedonev в настоящее время у меня все в одном стеке, но здесь есть 100% гибкость, чтобы настроить или разделить стек, поскольку приложение еще не запущено. Что касается того, когда запускать, в идеальном мире мне бы хотелось, чтобы команда migrate запускалась один раз при создании базы данных, а затем один раз при каждом запуске конвейера. Команда будет запущена до развертывания ECS, поэтому миграция произойдет до того, как новые изменения кода вступят в силу. Спасибо

Giovanni S 14.02.2023 15:03
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
91
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я бы не стал решать это на этапе сборки конвейера CDK.

Скорее я бы выбрал подход CustomResource. С Custom Resources, особенно в CDK, вы всегда знаете о зависимостях и о том, когда вам нужно их запустить. Это полностью теряется в контексте конвейера CDK, и вам нужно выяснить/реализовать его самостоятельно.

Итак, как же выглядит пользовательский ресурс?


// this lambda function is an example definition, where you would run your actual migration commands
const migrationFunction = new lambda.Function(this, 'MigrationFunction', {
      runtime: lambda.Runtime.PROVIDED_AL2,
      code: lambda.Code.fromAsset('path/to/migration.ts'),
      layers: [
        // find the layers here: 
        // https://bref.sh/docs/runtimes/#lambda-layers-in-details
        // https://bref.sh/docs/runtimes/#layer-version-
        lambda.LayerVersion.fromLayerVersionArn(this, 'BrefPHPLayer', 'arn:aws:lambda:us-east-1:209497400698:layer:php-80:21')
      ],
      timeout: cdk.Duration.seconds(30),
      memorySize: 256,
    });

      const migrationFunctionProvider = new Provider(this, 'MigrationProvider', {
      onEventHandler: migrationFunction,
    });

    new CustomResource(this, 'MigrationCustomResource', {
      serviceToken: migrationFunctionProvider.serviceToken,
      properties: {
        date: new Date(Date.now()).toUTCString(),
      },
    });
  }

  // grant your migration lambda the policies to read secrets for your DB connection etc.
// migration.ts
import child_process from 'child_process';
import AWS from 'aws-sdk';

const sm = new AWS.SecretsManager();

export const handler = async (event, context) => {
  // an event provides more flexibility than env vars
  const { dbName, secretName } = event;

  // Retrieve the database credentials from AWS Secrets Manager
  const secret = await sm.getSecretValue({ SecretId: secretName }).promise();
  const { username, password } = JSON.parse(secret.SecretString);

  // Run the migration command with the database credentials
  const command = `php artisan migrate --database=mysql --host=your-database-host --port=3306 --database=${dbName} --username=${username} --password=${password}`;
  child_process.exec(command, (error, stdout, stderr) => {
    if (error) {
      console.error(`exec error: ${error}`);
      return;
    }
    console.info(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);
  });
};

Custom-Resource принимает вашу миграционную лямбда-функцию. Lambda запускает фактическую команду для переноса вашей базы данных. Пользовательский ресурс применяется каждый раз при запуске развертывания. Это применяется через значение date. Вы можете контролировать выполнение, изменяя любое свойство в CustomResource.

Мне нравится этот подход, и он кажется самым простым, но команда artisan, используемая для миграции базы данных (а также файлов миграции), основана на Laravel (php). Вероятно, я мог бы заставить что-то работать с bref - bref.sh - но, может быть, я что-то упускаю в этом контексте?

Giovanni S 16.02.2023 23:06

Ааа, извините! Пропустил эту информацию. У меня нет опыта работы с PHP в AWS, но я проверил некоторые документы. Bref — это способ использования PHP в качестве среды выполнения в Lambda. Я отредактирую свой ответ.

mchlfchr 17.02.2023 11:17
Ответ принят как подходящий

Вы можете выполнять миграции (1) в рамках развертывания стека с помощью конструкции Custom Resource, (2) после развертывания стека или этапа с помощью шага post, (3) или после запуска конвейера с помощью правила EventBridge.

1. Внутри стека: миграции как пользовательский ресурс

Один из вариантов — определить ваши миграции как CustomResource . Это функция CloudFormation для выполнения определяемого пользователем кода (обычно в Lambda) в течение жизненного цикла развертывания стека. См. Ответ @mchlfchr для примера. Также рассмотрите конструкцию CDK Trigger, реализацию пользовательского ресурса более высокого уровня.

2. После стека или этапа: «пост» шаг

Если вы разделите свое приложение, скажем, на StatefulStack (базу данных) и StatelessStack (контейнеры приложений), вы можете запустить свой код миграции как postШаг между ними. Это подход, предпринятый в OP.

В вашем StatefulStack, производителе переменных, выставьте переменную экземпляра CfnOutput для значений переменных среды: readonly databaseProxyEndpoint: CfnOutput. Затем используйте переменные в действии миграции конвейера, передав их на шаг post как envFromCfnOutputs. CDK синтезирует их в CodePipeline Variables:

pipeline.addStage(myStage, { // myStage includes the StatefulStack and StatelessStack instances
    stackSteps: [
        {
            stack: statefulStack,
            post: [
                new pipelines.CodeBuildStep("Migrate", {
                    commands: [ 'cd /var/www/html', 'php artisan migrate --force',],
                    envFromCfnOutputs: { TABLE_ARN: stack1.tableArn },
                    // ... other step config
                }),
            ],
        },
    ],
    post: // steps to run after the stage
});

Параметр stackSteps метода addStage запускает пост-шаги после определенного стека на этапе. Опция post работает аналогично, но запускается после этапа.

3. После выполнения конвейера: правило EventBridge

Хотя это, вероятно, не лучший вариант, вы можете запускать миграции после выполнения конвейера. CodePipeline генерирует события во время выполнения конвейера. С помощью правила EventBridge прослушивайте CodePipeline Pipeline Execution State Change события, где "state": "SUCCEEDED".


Примечание о режимах отказа: три варианта имеют разные режимы отказа. Если миграция завершается сбоем в качестве пользовательского ресурса, развертывание StatefulStack завершится ошибкой (с откатом изменений) и выполнение конвейера завершится ошибкой. Если миграция реализована как шаг, выполнение конвейера завершится ошибкой, но StatefulStack не будет откатываться. Наконец, если миграция запускается по событию, неудачная миграция не повлияет ни на стек, ни на выполнение, поскольку они уже будут завершены, когда миграция запустится.

Отличный ответ, спасибо! Теперь мне интересно, возможно ли вообще №2? Срок миграции истекает, потому что он не может получить доступ к RDS (я думаю, это имеет смысл, потому что CodeBuild не находится в VPC, SG и т. д.). Кажется, я не могу добавить VPC из-за того, что он «пересекает границы этапа». Видел ваш ответ здесь stackoverflow.com/a/72010255/650241, который, похоже, объясняет проблему с № 2. Полагаю, я собираюсь попробовать № 1, если я что-то не упустил?

Giovanni S 16.02.2023 15:58

@GiovanniS Рад помочь. # 2 должен работать в вашем случае. CodeBuild может прекрасно работать с VPC с некоторой дополнительной конфигурацией. Точно так же ограничения в упомянутом вопросе, похоже, здесь не применяются. Ваш пост-шаг просто использует переменную CodePipeline из более раннего действия. При этом № 1 также является хорошим идиоматическим решением, если Lambda является вариантом для вашей задачи миграции.

fedonev 17.02.2023 09:24

Другие вопросы по теме