Techtouch Developers Blog

テックタッチ株式会社の開発チームによるテックブログです

えぇっ、Nx Cloud を知らない!?――後編:「実際の導入の仕方とハマりどころ」――

記事のタイトル画像。記事のタイトルと、筆者であるcanalunのアイコンがのっている。

おはようございます、テックタッチの canalun(@i_am_canalun) と言います👶

DOM 大好きエンジニア(DOMDOMタイムス、みんなも読んでみてね)ですが、DOM 以外のこともやっている毎日です。
というわけで、今日は Nx Cloud を実際に導入する時のやり方とハマりどころについて書いてみます✌

なお、この記事は、Nx Cloud について前後編2回にわたり解説するシリーズの後編にあたります。
「そもそも Nx Cloud ってなに?」という方は、ぜひ👇前編の記事をお読み下さい!Nx Could の CI の自動分散並列実行CI 結果のキャッシングについて説明をしています。

tech.techtouch.jp

では早速行きましょう!
目次を見てオモロそうなところだけ読むもよし、とりあえず上から読んでみるもよしです。自由にやってね!!

前回のおさらい(ちょっとだけ)

一応前回のおさらいをしてみましょう。
めちゃくちゃまとめると、要はこういうことでした📖

  • Nx というモノレポフレームワークがある
  • Nx を使っている環境では、Nx Cloud を使うことで CI/CD を大幅に高速化できる
  • テックタッチでは Nx Cloud のおかげで CI の実行時間が 40% 削減された
    • 高速化のためのメジャーな打ち手はだいたい実施済みのところから 40% 減った
    • 直近の1ヶ月でも 200 時間強の削減効果を出しています……!
  • Nx Cloudによる高速化は、主にコマンドの自動分散並列実行コマンド実行結果のキャッシュによって実現される

まあ細かい話はぜひ前編を読んで頂こうということでね、ここからはさっそく後編の話を始めていきますよ〜!

えっ、3ステップで導入できるの!?!?!?

めちゃくちゃ頼りになるエヌクラですが、「どうせ導入するの大変なんでしょ〜」という声が聞こえてきます。

「ワーワー!」

「どうせ導入するの大変なんだろ〜!!!!」

「時間返せ!!」

「そうだそうだ〜〜!!」

「わーわー!!!」

群衆の声が聞こえてきますね。

ところがどっこい!!!実は簡単です!!!

Nx Cloud の訴求ポイントの1つに「いま使っている Nx コマンドをほとんど変えずに使えます!」ということがあります。これは実際その通りで、Nx Cloud を使うための設定を少し追記するだけですぐに利用を開始できます

細かい疑問が浮かんだり、動かなかったりしたら公式ドキュメントをみていただくのは前提として、ここではどのくらい簡単かを示しておく意味で導入手順をかいつまんで紹介します。

1. tokenの設定

Nx のサーバーを使わせてもらおうということで token の設定が必要になります。

まず token を Nx Cloud のコンソールに公式ページからログインして色々やって取得してね👶

そのあと Nx 公式ドキュメントにあるように token を設定します。
私が設定したときは 16 系だったので下記のようにしました。cacheableOperationsのところは適当です。キャッシュを使いたいターゲットを各自でちゃんと入れてね。

"tasksRunnerOptions": {
    "default": {
      "runner": "nx-cloud",
      "options": {
        "accessToken": "SOMETOKEN",
        "cacheableOperations": [
          "build",
          "test"
        ],
      }
    }
  }

token を直接書くのは……という人はnx-cloud.envファイルを使うと良いかと思います👶
(テックタッチでもnx-cloud.envを使っています)

2. nx-cloudのインストール

token が設定できたらyarn add @nrwl/nx-cloudで nx-cloud をいれます。
自分がやったときとは公式ドキュメントの記述が変わっており、いまはnpx nx connectというコマンドを一回叩けばそれで終わりなようです
どちらでもいいのかなという気はしますが、おとなしくnpx nx connectしたほうがよさそうですね👀

実はここまでやると既に、Nx を介したコマンド実行においてキャッシュが効くようになっているはずです。
公式ドキュメントのリモートキャッシュの導入手順の手順が済んでいるわけですからね。

3. CIフローの書き換え

キャッシュが効くようになったので自動分散並列実行(Distribute Task Execution: DTE)も有効化しましょう。
Nx Cloud の DTE は CI プラットフォーム上に agent を立ち上げて行われます。
そういった DTE 体制が毎回の CI で構築されて活用されるように CI フローを書き換える必要があります

書き換えは公式から出されている CI プラットフォームごとのテンプレートに従えばよいです。
ここにも一応、2023/12/23 日時点での CircleCI 向けテンプレートを転載しておいてみます。

version: 2.1
orbs:
  nx: nrwl/nx@1.5.1
jobs:
  main:
    docker:
      - image: cimg/node:lts-browsers
    steps:
      - checkout
      - run: npm ci
      - nx/set-shas

      # Tell Nx Cloud to use DTE and stop agents when the build tasks are done
      - run: npx nx-cloud start-ci-run --stop-agents-after=build
      # Send logs to Nx Cloud for any CLI command
      - run: npx nx-cloud record -- npx nx format:check
      # Lint, test and build on agent jobs everything affected by a change
      - run: npx nx affected --base=$NX_BASE --head=$NX_HEAD -t lint,test,build --parallel=2 --configuration=ci
  agent:
    docker:
      - image: cimg/node:lts-browsers
    parameters:
      ordinal:
        type: integer
    steps:
      - checkout
      - run: npm ci
      # Wait for instructions from Nx Cloud
      - run:
          command: npx nx-cloud start-agent
          no_output_timeout: 60m
