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:
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:
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):
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).
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
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.
Tienes que elegir si crear la conexión para todos los repositorios o solo los seleccionados, y hacer clic en Install.
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.
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.
Después de eso, selecciona la región, un nombre de proyecto y una compilación única y haz clic en Next.
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.
Ahora el CodePipeline está listo para ser creado y se muestra una página de revisión. Confirma y crea el CodePipeline.
Está hecho. Hemos creado el CodePipeline y añadido 2 etapas:
- Source
- Build
Primera ejecución…
¡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.
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”.
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.
Se abrirá una nueva ventana, la etapa Build será editable y necesitas hacer clic en el botón Create project.
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 commandsy 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.
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.
Esta vez revisaremos los logs de CloudWatch generados para esta ejecución para buscar errores.
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).
Debes añadir la Acción sts:AssumeRole y los Recursos de los 4 roles CDK creados en el bootstrap.
Cuando se cree, puedes revisar que el nuevo permiso se ha añadido al rol de CodeBuild.
Si vuelves al servicio CodePipeline y lo ejecutas nuevamente, ¡tendrá éxito!
Ahora, si haces algún cambio en tu repositorio, el pipeline se ejecutará automáticamente y tu infraestructura se actualizará ejecutando el comando
cdk deploydel 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:
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.
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)
El pipeline se ejecuta automáticamente como se esperaba
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.
Pero espera un minuto, ¿tenemos tres etapas? Aparece una nueva
SelfMutate. Bueno, esperemos a que termine…
¿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
truepara permitir que el pipeline se reconfigure cuando se le añadan assets o etapas, ytruees la configuración recomendada.Puedes establecer esto temporalmente en
falsemientras estás iterando en el pipeline mismo y prefieres desplegar cambios usandocdk 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á:
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 synthporcdk deployy también añadimos los permisos necesarios.
Y como hemos añadido los permisos apropiados, no falla.
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):
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.
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.
¡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:
4. Próximos pasos
Lectura adicional:
- SAM + CDK: ¿Interesado en cómo AWS SAM y AWS CDK pueden trabajar juntos? He explorado esto en otro artículo: Cómo crear aplicaciones serverless con CDK y SAM. Es un gran siguiente paso para aquellos que buscan expandir su conocimiento de arquitectura serverless.
- SAM: Cómo crear aplicaciones Serverless con SAM
- Terraform: Cómo crear aplicaciones Serverless con Terraform
Espero escuchar tus pensamientos y experiencias con AWS CDK. Siéntete libre de compartirlos en los comentarios a continuación. ¡Feliz codificación!







































