Dockerコンテナ内で独自にDevContainersっぽく使えるAngularプロジェクト環境を構築してみる


※ 当ページには【広告/PR】を含む場合があります。
2023/09/25
蛸壺の技術ブログ|Dockerコンテナ内で独自にDevContainersっぽく使えるAngularプロジェクト環境を構築してみる

Dockerコンテナを開発環境として使っているエンジニアの方にとっては、VSCodeの「Dev Containers」エクステンションをバリバリ使っている方も多いでしょう。

例えばAngularアプリで複数の類似プロジェクトを開発が頻発する場合、共通のnpmパッケージを一つにまとめ上げた上で、プロジェクトのビルド効率性を底上げして、リソースだけを一元管理したい時があります。

特にAngularでのSPAプロジェクトの開発では、npmパッケージのインストール後はどうしても
node_modulesのファイルサイズが大きくなってしまいます。

1つ2つ程度なら、まだ個別のプロジェクトとして管理・開発しても良いですが、ブロジェクトの数が乱発してくると、ほぼ同じライブラリー群を持った
node_modulesフォルダがブロジェクトの数だけ増殖し、パソコンのディスクをかなり圧迫することになります。

同じnpmパッケージを使う複数のプロジェクトを一つに束ねて整理できるようになったら、デスクサイズのサイズの大幅な圧縮が可能になります。

そこで、今回はDockerコンテナ起動時にマウントするボリュームを設定して、「Dev Containers」のコンセプトに近いような複数のプロジェクトを横断的に管理・開発できるnodejsプロジェクトの構築方法を検討していきます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

はじめに

「Dev Containers」を使ったことの無い人のために、すこしおさらいしておきましょう

Dev Containersは何がいいのか?

VSCoed公式からのDev Containersの仕組みを示した模式図を見ると、おおよその概要が掴みやすいかもしれません。

合同会社タコスキングダム|蛸壺の技術ブログ

出図:https://code.visualstudio.com/docs/devcontainers/containers

            
            + VSCodeにエクステンションと「devcontainer.json」と呼ばれる設定ファイルを入れるだけで、
    あとは自動でDockerコンテナの開発環境がバックグラウンドで準備される
+ 開発コンテナには必要なライブラリやランタイムをDockerのVolume内で隔離・管理できる
+ プロジェクトのリソースファイルと開発環境を分離できるので、
    類似のプロジェクトでは開発コンテナの統一・再利用が可能となる
        
どうでしょう。

一見、割といい事づくしな素晴らしい開発環境が簡単に構築できそうな話に聞こえてきます。

ですが、「Dev Containers」を使う際には少しだけ気をつけるべき落とし穴があります。

DevContainersを使う注意点

公式のこちらのページにも言及されているように、DevContainersは、デフォルトでローカルのファイルシステムに置かれているリソースを「バインドマウント」 する使用になっています。

このバインドマウントを使うということが、場合によっては厄介に感じるかもしれません。

以下の技術記事で実際の「バインドマウント」と「ネームドボリュームマウント」でのアクセス速度の比較をされていますので、興味があれば覗いてみてください。

参考|VS Code devcontainer で disk が遅すぎるのをなんとかしたい

書き込み・読み込み速度の詳しい比較が避けますが、スペックの低いパソコンでDevContainersを使ってみると、なにかにつけて
非常に処理が遅いと感じられるはずです。

個人的な経験で言えば、低スペックのPC上でDevContainersを使ったときに、Nodejsアプリなら
yarn installがめちゃくちゃ重く、Rustで使うcargo buildはコンパイル待ちがフォーエヴァーです...。

もちろん対処法に書かれているように、
Named Volumeを追加で用意することもできるようですが、設定ファイルをゴチャゴチャといじり始めるくらいならいっそのこと「DevContainersっぽいもの」を自前で作ってみようじゃないか、というモチベーションでやってみます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

Dockerで単一のNodejs開発環境で複数のAngularプロジェクトを束ねる

今回説明するテクニックを使うことで、Angularに限らずNodejsベースのフレームワーク等でnode_modulesの共有化ができると思います。

このブログでは、とりあえず「Angular」を題材に話を進めていきます。

angular.jsonかDockerのvolumeのどちらを使った方が良い?