workflows:
  build:
    jobs:
      - agent:
          matrix:
            parameters:
              ordinal: [1, 2, 3]
      - main

なんとなくポイントかなと思うのは下記です。

  • Nx Cloud に接続して(nx-cloud start-ci-run)、agent を立ち上げて(start-agent)、CI コマンドを実行するという流れを1つの job でやるわけではない。1つは接続と CI コマンド実行、1つは agent の立ち上げというように2つの job に分け、それらを並置して実行する

  • nx-cloud start-agentでは、parametersで指定した数だけ agent が立ち上がる

CIの設定ファイルにてparametersの直下にnamesというプロパティを作り、いろいろな顔文字の文字列で構成された配列を値として設定している
parameters では CI 上での agent の表示名を文字列で指定できます
配列の要素数だけ agent が立ち上がります

というわけで、ここで紹介した3ステップで動くようにはなるはずです!
ただ現実はそう甘くなく、テックタッチで Nx Cloud を導入した時は何箇所かハマりどころがありました。次からはそこを見ていってみます👀

具体的なハマりポイント

めちゃ個人的にですが、Nx は本当にできることが多いし「こんなことまで!?」という驚き(e.g. eslint カスタムルールのテンプレートとテストをコマンド1つで展開できる)があって最高なのですが、いかんせんドキュメントが少し分かりづらいんですよね。

というわけで、実際どこにハマってどう解決したかを未来のNx Cloudユーザーに向けて記しておきます。

  • 先ほども書きましたが「nx-cloud start-ci-runのあとにタスク実行する」job と、「start-agentを実行する」job は、並置して実行する必要があります

  • Nx Cloud に任せた job が終わっても agent が動き続けてしまう場合は、最後にnx-cloud stop-all-agentなるコマンドを実行してみましょう。これは agent 群を停止するためのコマンドです: https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-stopallagents

  • Nx Cloud をガチで活用しだすと、いくつかの Nx コマンドを Nx Cloud にまとめて渡したい時が来ると思います。
    このときstop-agents-on-failureというオプションに注意して下さい。これは「正常終了しなかったコマンドが1つでも発生したら、その時点ですべてのコマンドの実行を止めて agent も全て停止する」というものです。これはデフォルトでtrueになっています
    したがって、例えば lint が失敗しても test は最後までやってほしい、という人はこれをfalseに設定しなおすのが良さそうです👶 なお、このオプションに関する公式の説明はここにあります。

今後に期待したいところ

さて最後に少し「こんなこともできたらいいねえ」という点を挙げてみます。
前後編を通して Nx Cloud アゲ感が強かったので、ちょっとバランスを取っておこうかなという思いからです。心を鬼にして書きます👹

タスクごとの平均所要時間を分散時に考慮してほしい

1台の agent へ1回に割り当てるタスクの数(parallel)を2以上にしておいたとき、Nx Cloud は各タスクの平均実行時間は考慮しないのか、時間のかかるタスクいくつかを1つの agent にアサインしてしまうことがあります😭エーン

すべてのタスクの割当が済んだあとは、自タスクを消化しきった agent を順次閉じてほしい

現在の自動分散実行において、agent は順次閉じられるのではなく、すべてのタスクの実行が終了したとき(すべてのタスクの割当が済んだときではない)に一斉に閉じられます。つまり agent のいずれかがタスクを処理している限り、他 agent もみんな CI 上で稼働し続けます😭シクシク
したがって仕事が終わっている agent についても、CI プラットフォーム(e.g. CircleCI)のクレジットがかかってしまいます。

なおこれは余談ですが、これについて1点目として挙げた「割り当てるときにタスクの所要時間を考えない問題」とあわせて考えますと、タスクごとの平均所要時間にばらつきがある状況でparallel(1台の agent へ1回に割り当てるタスクの数)を2以上にした場合、仕事が終わった agent が一番遅い agent と最後まで一緒に動き続ける可能性が大きくなり、CI コストの無駄が発生しやすくなるわけです。この点からすると、parallelは小さい方がよさそうに思えます。

一方でparallel(1台の agent へ1回に割り当てるタスクの数)は大きければ大きい方がやはり望ましそうです。というのも、テックタッチの環境で何度か試したところ、Nx Cloud が各 agent にタスクを割り当てる際には「割り当てる行為そのもの」にも時間がそれなりにかかっていそうでした(本当に他の環境でもそうかは分からないのでみんなも確かめてみよう!)
実際、この割り当てそのもののコストは、細かなタスクの集合であるような Nx コマンド(e.g. lint)で顕著に現れます。1つ1つのプロジェクトの lint には時間がかからないはずなのに、プロジェクト数が多いことで、割当コストを含めると長い時間がかかってしまうわけです。

このように考えると、当たり前なのですが一番いいのはタスクごとの平均所要時間にばらつきがない状況を実現して、一度にそれなりの数をとっていくようにする(parallelを大きくする)ことだと、やはり結論づけられると思います。

えんもたけなわ

かのマハトマ・ガンジーは「速度を上げるばかりが、人生ではない」と言ったそうですが、CI は速度です。「CI は速度を上げるばかりである」です。

というわけで Nx Cloud、みなさんも検討してみて下さいネ👶

周りで「使ってるよ!」という話をあまり聞かないので、もし使っていてかつ気が向いたらcanalun(@i_am_canalun)まで教えてくださいナ🥷(色々情報交換させてください😭)