KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

【GitHub Actions】何がややこしいのか少し考えてみた【よくわからん】

突然ですが皆さんGitHub Actions使ってますか?
単純なビルド&デプロイから、テストコードの実行などなど色々できて便利ですよね。

どこかのチームが便利なワークフローを作ってくれたので、
「あれ参考にしてうちのプロジェクトにも入れて!」という話はよくあるのではないでしょうか。
「意味は分からんけど、丸ごとコピーして動いたからヨシ!」というのはまだ可愛いもので、
「動かしてみたらエラーまみれで、何処を直せばいいのかさっぱりわからん・・・」となることもあるかと思います。

そこで今回は他人の作ったワークフローを読み解くにあたって、
何がややこしくなっている原因なのかを少し考えてみることにしました。

今回の記事で掲載しているワークフローは雰囲気を表現したいだけであり、
必要な行を一部省略しています。実用に耐えうるものではないのでご注意ください。

手動実行では登場しない手順が多い

例えば、Node.jsのアプリをビルド&デプロイする必要があるとしましょう。
手順に起こすとこんな感じになるかと思います。

  • ソースコードのクローン
  • Node.jsnpmのインストール
  • 依存関係のインストール
  • ビルド
  • デプロイ

これをワークフローにするとどうなるのか、stepsに絞るとこんな感じです。

steps:
  # ソースコードのクローン
  - uses: actions/checkout@v4
  # `Node.js`や`npm`のインストール
  - uses: actions/setup-node@v4
  # 依存関係のインストール
  - run: npm ci
  # ビルド
  - run: npm run build
  # デプロイ
  - run: npm run deploy

ふむ、これくらいであれば少し読めば理解できそうですね。
ですが、実際のプロジェクトではこのようになっているのではないでしょうか?

steps:
  # ソースコードのクローン
  - uses: actions/checkout@v4
  # `Node.js`や`npm`のインストール
  - uses: actions/setup-node@v4
  # ★ node_modulesのキャッシュ検索
  - uses: actions/cache@v4
    id: node_modules_cache
    with:
      path: "./node_modules"
      key: node_modules-${{ hashFiles('./package-lock.json') }}
  # ★ node_modulesのキャッシュが無ければ、依存関係のインストール
  - if: steps.node_modules_cache.outputs.cache-hit != 'true'
    run: npm ci
  # ビルド
  - run: npm run build
  # デプロイ
  - run: npm run deploy

なんか増えましたね。

数にもよりますが、依存関係のインストールには大抵時間がかかります。
そのため、以前インストールしたときのキャッシュが残っていればそれを採用、
無ければインストールするという手順にすることで、高速化が期待できます。

更に依存関係の中にCSS系のライブラリである、Panda CSSが含まれていたとしましょう。

steps:
  # ソースコードのクローン
  - uses: actions/checkout@v4
  # `Node.js`や`npm`のインストール
  - uses: actions/setup-node@v4
  # node_modulesのキャッシュ検索
  - uses: actions/cache@v4
    id: node_modules_cache
    with:
      path: "./node_modules"
      key: node_modules-${{ hashFiles('./package-lock.json') }}
  # node_modulesのキャッシュが無ければ、依存関係のインストール
  - if: steps.node_modules_cache.outputs.cache-hit != 'true'
    run: npm ci
  # ★ node_modulesのキャッシュが有れば、prepareの実行
  - if: steps.node_modules_cache.outputs.cache-hit == 'true'
    run: npm run prepare
  # ビルド
  - run: npm run build
  # デプロイ
  - run: npm run deploy

またなんか増えましたね。

Panda CSSに限らないのですが、依存関係のインストールを行った際、
自動で初期化処理が走るライブラリというものが存在します。

依存関係をキャッシュから復元したため、それらの処理が走りません。
そのため明示的に実行してやる必要があります。

更にビルド&デプロイであればジョブを分けてあることが多いのではないでしょうか?
ジョブを分けることで、デプロイだけがNWエラー等でダメだった場合、
ビルドからやり直すのではなく、デプロイだけリランするなど利便性を高くすることができます。

ですが、別ジョブになると別環境となってしまうため、
ビルド成果物をどうにかして引き継がないといけません。
その場合はこのようにします。

jobs:
  build:
    steps:
      # ソースコードのクローン
      - uses: actions/checkout@v4
      # `Node.js`や`npm`のインストール
      - uses: actions/setup-node@v4
      # node_modulesのキャッシュ検索
      - uses: actions/cache@v4
        id: node_modules_cache
        with:
          path: "./node_modules"
          key: node_modules-${{ hashFiles('./package-lock.json') }}
      # node_modulesのキャッシュが無ければ、依存関係のインストール
      - if: steps.node_modules_cache.outputs.cache-hit != 'true'
        run: npm ci
      # node_modulesのキャッシュが有れば、prepareの実行
      - if: steps.node_modules_cache.outputs.cache-hit == 'true'
        run: npm run prepare
      # ビルド
      - run: npm run build
      # ★ ビルド成果物をアーティファクトへ格納
      - uses: actions/upload-artifact@v4
        with:
          name: artifact
          path: dist
  deploy:
    needs: build
    steps:
      # ★ ビルド成果物をアーティファクトから取り出す
      - uses: actions/download-artifact@v4
        with:
          name: artifact
          path: dist
      # デプロイ
      - run: npm run deploy

そろそろ認知負荷君が白旗を上げそうになっているのではないでしょうか?

ここで最初の手順を思い出してみましょう。

  • ソースコードのクローン
  • Node.jsnpmのインストール
  • 依存関係のインストール
  • ビルド
  • デプロイ

元々は5ステップだったはずの手順がワークフローにしたとき、9ステップになってしまいました。
このように、手動実行では登場しない手順がワークフロー上では登場し長くなる傾向があります。

他にもVitestなどのテストを実行する際、実行環境のCPUコア数を取得しておき、
テスト実行するときのワーカー数にそれを与えてやることで高速化できる場合があるなど、
様々な理由で更に長くなっていく余地があります。

可読性よりパフォーマンス重視になりがち

皆さんコーディングする際に重視するポイントはなんでしょうか?
可読性?保守容易性?とにかくパフォーマンスこそジャスティス!という方もいるかもしれませんね。

GitHub Actionsでは分単位で課金されるため、早いに越したことはないです。
また、日本CTO協会が公開している、DX Criteriaというものがあり、
その中で、継続的インテグレーションについてこのような記載があります。
すべてのインテグレーションテストにかかる時間が計測されており、それは30分以内に完了するか。
処理時間の長すぎるワークフローは生産性の妨げとなる可能性があるということです。

可読性などは人それぞれの好みの問題もあり、分かりづらい部分がありますが、
パフォーマンスに関しては、こっちのほうが早い、コスト削減になるという誰にでも分かりやすい指標があります。

まとめ

まとめると以下の2点です。

  • 高速化などの理由でワークフローが長く複雑になりがち
  • コスト削減という分かりやすい大義名分でパフォーマンスが優先され、可読性が後回し

ワークフローを読む際は、「本当に必要な手順」と、「高速化したいだけで無くても動く手順」
これらを切り分けて考えると少し理解しやすくなるのではないでしょうか?

ワークフローを書く側としては、しっかりコメントやドキュメントを残すなどして、
理解しやすくなるようにしておきたいですね。

おわりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp