tech.guitarrapc.cóm

Technical updates

Ubuntu環境のCLIバージョン管理ツールをasdfからaquaに移行した話

私はWindowsでWSL Ubuntu環境を3つ動かしています。それぞれの環境に同じCLIツールをインストールする「バージョン管理ツール」をasdfからaquaに移行した記録を書き出しておきます。

asdfとaqua

asdfとaquaはともにCLIツールのバージョン管理ツールです。どちらもツールをインストールするまでの流れが似通っていますが、ツールの定義を誰が管理しているかが決定的な違いです。

image

簡単に両者を見てみましょう。

asdf

asdfは複数のランタイムバージョン管理、コマンドラインツールのバージョン管理ができるCLIです。asdfの特徴はプラグイン(asdf-vm/asdf-plugins)で、自分や誰かがCLIツールの定義をGitHubリポジトリに配置すると、asdf利用者はそれを参照して定義されたツールをインストールできます。

ツールのインストール例

例えば、Node.jsをインストールするには次のようにします。

# asdfのnode.jsプラグインを追加
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git
# node.jsをインストール
asdf install nodejs latest

ツールのバージョン管理

ツールのバージョンは$HOME/.tool-versions$PWD/.tool-versionsに保存されています。これを使ってグローバルやパス単位にツールバージョンを制御できます。

# global version
asdf global nodejs lastest

# curent dir version
asdf local nodejs latest

aqua

aquaはコマンドラインツールのバージョン管理をするCLIです。aquaはインストールするツールをYAML定義し、aquaproj/aqua-registryに配置されたツール定義を参照してCLIをインストールします。

ツールのインストール例

例えば、Node.jsをインストールするには次のようにします。

# yaml定義を作成
aqua init
# yaml定義にnodejsを追加
aqua g -i nodejs/node
# インストール
aqua i

aquaは遅延インストール(Lazy Install)がデフォルトで有効になっているので、明示的にaqua -iを実行せずともツール定義に記載されていればツール実行時に自動でインストールされます。便利ですが厄介な機能でもあります。

sed -i "s|cli/cli@.*|cli/cli@v2.1.0|" aqua.yaml
$ gh version
INFO[0000] download and unarchive the package            aqua_version=2.16.4 env=linux/arm64 exe_name=gh package_name=cli/cli package_version=v2.1.0 program=aqua registry=standard
gh version 2.1.0 (2021-10-14)
https://github.com/cli/cli/releases/tag/v2.1.0

ツールのバージョン管理

ツールのバージョンは$AQUA_GLOBAL_CONFIG$PWD/aqua.yamlに保存されています。ツール定義のバージョンを変えることで、グローバルやパス単位にツールバージョンを制御できます。

グローバルなツールをインストールする際は-aオプションが必要です、あまり見かけないコマンド体系ですね。

# global version
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/aquaproj-aqua"
vi "${XDG_CONFIG_HOME:-$HOME/.config}/aquaproj-aqua/aqua.yaml"
export AQUA_GLOBAL_CONFIG=${AQUA_GLOBAL_CONFIG:-}:${XDG_CONFIG_HOME:-$HOME/.config}/aquaproj-aqua/aqua.yaml
aqua i -a

# current dir version
aqua i

なぜaquaなのか

asdfからaquaに移行したきっかけは、ここ数年で見かけることが増えた「悪意を持った第三者のOSSへの攻撃事例」でした。asdfはツール管理を3rdパーティから取得する仕組み上、潜在的なセキュリティリスクがあると考えています。一方aquaは公式レジストリがあり、多くのユーザーによるレジストリをwatch、自動化されたパッケージ更新があることから信頼性が高いと考えました。

検討ポイント asdf aqua
ツール定義の取得元 3rdパーティ 公式レジストリ
ツール定義の更新 リポジトリによるが基本手動 Renovateによる自動更新
チェックサム管理 リポジトリによるがないことが多い Renovateによる自動更新

asdfのプラグインは各自のリポジトリ(3rdパーティ)で管理されており、asdfから利用するときにプラグインの定義が今どうなっているか気にする機会はまずありません。asdfプラグインを使うときに定義が変なことをしていないか精査することはあっても、その後プラグインが更新されるたびにチェックすることは困難だなぁ、と気になっていました。1

aquaは公式レジストリがあり、ツールの定義はレジストリに集約されています。レジストリへのツール定義追加時はレビュー、ツール定義更新時はRenovateが自動実行しチェックサムも更新します。ツール定義が公式レジストリで提供され、ツール定義が自動化込みでメンテされているというのは使うときに安心できるポイントです。レジストリの作りの丁寧さは尊敬を覚えます。

