avatar Artículo

Cómo añadir CI/CD a mi proyecto CDK

Te mostraré cómo puedes desplegar un proyecto CDK usando las herramientas de desarrollo de AWS, y un enfoque diferente para crearlo con la consola de AWS y con IaC.


TLDR

Ya tengo un proyecto CDK en GitHub aquí, pero para desplegarlo tengo que ejecutar el comando del CDK Toolkit cdk deploy desde mi máquina local.

Quiero añadir automatización a mi proceso de despliegue e integrarlo con el ecosistema de AWS… así que usaré las herramientas de desarrollo de AWS para hacerlo.

Te mostraré 2 enfoques diferentes:

  • Crear el pipeline con la consola de AWS: ¿Por qué? Te ayuda a entender el bajo nivel de la solución y cómo funcionan los servicios de AWS
  • Crear el pipeline con IaC (CDK): ¿Por qué? Siempre debes intentar automatizar todo. En este caso, crearé el pipeline que nos permitirá desplegar el código CDK automáticamente.

Quiero implementar la solución más simple, con el principio KISS en mente, y por esta razón, mi diagrama de arquitectura es el siguiente:

solution-1

Explicación: En un despliegue CDK, no necesito ejecutar el comando cdk synth y gestionar los artefactos generados, así que la solución más simple es ejecutar el comando cdk deploy directamente.

Si necesitas más información al respecto, escribí un post relacionado: Cómo crear infraestructura con CDK.

Sin embargo, tras investigar, la recomendación de AWS para desplegar un pipeline CI/CD de proyectos CDK es algo similar a lo siguiente:

solution-2

Lo explicaré en detalle en este post.

He preferido incluir toda la información en el mismo post, aunque fácilmente podría dividirlo en 2 o 3, y este post tiene mucho contenido e imágenes.

1. Introducción

Antes de empezar a mostrarte cómo añadir el CI/CD, te presentaré los conceptos básicos involucrados:

SDLC (Software Development Lifecycle)

es un proceso para planificar, crear, probar y desplegar un sistema de información - Wikipedia

Dependiendo de dónde mires, habrá un número diferente de fases en el proceso SDLC.

Para este artículo, explicaremos qué significa CI/CD sobre 4 fases del proceso de lanzamiento de software: source, build, test y production (deployment):

software-release-process

CI/CD se refiere a Integración Continua y Entrega Continua e introduce automatización y monitoreo al SDLC completo.

Integración continua (CI) es una práctica de desarrollo de software donde los desarrolladores fusionan regularmente sus cambios de código en un repositorio central, después de lo cual se ejecutan compilaciones y pruebas automatizadas

  • Los objetivos clave de CI son
    • encontrar y abordar errores más rápidamente,
    • mejorar la calidad del software,
    • y reducir el tiempo que lleva validar y lanzar nuevas actualizaciones de software
  • La integración continua se enfoca en commits más pequeños y cambios de código más pequeños para integrar

Entrega continua (CD) es una práctica de desarrollo de software donde los cambios de código se compilan, prueban y preparan automáticamente para un lanzamiento a producción

  • Beneficios
    • Automatizar el proceso de lanzamiento de software
    • Mejorar la productividad del desarrollador
    • Mejorar la calidad del código
    • Entregar actualizaciones más rápido
  • El punto de la entrega continua no es aplicar cada cambio a producción inmediatamente, sino asegurar que cada cambio esté listo para ir a producción

Despliegue continuo (CD), las revisiones se despliegan en un entorno de producción automáticamente sin aprobación explícita de un desarrollador, haciendo que todo el proceso de lanzamiento de software sea automatizado.

Así que sí, el “CD” en “CI/CD” significa 2 cosas diferentes:

  • Entrega Continua (preparar despliegue a prod)
  • y Despliegue Continuo (desplegar automáticamente en prod)

2. CodePipeline para CDK con la consola de AWS

Primero, crearemos la solución con la consola de AWS porque ayuda a entender cómo funcionan los servicios involucrados.

