RACCOON TECH BLOG

株式会社ラクーンホールディングスのエンジニア/デザイナーから技術情報をはじめ、世の中のためになることや社内のことなどを発信してます。

CircleCIを導入してCI/CDを簡単に始めよう!

こんにちは。開発チームの尾碕です。

ラクーンでは、「スーパーデリバリー」や「Paid」「T&G売掛保証」「URIHO」「COREC」などの様々なWEBサービスを提供しています。
サービスのシステムを高品質に保ち続けるためには継続的インテグレーション(CI)継続的デリバリ(CD)は欠かせません。

弊社では、CI/CDツールの代表である「Jenkins」を利用していますが、最近よく名前を聞くCircleCIを調べてみましたので、ご紹介したいと思います。
今回は、CircleCIを使ってWebアプリケーションのテスト・デプロイ・ビルドまでの導入手順を説明します!

CircleCIとは?

CircleCIとは、ビルド・テスト・デプロイのCI/CDを自動で実行するクラウドサービスです。
クラウドサービスなので、Jenkinsなどのオンプレ型のようにCIサーバーを設置せずにすぐに利用することが可能です。

CircleCIは、コンテナと呼ばれる実行環境でビルドやテストを実施します。
実行環境では、Docker・VMマシン・MacOSを選ぶことができ、実行速度が速いDockerが主流となっています。

GitHubやBitbucketなどのリポジトリサービスと連携を行い、pushする度にコンテナ内で設定した処理を自動で実行します。

コンテナが複数あれば、並列処理でテストを実施できます。

無償版と有償版の比較

CircleCIではコンテナを1つまで無料で利用できますが、複数利用する場合は有償になります。
他にもMacOSやオンプレミス版の実行環境の料金プランがあります。この記事では、Linux実行環境での無料版を利用しています。

Linux実行環境での無償版と有償版の違い

機能 無料版 有償版
リポジトリ数 無制限 無制限
ユーザー数 無制限 無制限
月間ビルド時間 1,000分まで無料 無制限
コンテナ数 1つ 1つ追加する毎に50$
同時処理数 1回まで コンテナの数だけ並列処理が可能

他の料金プランについての詳細はこちらをご覧ください。

CircleCIを動かす

実際にCircleCIを動してみましょう。

Gitアカウントの連携

CircleCIのTOPから登録をクリックしてユーザ登録画面を開きます。
CircleCIを利用するには「GitHub」か「Bitbucket」のアカウント連携が必要です。

連携が終わると、CircleCIのメニューが開きます。
日本語は未対応で英語しかサポートされていません。

JOBSGetting Startedの中に、連携したGithubかBitbucketのアカウントが表示されています。
下の画面の時点ではリポジトリが存在しないので、リポジトリを追加します。

ページを更新すると追加したリポジトリが表示しています。
チェックボックスを入れてFollow をクリックすると完了です。

JOBSに追加したリポジトリの実行結果が表示されています!
ですが、CircleCIの設定ファイルが存在しないため失敗しています。

CircleCIでは、ビルドやテストなどの一連のプロセスをJobと呼びます。
JobはYAML形式の設定ファイルに定義します。.circleci/config.ymlを作成してリポジトリにpushします。

以下の設定は、実行環境に「Hello world!」と表示するJobになります。

.circleci/config.yml

version: 2
jobs:
  build:
    docker:
      - image: circleci/openjdk:8-jdk
    steps: 
      - run: echo 'Hello world!'

.circleci/config.ymlを追加してpushすると、定義したJobが実行されて成功しています。

Jobをクリックすると詳細画面を開くことができます。

実行結果のログでは「Hello world!」と出力されています。
CircleCIを実行できました!

ビルドからテストまで

CircleCIを動かせるようになったので、次はWebアプリケーションのビルド・テストを行います。

アプリケーション

今回はJava8+SpringBoot+GradleのWebアプリケーションでCI/CDを行います。

ビルド・テストを実施するJobの定義は以下の通りです。