そもそも、angular.jsonを基準としてみた場合、「Angular」自体にも一つのプロジェクト(一つのangular.json)に、複数のAngularのサブプロジェクトを追加して開発していくスタイルも可能です。

これは複数のAngularリソースを共存させるプロジェクトはCLIコマンドと
angular.jsonを上手く調整することで可能になります。

例えば、

            
            $ ng new my-workspace --no-create-application
$ cd my-workspace
$ ng generate application app-a
$ ng generate application app-b
$ ng generate application app-c
#...
        
としてこのように設定した時、angular.jsonprojectsは以下のように複数のAngularプロジェクトが生成されることになります。

            
            {
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "app-a": {
      ...
    },
    "app-b": {
      ...
    },
    "app-c": {
      ...
    },
    //...中略
  },
  //...中略
}
        
Multiple projects

一見これだとすべてのサブプロジェクト(ここでは
app-a/app-b/app-c)でnode_modulesを共有できそうな気がします。

ただ実際には、下の模式図のようなプロジェクト構造になっています。

合同会社タコスキングダム|蛸壺の技術ブログ

結局はサブプロジェクト毎に格アプリのビルドに最適化・専有化した
package.jsonとそのnode_modules(npmパッケージ)が出来てしまうのです。

サブプロジェクトを一つの飲食店舗として捉えると、まさにプロジェクト全体は「フードコートのような食の複合施設」になってしまいます。

つまり入店する店舗が多ければそれだけ大規模なリソースが必要になる、ということです。

これは今回目指しているような
「Nodejs開発環境とAngularアプリリソースの完全分離」とは意味合いの異なるものです。

そこで登場するのが「DevContainer」的考え方の延長で、「Dockerの定義済みボリューム」を上手く使うやり方になります。

DockerボリュームでNodejsアプリ用のワークスペースを整える

Dockerでのホスト側ファイルシステムのマウントについては先行して以下の記事で説明していました。

すこし複雑に感じられる場合には、ここからはDockerでの「ボリューム」を理解してからお読みください。

合同会社タコスキングダム|蛸壺の技術ブログ
docker-composeでvolumeマウントするフォルダ・ファイルを選択的にinclude/exclueする方法

docker-composeを使ったホスト側のファイルシステムからDockerコンテナへ選択的にコピーする際の基本テクニック

Dockerボリュームを上手く使って、一つのワークスペースに
node_modulesをインストールし、複数のAngularプロジェクトで共有できるようにさせてみます。

さきほどのAngular独自のマルチブロジェクトと比較したイメージ化してみると以下のように考えられます。

合同会社タコスキングダム|蛸壺の技術ブログ

先述したプロジェクト構造とは異なり、各プロジェクトはリソースのみを保持し、対してDockerコンテナ側からアクセスできるボリューム上に
「ワークスペース」を作成し、そこだけにpackage.jsonからインストールしたnode_modules等のビルド環境に関わるリソースを配置しています。

そしてDockerコンテナ(開発環境)側は、ワークスペースにマウントされたリソースが"どれか"、に応じてアプリをビルドします。

その意味では、持ち込んだ食材(リソース)によって、自在に商品を変えて対応する「移動販売のような変幻自在な屋台」のようなものです。

この場合、
node_modulesフォルダの中身があたかも複数のプロジェクトで共有されている状態になるので、共有化させたいプロジェクトの数が多ければ多いほどディスク容量の大幅削減になります。

他方、デメリットにあたるかは分かりませんが、Dockerの仕組みを良く理解しておく必要があります。

コンテナ技術の知識はあらかじめ理解した上でないと、Dockerのバインドマウントやボリュームマウントの使い分けに、戸惑ってしまうかもしれません。

実践〜Dockerボリュームでワークスペース構築

大体のコンセプトを把握していただいたところで、docker-composeによるワークスペースの実装を見ていきます。

まずは必要最低限の
docker-compose.ymlを以下のように与えます。

            
            version: '3.9'

services:
  app:
    #...他のタグは省略
    volumes:
      #👇リソースはprojectフォルダにバインドマウント
      - ./:/app/project
      #👇念の為node_modulesをバインドマウントから除外
      - /app/project/node_modules/
      #👇cacheボリュームをworkspaceフォルダにマウント
      - cache:/app/workspace
    #👇作業ディレクトリをワークスペースのフォルダに指定
    working_dir: "/app/workspace"

