本記事は、エムスリーキャリア Advent Calendar 2022の7日目の記事です。
2022年ということで「x86/ARM混成チームで辛いならDevContainers使おうぜ」という切り口で執筆していますが、x86の方々もDevContainersはメリットいっぱいですので是非お試しください!
2022年の開発環境事情
エムスリーキャリアのWebエンジニアは支給される開発マシンをWindowsノートPCかMacBook Proから選択することができます。
また本番環境で動くRailsやSpringBoot等はLinuxがインストールされたサーバーにデプロイしています。
つまり各環境で動いているOSの種類がWindows・macOS・Linuxとバラバラで、この差を埋めるために各チームで工夫を凝らしてきました。
とはいえコンピュータアーキテクチャはインテルとAMDが採用しているx86 64bit(以下、x86_64
)で統一されており、環境差で開発が出来ないというような致命的な問題ありませんでした。
しかし2022年、M1 Macを始めARMアーキテクチャを搭載したPCが普及し、開発者たちは頭を抱えることになるのであった−−というお話。
新入社員あるある「libv8, node-sassが動かないんすけど?」
2021年10月にIntel搭載MacBook Proの出荷が終了したことで、エムスリーキャリアでも新入社員を皮切りにAppleシリコンMacを導入することになりました。
業務で利用するソフトウェアのほとんどは問題なく動きましたが、開発プロジェクトの依存関係がインストールできなかったり、うまく動作しなかったりする問題が発生。
具体的には、Ruby on Railsで開発していると高確率で以下のライブラリがコケます。
- bundler
-
libv8
<therubyracer
,mini_racer
@0.3.1以下が依存
-
- yarn
-
node-sass
<@rails/webpacker
@5.2.1以下が依存
-
これらがAppleシリコンMacで動かない件は、依存関係を修正することで解決できます。
依存関係の修正方法
`therubyracer`は2017年を最後に更新されておらず、また`mini_racer`も環境依存でインストールに失敗しやすい`libv8`をやめて0.4.0から`libv8-node`を使うようになったため、`mini_racer`0.4.0以上に移行することで解決します。
また、RubyからJavaScriptをコールする`execjs`にはインストールされているJSランタイムを自動的に選択する仕組みがあり、Webpackerの導入等でNode.jsを入れている場合は`therubyracer`も`mini_racer`も不要になっているケースがほとんどです。
`node-sass`は(内部で利用している`LibSass`ごと)非推奨となり、`DartSass`を利用する`sass`(紛らわしいですがパッケージ名です)への移行が推奨されています。
`node-sass`に依存していた`@rails/webpacker@4`も、5.2.2以降に更新することで`sass`を利用するようになります。
日頃からRailsのトレンドにアンテナを張って、依存関係を適切に修正していればこのような問題は回避できますが、それが難しいプロジェクトがあるのも事実です。
その場合、
- 本番環境ごと古い依存関係を修正する(それに伴う影響範囲も動作確認・修正する)
- または、本番環境との差異を覚悟した上で開発環境のみ修正する
の難しい選択を新入社員とその配属チームに強いることになりました。
そこで第3の選択肢「x86前提の開発環境をコンテナ化してDev Containersで開発する」を提案します。1
Dockerにはアーキテクチャエミュレーション機能があるよ
普段何気なくDockerを使っていると気づきにくいですが、Dockerにはホストマシンとコンテナとのアーキテクチャ差異を吸収するエミュレーション機能があります。
WindowsやmacOSでDocker Desktopを利用している人は自動的に機能オンになっています。
以下のコマンドでアーキテクチャエミュレーションが動作しているか確認できます。
# linux/amd64のalpineイメージをpull
$ docker pull --platform=linux/amd64 alpine
# alpineイメージで`arch`コマンドを実行
$ docker run --rm alpine arch
> x86_64 # x86_64で動作している
# linux/arm64のalpineイメージをpull
$ docker pull --platform=linux/arm64 alpine
# alpineイメージで`arch`コマンドを実行
$ docker run --rm alpine arch
> aarch64 # ARMで動作している
ちなみに--platform
を省略した場合は、ネイティブで動作するイメージが自動的にpullされます。2 3
# Intel Macでの実行例
# --platformを省略してalpineイメージをpull
$ docker pull alpine
# alpineイメージで`arch`コマンドを実行
$ docker run --rm alpine arch
> x86_64 # x86_64で動作している
【ここから本題】x86前提の開発環境をコンテナ化してDev Containersで開発する
Dev Containersとは
Dev Containersとは、Visual Studio Codeの開発元であるMicrosoft公式が提供する拡張機能です。
まるでDockerコンテナ上にVSCodeをインストールしたかのように、VSCodeの機能を使ってコンテナ上で開発できるようになります。
具体的には、VSCodeの以下機能がコンテナで利用できます!
- コードエディターとしての全機能
- ソース管理(何とホストマシンのgit設定が自動的に反映されます!SUGOI!)
- 統合ターミナル
- デバッガー実行
- 豊富な拡張機能
前提条件
以下をインストールしていることが前提です。
- Docker Desktop (v4.12.0で確認)
- Visual Studio Code
- 拡張機能:Dev Containers
説明用に空のRailsプロジェクトを用意しました
手元にRailsプロジェクトがない人もいるかと思いますので、空のRailsプロジェクトを用意しました。
実際の開発現場でもありそうな少し古い環境を想定して作成しました。
- ruby 2.7.7
- rails 6.0.6
- sprockets 4.1.1
- node.js 14.21.1
- webpacker 4.0.3
- ARM上のインストールに問題のある、mini_racer@0.3.1, @rails/webpacker@4.0.3を使用
- レガシーなJSはsprockets・uglifierで、Vue.jsを使うなど新しいJSはwebpackerでプリコンパイル
- 説明簡略化のためDBが必要なActive Recordは未使用
以下のコマンドでクローンしてお使いください。
これ以降、VSCodeでrails_dev_container_example
を開いた状態で説明します。
git clone https://github.com/morooka-cube/rails_dev_container_example.git
Dev ContainersでRails開発を始める
ここでは各種設定ファイルを作成してRails開発を始める方法を説明します。4
設定ファイルの作成
まず.devcontainer
フォルダを作成し、コンテナの素材となる.devcontainer/Dockerfile
を作成します。
内容を簡単に説明すると、以下3つのビルドステージに分けて最終的に3.を出力しています。
-
node.js
とyarn
の実行ファイルを取得 -
yarn install
を実行 -
bundle install
を実行し、1.からnode.js
とyarn
を、2.からyarn install
の結果をコピー
# --- node.jsとyarnの実行ファイルが入ったイメージ ---
FROM node:14.21.1-slim AS node
# --- yarn installを行うビルドステージ ここから
FROM node AS yarn
# ワーキングディレクトリを設定
WORKDIR /app
# ビルドコンテキストからyarn installに必要なファイルをコピー
COPY package.json yarn.lock /app/
# yarn installを実行
RUN yarn install
# --- yarn installを行うビルドステージ ここまで
# --- 最終的なビルド結果となるステージ ここから
FROM ruby:2.7.7
# nodeからnode.jsとyarnの実行ファイルをコピー
COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /usr/local/include/node /usr/local/include/node
COPY --from=node /usr/local/bin/yarn /usr/local/bin/yarn
COPY --from=node /usr/local/bin/yarnpkg /usr/local/bin/yarnpkg
COPY --from=node /opt/yarn-v1.22.19 /opt/yarn-v1.22.19
RUN \
# nodeとyarnが実行できるようシンボリックリンクを設定
ln -s /usr/local/bin/node /usr/local/bin/nodejs && \
ln -s /opt/yarn-v1.22.19/bin/yarn.js /usr/local/bin/yarn.js
# ワーキングディレクトリを設定
WORKDIR /app
# ビルドコンテキストからbundle installに必要なファイルをコピー
COPY Gemfile Gemfile.lock /app/
# bundle installを実行
RUN bundle install
# yarn install結果をコピー
COPY --from=yarn /app /app
# ポート3000番に穴を開ける
EXPOSE 3000
# --- 最終的なビルド結果となるステージ ここまで
2つ目に、コンテナの起動方法を記述する.devcontainer/docker-compose.yml
を作成します。
services:
rails:
build:
# プロジェクト全体をビルドコンテキストに設定
context: ../
dockerfile: .devcontainer/Dockerfile
# x86_64用を指定してビルド(amd64なのは多分大人の都合...)
platform: linux/amd64
image: rails_dev_container_example
volumes:
# プロジェクト全体を/appにマウント
- ../:/app
# /app/node_modulesのみビルド時のものを使用(volume trick)
- /app/node_modules
ports:
# コンテナの3000をホストの3000にフォワード
- 3000:3000
# 何も実行しない状態でコンテナを待機させる
command: sleep infinity
最後に、Dev Containerの設定ファイル.devcontainer/devcontainer.json
を作成します。
{
"name": "Rails on x86",
"dockerComposeFile": "docker-compose.yml",
// VSCodeで接続するコンテナ
"service": "rails",
// VSCodeで開くフォルダ
"workspaceFolder": "/app"
}
ここまでで、.devcontainer
上に3つのファイルが保存されているはずです。
Dev Containerの起動
それでは早速Dev Containerを立ち上げてみましょう!
VSCodeでCommand
+Shift
+P
のショートカットでコマンドパレットを開き、Dev Containers: Reopen in Container
を選択します。
するとコンテナのビルドなどが行われ(結構長い)、Dev Containerが起動します!
アーキテクチャシミュレーションのバグ?でコード変更監視がエラーになるため、/config/environments/development.rb
を修正します。
- config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+ config.file_watcher = ActiveSupport::FileUpdateChecker
Dev Container上でターミナルを開き、以下コマンド実行後ブラウザでhttp://localhost:3000
にアクセスして、Rails Serverが起動していることを確認してください。
$ rails server -b 0.0.0.0 -p 3000
webpack-dev-server
も同時に起動する
ここまででDev Containerを使ってRailsの開発環境を構築することが出来ましたが、Dev Containerの真の姿はまだこれからです。
Webpackerを使う開発のベストプラクティスとして、「webpack-dev-server
を利用してホットリロードを有効にする」というものがあります。
今の状態でも、ターミナルを2枚立ち上げて片方で$ bin/webpack-dev-server
を起動する手段が使えますが、毎回必要なので面倒ですし忘れてしまいそうです。
docker-compose.yml
を修正してwebpack-dev-server
を立ち上げるよう設定しましょう。
services:
rails:
# ...略
ports:
# コンテナを3000をホストの3000にフォワード
- 3000:3000
+ environment:
+ # webpackコンテナで実行しているwebpack-dev-serverを見にいく
+ - WEBPACKER_DEV_SERVER_HOST=webpack
# 何も実行しない状態でコンテナを待機させる
command: sleep infinity
+ webpack:
+ image: rails_dev_container_example
+ volumes:
+ - ../:/app
+ - /app/node_modules
+ ports:
+ - 3035:3035
+ environment:
+ - WEBPACKER_DEV_SERVER_HOST=0.0.0.0
+ # webpack-dev-serverを実行する
+ command: bin/webpack-dev-server
変更後、コマンドパレットでDev Containers: Rebuild container
を選択します。
これでDev Containerとは別のコンテナで、webpack-dev-server
が起動するようになりました!
VSCodeのデバッガー実行・拡張機能を有効にする
VSCodeは優れたコードエディタであると共に、豊富な拡張機能を利用することでIDEにも引けを取らない強力なデバッガーとなります。
Dev Containerはインストールする拡張機能をdevcontainer.json
に記述するので、この設定ファイルをチームで共有することでメンバー全員が同じ拡張機能を使うことが出来ます!
それではDockerfile
に手を加えて、デバッガーに必要なGemをインストールしましょう。
# ...略
RUN \
# nodeとyarnが実行できるようシンボリックリンクを設定
ln -s /usr/local/bin/node /usr/local/bin/nodejs && \
ln -s /opt/yarn-v1.22.19/bin/yarn.js /usr/local/bin/yarn.js && \
+ # デバッグ用のGemをインストール
+ gem install solargraph ruby-debug-ide debase
# ...略
次にdevcontainer.json
を編集して、インストールする拡張機能を指定します。
{
"name": "Rails on x86",
"dockerComposeFile": "docker-compose.yml",
// VSCodeで接続するコンテナ
"service": "rails",
// VSCodeで開くフォルダ
- "workspaceFolder": "/app"
+ "workspaceFolder": "/app",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "endverbraucher.pack-rails",
+ "castwide.solargraph",
+ "Vue.volar",
+ "walkme.Ruby-extension-pack"
+ ]
+ }
+ }
}
最後にデバッガーの実行設定を新しいファイル.vscode/launch.json
に記載します。
{
"version": "0.2.0",
"configurations": [
{
"name": "Rails server",
"type": "Ruby",
"request": "launch",
"program": "${workspaceRoot}/bin/rails",
"args": [
"server", "-b", "0.0.0.0", "-p", "3000"
]
}
]
}
コマンドパレットでDev Containers: Rebuild containerを選択し、Dev Containerが起動したらF5
キーを押してデバッガーを実行します。
これでDev Container上でRailsをデバッグすることが出来ました!
謝辞
同僚の@akitoshigaさんにAppleシリコン搭載Macでの動作確認を行ってもらいました!
彼は4日目と20日目も担当しております。
なお4日目を見て分かる通り彼はRubyMine派ですが、Dev Containerのデバッグ画面を見てVSCodeへの乗り換えを検討したいと言うほど感動していました
終わりに
エムスリーキャリアでは積極的にエンジニアを採用しております。
Dockerなど新めの技術を取り入れた開発環境で医療従事者の働き方を革新しませんか?
Appleシリコン搭載Macによる開発体制も整っておりますのでApple派の方も安心です。
US配列も選べるよ!
-
後述の通りアーキテクチャエミュレーションを行うため、パフォーマンスの低下・予期せぬ誤動作が発生する可能性があります。あくまで選択肢の一つであり、工数と現場の理解さえあれば1.本番環境ごと古い依存関係を修正するがベストな選択肢であることを強調しておきます ↩
-
Dockerマニフェストリストが存在する場合のみ。自前でビルド・プッシュしているイメージはマニフェストがないケースがほとんどです ↩
-
ネイティブで動作するイメージが登録されていない場合はエラーになります ↩
-
実のところDev Containersはウォークスルー方式でセットアップすることもできますが、既存のプロジェクトをDev Container化するにはいささか冗長に感じました ↩