.circleci/config.yml

version: 2
jobs:
  build:
    docker:
      - image: circleci/openjdk:8-jdk
    working_directory: ~/repo
    steps: 
      - checkout 
      # 依存関係をダウンロードしてキャシュする
      - restore_cache:
          keys:
          - v1-dependencies-{{ .Branch }}-{{ checksum "build.gradle" }}
          - v1-dependencies-{{ .Branch }}-
          - v1-dependencies-
      - run: gradle dependencies
      - run: gradle build -x test
      - save_cache:
          paths:
            - ~/.gradle
          key: v1-dependencies-{{ .Branch }}-{{ checksum "build.gradle" }}
      - run: 
          name: テストの実施
          command: gradle test

.circleci/config.ymlの説明

Key名 説明
version CircleCIのバージョンを入力します。現時点では2.02.1のみ対応しています。
jobs Jobと呼ばれる実行環境や実際に実行されるステップを記述します。
必須項目で原則buildを指定します。
docker Jobの実行環境の1つであるDockerを指定します。 imageでDockerイメージを指定します。
CircleCIは様々なDockerイメージを提供しています。CircleCI提供Dockerイメージ一覧
working_directory stepsを実行するディレクトリを指定します。
steps Jobで実際に実行するコマンドを1つ以上定義します。
ユーザーが自由に指定できるrunコマンドとCircleCIが用意したコマンドがあります。
checkout 指定したworkspaceにリポジトリのソースコードのチェックアウトを行います。
restore_cache CircleCIから指定したkeyのキャッシュを読み込みます。
save_cache pathで指定したファイルをCircleCIのキャッシュに保存します。
run 実際に実行するコマンドを指定します。
namecommandで実行するコマンドの名前を決めることも可能です。

Java8のSpringBootのWebアプリケーションのため、dockerimagecircleci/openjdk:8-jdkに指定しています。
ダウンロードした依存関係をsave_cacheでキャッシュすることで、2回目以降にJobを実施するときにrestore_cacheでキャッシュした依存関係を読み込んでテスト時間を短縮します。

変更したソースコードと設定ファイルをpushするとJobが実行されます。
Jobの詳細画面を開くと、Jobのテストの実施のログに、ユニットテスト結果が出力されています。

これでリポジトリにpushする度にビルドとテストを実施できます!

デプロイする

ビルド・テストまで説明したので次はデプロイの方法を説明します。

今回はSpringBootのWebアプリケーションをjarにビルドしてデプロイします。
※実際の本番環境でデプロイする場合は、ブルーグリーンデプロイメントなどの手法でデプロイを行いますが、テストのため今回はビルドしたJarをテストサーバーにデプロイしています。

CircleCIにSSH秘密鍵を登録する

CircleCIからデプロイ先に接続する際、SSH接続をする場合があります。
その場合、CircleCIにSSH秘密鍵を登録します。

Projectの設定からSSH Permissionsを選んでAdd SSH Keyをクリックします。

ホスト名と秘密鍵を入力するモーダルが開くので、デプロイ先のホスト名とSSH秘密鍵を入力してAdd SSH Keyをクリックして登録します。

登録が終わると、Fingerprintがセットされます。この値は設定ファイルで利用します。

ビルド・テスト・デプロイを実施する

秘密鍵を追加したので、ビルド・テスト・デプロイを行うJobを定義します。

ビルド・テスト・デプロイを実行するJobの設定です。

.circleci/config.yml