Como queremos crear un nuevo Pipeline, debemos acceder al servicio CodePipeline y hacer clic en Create pipeline.

El paso 1 en CodePipeline es elegir la configuración del pipeline. Tenemos que crear un nuevo rol de servicio (o usar uno existente).

codepipeline1

El paso 2 es añadir la fuente de la etapa eligiendo el proveedor de origen. Tengo mi repositorio de código en GitHub, así que elijo GitHub (versión 2), pero podrías elegir uno diferente.

Hay 2 opciones para el proveedor de origen de GitHub:

  • versión 1 (no recomendada) que usa aplicaciones OAuth para acceder a tu repositorio de GitHub
  • versión 2 (recomendada) que usa una conexión con aplicaciones de GitHub para acceder a tu repositorio

codepipeline2

Si eliges GitHub versión 2, el siguiente paso es crear una nueva conexión a GitHub y si no tienes ninguna aplicación de GitHub creada necesitas crear una nueva, así que debes hacer clic en Install new app.

codepipeline3

Tienes que elegir si crear la conexión para todos los repositorios o solo los seleccionados, y hacer clic en Install.

codepipeline4

La conexión de GitHub está lista para usar y serás redirigido al Paso 2 de la creación del CodePipeline.

Ahora puedes elegir tu repositorio y tu rama y hacer clic en Next.

codepipeline5

El paso 3 es añadir la etapa de compilación, y debes seleccionar AWS CodeBuild porque queremos usar este servicio para añadir comandos personalizados.

codepipeline6

Después de eso, selecciona la región, un nombre de proyecto y una compilación única y haz clic en Next.

codepipeline7

Con esta configuración específica, fallará, ¿sabes por qué?

El paso 4 es añadir la etapa de despliegue. Aquí están todas las opciones disponibles pero omitimos este paso porque no lo necesitamos.

codepipeline8

Ahora el CodePipeline está listo para ser creado y se muestra una página de revisión. Confirma y crea el CodePipeline.

codepipeline9

Está hecho. Hemos creado el CodePipeline y añadido 2 etapas:

  • Source
  • Build

Primera ejecución…

codepipeline10

¡Como puedes ver, la ejecución ha fallado!

¿Sabes qué causó el error? Investiguémoslo…

Si haces clic en el enlace del ID de ejecución, serás redirigido al resumen de ejecución del pipeline y verás el mensaje de error Project cannot be found en CodeBuild.

codepipeline11

También puedes hacer clic en el nombre de acción AWS CodeBuild y serás redirigido al servicio CodeBuild… donde recibirás la misma información de error: “Resource not available”.

codepipeline12

Sí, no hay ningún proyecto CodeBuild creado en el pipeline, solo añadimos un nombre de un recurso CodeBuild creado (y este recurso no existe porque nadie lo ha creado).

Para arreglarlo, necesitas editar el Pipeline y editar la etapa Build para crear un nuevo proyecto CodeBuild.

codepipeline15

codepipeline16

Se abrirá una nueva ventana, la etapa Build será editable y necesitas hacer clic en el botón Create project.

codepipeline17

Puedes crear el proyecto de compilación eligiendo lo siguiente:

  • Operating System: Amazon Linux 2
  • Runtime(s): Standard
  • Image: la imagen más actualizada
  • New role name
  • Buildspec: Insert build commands y haz clic en Switch to editor y añade lo siguiente:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    version: 0.2
    phases:
      install:
        commands:
          - npm install
          - npm install -g typescript
          - npm install -g aws-cdk
      build:
        commands:
          - npm ci
          - npm run build
          - cdk deploy
    
  • Añade un log de CloudWatch, eligiendo como nombre de grupo /aws/codebuild/blog-backend-infrastructure

Cuando hayas terminado de llenar todos los campos, haz clic en Continue to CodePipeline.

codepipeline18

codepipeline19

Nuevamente, con esta configuración la ejecución fallará. ¿Sabes por qué?

Ahora puedes volver al CodePipeline y forzar la ejecución nuevamente haciendo clic en Release change.

