Madogiwa Blog

主に技術系の学習メモに使っていきます。

circleci ver2.1の新機能`executor`と`commands`を使ってconfigファイルをスッキリさせるMEMO

今更ながらcircleciのVer 2.1の新機能であるexecutorcommandsを使ってcircleciのconfigファイルをスッキリさせてWorkflowもいい感じに見直したので、そのへんをメモしておきます📝

circleci ver2.1新機能executorcommandsとは?

executorcommandsとはcircleciの2.1から追加された新機能です✨

circleci.com

今回は、それぞれの簡単な説明と実際に自分の個人開発で使っているcircleciのconfigを改善してみたので、そのへんを次から書いていきます👍

executor

executorは公式のドキュメントにも記載のある通りjobの実行環境を指定することが出来ます。今まではdockerで記載して、それぞれでimageを記載しているような感じになってしまっていましたが、executorを使うとスッキリと実行環境をjobごとに指定出来ますね🙌

Executors define the environment in which the steps of a job will be run, allowing you to reuse a single executor definition across multiple jobs. https://circleci.com/docs/reference-2-1/#executors

使い方はこんな感じでしょうか⚙

version: 2.1

executors:
  alice:
    docker:
      - image: ruby:2.7.0-alpine
  bob:
    docker:
      - image: node:14.5.0-alpine

jobs:
  alice:
    executor:
      name: alice
    steps:
      - run:
          command: "echo hello!"
  bob:
    executor:
      name: bob
    steps:
      - run:
          command: "echo goodbye!"

実行環境が見やすく定義出来ますね✨

commands

commandsは公式のドキュメントにも記載のある通り、commandsを使うとstepで実行可能なコマンドを定義して再利用することが出来ます🙌

並列性を高めるためにjobを分けたりするときにyamlの定義参照等を使わなくてもスッキリとかけるようになったのかなと思います✨

A command definition defines a sequence of steps as a map to be executed in a job, enabling you to reuse a single command definition across multiple jobs. https://circleci.com/docs/reference-2-1/#commands

使い方はこんな感じでしょうか⚙

version: 2.1

executors:
  alice:
    docker:
      - image: ruby:2.7.0-alpine
  bob:
    docker:
      - image: node:14.5.0-alpine

commands:
  say_hello:
    steps:
      - run:
          name: say hello!!
          command: echo hello!

jobs:
  alice:
    executor:
      name: alice
    steps:
      - say_hello
  bob:
    executor:
      name: bob
    steps:
      - say_hello

いい感じにコマンドが再利用出来ますね✨

見直した結果のBefore/After

今回個人で開発しているRailsアプリケーションで使っていたcircleciのconfigファイルを先程説明した機能で、ちょっと改善してみました⚙

環境は下記のような感じです。

image memo
ruby 2.7.0 with node.js (rails 6.0.3)
db postgresql

Before

改善前はjsまわりとrubyまわりでworkflowを分けていましたが、それだけだったので、どこで落ちたのかわかりにくい + 並列数が上げても並列に動かしにくかったなと💦

あとはjobのstep数が多く + 具体的なコマンドが書かれてしまっていたので可読性が良くなったなと😅

workflow

f:id:madogiwa0124:20200704201734p:plain

config

version: 2

default: &default
  # specify the version you desire here
  - image: circleci/ruby:2.7.0-node-browsers
    environment:
      RAILS_ENV: test
      PGHOST: 127.0.0.1
      DATABASE_USER: circleci
      DATABASE_PASSWORD: password

jobs:
  build:
    docker:
      - <<: *default
      - image: circleci/postgres
        environment:
          POSTGRES_USER: circleci
          POSTGRES_PASSWORD: password
    steps:
      - checkout
      - persist_to_workspace:
          root: .
          paths:
            - .
  node_build:
    docker:
      - <<: *default
    steps:
      - attach_workspace:
          at: .
      - restore_cache:
          keys:
          - v1-node-dependencies-{{ checksum "yarn.lock" }}
          - v1-dependencies-
      - run:
          name: install dependencies
          command: yarn install
      - save_cache:
          paths:
            - ./node_modules
          key: v1-node-dependencies-{{ checksum "yarn.lock" }}
      - run:
          name: run code analyze
          command: yarn lint
  ruby_build:
    docker:
      - <<: *default
      - image: circleci/postgres
        environment:
          POSTGRES_USER: circleci
          POSTGRES_PASSWORD: password
    steps:
      - attach_workspace:
          at: .
      - run:
          name: Configure Bundler
          command: |
            echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV
            source $BASH_ENV
            gem install bundler -v $BUNDLER_VERSION
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            - v1-dependencies-
      - run:
          name: install dependencies
          command: bundle install --jobs=4 --retry=3 --path ./vendor/bundle
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
      - run:
          name: run rubocop
          command: bundle exec rubocop
      - run:
          name: run brakeman
          command: bundle exec brakeman
      - run:
          name: run migration
          command: |
            bundle exec rake db:create
            bundle exec rake db:schema:load
      - run:
          name: run tests
          command: |
            TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
            bundle exec rspec $TEST_FILES
