Déploiement continu avec CircleCI - Partie 2


La première partie permettait de découvrir les concepts de base de CircleCI et comment les mettre en application. Dans cette partie on va voir comment aller plus loin en utilisant les différents moyens permettant de partager de l’information entre les jobs.

État des lieux

version: 2.1
jobs:
  build:
    docker:
      - image: circleci/node:11.10.1
    steps:
      - checkout
      - run: yarn install
      - run: yarn build

  lint:
    docker:
      - image: circleci/node:11.10.1
    steps:
      - checkout
      - run: yarn install
      - run: yarn lint

  test:
    docker:
      - image: circleci/node:11.10.1
    steps:
      - checkout
      - run: yarn install
      - run: yarn test:unit

  deploy:
    docker:
      - image: circleci/node:11.10.1
    steps:
      - checkout
      - run: yarn install
      - run: yarn build
      - run: yarn firebase deploy --token "$FIREBASE_TOKEN" --only hosting

workflows:
  version: 2
  integration:
    jobs:
      - build
      - lint
      - test
    - deploy:
          requires:
            - build
            - lint
            - test
          filters:
            branches:
              only: master

Notre configuration fonctionne mais on voit que l’on se répète. Chacun des jobs contient ces lignes :

docker:
  - image: circleci/node:11.10.1
steps:
  - checkout
  - run: yarn install

Les deux premières lignes, celles de la configuration de l’environnement d’exécution, sont juste une répétition au sein du fichier de configuration, elles n’ont pas d’impact sur les performances. Mais par soucis de maintenabilité on utilisera les executors et les commands pour factoriser ces fragments.

Deuxième problème, chaque job est obligé de réinstaller les dépendances car ils exécutent leurs instructions dans un environnement isolé. Pour pallier ce problème on va installer les dépendances, les stocker en cache pour qu’elles puissent être partagées par les jobs d’un workflow. Les dépendances mises en cache pourront aussi être utilisées par les jobs futurs.

Le dernier problème est la nécessité de devoir re-construire l’application de production dans le job de déploiement alors que cette étape a été réalisée par le job de build. Encore une fois le problème vient du fait que les jobs sont isolés. Par défaut le job de déploiement ne peut pas lire ce qui a été généré par le job de build. On utilisera les workspaces pour résoudre ce souci.

Cacher les dépendances

Pour utiliser le cache on va définir un nouveau job chargé d’installer nos dépendances :

install:
  docker:
    - image: circleci/node:11.10.1
  steps:
    - checkout
    - restore_cache:
        key: v1-dependencies-{{ checksum "yarn.lock" }}
    - run: yarn install
    - save_cache:
        key: v1-dependencies-{{ checksum "yarn.lock" }}
        paths:
          - ./node_modules
  • restore_cache : restore le cache pour une clé donnée
    • key : clé de restauration du cache
  • save_cache : sauvegarde des fichiers en cache
    • key : clé de stockage du cache
    • paths : les élements à stocker, ici les node_modules

Cette étape tente de restaurer le cache contenant les node_modules permettant ainsi de passer l’étape d’installation des dépendances.

NB : la clé de cache contient un hash de notre fichier yarn.lock, généré par la fonction checksum. Donc si le fichier change, le hash change et le cache est re-généré.

On peut mettre à jour les autres jobs de notre configuration pour qu’ils utilisent le cache :

version: 2
jobs:
  install:
    docker:
      - image: circleci/node:11.10.1
    steps:
      - checkout
      - restore_cache:
          key: v1-dependencies-{{ checksum "yarn.lock" }}
      - run: yarn install
      - save_cache:
          key: v1-dependencies-{{ checksum "yarn.lock" }}
          paths:
            - ./node_modules

  build:
    docker:
      - image: circleci/node:11.10.1
    steps:
      - checkout
      - restore_cache:
          key: v1-dependencies-{{ checksum "yarn.lock" }}
      - run: yarn build

  # autres jobs...

workflows:
  version: 2
  integration:
    jobs:
      - install
      - build:
          requires:
            - install
      - lint:
          requires:
            - install
      - test:
          requires:
            - install
    - deploy:
          requires:
            - build
            - lint
            - test
          filters:
            branches:
              only: master

