Hero

CircleCI: Primeros pasos

Octubre 05, 2022

ibonelli
CircleCI

Arrancando con CircleCI: Primeros pasos y necesidades más comunes.

CircleCI

Hoy tenemos muchas opciones CI/CD. La mayoría de los servicios de desarrollo tienen en su capa gratis algún servicio CI/CD (como por ejemplo GitHub y GitLab). Alternativamente uno puede correr su propia solución con Jenkins y evitarse usar servicios en la nube. Pero incluso con esta masiva oferta de servicios CI/CD, creo que la capa gratis de CircleCI es una propuesta muy interesante.

La documentación es simplemente impresionante, tiene la posibilidad de hacer debugging conectandose vía SSH, y hay muchas soluciones ya listas para usar (por ejemplo AWS CLI ORB).

Como arrancar…

Existen diferentes maneras de comenzar. En resumen lo que provee CircleCI son ambientes dockerizados o VMs que podemos orquestar usando un archivo de configuración YAML que tendremos en <project-repo>/.circleci/config.yml.

Para mi la mejor manera de comenzar es ver la documentación sobre conceptos CircleCI que incluye una imagen que vale mil palabras describiendo los elementos del YAML.

Una vez que tenemos una idea y sabemos que planeamos hacer, el próximo paso es usar el editor online de CircleCI. Si bien podemos editar el archivo offline e incluso verificarlo, usar el editor online nos permite chequear la sintaxis rápido y evitarnos un montón de errores.

¿Qué imágenes de Docker usamos?

Lo mejor es simplemente usar las imágenes de Go, NodeJS, Python, PHP, etc… de CircleCI.

Por ejemplo en el caso de Node podemos hallar las diferentes versiones disponibles en cimg/node.

En mi caso, busqué una imagen que fuera lo más similar posible a mi ambiente local:

jobs:
  build:
    docker:
      - image: cimg/node:16.17.0
    steps:
      - checkout
      - run: node --version

Configurando el ambiente

En el caso de Docker/Linux, y para cuentas en la capa gratis, CircleCI soporta: Small, Medium, Medium+ & Large.

  • small: 1 CPU, 2 GB RAM & 5 credits/min
  • medium: 2 CPU, 4 GB RAM & 10 credits/min
  • large: 4 CPU, 8 GB RAM & 20 credits/min

Si bien se puede omitir, para mi es mejor declararlo en el config.yml: Docker Execution Environment

jobs:
  build:
    docker:
      - image: buildpack-deps:trusty
        auth:
          username: mydockerhub-user
          password: $DOCKERHUB_PASSWORD  # context / project UI env-var reference
    resource_class: xlarge
    steps:
      ... // other config

Una cosa interesantes sobre los ambientes de ejecución es que ese puede hacer reusables:

executors:
  docker-executor:
    docker:
      - image: cimg/node:16.17.0
jobs:
  build:
    executor: docker-executor
    steps:
      - build
      ...

Particularidades y sintaxis

El cambiar de directorio “no funciona”

Una cosa que me llamó la atención es que el cambio de direcrtorio “no funciona”, pero esto es ”a propósito“.

Los “jobs” no mantienen los cambios de directorios entre tareas. Para poder hacer un “cd” deberemos hacer:

- run:
    command: cd mypath ; npm run build

Si no lo hacemos dentro de la línea de comando, se toma el directorio de trabajo.

Cómo evitar problemas de sintaxis cuando necesitamos usar ”:”

Cuando usamos los dos puntos (”:”) dentro de un YAML tenemos problems ya que el parser YAML se confunde. La solución es simple, solo debemos reemplazar:

- run:
    name: Build header
    command: chdr="'Authorization: Bearer SENDGRID_KEY'"

con:

- run:
    name: Build header
    command: |
      chdr="'Authorization: Bearer SENDGRID_KEY'"

El uso del pipe (”|”) y el nuevo renglón ya nos evita los problemas.

Depurar usando SSH

La mejor manera de encontrar problemas en CircleCI jobs es el acceder al ambiente de trabajo mediante ssh. Cualquier pipeline que haya fallado se puede lanzar nuevamente con acceso SSH:

  1. Primero debemos ir al dashboard de CircleCI
  2. Luego entramos al pipeline que falló
  3. Y finalmente podremos elegir correr nuevamente el pipeline con acceso SSH.

CircleCI tiene un artículo en su blog que detalla paso a paso (con imágenes) como hacerlo.

Ejecución condicional

Supongo que es obvio, pero prefiero aclarar que siempre debemos mantener nuestros secretos fuera de los archivos YAML. Por eso tenemos para cada proyecto en las configuraciones: Variables de ambiente (Environment Variables). Una vez declaradas, las tenemos disponibles para usarlas.