Y, como se esperaba, falla nuevamente.

codepipeline14-error2

Esta vez revisaremos los logs de CloudWatch generados para esta ejecución para buscar errores.

codepipeline21

Puedes ver que el rol de CodeBuild está intentando asumir el rol de CDK para realizar los comandos CDK, y por supuesto, no especificamos ningún permiso al nuevo rol, así que no puede asumir ningún rol.

Cómo funciona cdk deploy: Detrás de escena, cuando se ejecuta el comando cdk deploy, CDK está usando los roles CDK creados en el proceso de bootstrap para realizar algunas acciones: realizar una búsqueda, subir archivos y desplegar la plantilla subida en el S3 al servicio CloudFormation.

Por lo tanto, necesitas actualizar el rol de CodeBuild para añadir el permiso asumido a los roles CDK. Para hacer esto, crea un nuevo permiso (nueva política en línea).

codepipeline22

Debes añadir la Acción sts:AssumeRole y los Recursos de los 4 roles CDK creados en el bootstrap.

codepipeline23

codepipeline24

Cuando se cree, puedes revisar que el nuevo permiso se ha añadido al rol de CodeBuild.

codepipeline25

Si vuelves al servicio CodePipeline y lo ejecutas nuevamente, ¡tendrá éxito!

codepipeline26

Ahora, si haces algún cambio en tu repositorio, el pipeline se ejecutará automáticamente y tu infraestructura se actualizará ejecutando el comando cdk deploy del CDK Toolkit dentro del servicio CodeBuild.

Si quieres, puedes verificar los logs en el servicio CloudWatch para verificar que la ejecución del comando cdk deploy fue como esperábamos:

codepipeline27

2.1. Mejora: Usar un archivo Buildspec dentro del código

Has hecho mucho trabajo manual, y la primera mejora que puedes automatizar es la definición del proceso de compilación en sí.

Necesitas actualizar, en el proyecto CodeBuild, la configuración buildspec del proyecto, seleccionar Use a buildspec file y hacer clic en Update buildspec.

Ahora cuando el pipeline se ejecute buscará el archivo buildspec dentro del código (en la carpeta raíz). Has configurado el servicio CodeBuild pero aún no tienes el archivo buildspec.yml añadido a tu código.

A continuación, debes añadir el archivo buildspec.yml con el mismo contenido que proporcionaste en el editor en línea para actualizar los comandos de compilación en el código. Más información sobre el archivo buildspec

En la siguiente imagen, puedes ver el IDE VSCode y el nuevo archivo buildspec.yml con el mismo contenido que antes.

codepipeline29

2.2. Pruébalo: ejecución automática del pipeline cuando se hace un commit

Si haces commit del nuevo archivo a tu repositorio (buildspec.yml)

codepipeline30

El pipeline se ejecuta automáticamente como se esperaba

codepipeline31

3. CodePipeline para CDK con IaC

Ahora que hemos desplegado el CodePipeline con la consola de AWS, haremos lo mismo con Infraestructura como Código con CDK.

Para hacer esto, añadiré un recurso CodePipeline a mi proyecto CDK para el blog.

Estoy usando la conexión GitHub v2 porque es la forma recomendada, y requiere primero usar la consola de AWS para autenticarse con el proveedor de control de código fuente, y luego usar el ARN de conexión en tu definición de pipeline.

En otras palabras, ¡si usas GitHub v2 necesitas crear la conexión manualmente!

3.1. Propiedad selfmutation

Quiero mostrarte primero cómo funciona “selfmutation” en los pipelines CDK porque esto es importante saberlo.

Este es el código para añadir el recurso CodePipeline con 2 etapas:

  • Source con GitHub v2 (con una conexión)
  • Fase Build (cdk synth)