自動化するメリットの1つは人の手が入り込みにくいことです。自動化の仕組み自体にアプローチされるとダメですが、自動化されたフローに対して人の手で介入するのは難しくなります。例えば、私が自動化対象のマニフェストを変更するPRを出しても、相応の理由がない限りリジェクトされるでしょう。2

asdfからaquaへの移行

私はAnsibleでWSL構築を自動化しており、asdfからaquaへの移行はAnsibleのプレイブックを修正するだけで済みました。具体的には、次の2点で対応しています。

aquaの注意点

ローカル環境のCLI管理としてaquaを使っていくにあたり次の点に注意が必要です。GitHub Actionsなど環境が都度新しくなる場合は気にする必要はありません。

ツールのアンインストール

aquaのアンインストールaqua rm パッケージはpkgsディレクトリから削除されるだけで、ツールのバイナリはbinフォルダに残ります。binにファイルがあるとaqua-proxyがそのコマンドは存在すると返す上に、ツール実行時に遅延インストールでツールをPkgsに持ってきて実行します。ツールをアンインストールしたのにツールがまた実行できちゃうという不思議な体験ですね。

# パスはaquaのものを使っている
$ which kubectl
/home/guitarrapc/.local/share/aquaproj-aqua/bin/kubectl
# 素直にアンインストールしてみる
$ aqua rm kubectl
INFO[0000] removing a package                            aqua_version=2.37.2 env=linux/amd64 exe_name=kubectl package_name=kubernetes/kubectl program=aqua
# アンインストールしたのに残ってる!? (と思われても仕方ない)
$ which kubectl
/home/guitarrapc/.local/share/aquaproj-aqua/bin/kubectl
# アンインストールしたはずなのに遅延インストールされて実行できる
$ kubectl verison --client
INFO[0000] download and unarchive the package            aqua_version=2.37.2 env=linux/amd64 exe_name=kubectl package_name=kubernetes/kubectl package_version=v1.31.0 program=aqua registry=standard
Client Version: v1.31.0
Kustomize Version: v5.4.2

対処は、アンインストール時にモードを-mode lp-m lpで指定します。

aqua rm -m l kubectl # リンクしか削除しない (デフォルト)
aqua rm -m lp kubectl # リンクとパッケージを削除

アンインストールされてコマンドが実行できなくなっていますね。

$ aqua rm -m lp kubectl
INFO[0000] removing a link                               aqua_version=2.37.2 env=linux/amd64 exe_name=kubectl program=aqua
INFO[0000] removing a package                            aqua_version=2.37.2 env=linux/amd64 exe_name=kubectl package_name=kubernetes/kubectl program=aqua
$ kubectl
Command 'kubectl' not found, but can be installed with:
sudo snap install kubectl

ただ、定義にkubectlが残っているとaqua which コマンドでパスが返って来ます。とはいえ定義から消してもパス取得エラーを返すのは違和感があるのですが...

$ aqua which kubectl
/home/guitarrapc/.local/share/aquaproj-aqua/pkgs/http/dl.k8s.io/v1.31.0/bin/linux/amd64/kubectl/kubectl
# kubectlの定義を消す
$ vim $AQUA_GLOBAL_CONFIG
# エラーに変わる。aquaの定義になくアンインストール済みなので、管理下として返すのは違和感がある
$ aqua which kubectl
FATA[0000] aqua failed                                   aqua_version=2.37.2 doc="https://aquaproj.github.io/docs/reference/codes/004" env=linux/amd64 error="command is not found" exe_name=kubectl program=aqua
# curlでインストールするとそっちが優先されるので困ることはない
$ curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.31.2/2024-11-15/bin/linux/amd64/kubectl
$ sudo chmod +x kubectl
$ sudo mv kubectl /usr/local/bin/kubectl
$ kubectl version --client
Client Version: v1.31.0
Kustomize Version: v5.4.2

aquaのアンインストールモードは環境変数で設定できるので、基本的にバイナリからもファイルを消すようにするのが自然に感じます。

export AQUA_REMOVE_MODE=pl

まとめ

もともと手動でツールインストールするのに疲弊してasdfを使い始めました。長年asdfを使ってたのですが、昨今のOSSに対する攻撃を見ていてasdfを継続して使うリスクが気になり始めました。1年ほど手動に戻すかaquaで悩んだ末、aquaに移行しました。

Github Actionsのツール管理にもaquaを使っているのですが、3か月あまり使った今も手動インストールに戻す理由がないぐらいには満足しています。


  1. プラグインが更新されてもasdf上で利用前に気づくようなフローではないので自分で頑張るしかない
  2. 人の考えはわかりませんが、少なくとも現在のaquaはRenovateによる自動更新が徹底されている