Una vez que tu repositorio (github en mi caso) está conectado al proyecto CircleCI, todos los commits van a iniciar las tareas CircleCI asociadas (descritas en el archivo <project-repo>/.circleci/config.yml). Esto significa que debemos asegurarnos que los jobs solo se activen para las ramas (branches) que sean necesarias:

...
workflows:
  version: 2.1
  build-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: main

En este ejemplo la sentencia “filters: branches: only: master” significa que el job deploy solo se va a correr para el branch main, pero el build se va a correr para todos los commit.

Nos podemos poner bastante creativos con esta sintaxis y hacer pasos condicionales en jobs y workflows condicionales:

- when:
    condition:
      or:
        - and:
          - equal: [ main, << pipeline.git.branch >> ]
          - or: [ << pipeline.parameters.param1 >>, << pipeline.parameters.param2 >> ]
        - or:
          - equal: [ false, << pipeline.parameters.param1 >> ]
    steps:
      - run: echo "I am on main AND param1 is true OR param2 is true -- OR param1 is false"

Pero no estoy muy seguro de cuán común sea esta necesidad. En mi caso con el filtrado que describo primero me alcanzó para lo que necesitaba.

Otra funcionalidad interesante es el uso del atributo when que también puede condicionar que se hace:

- run:
    name: Upload CodeCov.io Data
    command: bash <(curl -s https://codecov.io/bash) -F unittests
    when: always # Uploads code coverage results, pass or fail

Pero este “when” solo acepta valores como: “always”, “on_success”, “on_fail”. El propósito de esto es poder reaccionar a los resultados de los pasos anteriores.

Persistiendo datos entre trabajos

Cuando estamos haciendo builds y deploys en jobs separados debemos mantener/persistir la data entre los trabajos. Para eso debemos:

version: 2.1
jobs:
  build:
    docker:
      - image: circleci/node:10.16.3
    working_directory: ~/repo
    steps:
      # Persist into the workspace for use in deploy job. 
      - persist_to_workspace:
          root: public
          paths:
            - '*' 
      - checkout
      # Pull the theme's submodule code
      - run: git submodule sync
      - run: git submodule update --init
      # Download previously cached dependencies
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "package.json" }}
      # Update node modules
      - run: npm install
      # Install the hexo-cli
      - run: sudo npm install -g hexo-cli
      # Cache the current state of the node modules
      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ checksum "package.json" }}
      # Generate the static files of the website
      - run: hexo clean
      - run: hexo generate
  deploy:
    docker:
      - image: 'circleci/python:3.7.4'
    working_directory: ~/repo
    steps:
      - attach_workspace:
          at: public
      - run:
          name: Install awscli
          command: sudo pip install awscli
      - run:
          name: Deploy to S3
          command: aws s3 sync public/ s3://agileexplorations.com --delete

workflows:
  version: 2.1
  build-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

Este ejemplo usa persistir en el workspace & attach workspace. Estos conceptos son revisados en más detalle en usando Workspaces y compartiendo información entre Jobs.

aws-s3 en circleci (y su uso para debugging)

Puede haber situaciones donde nuestro job no fallé, pero no logre hacer lo que esperamos. Cuando esto sucede, podríamos forzar un error y usar SSH para ver qué pasó. Pero una alternativa es no forzar el fallo, y subir archivos a S3 que nos permitan averiguar qué fue lo que sucedió.

Herramientas (como el AWS CLI) viene previamente armados como ORBs ya sea por CircleCI o la comunidad. En particular AWS CLI se puede usar de la siguiente manera:

version: '2.1'
orbs:
  aws-s3: circleci/aws-s3@3.0
jobs:
  build:
    docker:
      - image: cimg/node:16.17.0
    steps:
      - checkout
      - run:
          name: Make sure we have a project folder
          command: mkdir -p ~/project
      - run: echo "lorem ipsum" > ~/project/build_asset.txt
      # For this to work you'll need to have in your Environment Variables
      # AWS_REGION, AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY
      - aws-s3/copy:
          arguments: '--no-progress'
          from: ~/project/build_asset.txt
          to: 's3://wk-circleci-logs/7sabores/'
      - aws-s3/copy:
          arguments: '--no-progress'
          from: ~/project/netlify-deploy-output.txt
          to: 's3://wk-circleci-logs/7sabores/'
workflows:
  s3-example:
    jobs:
      - build

También está disponible en esta ORB el comando aws-s3/sync. Se puede ver un ejemplo de su uso en el ORB repo.

Recibe consejos y oportunidades de trabajo 100% remotas y en dólares de weKnow Inc.