Los pipelines CDK generarán proyectos CodeBuild para cada ShellStep que uses

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const codePipelineName = `blog-backend-infrastructure-cdk`;
const pipeline = new CodePipeline(this, codePipelineName, {
  pipelineName: codePipelineName,
  synth: new ShellStep('Synth', {
    // input: CodePipelineSource.gitHub('alazaroc/aws-cdk-pipeline', 'main'),
    input: CodePipelineSource.connection(
      'alazaroc/blog-backend-infrastructure',
      'main',
      {
        connectionArn:
          'arn:aws:codestar-connections:eu-west-1:xxxxxxxx:connection/fb936fb8-a047-43d5-90bd-xxxxxxxxxx', // Created using the AWS console * });',
      },
    ),
    commands: ['npm ci', 'npm run build', 'npx cdk synth'],
  }),
});

Debes desplegar el pipeline manualmente una vez. Después de eso, el pipeline se mantendrá actualizado desde el repositorio de código fuente.

Si ejecutas el comando cdk deploy, como puedes ver el pipeline se crea y se ejecuta automáticamente.

cdk-codepipeline-1

cdk-codepipeline-2

Pero espera un minuto, ¿tenemos tres etapas? Aparece una nueva SelfMutate. Bueno, esperemos a que termine…

cdk-codepipeline-3

¿PipelineNotFoundException? ¿Qué pasó aquí? ¡Esperamos a que el pipeline terminara de ejecutarse y el pipeline ya no existe!

Investiguemos sobre SelfMutate. La documentación de CDK dice:

Si el pipeline se actualizará a sí mismo

Esto necesita establecerse en true para permitir que el pipeline se reconfigure cuando se le añadan assets o etapas, y true es la configuración recomendada.

Puedes establecer esto temporalmente en false mientras estás iterando en el pipeline mismo y prefieres desplegar cambios usando cdk deploy.

No hemos añadido esta propiedad a nuestro código y el valor predeterminado aplicado es true, así que el pipeline se ha actualizado a sí mismo y, como el código NO está commiteado en el código fuente, el pipeline ha sido eliminado.

Hemos ejecutado previamente el comando deploy pero no un commit previo.

No hice commit de mi código CodePipeline para hacer el resultado “más dramático” (pipeline eliminado automáticamente). Pero si haces commit del código, no pasará nada. Tendrás una etapa más para permitir que el pipeline se autoconfigure si hay algún cambio, y en cada ejecución esto se verificará.

Quiero mostrarte qué pasará si estableces la propiedad selfmutate en false (y el código no está commiteado).

Este es el cambio necesario en el código del recurso CodePipeline de CDK, añadiendo esta línea:

1
selfMutation: false,

Y si ejecutas el comando cdk deploy nuevamente, el pipeline se actualizará:

cdk-codepipeline-4

Ahora, solo hay 2 etapas en el CodePipeline, el Source y el Build, las 2 que hemos configurado y funcionan perfectamente.

3.2. CDK Deploy

Cambiaremos el código de nuestro servicio CodePipeline para que en lugar de ejecutar un comando cdk synth, ejecute el comando cdk deploy.

Además, para evitar el error de rol asumido que vimos en el ejemplo de la consola de AWS (en este mismo post), añadiremos los permisos IAM necesarios al rol de CodeDeploy para ejecutar el comando deploy.

Para personalizar el proyecto CodeBuild, cambia ShellStep por CodeBuildStep. Esta clase tiene más propiedades para personalizarlo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const codePipelineName = `blog-backend-infrastructure-cdk`;
  const pipeline = new CodePipeline(scope, codePipelineName, {
    pipelineName: codePipelineName,
    // synth: new ShellStep('Deploy', {
    synth: new CodeBuildStep('Deploy', {
      input: CodePipelineSource.connection(
        'alazaroc/aws-cdk-pipeline',
        'main',
        {
          connectionArn:
            'arn:aws:codestar-connections:eu-west-1:xxxxxx:connection/4d6c1902-bda7-43fb-8508-xxxxxx',
        },
      ),
      commands: ['npm ci', 'npm run build', 'npx cdk deploy --require-approval never'],
      rolePolicyStatements: [
        new aws_iam.PolicyStatement({
          actions: ['sts:AssumeRole'],
          resources: ['*'],
          conditions: {
            StringEquals: {
              'iam:ResourceTag/aws-cdk:bootstrap-role': [
                'lookup',
                'image-publishing',
                'file-publishing',
                'deploy',
              ],
            },
          },
        }),
      ],
    }),
    selfMutation: false,
  });