version: 2
jobs:
  build:
    docker:
      - image: circleci/openjdk:8-jdk
    working_directory: ~/repo
    environment:
      BUILD_APP: build/libs/sample_app-*.jar # 成果物
      HOST_NAME: host.name # デプロイ先のホスト名
      USER_NAME: username  # デプロイ先のユーザー名
      APP_DIR: /home/sample_app/ #デプロイ先のディレクトリ
      APP_NAME: sample-appd
    steps: 
      - checkout 
      # 依存関係をダウンロードしてキャシュする
      - restore_cache:
          keys:
          - v1-dependencies-{{ .Branch }}-{{ checksum "build.gradle" }}
          - v1-dependencies-{{ .Branch }}-
          - v1-dependencies-
      - run: gradle dependencies
      - run: gradle build -x test
      - save_cache:
          paths:
            - ~/.gradle
          key: v1-dependencies-{{ .Branch }}-{{ checksum "build.gradle" }}
      - run: 
          name: テストの実施
          command: gradle test
      # 設定した秘密鍵のFingerPrintを追記する
      - add_ssh_keys:
          fingerprints:
            - "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
      - deploy:
          name: deployment
          command: |
            gradle build #ビルドを実行して成果物を作成する
            ssh-keyscan ${HOST_NAME} >> ~/.ssh/known_hosts
            sudo apt install -y rsync
            ssh ${USER_NAME}@${HOST_NAME} "service ${APP_NAME} stop" # サービスを停止する
            rsync -vc ${BUILD_APP} ${USER_NAME}@${HOST_NAME}:${APP_DIR} # 成果物をデプロイする
            ssh ${USER_NAME}@${HOST_NAME} "service ${APP_NAME} start" # サービスを起動する

.circleci/config.ymlの説明

Key名 説明
environment 環境変数を設定します。
add_ssh_keys 設定したSSH秘密鍵の設定を行います。 fingerprintsに設定画面で表示されていたfingerprintの値を入力してください
deploy デプロイを実施する時の処理です。 nameにデプロイ名、commandにデプロイで実施するコマンドを入力してください。

先ほど設定した時に表示されていたfingerprintの値をadd_ssh_keysfingerprintsに入力してください。

deploycommandでは、steprunと同じように実行するコマンドを入力します。複数行のコマンドを実行する場合は1行目に|を指定します。

今回、rsyncで成果物をデプロイするため、デプロイ先からAre you sure you want to continue connecting (yes/no)?と聞かれて止まらないようにssh-keyscanコマンドを実行しています。

デプロイしたjarファイルをserviceで起動・停止するために以下の設定を行います

build.gradle

// デーモン化が可能なjarをビルドする
bootJar {
     launchScript()
}

デプロイ先

# シンボリックリンクでサービスに登録する
$ sudo ln -s /home/sample_app/sample_app-X.X.X-SNAPSHOT.jar /etc/init.d/sample-appd

これでリポジトリにpushする度にビルド・テスト・デプロイが実施されるようになりました!

※今回はテスト環境のため、デプロイを行うとサービスが一瞬停止してしまいます。

WorkflowでJobを細分化

ビルドからデプロイまでの手順ができるようになりましたが、今の設定ではどのブランチでもデプロイしてしまいます。
CircleCIのWorkflowを利用することで、masterブランチのみデプロイする設定も可能です。

Workflowとは

Workflowは、複数のJobを実行できる機能です。
Jobの実施は並列処理で実行されます。そのため、1つのJobが失敗しても他のJobが止まらずに実行されます。
Worflowの設定で、Jobの実行は直列処理で実行も可能です。
他にJob毎の実行条件を設定することができます。

manageブランチのみデプロイを実行する。

workflowの機能を使って、ビルドテストデプロイの順番でJobを実行するようにします。
また、masterブランチのみデプロイJobを実行するように定義します。

.circleci/config.yml

version: 2
jobs: 
  build:
    docker:
      - image: circleci/openjdk:8-jdk
    working_directory: ~/repo
    steps:
      - checkout 
      # 依存関係をダウンロードしてキャシュする
      - restore_cache:
          keys:
          - v1-dependencies-{{ .Branch }}-{{ checksum "build.gradle" }}
          - v1-dependencies-{{ .Branch }}-
          - v1-dependencies-
      - run: gradle dependencies
      - run: gradle build -x test
      - save_cache:
          paths:
            - ~/.gradle
          key: v1-dependencies-{{ .Branch }}-{{ checksum "build.gradle" }}
      # 他のjobでも共有する
      - persist_to_workspace: 
          root: ~/repo
          paths: 
            - .
  test:
    docker:
      - image: circleci/openjdk:8-jdk
    working_directory: ~/repo
    steps:
      # 共有したworkspaceをアタッチする
      - attach_workspace:
          at: ~/repo
      - run: 
          name: テストの実施
          command: gradle test
  deploy:
    docker:
      - image: circleci/openjdk:8-jdk
    working_directory: ~/repo
    environment:
      BUILD_APP: build/libs/sample_app-*.jar # 成果物
      HOST_NAME: host.name # デプロイ先のホスト名
      USER_NAME: username  # デプロイ先のユーザー名
      APP_DIR: /home/sample_app/ #デプロイ先のディレクトリ
      APP_NAME: sample-appd
    steps:
      # 共有したworkspaceをアタッチする
      - attach_workspace:
          at: ~/repo
      # 設定した秘密鍵のFingerPrintを追記する
      - add_ssh_keys:
          fingerprints:
            - "XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX"
      - run : sudo apt install -y rsync
      - run : ssh-keyscan ${HOST_NAME} >> ~/.ssh/known_hosts
      - run : gradle build #ビルドを実行して成果物を作成する
      - deploy:
          name: masterをdeploy
          command: |
            ssh ${USER_NAME}@${HOST_NAME} "service ${APP_NAME} stop" # サービスを停止する
            rsync -vc ${BUILD_APP} ${USER_NAME}@${HOST_NAME}:${APP_DIR} # 成果物をデプロイする
            ssh ${USER_NAME}@${HOST_NAME} "service ${APP_NAME} start" # サービスを起動する
workflows:
  version: 2
  build_test_deploy: # workflow名
    jobs:
      - build
      - test: 
          requires: # buildが成功したら
            - build
      - deploy: 
          requires: # buildとtestが成功したら
            - build
            - test
          filters: 
            branches: # masterブランチのみ実行する
              only: 
                - master

steps説明

Key名 説明
persist_to_workspace workspaceを他のjobでも共有するように設定します。
rootでworkspaceのrootパスを指定します。
pathで共有するパスを指定します。
attach_workspace 共有してあるworkspaceをアタッチします。 atを指定した階層のファイルが利用できるようになります。

workflows説明

Key名 説明
workflows 実行するJobの順序を設定します。workflows名を設定して、jobsにJobの設定を行います。
version バージョンはJobと同じく2.02.1を指定します。
jobs 実行する各Jobの設定を行います。workflowsの前に定義したjobsのJob名を指定します。
requires 指定したJob名が実施して成功したら実施するように設定します。
filters Jobを実行する条件を指定します。branchesで実行するブランチを設定できます。

workflowsで対象のJobにfilter: branchesで実行するブランチを指定できます。

      - deploy: 
          requires: # buildとtestが成功したら
            - build
            - test
          filters: 
            branches: # masterブランチのみ実行する
              only: 
                - master

Workflowを利用することで、1回のpushで複数のJobを実行することができます。

masterブランチにpushすることで、buildJobとtestJobとdepoyJobが実行されています。

WORKFLOWSからmasterブランチの実行結果を見ると、Jobが設定した順番で実行されています。

developブランチでpushすると、buildJobとtestJobが順番に実行されています。

これでmasterブランチにpushした時だけデプロイするようになりました!

おわりに

CircleCIを利用すれば、設定ファイル1つで簡単にビルド・テスト・デプロイができました。
現在は英語しか対応していませんが、日本支社が最近設立されたので日本語対応も近いかもしれません。
CircleCIを利用して簡単に開発の品質と効率を向上しましょう!

関連記事

一緒にラクーンのサービスを作りませんか? 採用情報を詳しく見る

関連記事

運営会社:株式会社ラクーンホールディングス(c)2000 RACCOON HOLDINGS, Inc