CircleCI のコンテナランナーを動かしてみた

## はじめに
CircleCI でカスタマーサクセスをしている鈴木です。もし、これを読んでいる方で Nintendo Switch のプロコントローラーを転売目的で大量に抱えている方がいたらやめていただくか、定価以下で市場に流通させていただきたいです。

[CircleCI の Self-hosted Runner](https://circleci.com/docs/ja/runner-overview/) はこれまで VM や物理マシンにエージェントをインストールする形式でしたが、Kubernetes 上で動作する[コンテナランナー](https://circleci.com/docs/ja/container-runner/)というものをリリースしました。

コンテナランナーに対して、従来の Self-hosted Runner をマシンランナーと呼ぶことになりました。マシンランナーには「ひとつのマシンランナーが実行できるジョブはひとつだけ」という制約があります。一方、コンテナランナーでは複数のジョブを並列実行できます。本記事ではコンテナランナーを試していきます。

## 事前の確認
**1． Kubernetes クラスター と Helm**
コンテナランナーを動作させる Kubernetes クラスターが当然ながら必要です。また、コンテナランナーのインストールに使用する Helm も必要です。
この記事を書いている時点では、Kubernetes 1.12+、 Helm 3.x をサポートしています。

**2． 利用規約への同意**
CircleCI の Self-hosted Runner を使用するには、CircleCI の UI にあるOrganization Settings の Self-Hosted Runners という項目でランナーの利用規約に同意しておく必要があります。
![2022-10-24-00-54-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/85389d8d-609c-074c-17e8-84f80f65abe1.png)

## Namespace とリソースクラスの設定
CircleCI でジョブの実行環境を定義しているのがリソースクラスです。
コンテナランナーでジョブを実行するには、コンテナランナーにカスタムのリソースクラスとして設定しておく必要があります。設定手順は私のチームメイトの Nagisa さんが書いている以下の記事をご参考ください。
[CircleCI RunnerをUIから簡単に使えるか試してみた！](https://qiita.com/Nagisa-kjm/items/2c09bf787d9ead6052d7)
コンテナランナーのリリース前の記事ですが、カスタムのリソースクラスを作成する手順は同じです。

私は以下のように設定しました。
![2022-10-24-01-59-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/06bd472c-425b-2de6-161d-520fa5f5a63a.png)
ちなみに、Namespace はデフォルトでは Org 名が表示されています。もし、CircleCI Orb を作ったことがある場合はすでに Namespace を持っているはずです。CircleCI CLI でご確認ください。

## リソースクラスのトークン発行
上の画面の「Save and Continue」ボタンを押すと画面が遷移し、リソースクラスのトークンが表示されます。将棋の大山康晴十五世名人は「一回目のチャンスは見送れ」といった言葉を残しているそうですが、このトークンはここでしか表示されないので見送らないでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/3051a9ae-9c2c-ce42-97ff-8d8d255c280c.png)

## Helm によるコンテナランナーのインストール
トークンの表示部分の下には下図のインストラクションが続いています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/8fb158b6-a696-3f0a-ef3d-b152bd349532.png)
念のため Prerequisites を満たしているか確認した後、Installation Steps を手順通りに進めます。前段で確認したリソースクラスのトークンは Step 3 で作成する values.yaml 内に格納します。

手順を進めたら、念のためコンテナランナーの Pod が立ち上がっているか確認します。
![2022-10-23-23-56-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/a8b9800b-d5dc-0940-b4ef-5c1d7fc6dfa1.png)
ちなみに私は、ローカルのLinuxマシンに kind で K8s クラスターを立ち上げ、そこにコンテナランナーをインストールしました。クラスターを立ち上げる際に、Ingress で コンテナランナーが CircleCI とやりとりするための Port 22 を開けています。

確認を終えたら「Continue」ボタンを押して次の画面に進みます。

## config.yml の作成
次の画面ではサンプルの config.yml が表示されます。動作確認のためこれをそのまま利用します。
作成したカスタムのリソースクラスがサンプル内に記述されていることがわかります。
![2022-10-24-02-20-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/e88c1a52-d5d7-2ea5-035c-b27856e4b26b.png)
この config.yml をリポジトリの .circleci ディレクトリに保存して、変更を push します。


## ビルドの確認
CircleCI 上でビルドの確認をします。無事に成功しました。Executor のところがカスタムのリソースクラスになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/cd0946d6-53a0-ccf8-f292-b0d4a69b569c.png)


## コンテナランナーの良い点
この記事のはじめのところで、以下のように書きました。
> マシンランナーには「ひとつのマシンランナーが実行できるジョブはひとつだけ」という制約があります。一方、コンテナランナーでは複数のジョブを並列実行できます。

実際に複数のジョブを走らせてマシンランナーとの違いを見たいと思います。

### マシンランナーでの動作
まずは、手元の MacBook Pro にインストールしたマシンランナーで立て続けに３つのパイプラインをほぼ同時に走らせてみました。
開始直後に確認すると、３つのうち１つ目のパイプラインのみが実行中で、残り２つは Queue で処理を待っています。（ジョブ名の前のアイコンが３点リーダーになっています。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/e39c8aac-1df0-d729-2f1a-021ce79e28cf.png)

実際に処理を待っているジョブを開くとステータスが「Queued」になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/566e2ae1-9f42-e8c3-18c1-5bffbfebda29.png)

このジョブは２分間スリープするだけのジョブなのですが、３つ目のパイプラインは前の２つのパイプラインの完了を待つ必要があるので終了までに６分間ほどかかりました。

### コンテナランナーでの動作
次に、コンテナランナーでも同様に３つのパイプラインを走らせて、開始直後の様子を確認してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/76100f6c-3947-a0f9-2e8e-682976dbb831.png)
少しわかりにくいかもしれませんが、３つのパイプラインが同時に実行されています。どのパイプラインも２分ほどで処理が完了しました。

３つのパイプラインを同時実行しているときの Pod を確認すると ビルド用のエージェントが立ち上がって処理をしてくれています。
![2022-10-24-10-59-20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11243/36f1953f-08e7-5c07-153d-c0d1f410a813.png)


## コンテナランナーとマシンランナーの使い分け
コンテナランナーでは複数のジョブを手軽に走らせられることを確認しましたが、ほかにも
 - Docker イメージをビルドしたいときはマシンランナー
 - Docker イメージを指定して実行したいときはコンテナランナー
といったような使い分けのポイントがあります。

このあたりはCircleCI のエンジニアである bufferings さんが書いてくれているこちらの記事もお読みください。 -> [CircleCI の Runner に入門した](https://bufferings.hatenablog.com/entry/2022/10/15/153050)

## おわりに
というわけで CircleCI のコンテナランナーを動かしてみました。Kubernetes クラスターと Helm の準備ができていれば動作まではあっさり進められます。

私の場合、コンテナランナーの Pod の CrashLoopBackOff が解決できずにそこで時間がかかってしまいました。原因は Helm でインストールするときに作成した values.yaml の一行目が抜けているというミスでした。

また、そもそもの Kubernetes クラスターを立ち上げるところも迷いました。サイボウズのHirakobaさんが試していただいている[Zennのスクラップ](https://zenn.dev/korosuke613/scraps/15388abb99d210)や前述の [bufferings さんの記事](https://bufferings.hatenablog.com/entry/2022/10/15/153050)では GKE を利用なさっていたので、私は EKS で試してみようかと思ったのですが、勝手がわからず、慎ましく手元の小さなクラスターですすめることにしました。Kubernetes に自ら触れる機会がほとんどなかったので今回はとても勉強になりました。