ZellijでDocker開発環境をちょっと自動化してみた
はじめに
こんにちは、ヨシです。
最近は大分暖かくなってきて気持ちよいの季節になってきましたね。その一方であれが到来しています。そう、花粉です。
現在は京都に住んでいるのですが、この間しばらく東京に行く機会がありまして、その際に花粉症が発症してしまいました。目薬や漢方が必須となりそうなので、そろそろ処方してもらおうかと思います。
さて、本日は Zellij というターミナルマルチプレクサのツールとそれを使った開発環境のオートメーションについての記事を書いていきたいと思います。
Zellijとは
まず、Zellij(ゼリッジ)とは、tmux などに代表されるターミナルマルチプレクサ(Terminal Multiplexer)ツールの一種で、ターミナルエミュレータ内でセッション、タブ、ペインといった単位で画面を分割して、その中で仮想ターミナルを起動させることができます。
https://zellij.dev/tutorials/basic-functionality/ より引用
Zellij は Rust 言語で開発されており、tmux に比べてモダンで、プラグインなどを導入しなくても最初から様々な機能が利用できる(batteries included)なものとして利用できます。
特徴的なのは初学者にとって非常にわかりやすく、ユーザーフレンドリーな UI になっている点です。こういったツールで大変なのはキーバインドを覚えるということですが、Zellij の場合には status-bar という画面下部の領域(下記画像の③の領域)に主なキーバインドを表示してくれて、モード変更に基づいてどのようなアクションが可能かを表示してくれます。実際、このキーバインドの表示によって簡単に Zellij の操作を覚えることができました。
https://zellij.dev/documentation/overview より引用
status-bar は後から非表示にすることもできますが、このような初学者に非常に優しい作りとなっているため、これからターミナルマルチプレクサを使いたいという方は tmux よりも Zellij を使う方がおすすめです。
もちろん、tmux の方が歴史があるため、tmux でしか使えないような機能が多々あったりしますが、初学者にとっては使わないような機能もあるので、割り切って Zellij を使うという方法も全然アリだと思います。
逆に、tmux ではセッションの復活に特別にプラグインを導入しないといけなかったりしますが、Zellij では最初からそういった重要な機能が搭載されています。こういった点においても新しい Zellij を使い始めるのがおすすめです。
オートメーション
さて、今回の記事で紹介したいのは、Zellij の機能である、zellij run
コマンドによる開発環境のちょっとしたオートメーションです。
Zellij はまだまだ発展途上なので、オートメーションについて一部欠けた機能もありますが、かなり簡単にターミナル上の開発環境のオートメーションができるのでここではそれについて解説したいと思います。
Zellij では手動(キーバインド)でタブ内のペイン分割を行うことが日常的ですが、CLI を通じてタブを新しく分割して、そのタブで特定のタスク(コマンド)を実行するという機能が付属しています。
よく使うコマンドは以下のコマンドで、それぞれ新しくタブを開いてタスクを実行できます。
zellij run
新しいペインを生成し、引数に渡したコマンドを実行するzellij edit
新しいペインを生成し、引数に渡したパスをエディタで開く
今日紹介するのは、zellij run
の方で、このコマンドを使うことでなにかしらの一連のタスクをタブ内で複数のタブを開きそれぞれ実行していくことが可能となります。
例えば以下のように --
の後に実行したいコマンドを渡すことで、このコマンドを実行したペインから別のペインを生成して echo 'hello'
を実行できます。
zellij run -- echo 'hello'
zellij run
の特徴として、引数のコマンドをペインと紐づけて実行することができ、その実行ペインでは、終了ステータスの取得と再実行などが簡単に行うことができます。
ユースケース
オートメーションのユースケースとしては、公式のチュートリアルで紹介されているとおり Rust 言語の開発において、Vim などのモーダルエディタを開いて、その横で cargo run
, cargo test
などのタスクの実行コマンドと結びついたペインを開くことが一つのコマンドで可能となります。
https://zellij.dev/tutorials/layouts/ より引用
上記チュートリアルで紹介されているレイアウト機能(zellij --layout
)を使ったオートメーションです。しかし、レイアウト機能を使うよりも zellij run
の方が細かいコマンドの調整などができるので、本記事では zellij run
によるオートメーションを紹介します。
なお、レイアウト機能を使わなくても上記のような環境の構築は可能です。※ただしレイアウトをきれいに作るのは少し難しいです。
docker コンテナの起動後のコマンドの連鎖実行
この記事のオートメーションのユースケースとして考えるのは Docker を使ったモノレポでの開発環境で、docker compose up
によってコンテナを起動後に、コンテナ内で、複数のフロント画面(first
と second
の2つとします)について npm run dev
を実行させる、ということをやりたいのですが、これを Zellij を使って一つのタブ内にそれぞれのコマンドを連鎖実行させていきます。
このモノレポでは以下のような proj
というシェルスクリプトが定義されたファイルが用意されており、このスクリプトから各種のコマンドが実行できるとします。
#!/usr/bin/env bash
function up {
docker compose up -d $@
}
function down {
docker compose -f docker-compose.yml down
}
function front_npm {
case ${1} in
"first" | "second")
type=${1}
shift
;;
*)
type=first
esac
workdir="/app"
container=node-${type}
docker compose exec -w ${workdir} ${container} npm ${@}
}
function get_front_type() {
case ${1} in
"first" | "second")
echo "${1}"
;;
*)
echo "${2}"
;;
esac
}
function front_start {
type=`get_front_type ${1}`
echo "Starting ${type} server..."
front_npm ${type} run dev
}
subcommand="$1"
shift
case $subcommand in
up)
up $@
;;
down)
down
;;
front:start)
front_start ${1}
;;
front:npm)
front_npm ${@}
;;
*)
echo "help"
;;
esac
シェルスクリプトを用意したら、以下のコマンドでファイルパーミッションを変更して実行できるようにします。
chmod +x proj
もちろん、このようなシェルスクリプトを用意せず、具体的なコマンドを実行するようにしても大丈夫です。
実装
さて、Zellij によるオートメーションコマンドの定義を行っていきます。実際、そこまで大したものではないので一つのコマンドを利用しているシェル用で定義してあげれば十分です。
※ もちろんシェルシェルスクリプト自体に以下のオートメーション用スクリプトを定義しておいてもよいですが、シェルスクリプトが共有されていたとしても開発チーム全員がそもそもターミナルマルチプレクサを使っている、そして Zellij を使っているという状況はありえなさそうなので、別途コマンドを外側で定義することを想定しています。
例えば、bash であれば以下のような関数をどこかに定義してあげて proj-start
というようにコマンド実行できるようにします。
function proj-start {
if [ ! -f "./proj" ]; then
echo "this directory doesn't contain a script file"
return
fi
./proj up
if [ $? -eq 0 ]; then
zellij run -d down -- ./proj front:start first
zellij run -d right -- ./proj front:start second
else
echo "fail"
fi
}
- まず、シェルスクリプトのファイルがあるかどうかを判断して、なければコマンドを終了します
- そして、コンテナを起動するために
docker compose up -d
を実行するために./proj up
を実行します - コンテナ内で
npm run dev
を実行するためには、コンテナが正しく起動していることが重要なので./proj up
の結果としての終了ステータスをチェックし、コンテナが正常に起動できていれば、zellij run
で新しいペインを開き各タスクを実行できます
さて、肝心の以下の部分ですが、まずペインで実行したコマンドは zellij run
の --
の後に実行したいコマンドを渡します。この --
という記号は以降の入力がオプションではないということを示すための文字列です。
zellij run -d down -- ./proj front:start first
zellij run -d right -- ./proj front:start second
-d
オプションはペインを開くための方向で、-d down
で下に開き、-d right
で右に開きます。この場合には下に新しいペインを開いてから、そのペインについて右にまた新しいペインを開く、というようなことをやってます。
やることはこれだけです。このコマンドを実際に起動してみます。ペインが一つのタブで対象となるプロジェクトのディレクトリに入り、上記で定義した proj-start
コマンドを実行してみます。
proj-start
コマンドの実行によって、最初のペインで Docker コンテナの起動を行い、起動後に、コンテナ内での npm run dev
を first
と second
について実行します。これによって少しだけ効率化することができました。
なお、動画にあるように、Zellij のペインと実行したタスク用コマンドが紐づいているため、それらの終了ステータスの把握や、停止からの再実行が簡単に行うことができます。
まとめ
このようなちょっとしたオートメーションではありますが、これを機会にさらにターミナルマルチプレクサの使いこなしていきたいですね。また、Zellij はまだまだ発展途上なのでオートメーション機能はこの後も更に進化していくと思われます。ぜひキャッチアップしていきましょう。