Désormais on installe une seule fois les dépendances que l’on partage entre les différents jobs. On n’oublie pas de préciser que chacun des jobs requiert que le job install ait été complété.

Avec du cache on gagne environ 20s sur le processus de déploiement.

circleci-with-cache

Les workspaces

Un workspace est un espace qui permet à un job de partager des données avec un job lui succédant. Dans notre cas on va construire l’application dans le job de build, stocker les fichiers statiques dans le workspace pour que le job deploy puisse les récupérer si nécessaire.

build:
  docker:
    - image: circleci/node:11.10.1
  steps:
    - checkout
    - restore_cache:
        key: v1-dependencies-{{ checksum "yarn.lock" }}
    - run: yarn build
    - persist_to_workspace:
        root: ~/project
        paths:
          - ./dist
  • persist_to_workspace : indique que l’on veut écrire dans le workspace
    • root : le dossier dans lequel on doit aller chercher l’information, par défaut ~/project
    • paths : liste des chemins des fichiers/dossiers à écrire dans le workspace
deploy:
  docker:
    - image: circleci/node:11.10.1
  steps:
    - checkout
    - restore_cache:
        key: v1-dependencies-{{ checksum "yarn.lock" }}
    - attach_workspace:
        at: ~/project
    - run: yarn firebase deploy --token "$FIREBASE_TOKEN" --only hosting

En utilisant les workspaces en complément du cache on est parvenu à gagner une trentaine de secondes par rapport à la configuration de la première partie 🎉.

circle-ci-final-build

🍒 sur le 🍰 : les executors et les commandes

Ici on peaufine notre configuration afin d’éviter un maximum les répétitions dans notre fichier de configuration. Les commands permettent de factoriser des étapes de nos jobs. Les executors permettent de factoriser les environnements dans lesquels seront exécutés nos scripts.

commands:
  checkout_and_restore_cache:
    steps:
      - checkout
      - restore_cache:
          key: v1-dependencies-{{ checksum "yarn.lock" }}

executors:
  node:
    docker:
      - image: circleci/node:11.10.1

On peut mettre à jour nos jobs :

install:
  executor: node
  steps:
    - checkout_and_restore_cache
    - run: yarn install
    - save_cache:
        key: v1-dependencies-{{ checksum "yarn.lock" }}
        paths:
          - ./node_modules

La configuration finale

version: 2.1

commands:
  checkout_and_restore_cache:
    steps:
      - checkout
      - restore_cache:
          key: v1-dependencies-{{ checksum "yarn.lock" }}

executors:
  node:
    docker:
      - image: circleci/node:11.10.1

jobs:
  install:
    executor: node
    steps:
      - checkout_and_restore_cache
      - run: yarn install
      - save_cache:
          key: v1-dependencies-{{ checksum "yarn.lock" }}
          paths:
            - ./node_modules

  build:
    executor: node
    steps:
      - checkout_and_restore_cache
      - run: yarn build
      - persist_to_workspace:
          root: ~/project
          paths:
            - dist/*

  lint:
    executor: node
    steps:
      - checkout_and_restore_cache
      - run: yarn lint

  test:
    executor: node
    steps:
      - checkout_and_restore_cache
      - run: yarn test:unit

  deploy:
    executor: node
    steps:
      - checkout_and_restore_cache
      - attach_workspace:
          at: ~/project
      - run: yarn firebase deploy --token "$FIREBASE_TOKEN" --only hosting

workflows:
  version: 2
  integration:
    jobs:
      - install
      - build:
          requires:
            - install
      - lint:
          requires:
            - install
      - test:
          requires:
            - install
      - deploy:
          requires:
            - build
            - lint
            - test
          filters:
            branches:
              only: master

Conclusion

On a enfin une configuration de bogass 😎 Performante grâce à l’utilisation du cache et des workspaces, maintenable grâce aux commandes et aux executors, sympathique grâce aux pommes.

Merci de m’avoir lu.

Liens utiles