volumes:
  #👇ワークスペースとなるボリューム名(名前は任意)
  cache:
        
さて、今回のお話の中で関係がある設定は、servicesタグ中のvolumes/working_dirと、トップのvolumesタグです。

Dockerコンテナ側のファイル構造ですが、今回は説明の便宜上Dockerコンテナ内の
/app以下に具材を配置することを想定して話を進めます。

ここでは
/app以下に、workspaceフォルダと、同階層のprojectフォルダを置いておくことが今回のテクニックのミソです。

            
            /app
├── workspace (Volumeマウントかつ作業フォルダ)
└── project (Bindマウント)
        

前回の記事でも取り上げたように、「Volumeマウント」であるworkspaceフォルダへはDockerコンテナのプロセスしかアクセスできない代わりに高速でディスクの読み書きが可能である一方、projectフォルダはホストとDocker双方のプロセスでファイルシステムが共有されますが低速なファイル処理になります。

他方で、実際にはもう少しファイル数も多いと思いますが、説明のためにシンプル化した以下のようなプロジェクトがあったとします。

            
            .
├── app-a
│   ├── src (リソースフォルダ)
│   ├── ...
│   ├── package.json
│   └── docker-compose.yml (上で説明した内容を適用)
├── app-b
│   ├── src (リソースフォルダ)
│   ├── ...
│   ├── package.json
│   └── docker-compose.yml (上で説明した内容を適用)
└── app-c
    ├── src (リソースフォルダ)
    ├── ...
    ├── package.json
    └── docker-compose.yml (上で説明した内容を適用)
        
ブロジェクト側のほうはリソースの準備だけでOKで、個別にyarn installする必要はありません。

なお、各プロジェクトの
package.jsondependencies等は共通のnpmパッケージになるように、内容を同じに設定しておきます。

このプロジェクトを利用する初回は、ブロジェクトのどれかの
package.jsonを使って、ワークスペースにnpmパッケージをインストールします。

            
            $ cd app-a
#👇初回はコンテナにアタッチモードで中に入って作業
$ docker-compose up -d && docker-compose exec app bash

###...アタッチモードでワークスペースに入る

#👇プロジェクトのpackage.jsonをワークスペースへ持ってくる
$ cp ../project/package.json ./package.json
#👇package.jsonからnpmパッケージをインストール
$ yarn install
        
これで、cacheと言うボリューム内にnode_modulesを展開することができました。

あとはコンテナで何を実行するかによりますが、
app-bのAngularアプリサーバーを立ち上げたいなら、

            
            $ cd app-b
$ docker-compose up -d && docker-compose exec app bash

###...アタッチモード

#👇プロジェクトのリソースをワークスペースへコピー
$ cp -r ../project/ ./
#👇アプリをビルド・デバックサーバーを起動
$ yarn start
        
同様にapp-cなら、

            
            $ cd app-c
$ docker-compose up -d && docker-compose exec app bash

###...アタッチモードでワークスペースに入る

#👇プロジェクトのリソースをワークスペースへコピー
$ cp -r ../project/ ./
#👇アプリをビルド・デバックサーバーを起動
$ yarn start
        
でワークスペースにおいて共通化したnode_modulesで問題がなければ動作するはずです。

もし、アタッチモードに入らずに
docker-compose up等でDockerプロセスを起動させる場合には、docker-compose.ymlcommandタグを使って、cp -r ../project/ ./相当の前処理を挟むと良いでしょう。

この辺は開発者側のひと工夫が要求されます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法】Dockerをこれから学びたい人のためのオススメ書籍&教材特集

まとめ

最後に首題にて「Dev Containersっぽく使える」と表現したものの、実際の本家・Dev Containersはバックグラウンドでもっと気の利いた複雑なことをやってくれているありがたいツールです。

基本的には、「Dev Containers」を使ったほうが何も考えなくて良いし開発も捗る、というのならそのまま使い続けるほうが良いでしょう。

あくまでも「Dev Containers」の挙動が極端に重かったり・機能が気に入らなかったりする場合には、今回紹介したDockerのマウントタイプを切り替えるテクニックを使って、複数のプロジェクトフォルダの構成を手動管理してみてください。