📖

Docker開発環境(7): docker-buildenvの第3の型myenv

2021/08/14に公開

前回の続きです。

https://zenn.dev/anyakichi/articles/3e94b4a4899fb6

これまで docker-buildenv を使った組み込みファームウェアのビルド環境、python/nodejs アプリケーションの実行環境を見てきました。これらの用途はビルドすることや実行することであり、これはつまり「アプリケーションのための」環境であるということです。

次に「私たちのための」環境を作り出す道具としての docker-buildenv の側面を見ていきます。

Docker 編集環境: myenv

docker-buildenv によるビルド環境では、ビルドはコンテナで行いつつ、編集は(既に環境の整った)ホスト側で行うのが通常のスタイルであるということは第3回でも解説しました。でも「いつもの環境」を Docker でパッケージングしてしまえば、いつでもイメージをダウンロードして「いつもの環境」で編集を行うことができます。各人の好みのエディタ・開発ツール・設定ファイル類を docker-buildenv で Docker 化した環境を myenv と呼んでいます。

いや、別にホストに環境はあるし、と思うかもしれませんが、これが威力を発揮するのは以下のような場合です。

  • ビルド用の共有ホストが用意されているのだが、OS が古く使えないソフトウェア・機能がある。
  • クラウド上の使い捨ての環境で一時的に開発作業をしたい。

普段の開発は自分のホストで行えばよいのですが、アウェイの環境に持ち込めるポータブルな「いつもの環境」があると非常に便利なわけです。

myenv を構築する

「私の」myenv は既に作成されています。

https://github.com/anyakichi/docker-myenv

先日多段 fzf の記事で使用したのもこの環境です。

https://zenn.dev/anyakichi/articles/20751fa835ff78

$ din anyakichi/myenv

とすると、私(あにゃきち)のいつものセットアップがされた環境を使うことができます。zsh, neovim, fzf などなど専用にカスタマイズされているのですが、人のアカウントで勝手にログインしたような、不思議な感じがするかもしれません。

構築方法自体は難しくありません。私は普段 Arch Linux を使っているのですが、archlinux のイメージをベースに自分の普段使っているアプリケーション(CLI のみ)をインストールして、設定ファイル類を展開した上で docker-buildenv としてセットアップしているだけです。

なお、必要なのは docker-buildenv のうち entrypoin.sh だけで buildenv.sh は使わないので、buildenv のセットアップをする必要はありません。また、ビルド環境をセットアップする場合には常に builder という名前のユーザーを作って使っていましたが、ユーザー名についても環境変数で切り替えられるようにもともと作られているので、設定ファイルの都合などでホームディレクトリの名称が変えられない、という場合でも大丈夫です。

「だいたいこれくらいあればいいかな」というところまでが slim で定義してあり、各種 language server 類など全部入りにしてあるものが latest で提供されています。この環境は所詮 "myenv" なので、どう作るかは各人が自由に決めればよいのではないかと思います。

もちろん直接実行モードも使えるので、

$ din anyakichi/myenv nvim Dockerfile

のように起動することもできます。

私の環境はインタラクティブシェルとして起動されないと入らない設定もいろいろとあって、直接実行モードの使い勝手はあまり良くないのですが。このあたりはセットアップ次第だと思います。

myenv の使い方

myenv を使うのはほとんどの場合 ssh で入れるけどなんにも入っていない、みたいな環境なのですが、普通の buildenv とはちょっと使い方の考え方が変わります。

セッションの維持

まず、ssh が切れてしまうケースを考えましょう。通常は tmux などを使ってセッションの維持をすると思うのですが、tmux が入っていない、もしくはちょっとバージョンが古くて使いにくい、ということがあるとしましょう。

myenv の場合は Docker コンテナをバックグラウンドで起動すれば接続断から守られます。

$ din --name myenv -d anyakichi/myenv

使うときには attach しましょう。

$ docker attach myenv

tmux が使いたい場合は、コンテナの中に tmux を入れておいてそこから起動すれば OK です。これで tmux まで含めて最新の環境で作業をすることができます。

このような関数を作っておいても良いでしょう。

myenv() {
    if [[ -z "$(docker ps -q --filter "name=^myenv$")" ]]; then
        din --name myenv -d "$@" anyakichi/myenv
    fi
    docker attach myenv
}

起動していなければ起動し、起動してあれば attach だけします。

コンテナを残したまま接続を切りたい場合はデタッチしてください。なお、デフォルトのデタッチキー C-p C-q はシェルのヒストリを使う場合などに支障があるので、どこかにマップをずらしておくと良いです(私は C-\ C-x にしています)。