workflows:
  version: 2
  build:
    jobs:
      - build
      - ruby_build:
          requires:
            - build
      - node_build:
          requires:
            - build

After

改善後はjs側はライブラリのinstallと静的解析で分けて、ruby側はライブラリのインストールと静的解析とテストで分けてみました🤖

これでjs側とruby側、またruby側も静的解析とテストで並列にCIを回すことができるようになりそうです🙌

あとはexecutorで実行環境をまとめたので、どの環境で動くのかが明確になったのと、commandsを使って実行コマンドを整理したのでstepの中がスッキリして見やすくなったかなと思います✨

workflow

f:id:madogiwa0124:20200704203025p:plain

config

version: 2.1

web: &web
  - image: circleci/ruby:2.7.0-node-browsers
    environment:
      RAILS_ENV: test
      PGHOST: 127.0.0.1
      DATABASE_USER: circleci
      DATABASE_PASSWORD: password
db: &db
  - image: circleci/postgres
    environment:
      POSTGRES_USER: circleci
      POSTGRES_PASSWORD: password

executors:
  web:
    docker:
      - <<: *web
  web-db:
    docker:
      - <<: *web
      - <<: *db

commands:
  attach_current:
    steps:
      - attach_workspace:
          at: .
  install_node_deps:
    steps:
      - run:
          name: install node dependencies
          command: yarn install
  cache_node_deps:
    steps:
      - save_cache:
          name: Cache node dependencies
          paths:
            - ./node_modules
          key: v1-node-dependencies-{{ checksum "yarn.lock" }}
  restore_node_deps:
    steps:
      - restore_cache:
          name: Restore node dependencies
          keys:
            - v1-node-dependencies-{{ checksum "yarn.lock" }}
            - v1-dependencies-
  configure_bundler:
    steps:
      - run:
          name: Configure Bundler
          command: |
            echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV
            source $BASH_ENV
            gem install bundler -v $BUNDLER_VERSION
  install_ruby_deps:
    steps:
      - run:
          name: install dependencies
          command: bundle install --jobs=4 --clean --path ./vendor/bundle
  cache_ruby_deps:
    steps:
      - save_cache:
          name: Cache ruby dependencies
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
  restore_ruby_deps:
    steps:
      - restore_cache:
          name: Restore ruby dependencies
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            - v1-dependencies-

jobs:
  build:
    executor:
      name: web
    steps:
      - checkout
      - persist_to_workspace:
          root: .
          paths:
            - .
  node_build:
    executor:
      name: web
    steps:
      - attach_current
      - restore_node_deps
      - install_node_deps
      - cache_node_deps
  node_lint:
    executor:
      name: web
    steps:
      - attach_current
      - restore_node_deps
      - install_node_deps
      - run:
          name: run code analyze
          command: yarn lint
  ruby_build:
    executor:
      name: web
    steps:
      - attach_current
      - configure_bundler
      - restore_ruby_deps
      - install_ruby_deps
      - cache_ruby_deps
  ruby_lint:
    executor:
      name: web
    steps:
      - attach_current
      - restore_ruby_deps
      - configure_bundler
      - install_ruby_deps
      - run:
          name: run rubocop
          command: bundle exec rubocop
      - run:
          name: run brakeman
          command: bundle exec brakeman
  ruby_test:
    executor:
      name: web-db
    steps:
      - attach_current
      - restore_ruby_deps
      - configure_bundler
      - install_ruby_deps
      - run:
          name: run migration
          command: |
            bundle exec rake db:create
            bundle exec rake db:schema:load
      - run:
          name: run tests
          command: bundle exec rspec spec/
workflows:
  version: 2
  build:
    jobs:
      - build
      - ruby_build:
          requires:
            - build
      - ruby_lint:
          requires:
            - ruby_build
      - ruby_test:
          requires:
            - ruby_build
      - node_build:
          requires:
            - build
      - node_lint:
          requires:
            - node_build

参考資料

tech.recruit-mp.co.jp

circleci.com