育てる開発環境
これは Kyash Advent Calendar 2023 の13日目の記事です。
近頃、寒いのか暑いのかわからない気候で大変ですね。 体調管理が難しくて、鼻がぐずったりお腹こわしたり大変です。 もうちょっと厚めの毛布にしたほうがいいかなぁ〜なんて悩みながら日々を過ごしています。
Kyash で働き始めて1年ぐらい経過して、はじめの頃は Software Engineer in Test(SET)として自動化を推し進めていたのですが、最近はBackend Engineerとしてプロダクトの開発のお仕事のほうが多くなっています。 SETとして社内のプロダクトのリソース全てに関与していたとき、自動化を推し進めるにあたって考慮しなければならないことが多いなぁと感じていたのですが、Backend開発のお仕事においても同様の感想を持ちました。 その中でも特に課題に感じたのは開発環境です。
Kyashでは、各々の開発者、主にWeb開発に関わるメンバーにおいては、それぞれの開発環境が提供されています。 提供 という言葉を使ったわけは、各々にEC2インスタンスが1つ割り当てられ、開発用途として自由に使っても良いサーバーが提供されているという意味合いを表現したかったからです。 EC2インスタンス上で開発を進めていたのですが、少し使いにくいなと思う場面がでてきて、改善に努めています。
まず、メモリやストレージを食い尽くしたりCPU使用率が上がったとき、EC2インスタンス自体が立ち上がらなくなってしまった場合、自分で解決する手立てがないことです。 EC2インスタンスはTerraformで管理していて、マネジメントコンソールから色々操作する権限は少ないです。 これでは壊れたときに、自力で解決する機会が少なくなり、結局、直すにも他のチームに頼らざるを得ない状況です。
2つめは、コンテナのログを見るために、わざわざEC2インスタンスにログインし、docker compose
コマンドを実行する必要があることです。
Kyashでは、マイクロサービスに寄せたプロジェクト構成を採用しており、全ての機能を成立させるには30個ほどのコンテナを起動させる必要があります。
コンテナ自体はEC2インスタンスで起動しているため、どうしてもEC2インスタンスにターミナルからログインして、$ docker compose log xxx
を実行する必要があります。
複数のプロジェクトにまたがる改修をしていたり、機能調査するために複数のインスタンスの状態を確認したい場合、何個もターミナルを開かざるを得ない場合があります。
3つめは、デバッグポートを開けていないので、ブレイクポイントを置いたリモートデバッグができないことです。 KyashではIntelljのGolandやVScodeを統合開発環境として利用できますが、printfデバッグしかできなくなってしまいました。 Kyashはリリースされてから一定期間が経過し、ソースコードの量は膨大でして、どこでどんな値を変数に入れているのか分かりにくかったり、ある時点の状態でセッションにどういう値を持っているのかを追うのがしんどい場面があります。 そういうときに、ブレイクポイントを置いて、デバッグができないのはとても辛い状況でした。
他にもあるのですが、代表的なものは上記3つでした。 一方で、Docker Desktopのライセンスは会社で支払っており、ライセンスも付与してもらっていたので、これをうまく使いたいなと考えていました。 ということで、EC2インスタンスを利用した開発環境を導入する以前は上述の現象はなかったことだったので、昔のKyashの偉人が作ってきた開発環境を生き返らせることにしました。
VirtioFS
私が初めてKyashの開発環境を構築した1年前は、まだDocker DesktopでのファイルシステムのデフォルトはgRPC FUSEでした。 VirtioFSはまだ正式採用されていませんでしたが、現在は正式採用されました。
かつてのKyashでは、Intel Macを開発者に支給しており、開発環境もmacOS上のDockerに構築していました。
しかし、起動するコンテナが30個を超えてきたあたりから、macOSの動作が緩慢になり、 profiles
タグを指定して、特定のコンテナのみ起動するなど、工夫して利用していました。
その後、profiles
タグを指定して起動するコンテナを選びながら利用することも手間となり、EC2インスタンス上の開発環境を構築することになりました。
VirtioFSは、2022年末にDocker Desktopで正式採用されたファイルシステムで、ファイルアクセスの高速化が期待できるアップデートでした。 Docker Desktopが重くなる現象は私も体験済みで、色々工夫しなければならないことは知っていたのですが、今回扱う端末はM1 MacでIntel Macより性能が良いともっぱらの噂だったし、VirtioFSが正式採用されたと聞いたので、かつてのmacOS上に構築する開発環境も工夫次第で良くなるんじゃないかと考えました。
Docker image
- arm64
支給されたmacOSはM1チップ搭載のMacbook Proでしたので、コンテナのベースイメージを arm64
に対応したimageでビルドし直すことにしました。
docker composeで起動する30個のコンテナは、3種類ぐらいのDockerfileからビルドされたイメージを利用していたので、それぞれのイメージをビルドし直しました。
OSバージョンが古く、サポート期限が切れていたので、最新のLTSイメージを採用しました。
- image size
ビルドイメージのサイズが大きいと、ローカルストレージが圧迫し、ビルドにも時間がかかってしまうので、 arm64
に対応したイメージで軽量なイメージを選びました。
Production環境と同等のイメージを採用したかったのですが、既存のビルドイメージの最新版のほうがサイズが小さいことがわかったので、結果的に既存のビルドイメージの最新版の arm64
に対応したものを選択しました。
なお、KyashはGo言語を主に使っているので、distrolessも検討したのですが、Dockerfileの内容を少し変更しなければならなかったため、今回は見送りました。
次の機会に軽量なコンテナイメージを利用できるようにしたいものです。
docker compose v2
これはパフォーマンスチューニングにあまり影響はないと思いますが、docker-compose.ymlをcompose.ymlに変更し、記述方法もDocker Compose V2に準拠した記述に変更しました。
最新のDocker Desktopを利用している場合、docker-compose
コマンドは docker compose
にエイリアスされるようなので、せっかくなので、V2に合わせました。
ymlファイルの version
の指定をなくしました。
- version: '3'
services:
compose.ymlの分割
既に利用していたdocker-compose.ymlには、1つのファイルに全ての services
を記述していたので、1,500行ほどの巨大なファイルになっていました。
profile
タグをつけてある程度整理されていたとはいえ、どのようなコンテナが設定されているのか見通すのが困難な状態でした。
せっかく profile
タグをつけていたので、 profile
の種類ごとにcompose.ymlを作成し、 docker compose -f
でファイル指定して起動するようにしました。
EC2ローカル環境で利用している docker compose
コマンドは -f
を指定しないので、デフォルトの docker-compose.yml
を参照します。
今回作成するymlファイルはEC2ローカル環境に影響しないように、 compose-arm64.yml
とし、それを profile
ごとに分割しました。
volumes
Docker Desktopが重くなる原因の中でとても有名なものは、LinuxとMacのファイルシステムが異なるので、volumeマウントすると遅くなるというのは見聞きしていたので、volumeマウントのチューニングを行いました。
ローカル開発環境だけで使うのなら、そこまで即時ホスト上に反映する必要もないと思ったので、ほとんど cached
か delegated
をvolumeに指定しました。
volumes: - ./tmp/postgresql/data:/var/lib/postgresql/data:delegated - ./tmp/mysqldata:/var/lib/mysql:deletated
Makefile
compose.yml
を分割すると、docker compose
実行時にいちいちymlを指定するのが面倒なため、Makefileを作成することで、コマンドの簡略化を行いました。
主に作成したmakeコマンドは以下です。
- up :
-f
を指定して、docker compose upするコマンド - down :
-f
を指定して、docker compose downするコマンド - rm : 起動中のコンテナ一覧を peco に渡し、pecoから操作するコンテナを選んで stop してから rm するコマンド
- bootstrap : コンテナ起動時、初期設定を行うためのコマンド
- migrations : 各コンテナで必要になるDBの初期データを投入するためのコマンド
上記を組み合わせて、以下のコマンドを定義しました。
- setup : 初めて開発環境を構築するときに実行するコマンド
- reset : 全てのコンテナを削除し、DBデータやCacheを削除するコマンド
- restart : 起動中のコンテナを1つ選んで削除し、再起動するコマンド
その他
環境構築時、手動で行っていたファイル変更や、足りないgo moduleをインストールする手順をスクリプトにまとめ、bootstrap時に実行するようにしました。 そうすることで、ほぼmakeコマンドのみで開発環境をコントロールできるようにしています。
おわりに
私が開発するときに、考慮しなければならないことや不便に感じることはできるだけ解消したいと考えています。 自分が開発するにあたって、気持ちよく作業するためにツールを開発したりチューニングしたりするのは開発者の嗜みだと思っています。 堅苦しいことばで表現すると開発生産性や作業効率の向上を行うために、組織理論や開発手法などにフォーカスが当たりがちですが、自分の手元の端末の開発しやすさにも目を向けて、開発環境もHackingしていきたいなと思っています。 組織の生産性を上げるためには小難しい施策が必要かもしれませんが、自分の生産性を上げるためには自分自身が工夫したり試行錯誤することが必要です。 キーボードやマウス、PC、モニターに持つこだわりを開発環境にも持って、開発しやすい開発環境を育てていきたいですね。
Kyashでは在宅勤務メンバーが多いため、ローカル環境の立ち上げに手間取ってしまうと回復させるためのフォローが大変です。
なので、できれば make
コマンド一発で全ての環境が立ち上がることを理想としています。
幸いDocker Desktopを使った環境構築のノウハウは、検索でとても多くの情報を得ることができます。
解決できるヒントが数多くあるということは、自力解決能力を養う機会も多くなるため、レベルアップのためにDocker Desktopを使っていきたいと思います。
ちなみに
Quality Assuranceをリードするエンジニア(SET)として在籍しているのに、実装したり、ローカル環境再構築など、開発業務にコミットし過ぎでは?と思うときが時たまあります。 ただ、品質を上げたり不具合を少なくしたりするためには、システムテストの数を増やしたり、精度や頻度を上げることも有効であると考えていますが、そもそも不具合を作り込まない組織にすることも大切だとも考えています。 そのために、個々の開発者がストレスなく開発できるような環境を提供することや、リリース前に不具合に気づくことができる下地作りが必要だと思っています。 それが自動テストを動作させる上で必要なことだし、今やっていることが、Quality Assuranceをリードするエンジニアとして達成したい状態に近づくことに、大きく乖離しているとは思っていません。 歌って踊れてなんでもできるエンジニアになろうかなって思っています。
来年は、卒業されていったKyashの数々の偉人の積年の願いであるStaging環境をもっといい感じに運用するぞ〜!!!
明日の投稿もぜひお楽しみに。バイバーイ。