Picturesocial - Cómo añadir integración continua y entrega continua a mi aplicación
Tags: devops, aws, kubernetes, cicd
Published: 2023-05-08 16:35:02

Episodio escrito por: [Ricardo Ceci](https://linkedin.com/in/ricardoceci) y [Jose Yapur](https://linktr.ee/develozombie)

{% embed https://www.youtube.com/watch?v=2Eptv00imxo %}

Si es la primera vez que llegas a escuchar o leer sobre Picturesocial es una serie de contenido en la que aprendemos de cero el proceso de desarrollo, integración, despliegue y mantenimiento de una red social ficticia creada para aprender sobre tecnología con ayuda de especialistas de Nube, Seguridad, Software y DevOps, puedes encontrar todos los episodios de la serie en <https://bit.ly/m/picturesocial>

En la temporada anterior de Picturesocial aprendimos sobre el proceso de construcción de nuestra aplicación desde cero, sin embargo, hoy día nos enfrentamos a los retos de llevar una aplicación a producción y que debemos planificar desde el inicio. Sabemos tambien, que los escenarios ideales, en la mayoría de los casos, son propios de la academia y no de la vida real. Picturesocial nació como un proyecto que estaba “listo” para ir a producción, porque “¿Qué es lo peor que puede pasar?”

La ingenuidad, el exceso de fe o el elevado amor personal de desarrolladores senior 😆, nos llevó a donde estamos el día de hoy, con una red social en donde los pases a producción tardan días, se hacen muchos cambios al mismo tiempo por cada pase y es muy difícil detectar dónde están los problemas y por ende también a resolverlos.

En el mundo ideal queremos que Picturesocial tenga procesos libres de mamíferos (tal cual como suena 😅), en donde los desarrolladores solo hagan push a sus repositorios, los flujos de aprobación entren en vigor y una vez que se haga merge al repositorio principal entonces todo lo demás que aprendimos en la temporada anterior lo hagan los seres binarios perfectos (las máquinas)

Completamente conscientes de nuestras deficiencias para resolverlo solos, es que decidimos llamar a personas brillantes de las comunidades de AWS en todo América Latina, para que nos rescaten. En esta primera entrega hemos invitado a Ricardo Cecci, quien es AWS Community Builder y miembro del AWS UG de Buenos Aires, quien nos ayudará a resolver este problema y con quien estoy co-escribiendo este artículo.

Los retos por los que pasa Picturesocial son comunes en muchos proyectos, es usualmente al momento de pasar a producción, cuando observamos a ciencia cierta que los pases tardan días, porque dependen de muchas personas y necesitan que se hagan de forma ordenada (no se puede hacer un despliegue y luego una compilación, o si? 🥶), este proceso de días, afecta directamente el time to market y a la vez ocasiona esa sensación de que nuestro corazón se va a detener o el mundo va a explotar si omitimos un paso en el proceso que ya está en ejecución.

Un flujo de Integración Continua / Despliegue Continuo (CI/CD en inglés), nos va a permitir que nuestro proyecto pase por un circuito controlado automatizado de despliegue, donde, por ejemplo cada cambio en la rama master o main de nuestro repositorio haga que se muevan ciertas piezas automáticamente y nuestro código termine en producción si todos los chequeos pasaron, o no termine en producción si algo falló en el medio. En este circuito nosotros podremos definir todos los controles que queremos que ocurran y cómo va a ser ese despliegue a producción.

En AWS existen las siguientes herramientas que nos pueden ayudar en el proceso:

* AWS Codepipeline, con quien coordinaremos todo el proceso de salida a producción
* AWS CodeBuild, con esta herramienta haremos el build de nuestra imágen y la subiremos al repositorio de ECR.
* Por lo general también se utiliza AWS CodeDeploy, pero en este caso como estamos utilizando EKS no va a ser necesario utilizarlo.

Recuerdo cuando desplegabamos código manualmente, era como un rito, había que dejar de hacer todo, nos conectabamos servidor por servidor, e íbamos haciendo un pull en cada uno de los servidores, procurando no olvidarnos de ninguno, y que todas las librerías y dependencias estén actualizados en todos, que en ninguno nos hayamos olvidado de algún paso y cuando pasaba que nos olvidábamos (porque somos humanos) o algún servidor quedaba trabado en mitad del proceso, saltaban las alarmas y había que atacar servidor por servidor.

Si había que hacer migraciones o modificaciones en las bases de datos que requerían hacer un lanzamiento similar a un blue/green, había que armar una regla específica en el load balancer para que rutee cierta parte del tráfico manualmente y podamos probar y luego manualmente rutear todo el tráfico, si bien había herramientas para hacerlo más fácil, no dejaba de ser manual.

Una vez que implementamos el circuito de CI/CD, todo fue más fácil, podemos manejar salidas a producción con un click o tocando un botón y estaremos tranquilos de que si algo falla, podremos volver atrás o en el mejor de los casos se volverá automáticamente para atrás sin nuestra intervención y se nos avisará. 

En este caso, vamos a usar toda esa experiencia para implementar un proceso de CI/CD en el despliegue del servicio Pictures.

Crearemos una solución que haga lo siguiente:

1. Detectar nuevos push a nuestro repositorio de código (Github/CodeCommit), en la branch principal. Suele llamarse ‘main’.
2. A partir de allí un pipeline de AWS CodePipeline escuchará el cambio.
3. A través de AWS CodeBuild construiremos la imagen de Docker y la subiremos a Elastic Container Registry (ECR)
4. Luego haremos que nuestro cluster de EKS tome la nueva imagen y así nuestro servicio estará desplegado a producción.

Y lo más divertido es que todo se producirá automáticamente sin intervención nuestra, salvo la de crear la automatización en primer lugar 😃

Lo primero que te recomendamos aquí es armarte de paciencia, dado que esto es algo que harás una sola vez, e iteraras algunas otras sobre la mejora de la implementación, si no cambias tus procesos no tendrás que prácticamente tocarlo una vez que lo tengas pulido y solo tendrás que ocuparte de hacer commits y push a las diferentes ramas que tu pipeline este escuchando.

Los Servicios de AWS que vamos a utilizar son:

* AWS CodePipeLine.
* AWS CodeBuild
* Amazon EKS

Hemos preparado una branch con todo lo que necesitas para ejecutar este episodio

```bash
git clone https://github.com/aws-samples/picture-social-sample.git -b cicd
```

Primero hemos agregado un archivo Dockerfile dentro de la carpeta de nuestro servicio pictures.

**Contenido del archivo:**

```Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build-env /app/out .
EXPOSE 5075
ENV ASPNETCORE_URLS=http://+:5075
ENTRYPOINT ["dotnet", "pictures.dll"]
```

Una vez que hemos hecho esto, vamos a armar el pipeline, nuestro pipeline en este caso va a tener 2 pasos:

Paso 1:
Pull del Repositorio
Paso 2:
Armado de la imágen con CodeBuild, Publicación en ECR y en EKS
Al final del episodio el Pipeline debería de quedar así:
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image001.jpg)
Primero desde la barra de búsqueda de nuestra Consola de administración Buscamos CodePipeline
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image002.png)
Presionamos: Create pipeline
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image004.png)
Ingresamos el nombre que queramos darle, en este caso PicturesService-CICD, y creamos un rol, pongamos un nombre que podamos recordar porque este rol más tarde lo modificaremos y le agregaremos unos permisos adicionales y presionamos Next
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image006.png)
En Source Provider, en este caso vamos a elegir Github Versión 2, que es donde tenemos alojado nuestro repositorio:
Nota: En este caso te recomendamos que crees tu propio repositorio y lo conectes a tu cuenta, así puedes probar el pipeline sin problemas.
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image008.png)
Luego nos pedirá conectarnos a github con nuestras credenciales, o bien si ya nos hemos conectado con anterioridad, para elegir una cuenta existente.
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image010.png)
En el paso siguiente elegimos el repositorio desde el cual vamos a tomar el código y el branch (la rama) que queremos monitorear.
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image012n.png)
Presionamos Next y nos pedira en el siguiente paso elegir el Build Provider, en este caso vamos a elegir AWS CodeBuild
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image014.png)
Luego de elegir CodeBuild, seleccionamos: Crear nuevo proyecto (Create new project)
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image016.png)
Se nos abrirá una ventana y pasamos a completar lo que nos solicita:
Ponemos un nombre
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image018.png)
Environment:

