はじめに
この記事はCircleCI Advent Calendar 2020の5日目の記事です。
CircleCIでソリューションズエンジニアをしている車井と言います。
2020年11月17日にCircleCI Runner(以下、Runner)がリリースされました。
Runnerは自分・自社で管理しているサーバをCircleCIのビルドサーバとして利用することが出来る機能です。
本記事の執筆時点ではまだ日本語のマニュアルが出ていませんが、細かなユースケースや制限事項などは本家のマニュアルにおまかせするとして、ここではとりあえず動かしてみたいと思います。
タイミングよく、先日のAWS re:Invent 2020で、EC2にMacが加わったというニュースもありましたので、今回はLinuxとMacの両方をRunnerとして動かしてみたいと思います。
トライしてみましたが、Macのインスタンスがうまく作れなかったので、ここではLinuxだけをセットアップしてみます。
なお、実際にRunnerを使うには事前にCircleCIに問い合わせて、鍵開けしてもらう必要があります。
もし使ってみたいという方は、問い合わせて見てください。
Runnerの構成要素
Runnerは下記の図に記載されているとおり、Launch AgentとTask Agentによって構成されています。

- ローンチ エージェント (launch-agent)
 - CircleCI本体と通信して必要な情報を収集したり、タスクエージェントを起動したりします。
 - タスク エージェント (task-agent)
 - 1つ1つのタスクの実行を行います。
 
すなわち、この2つのエージェントをセットアップすれば動くことになりますが、実際にはタスクエージェントはダウンロードしてきたものをそのまま動かすだけですので、ローンチエージェントの設定が主な作業になります。
なお、図でもわかるとおり、永続化が必要なデータ(キャッシュデータやアーティファクトなど)はCircleCIが管理しているストレージ領域に保存されるため、Runner専用の領域を別に作成する必要はありません。
事前準備
Runnerをセットアップするには、事前に名前空間、リソースクラス、認証トークンを作成しておく必要があります。
これらの作成にはCircleCI CLI(コマンドラインツール)をインストールしておく必要があります。CircleCI CLIのインストール方法はこちらのマニュアルを参照してください。
まず、名前空間を作成します。名前空間はVCS(GitHubやBitbucket)上の組織に対して一意に割り当てることができる名前です。
# circleci create <名前空間名> <VCS種別> <Org名>
VCS種別は"github"か"bitbucket"を指定しますので、例えば私の https://github.com/kurumai というOrgに対して "kurumai"という名前空間を作成する場合は下記のコマンドを実行します。
# circleci namespace create kurumai github kurumai
これで名前空間が出来ましたので、次にリソースクラス名を登録します。
# circleci runner resource-class create <resource-class> <description>
<resource-class>には"名前空間/リソースクラス名"の書式で指定します。
リソースクラス名は任意で構いませんが、CircleCIのconfig.ymlで実行環境として指定する値になりますので、サーバの種類や特性を表す文字列が分かりやすいと思います。
今回はCircleCIの標準の実行環境にはない、Armベースのプロセッサを搭載しているAWS EC2のa1.medium をリソースクラスとして登録してみたいので、arm-runnerという名前で登録しました。
# circleci runner resource-class create kurumai/arm-runner "Arm runner"
+--------------------+-------------+
|   RESOURCE CLASS   | DESCRIPTION |
+--------------------+-------------+
| kurumai/arm-runner | Arm runner  |
+--------------------+-------------+
最後に、これらのリソースクラスを認証するためのトークンを作成しておきます。
# circleci runner token create <resource-class> <nickname>
nicknameは任意で構いません。
ここではリソースクラスと同じ名前を使っています。
# circleci runner token create kurumai/arm-runner arm-runner
api:
    auth_token: ********
これで事前準備は完了です。
Arm Runnerのセットアップ
まず、 a1.medium タイプのEC2インスタンスにRunnerをセットアップします。OSはUbuntuで東京リージョンに作成しています。
セキュリティグループはアウトバウンドはすべて許可、インバウンドはセットアップのためにSSH(22)だけを許可しています。
RunnerはCircleCIに対してポーリングしてジョブを実行しますので、CircleCI側から(インターネット側から)の通信は許可する必要はありません。
それでは、SSHでログインして、まずはエージェントモジュールをダウンロードします。
いきなりいろいろなものを実行していますが、1行目の platform=linux/arm64 だけ環境によって変更する必要がありますが、それ以外はマニュアルをそのままコピー&ペーストすれば動くと思います。
platform=linux/arm64
prefix=/opt/circleci
sudo mkdir -p "$prefix/workdir"
base_url="https://circleci-binary-releases.s3.amazonaws.com/circleci-launch-agent"
echo "Determining latest version of CircleCI Launch Agent"
agent_version=$(curl "$base_url/release.txt")
echo "Using CircleCI Launch Agent version $agent_version"
echo "Downloading and verifying CircleCI Launch Agent Binary"
curl -sSL "$base_url/$agent_version/checksums.txt" -o checksums.txt
file="$(grep -F "$platform" checksums.txt | cut -d ' ' -f 2)"
file="${file:1}"
mkdir -p "$platform"
echo "Downloading CircleCI Launch Agent: $file"
curl --compressed -L "$base_url/$agent_version/$file" -o "$file"
echo "Verifying CircleCI Launch Agent download"
sha256sum --check --ignore-missing checksums.txt && chmod +x "$file"; sudo cp "$file" "$prefix/circleci-launch-agent" || echo "Invalid checksum for CircleCI Launch Agent, please try download again"
次に /opt/circleci/launch-agent-config.yaml オーナーをroot、パーミッションは600で作成します。
api:
  auth_token: AUTH_TOKEN <- こちらに先程取得したトークン文字列を記載します