buildenv との連携

次に、buildenv と連携して使う場合を考えてみます。myenv の中で Docker デーモンまで動かすのはコンテナの実行権限の点からもハードルが高いので、Docker デーモンはホスト側のものを使うことにします。myenv からクライアントは使えるように Docker はインストールしておきます。

以下のように docker ソケットをコンテナに見せてしまうのが一般的な方法です。

$ din --name myenv -d \
    -v /var/run/docker.sock:/var/run/docker.sock \
    anyakichi/myenv

ssh の接続性を気にするような環境下では、buildenv もまたバックグラウンドで起動することになります。ホストマシン側から必要なディレクトリを見せる形で buildenv を起動します。キャッシュボリュームや環境変数の設定は必要に応じて行ってください。

$ mkdir /data/yocto-1 && cd $_
$ din --name buildenv -d -v /data/cache:/cache \
    -e YOCTO_DL_DIR=/cache/yocto/downloads \
    -e YOCTO_SSTATE_DIR=/cache/yocto/sstate \
    anyakichi/yocto-builder:dunfell

これを myenv 側から attach しましょう。myenv では tmux も起動しておきます。

myenv$ tmux
myenv$ docker attach buildenv

ここまでやっておくと、普段の生活環境とかなり近い一時的な作業環境が構築できます。

docker-buildenv のまとめ

これまで7回にわたって docker-buildenv の仕組みから応用方法までを解説してきました。docker-buildenv 自体は以下の 2 つの機能のみを持つ極めてシンプルなプラグイン的機能です。

  • uid/gid をホスト環境とコンテナ環境で揃える entrypoint
  • 手順書をパースして実行する buildenv

これらを効果的に用いることで、下記 3 つの応用ができました。

  • ファームウェアのビルド環境
  • アプリケーションの実行環境
  • 自分自身の生活環境

特にビルド環境・実行環境については、Docker でパッケージングを行ったことにより、誰でも、同じ環境で、すぐに、開発が始めることができます。プロジェクトに新しいチームメイトが入ってきた、既存の開発環境が手狭になったので新しいマシンに乗り換えたい、という場合であっても、5 分もあれば開発を始める準備ができます。必要なものは Docker イメージの中に含まれ、実行すべき手順は Docker イメージが「知っている」からです。

最後にこれまでフォーカスしてきませんでしたが非常に重要な視点として、環境の一貫性についての考え方について補足しておきます。

docker-buildenv では din コマンドを使ってコンテナに入る場合、/build にカレントディレクトリをマウントするなどの作法が決まっています。uid/gid は変更しないと不便なので変更するのですが、その他の環境は原則誰がやっても同じ状態のコンテナが起動します(なお、GitLab CI では /build 以下に作業ディレクトリがマウントされるので CI でも同じ環境だと言えるのですが、GitHub Actions などではそういうわけではなく、厳密には網は少し破れています。当初想定していたのが GitLab だったのです)。

ソフトウェアというのは表向きはどういう名前のディレクトリでビルドされてもいいし、ホストのツールのバージョンが多少違っても大丈夫なはずだし、それほど環境依存性はないはず、だと思って開発者は開発していると思うのですが、実際の開発の現場では微妙な環境の違いで「ビルドがこける」ということが起きがちです。

「あれー、俺の環境ではビルド通るんだけど、なんでそっちで通らないの」というのはもちろん何かしらの潜在的なバグの可能性もあり、不具合に気がつくのは悪いことばかりでもないのですが、経験的にはほとんどの場合は環境設定時のつまらないオペレーションミスだったと思います。「あれ、XX ってインストールしたました?」「いや、してないです」みたいな。

そのようなミスがなかったとして、「どこでもビルドできるはず」で開発しているつもりでも、例えばソースコードを展開したディレクトリの親をたどるとスペースの入ったディレクトリがあったら、とか Unicode のサロゲートペアが入っていても大丈夫、とか微妙なケースを指摘されるとだんだん不安になってくるものです。

docker-buildenv のビルド環境において異なるのはビルドユーザーの uid/gid くらいですが、これらはそもそも情報として使われるケースがまれですし、なおかつ文字列ではなく数値なので扱いを誤る可能性がかなり低いです。

docker-buildenv ではビルド環境のディストリビューションからインストールするパッケージ・ユーザー名・ソースコードの展開ディレクトリまで、複数の環境で極力一致させることで、現象の再現性を高めることに非常に重きを置いています。

次回は buildenv と myenv を組み合わせて、EC2 上で Yocto のビルドを行う実践例を見ていきます。

Discussion