1. Elegimos Managed Image (estas imágenes son administradas por AWS CodeBuild y no tendremos que preocuparnos por la gestión de seguridad y actualización del OS de esas imágenes)
2. AmazonLinux 2 como sistema operativo
3. Runtime: Standard
4. Image: En este caso la última versión vigente la momento de escribir este articulo es la 4.0
5. Environment type: Linux
6. Muy importante: Dado que con este entorno vamos a crear nuevas imágenes de docker, debemos asegurarnos de que la casilla Privileged, esté activada.
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image020.png)
7. Creamos un service role nuevo (Anotar el nombre)
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image022.png)
8. Desplegamos donde dice Additional Configuration para poder crear algumas variables de entorno que vamos a utilizar en el próximo paso:

Creamos las siguientes variables:

```bash
AWS_DEFAULT_REGION : us-east-1
IMAGE_REPO_NAME: nombre de la imagen que estamos generando (en nuestro caso pictures)
AWS_ACCOUNT_ID : Nuestro número de cuenta de AWS
AWS_CLUSTER_NAME : nombre de nuestro cluster de EKS (en nuestro caso picturesocial-823)
```

![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image024.png)
El próximo paso es indicarle a CodeBuild dónde van a estar las instrucciones de “Build de nuestro proyecto”, existen 2 opciones:

