頑張るときはいつも今

自称エンジニアのゴリラです。日々精進。

【学習記録】CircleCIに入門してみた

運動量よりも食べる量が異常に増えてきて、

体が重いです。

勉強も含めて個人でアプリを作りたいなと思って、

空き時間で取り掛かっています。

環境構築周りでCircleCIについて勉強したのでその際のメモです。

記事に書く内容

  • CircleCIの概要

  • CircleCIを使ってみる

  • CircleCIの設定ファイル(config.yml)について

CircleCIの概要

CircleCIって何?

CircleCIの公式&gclid=Cj0KCQiAyp7yBRCwARIsABfQsnTANY0-freoXkHqV5p4DuPaQ1JTlM7LM3DvcJaiyQ2mJieOsDp5bCIaAhcYEALw_wcB)

流行り(というかデファクトになりつつある)のSaaS型のCI/CDサービスです。

GitHubだとかBitbucketと連携して、テストだとかビルドができるようになります。

基本的なLinuxのコマンドは実行できるので、aws cliを利用してリソースの変更だとかもできるみたいです。

ところでCI/CDって何?

CI/CDを日本語に訳すと、*継続的インテグレーションと継続的デリバリー`という意味になります。

継続的インテグレーション
  - ビルドやテストを短期間で繰り返して開発効率をあげること
     - 定期的に実行(デイリービルド)
     - 短期間で繰り返しテスト
継続的デリバリー
  - CIの仕組みをインフラ構築まで拡張したような考え方
     - テストした成果物をそのまま本番環境に自動デプロイ
     - テスト環境と同じ構成で本番環境を動作させることができる

CircleCIでできることって何?

  • ビルド

    • ソースコードから実行可能なアプリケーションを構築できる
    • Dockerイメージのpullとか、依存パッケージのインストール、コンパイル
  • テスト

    • lintツールによるコードスタイルチェック
    • ○○specでのテスト実行
  • デプロイ

    • ビルドしてテストが通ったものを本番/stg環境にデプロイすることができるようになります

パターンによりけりですが、CircleCIと連携させて下図のような開発体制を構築することができます。

開発者がCommitしたら自動でテストを走らせて、Slackに通知させるだとか

masterとかdevelopであれば、本番/stg環境に自動デプロイするといったことができるようになります。

f:id:wa_football_1120:20200216132734p:plain

なんでCircleCI?

CircleCIはSaaS型のサービスです。

Jenkinsのように自前でサーバを組んだりする必要がなく、環境構築/運用コストが非常に低いことが魅力的です。

ビルド~デプロイの設定はymlファイルで設定するので、職人が作って中身がブラックボックスになりにくいことも魅力的です。

ただ、デメリットもあり、インフラ ~ ミドルまでのあたりはCircleCIの持ち物なので、

何か障害が起きた時だとかは自分たち原因究明するのはまず不可能です。

CircleCIを使ってみる

CircleCIはGithubもしくはBitbucketと連携させることが前提としてあります。

そのため、利用するにあたってはどちらかのアカウント登録が必要になります。

料金体系

CircleCI料金プラン

無料枠有料枠があります。

無料枠

個人開発なので無料枠を利用します。

  • 同時実行ジョブ(同時に実行できるCI/CD活動)
    • 1
  • ビルド時間の制限
    • 1000min/month

アカウント登録 ~ GitHub連携まで

CircleCIの公式&gclid=Cj0KCQiAyp7yBRCwARIsABfQsnTANY0-freoXkHqV5p4DuPaQ1JTlM7LM3DvcJaiyQ2mJieOsDp5bCIaAhcYEALw_wcB)

でアカウント登録 ~ GitHub連携までやっていくと、リポジトリcircleci-project-setupというリモートブランチが作成されます。 このブランチの中にはCircleCIの設定ファイル.circleci/config.ymlが含まれています。

git branch -r                                                                                                 [circleci-project-setup]
  origin/circleci-project-setup
  origin/master

git checkout -b circleci-project-setup

git pull origin circleci-project-setup                                                                        [circleci-project-setup]
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), done.
From github.com:*****/trainning-rails-vue
 * branch            circleci-project-setup -> FETCH_HEAD
 * [new branch]      circleci-project-setup -> origin/circleci-project-setup
Updating 2b6de12..6dfc64b
Fast-forward
 .circleci/config.yml | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
 create mode 100644 .circleci/config.yml

CircleCIの設定ファイル(config.yml)について

先ほどデフォルトで作成されたconfig.ymlの中身は下のようになっていました。

version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2 

jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
    executor: default
    steps:
      - checkout
      - run:
          name: Which bundler?
          command: bundle -v
      - ruby/bundle-install

orbs

Version2.1から追加された機能です。

CircleCIのcommandsやジョブ(設定)をパッケージとして公開し、再利用するための仕組みです。

今回設定されているruby@0.1.2Rubyのインストールだとかテストのコマンドがすでに定義されているパッケージになります。

参考

jobs

jobsのフィールドは一般的に下記のような構造になう量です。

jobs
  -job名(1つの場合はbuildという名前にする)
     - docker
     - executer 
     - steps

job名

1つ以上のjobを設定します。

jobが1つの場合はbuiltという名前にしなければいけません

docker

ほとんどのwebアプリの場合は、コンテナイメージを利用すると思いますが、

CircleCI上で動かすdocker imageの設定です。

  • image
    • ベースとなるdocker imageを指定する。複数指定もOK
    • environmentやcommandを使ってコンテナが動く際の動作を変えていく
  • auth(optional)
    • Docker hubを利用する場合の認証情報
    • username
    • password
  • aws_auth (optional)
  • steps
    • CI環境上で動作させるコマンドや実行結果の保存、キャッシュ操作などを設定

実際に設定したconfig設定

今回アプリケーションはデプロイまではせずに、テストを実行したいと思います。

(実際のリポジトリにはnuxtのプロジェクトも含まれていますが一旦はbackendのみを)

  • rubocopによるコードのLintチェック
  • Rspecによるテスト

上の基本構文に含まれていない、ブロックがありますが下のような設定です。

version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2 
commands:
  bundleinstall:
    description: "bundle install --path vendor/bundle"
    steps:
      - run: cd back && bundle install --jobs=4 --retry=3 --path vendor/bundle
executors:
  backend-executor:
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
        environment:
          RAILS_ENV: test
          RDS_HOST: 127.0.0.1
          TZ: Asia/Tokyo
          LANG: C.UTF-8
      - image: circleci/mysql
        command:
          mysqld --default-authentication-plugin=mysql_native_password
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: trainning_test
          MYSQL_ROOT_HOST: '%'
          TZ: Asia/Tokyo
jobs:
  rubocop:
    working_directory: ~/app
    executor: backend-executor
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "./back/Gemfile.lock" }}
            - v1-dependencies-
      - save_cache:
          paths:
            - ./back/vendor/bundle
          key: v1-dependencies-{{ checksum "./back/Gemfile.lock" }}
      - bundleinstall
      - run:
          name: Rubocop
          command: cd back && bundle exec rubocop
  rspec:
    working_directory: ~/app
    executor: backend-executor
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "./back/Gemfile.lock" }}
            - v1-dependencies-
      - save_cache:
          paths:
            - ./back/vendor/bundle
          key: v1-dependencies-{{ checksum "./back/Gemfile.lock" }}
      - bundleinstall
      - run: cd back && bundle exec rails db:schema:load
      - run:
          name: Rspec
          command: cd back && bundle exec rspec
workflows:
  version: 2
  backend_build_work_flow:
    jobs:
      - rubocop
      - rspec:
          requires:
            - rubocop

commandsブロック

複数のジョブで利用するコマンドを定義することができます。

今回は、front(nuxt)とbact(rails)を1つのリポジトリで管理する想定でしたので、

cd back &&で事前にRailsプロジェクトが入っているディレクトリに移動しています。

commands:
  bundleinstall:
    description: "bundle install --path vendor/bundle"
    steps:
      - run: cd back && bundle install --jobs=4 --retry=3 --path vendor/bundle
# parameterブロックを追加して引数を受け取ることもできる
commands:
  bundleinstall:
    description: "bundle install --path vendor/bundle"
    parameters:
         path:
           type: string
          default: "vendor/bundle"
    steps:
      - run: bundle install --jobs=4 --retry=3 --path << parameters.path >>

executorsブロック

複数のジョブで利用する実行環境を定義することができます。 今回はrubyのイメージとmysqlのイメージを利用しています。

localhostを複数のコンテナで共有するようです。 注意点としてはDBコンテナのホストを指定する際にはlocalhostではなく、127.0.0.1を指定しないとうまく動いてくれません。

CircleCIではDockerのNetwork Namespaceという機能を使ってlocalhostを複数のコンテナで共有しています。これを使うと複数のコンテナで使うネットワークを分けることができます。もともとはLinux Kernelが提供している機能でnetnsと呼ばれ、Dockerはそれをラップしている形となります。

executors:
  backend-executor:
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
        environment:
          RAILS_ENV: test
          RDS_HOST: 127.0.0.1
          TZ: Asia/Tokyo
          LANG: C.UTF-8
      - image: circleci/mysql
        command:
          mysqld --default-authentication-plugin=mysql_native_password
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: trainning_test
          MYSQL_ROOT_HOST: '%'
          TZ: Asia/Tokyo

jobsブロック

rubocop

  • working_directoryでappというディレクトリを作業ディレクトリにしていきます。
  • restore_cacheで以前に保存しておいたbundleインストールしたライブラリを復元します。
    • 動作時間の短縮につながります。
  • bundleinstallは上で定義した再利用可能なコマンドです
  • save_cacheでvendor/bundleに入っているライブラリ群を保存します。
rubocop:
    working_directory: ~/app
    executor: backend-executor
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "./back/Gemfile.lock" }}
            - v1-dependencies-
      - save_cache:
          paths:
            - ./back/vendor/bundle
          key: v1-dependencies-{{ checksum "./back/Gemfile.lock" }}
      - bundleinstall
      - run:
          name: Rubocop
          command: cd back && bundle exec rubocop

rspec

rspecも同じような形です。

bundle exec rails db:schema:loadとすることで、schema.rbと同じ内容のDBを作ります。

マイグレーション忘れなどでエラーが発生しないための工夫です。

rspec:
    working_directory: ~/app
    executor: backend-executor
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "./back/Gemfile.lock" }}
            - v1-dependencies-
      - save_cache:
          paths:
            - ./back/vendor/bundle
          key: v1-dependencies-{{ checksum "./back/Gemfile.lock" }}
      - bundleinstall
      - run: cd back && bundle exec rails db:schema:load
      - run:
          name: Rspec
          command: cd back && bundle exec rspec

workflow

公式

workflowはバージョン2から追加された機能なのですが、

名前の通りjobのワークフローを作ります。

jobをいつ、どの順番で実行するかを定義することができます。

今回はrubocopの後にrspecを実行して欲しかったのでrequiresを使って順番関係を定義しています。

workflows:
  version: 2
  backend_build_work_flow:
    jobs:
      - rubocop
      - rspec:
          requires:
            - rubocop

ローカルでcofigファイルの検証

この時点でgithubにpushすればCircleCIが自動的に変更を検知して、

configに書かれた内容が実行されるためローカルじゃなくてもconfigファイルの検証はできます。

が、なんとなくダサいので、ローカルで検証してからpushしたいと思います。

CircleCI CLI

ローカル環境からCircleCIに対してビルドしたり、

config.ymlの文法が正しいか検証ができるコマンドです。

リファレンスに沿ってダウンロードしてください。

#クイックインストール
curl -fLSs https://circle.ci/cli | bash

configファイルの検証

circleci config validateコマンドで検証ができます。

デフォルトで.circleci/config.ymlを検証対象とします。

circleci config validate                                                                                       [circleci-project-setup]
Config file at .circleci/config.yml is valid.

ローカルでjobを実行

circleci local executeでローカル環境で作成したjobを実行ができるのですが、

2.1についてはまだ対応していないようで下のようなエラーが発生します。

circleci local execute                                                                                         [circleci-project-setup]
Error:
You attempted to run a local build with version '2.1' of configuration.
Local builds do not support that version at this time.
You can use 'circleci config process' to pre-process your config into a version that local builds can run (see 'circleci help config process' for more information)

CLI側で2.1から2.0形式に変換してくれるコマンドが用意されています。

2.1から追加されたexecutorsorbsを展開して出力してくれます。

circleci config process .circleci/config.yml > process.yml
circleci local execute -c process.yml --job [実行したいjob名]

githubへのpush

ここまで来て、githubへプッシュすると、

CircleCIが自動的に動作していると思います。

画像では、rubocopに成功したがrspecのjobが失敗していることがわかります。 (まだ、アプリ自体がまっさらな状態なので、rails db:schema:loadでこけたようでした)

f:id:wa_football_1120:20200217054408p:plain

まとめ

CircleCIについて、少し学習をしてみました。

  • CircleCIは、SaaS型のCI/CDサービス

    • CI(継続的インテグレーション)は、ビルドやテストを短期間で繰り返して品質を向上させていく取り組み
    • CD(継続的デリバリー)は、インフラ環境への自動デプロイを含めてCIを実行していく取り組み
  • CircleCIのconfig.ymlにコンテナイメージや実行するジョブを定義していく

二番煎じどころではない今更勉強したので、かなり情報が溢れています。

ただ2.1については、DRYに書くための新機能(executors)があったりするので、

公式のリファレンスを確認した方がいいですね。

色々と進んできたら、masterブランチだけデプロイをするjobを含めて、

もう一回記事を書きたいと思います。