KAKEHASHI Tech Blog

カケハシのEngineer Teamによるブログです。

GitHub Actions に Python のパッケージインストーラー uv を導入する

こんにちは。

カケハシの Musubi AI在庫管理 チームにて業務委託のエンジニアをさせていただいております takanakahiko と申します。

今回はuvをGitHub Actionsに導入したらとても効果があったので、紹介することができればと思います。

uvとは

uvとはPythonのパッケージインストーラー・リゾルバーです。 その最大の特徴はRust言語で開発されており、従来のツールの100倍の速度で動作する点です。 pipやpip-toolsのdrop-in replacementが可能であることも特徴です。

開発をするのはAstralです。 AstralはRuffの開発で有名ですね。

Ruffについては、こちらの記事で紹介しています。

試しに手元で利用する

今回の目的はGitHub Actionsへの導入です。 その前に手元でひととおり使ってみます。

まずは、比較のために通常のpipを用いた場合の速度を確認しておきます。 time コマンドはそれに続くコマンドを実行し、その実行時間を出力するコマンドです。

# 計測のためにキャッシュを削除しておく
$ pip cache purge

# 仮想環境作成の計測
$ time python3 -m venv venv           
python3 -m venv venv  0.88s user 0.15s system 41% cpu 2.480 total

# インストールの計測
$ source venv/bin/activate
(venv)$ time pip install -r requirements.txt
(省略)
pip install -r requirements.txt  30.31s user 8.66s system 40% cpu 1:36.72 total

# この次に uv を試すので後片付けをしておく
$ deactivate
$ rm -rf venv

ではuvを利用しましょう。 計測しない場合は time は不要です。

まずはuvをインストールします。 複数通りの方法がありますが、pip経由でインストールしてみます。

pip install uv

仮想環境の作成自体もuvから行うことが可能です。 uvを利用するならこっちの方法で仮想環境を作成した方がトラブルが少なそうですね。

$ time uv venv
(省略)
uv venv  0.05s user 0.05s system 7% cpu 1.407 total

次に実際にパッケージのインストールをしてみましょう。 uvはディレクトリ内に .venv 等のディレクトリがあればそれを解釈するので、 activate は不要です。 もちろん source .venv/bin/activate でactivateしていつも通りに開発することも可能です。

$ time uv pip install -r requirements.txt
(省略)
uv pip install -r requirements.txt  4.66s user 4.43s system 97% cpu 9.293 total

実行結果をまとめてみます。

とんでもなく速いですね。 これはGitHub Actionsへの導入が期待できそうです。

仮想環境の作成 パッケージのインストール
pip 0.88s 30.31s
uv 0.05s 4.66s

GitHub Actions へ導入する

uvの発表は社内でも話題になりました。

良さそうなツールであればぜひ採用したいところです。 しかし、大人数が関与する開発において、各々の環境でどのような問題が発生しうるか予想ができないので、パッケージマネージャーの移行はかなり大変そうです。

そこでまず白羽の矢が立ったのがGitHub Actionsへの導入です。 これによってGitHub Actionsの待ち時間や課金金額の削減が見込めるとともに、 uvが実際のプロダクトの開発に耐えうるものなのか検証することもできそうです。 新しいツールなので、CDの方にいきなり入れることを避けCIにだけ導入することで様子を見てみることにしました。

実際に導入する中でいくつか気をつけるポイントがあったので紹介いたします。

ポイント1: 依存のキャッシュを行う

GitHub ActionsでPythonを利用する場合は通常 actions/setup-python@v5 を利用します。 これは cache: 'pip' のようにパッケージマネージャーを指定すれば requirements.txt などのパッケージ管理用のファイルに基づいて ~/.cache/pip のようなディレクトリをキャッシュしてくれます。 現状は pip pipenv poetry が指定可能で、uvには対応していません。( 対応のためのPR は作成されています )

そのため、自前でキャッシュを行う必要があります。 uvはパッケージのキャッシュ用のディレクトリとして ~/.cache/uv を利用するので、そのディレクトリをキャッシュすることにします。

GitHub Actionsの公式ドキュメントには actions/cache というアクションが用意されており、これを利用することでキャッシュを行うことができます。

pip用のsampleをベースに、 path key restore-keys を書き換えてみます。

- uses: actions/cache@v3
  with:
    path: ~/.cache/uv
    key: ${{ runner.os }}-uv-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-uv-

ポイント2: 仮想環境を利用しない場合は --system を利用する

上記のキャッシュを設定した上で、 pip installuv pip install にしようとしたらエラーになってしまいました。

error: Failed to locate a virtualenv or Conda environment (checked: VIRTUAL_ENV, CONDA_PREFIX, and .venv). Run uv venv to create a virtualenv.

記事の上記で説明した通り、uvは .venv ディレクトリを勝手に探して仮想環境へインストールしようとします。 詳しくはこちらに書いてあります。

上記リンクでも書いてある通り、コンテナー内やCI環境ではsystemのpythonにパッケージを追加したいことが多いため、その時のために --system というオプションが用意されています。

それを利用する形で、以下のようにします。

- run: pip install uv && uv pip install --system -r requirements.txt

実際に導入した結果

それを踏まえると最終的に以下のようなstepをworkflowに組み込むとうまく動作するでしょう

- uses: actions/cache@v3
  with:
    path: ~/.cache/uv
    key: ${{ runner.os }}-uv-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-uv-
- run: pip install uv && uv pip install --system -r requirements.txt

実は今回対象としているリポジトリはmonorepoなので、実際はこんな感じのcomposite actionを用意し、各ディレクトリ向けのjobから呼び出す形になっています。

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: v1-${{ runner.os }}-uv-${{ hashFiles(format('{0}/{1}', (inputs.working-directory || env.WORKING_DIR), inputs.file-name)) }}
- shell: bash
  run: |
    cd ${{ inputs.working-directory || env.WORKING_DIR }}
    pip install uv && uv pip install --system -r ${{ inputs.file-name }}

実際にPRを作成し、そこで発生するとあるWorkflowの実行時間をチェックしてみました。

チェックは、 GitHub Actionsの実行結果ログ画面の左下にある Usage から開くことができます。 GitHub ActionsはRun timeとは別にBillable timeというものがあり、それぞれのjobを分単位で切り上げしたものを実際に課金対象として請求されます。

Run time Billable time
pip install (cached) 37m 7s 48m
uv pip install 17m 37s 27m
uv pip install (cached) 14m 2s 22m

1コミットごとに発生するBillable timeが1 Workflow単体で見ても26分も節約することができました! 当然ですが、アジリティにも影響する重要な要素ですので、課金の節約以上に開発速度や開発体験の向上が期待できます!

まとめ

uvは新しいPythonの新しいパッケージマネージャーで非常に高速です。 uvをGitHub Actionsへの導入にあたってポイントが2つあります。

  • ~/.cache/uv をキャッシュする
  • ワークフローで仮想環境を利用しない場合は --system を利用する

新しいツールなので、CDの方にいきなり入れることを避けCIにだけ導入することで様子を見ている段階です。

導入することによってGitHub Actionsの課金額の削減やアジリティの向上といった良い影響があります。 ぜひ皆さんに利用していただきたいです。