* Con un archivo buildspec.yml en la carpeta raíz del repositorio (al mismo nivel que el Dockerfile) 
* Escribiendo las instrucciones de construcción en el proyecto. Vamos a elegir esta opcion y presionamos Switch to editor

![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image026.png)
En el repositorio les hemos dejado el archivo Buildspec-example.yml que es el que usaremos para completar el código.

¿Qué estamos haciendo aqui?
Declaramos la versión que vamos a usar y el listado de fases:

```yaml
version: 0.2
phases:
```

Primero declaramos las Fases
Fase 1: Pre-Build
En esta fase lo que estamos haciendo es

1. iniciando sesión en ECR
2. Descargando y configurando kubectl para que se conecte con nuestro cluster de EKS
3. Creamos una variable de entorno que utilizaremos luego como el tag que identificara la imágen imagen.

```yaml
pre_build:
 commands:
 - echo Logging in to Amazon ECR...
 - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
 - curl -o kubectl https://s3.us-west-2.amazonaws.com/amazon-eks/1.25.6/2023-01-30/bin/linux/amd64/kubectl
 - chmod +x ./kubectl
 - mkdir -p $HOME/bin && cp ./kubectl $HOME/bin/kubectl && export PATH=$PATH:$HOME/bin
 - echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc
 - source ~/.bashrc
 - aws eks --region $AWS_DEFAULT_REGION update-kubeconfig --name $AWS_CLUSTER_NAME
 - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
```

Luego viene la fase de Build:

1. Escribimos un mensaje en la consola para reconocer que comenzó la generación de las imágenes
2. Cambiamos de directorio y nos movemos a la carpeta Pictures, donde se encuentra el Dockerfile
3. Hacemos el build de la imágen tal como aprendimos en el episodio 1
4. Aplicamos un tag a nuestra imágen para luego subirla a ECR

```yaml
build: commands: - echo Build started on `date` - echo Building the Docker image... - cd pictures - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
```

Una vez que tenemos la imagen armada, vamos ahora si a enviarla a ECR y luego a EKS

1. Enviamos un mensaje a la consola para registrar el momento en el que se terminó de hacer el build de la imágen y avisamos que vamos a subir la imágen a ECR
2. Subimos la imágen a ECR
3. En el repositorio que les dejamos en el archivo manifest.yml hemos cambiado el nombre de la imágen a ECR_IMAGE_PLACEHOLDER, en este paso lo que estamos haciendo es reemplazar ese placeholder por le nombre real de la imágen genreada durante la fase de Build.
4. Luego hacemos un kubectl apply -f del archivo manifest.yml y con esto ya tendremos nuestra imágen publicada.

```yaml
post_build:
 commands:
 - echo Build completed on `date`
 - echo Pushing the Docker image...
 - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
 - echo Replacing in manifest with the image name
 - sed -i "s/ECR_IMAGE_PLACEHOLDER/${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com\/${IMAGE_REPO_NAME}:${IMAGE_TAG}/g" manifest.yml
 - kubectl apply -f manifest.yml
```

El archivo final debera quedarnos así:

```yaml

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
      - curl -o kubectl https://s3.us-west-2.amazonaws.com/amazon-eks/1.25.6/2023-01-30/bin/linux/amd64/kubectl
      - chmod +x ./kubectl
      - mkdir -p $HOME/bin && cp ./kubectl $HOME/bin/kubectl && export PATH=$PATH:$HOME/bin
      - echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc
      - source ~/.bashrc
      - aws eks --region $AWS_DEFAULT_REGION update-kubeconfig --name $AWS_CLUSTER_NAME
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - cd pictures
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG      
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG_V2
      - echo Replacing in manifest with the image name
      - sed -i "s/ECR_IMAGE_PLACEHOLDER/${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com\/${IMAGE_REPO_NAME}:${IMAGE_TAG}/g" manifest.yml
      - kubectl apply -f manifest.yml
```