runner:
  name: RUNNER_NAME <- こちらは任意の名前で構いません
  command_prefix: ["/opt/circleci/launch-task"]
  working_directory: /opt/circleci/workdir/%s
  cleanup_working_directory: true
エージェントを実行するためのユーザーを作成します。
ここでは circleci というユーザーを作成しています。
id -u circleci &>/dev/null || adduser --uid 1500 --disabled-password --gecos GECOS circleci
mkdir -p /opt/circleci/workdir
chown -R circleci /opt/circleci/workdir
ローンチエージェントのスクリプトを /opt/circleci/launch-task とし、オーナーをroot、パーミッションは755で作成します。
# !/bin/bash
set -euo pipefail
## This script launches the build-agent using systemd-run in order to create a
## cgroup which will capture all child processes so they're cleaned up correctly
## on exit.
# The user to run the build-agent as - must be numeric
USER_ID=$(id -u circleci)
# Give the transient systemd unit an inteligible name
unit="circleci-$CIRCLECI_LAUNCH_ID"
# When this process exits, tell the systemd unit to shut down
abort() {
  if systemctl is-active --quiet "$unit"; then
    systemctl stop "$unit"
  fi
}
trap abort EXIT
systemd-run \
    --pipe --collect --quiet --wait \
    --uid "$USER_ID" --unit "$unit" -- "$@"
ローンチエージェントをサービスとして起動するため、/opt/circleci/circleci.serviceファイルを作成します。先程と同様にオーナーはroot、パーミッションは755で作成します。
[Unit]
Description=CircleCI Runner
After=network.target
[Service]
ExecStart=/opt/circleci/circleci-launch-agent --config /opt/circleci/launch-agent-config.yaml
Restart=always
User=root
NotifyAccess=exec
TimeoutStopSec=18300
[Install]
WantedBy = multi-user.target
サービスを起動します。
systemctl enable /opt/circleci/circleci.service
正常に動いているかどうか確認しておきます。
systemctl start circleci.service
これでセットアップは完了です。
それでは、正しく動くかどうかチェックしてみます。
CircleCIの config.yml を下記のように作って動かしてみました。
Runnerを利用する場合は、machineにtrueを設定し、resource_classに作成した名前を指定します。
version: 2
jobs:
  prepare:
    docker:
      - image: circleci/node:latest
    steps:
      - checkout
      - run:
          name: Generate temp file
          command: |
            mkdir -p tmp
            echo "Hello CircleCI!!" > tmp/hello.txt
      - persist_to_workspace:
          root: .
          paths:
            - "tmp"
  arm-runner:
    machine: true
    resource_class: kurumai/arm-runner
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run: cat tmp/hello.txt
workflows:
  version: 2
  runner-workflow:
    jobs:
      - prepare
      - arm-runner:
          requires:
              - prepare
結果です。なんとかちゃんと動きました。
もしジョブがキューの状態で動かない場合は、サービスがちゃんと動いているのかや、設定を変更した場合はサービスを再起動したかどうかを確認すると良いと思います。
実際、私も設定を変更したにも関わらず、サービスを再起動するのを忘れてはまりかけました。
おわりに
CircleCI Runnerをセットアップして、通常のCircleCIのExecutorと組み合わせたワークフローを動かしてみました。
セットアップは手作業が伴いますが、それほどはまるところは多くはないと思いますので、ぜひ興味のあるかたはトライしてみてください。
Runnerをうまく使うことで、スケーラビリティが必要なテストはCircleCIの標準のものを使い、セキュリティが気になるデプロイのところだけRunnerを使うということが簡単にできるようになりました。
また、これまではテストが難しかった組み込み系のシステムでもRunnerは活躍できるのではないかと思っています。
CI/CDはWebやモバイルアプリのサービス開発を中心にSaaS利用が広まっていますが、まだまだソフトウェア開発の世界にはCI/CDに着手できていないところがいっぱいあると思います。ぜひ、この機会にCircleCIを触ってみてCI/CDの世界に触れてみてください。