Cuando lo despliegues creará el proyecto CodePipeline y ejecutará los 2 pasos definidos:

  • Source
  • Build (cdk deploy)

Cambiamos el cdk synth por cdk deploy y también añadimos los permisos necesarios.

Y como hemos añadido los permisos apropiados, no falla.

cdk-codepipeline-5

3.3. Despliegue recomendado de CodePipeline para proyectos CDK

Este enfoque es un poco diferente.

Usaré este otro ejemplo de CodePipeline para CDK, para que sea más fácil de entender. Además, iré paso a paso para entender perfectamente cómo funciona.

Este es el diagrama final de lo que construiremos (con el código CDK):

cdk-pipeline-diagram

Si quieres crear el Pipeline del proyecto CDK necesitarás incluir al menos dos stacks: uno para el pipeline y uno o más para la infraestructura que se desplegará con el pipeline.

El despliegue de la aplicación comienza definiendo MyPipelineAppStage, una subclase de Stage que contiene los stacks que conforman una sola copia de mi stack (MyIaCStack).

1
2
3
4
5
6
7
8
9
10
export class MyPipelineAppStage extends Stage {
  constructor(scope: Construct, id: string, props?: StageProps) {
    super(scope, id, props);

    const iaCStack = new MyIaCStack(this, 'iac-example-stack', {
      description:
        'Stack created with codepipeline in the example aws-cdk-pipeline',
    });
  }
}

Ahora, definimos MyIaCStack, que contiene todos los recursos AWS que se crearán en un stack diferente al Pipeline:

1
2
3
4
5
6
7
8
9
10
export class MyIaCStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Add resources
    new aws_s3.Bucket(this, 'MyFirstBucket', {
      enforceSSL: false,
    });
  }
}

Finalmente, creamos el stack principal, MyPipelineStack, que añadirá MyPipelineAppStage como una etapa dentro del recurso CodePipeline.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export class MyPipelineStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const codePipelineName = `test-iac-with-cdk`;
    const pipeline = new CodePipeline(this, codePipelineName, {
      pipelineName: codePipelineName,
      synth: new ShellStep('Synth', {
        // input: CodePipelineSource.gitHub('alazaroc/aws-cdk-pipeline', 'main'),
        input: CodePipelineSource.connection(
          'alazaroc/aws-cdk-pipeline',
          'main',
          {
            connectionArn: getMyGitHubConnectionFromSsmParameterStore(this), // Created using the AWS console * });',
          },
        ),
        commands: ['npm ci', 'npm run build', 'npx cdk synth'],
      }),
    });

    pipeline.addStage(
      new MyPipelineAppStage(this, 'Deploy', {
        // env: { account: "111111111111", region: "eu-west-1" }
      }),
    );
  }
}

Tenemos que hacer commit de todos los cambios anteriores, y después de eso desplegarlo.

cdk-codepipeline-7

Creará:

  • el stack principal con el CodePipeline, MyPipelineStack,
  • el Deploy-iacStack

Primero, se crea el stack del pipeline, y luego, cuando se ejecuta el CodePipeline, se crea el segundo stack que contiene todos los demás recursos.

cdk-codepipeline-6

cdk-codepipeline-8

¡Eso es todo, tenemos automatización en nuestro proceso de despliegue!

Así que como puedes ver, usando el constructor CodePipeline de CDK, se crea lo siguiente:

solution-2

4. Próximos pasos

Lectura adicional:

Espero escuchar tus pensamientos y experiencias con AWS CDK. Siéntete libre de compartirlos en los comentarios a continuación. ¡Feliz codificación!

Este artículo está licenciado bajo CC BY 4.0 por el autor.