Reemplazamos el contenido del editor por el del archivo:
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image028.png)
Luego seleccionamos continue to CodePipeline
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image030.png)
De vuelta en CodePipeline presionamos: Next y saltamos el paso de Deploy Stage dado que ya lo hemos hecho en el paso anterior.
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image032.png)
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image034.png)
Nos pedirá confirmar todo y luego de confirmar nuestro pipeline ya estará creado.

**Enhorabuena!**
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image036.png)
Esto no termina aqui, seguramente nuestra pipeline falle porque CodeBuild no tiene permisos para poder publicar en ECR, ni en EKS
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image038.png)
Vamos a darle los permisos que necesita:

1. Vamos a modificar el Rol creado cuando estabamos creando el proyecto en CodeBuild y vamos a darle los permisos que necesitamos, para esto nos vamos a IAM: <https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1>
2. Ya en IAM nos vamos a Roles
3. Allí buscamos el rol que creamos en el momento de crear el proyecto de CodeBuild

![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image040.png)

Vamos a agregar permisos creando una nueva Policy
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image042n.png)
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image044.png)
Nos vamos a la solapa JSON y pegamos el siguiente contenido:

Reemplazar donde dice ACCOUNT-ID por tu número de cuenta, y CLUSTER NAME por el nombre del cluster de EKS

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ecr:CompleteLayerUpload",
                "eks:DescribeCluster",
                "ecr:UploadLayerPart",
                "ecr:InitiateLayerUpload",
                "ecr:BatchCheckLayerAvailability",
                "ecr:PutImage"
            ],
            "Resource": [
                "arn:aws:ecr:us-east-1:[ACCOUNT-ID]:repository/pictures",
                "arn:aws:eks:us-east-1:[ACCOUNT-ID]:cluster/[CLUSTER-NAME]"
            ]
        },
        {
        "Sid": "VisualEditor1",
        "Effect": "Allow",
        "Action": "ecr:GetAuthorizationToken",
        "Resource": "*"
        }
    ]
}
```

En nuestro caso quedaría asi:
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image046.png)
Presionamos Siguiente, Ingresamos un nombre a nuestra policy
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image048n.png)
Luego presionamos Create policy y nuestra política estará creada.

Luego agregamos los permisos a nuestro rol:
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image050.png)
Así debería quedar:
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image052.png)
Copiemos el ARN de nuestro rol ya que lo vamos a utilizar para el próximo paso

Ahora lo que nos queda hacer, es darle permiso a CodeBuild en nuestro cluster de EKS para que pueda ejecutar el comando apply, para esto hay que a través de un Role Based Access Control darle permiso:
Desde nuestra consola ejecutamos el siguiente comando:

```bash
kubectl edit cm aws-auth -n kube-system
```

Luego dentro de mapRoles agregamos una nueva entrada:

Aqui reemplazamos el ARN por el que copiamos anteriormente, notese que quitamos el path /service-role/ del nombre del ARN

```yaml
- groups:
 - system:masters
 rolearn: arn:aws:iam::XXXXXXXX:role/PicturesBuildServiceRole 
 username: CodeBuild Role to Access EKS
```

Ahora si, podemos volver a nuestro pipeline, y lo tenemos listo para trabajar, presionemos “Release change”

Y veremos cómo ahora si se completa el trabajo:
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/image054.png)
**Ahora:**

Realiza algún cambio mínimo en el código y verás como cada vez que se haga un push a la rama que hayas configurado, automáticamente se desplegará en el Cluster.

Si llegaste hasta acá, hoy aprendiste cómo usar algunas de las capacidades de CI/CD que tienes disponibles en AWS y cómo es que puedes aplicarlas a tus proyectos contenerizados. En el siguiente episodio aprenderemos cómo agregar observabilidad a Picturesocial y por qué deberíamos hacerlo. Nos vemos en el siguiente episodio!

**Sobre los autores** 

![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/yapu.png)**José Yapur** es Senior Developer Advocate en AWS con experiencia en Arquitectura de Software y pasión por el desarrollo especialmente en .NET y PHP. Trabajó como Arquitecto de Soluciones por varios años, ayudando a empresas y personas en LATAM.
![Picturesocial](https://picturesocial.s3.amazonaws.com/2x1/2x1/ricardo.jpg)**Ricardo Ceci** es CTO @ Halsbrook y Fundador de .VNS | Software Agency, es un desarrollador apasionado por arquitecturas en la nube y desarrollo con nuevas tecnologías.