Dockerで開発環境を作ってビルド速度を改善した（Windows）

# はじめに
Windows10 で Node.js を使った開発をしています。
チームメンバーは自分以外全員 Mac で、みんなビルドが早いです。
最近自分の PC を新しくしましたが、やはり自分のほうが遅いのです。スペックはみんなのより上なはずなのに。

Windows でやるから遅いのかもしれないと思い、Linux で開発環境を作ってみようと思いました。

# 対象者

* Linux や Docker の基礎知識がある方

# 環境

* Windows10
* WSL2
* Ubuntu
* Docker CE

Docker Desktop は使いませんでした。
Windows10 に WSL2 を導入し Ubuntu をインストールします。
その Ubuntu に Docker を利用できる環境を作りました。
ここまでは Docker を利用するのに必要なので、準備済みとして進めます。

## 開発環境に入れたいもの
以下が開発環境に入れておきたいものです。

- Node.js
- git
- zsh
- Vim
- Go

:::note
入れたいものは他にもありますが多いため割愛しました。
:::


## 作る開発環境について

続いてどこに何を置くかです。

|  | Windows | Ubuntu | Dockerコンテナ |
|:-:|:-:|:-:|:-:|
| 入れる物  | VSCode  | プロジェクトのソースコード  | :arrow_up: に書いた入れたいもの  |

* Windows の VSCode を使ってソースコードを書きます
  * ソースコードは Ubuntu に置きます
  * Windows にはソースコードや開発環境は置きません
* 開発で使用するコマンド等は Docker コンテナに入って実行します
  * Ubuntu で起動した Docker コンテナに接続して利用する想定です
  * ビルド結果のファイルなど新たに作成されるファイルは Ubuntu 領域に保存します

## パーミッションに注意

ここまで説明した構成をそのまま作るとパーミッション周りで不都合が発生します。
たとえば Docker コンテナで `git clone` したソースファイルを VSCode で保存すると `Permission Denied` で怒られます。Docker コンテナではデフォルトが root なようでこれが原因です。
Ubuntu と Docker コンテナのユーザーを揃える必要がありそうなので、今回は Docker コンテナで使うユーザーを Ubuntu に合わせようと思います。

## １つのコンテナでいろいろやりたい
△△の作業はコンテナ A で、□□の作業はコンテナ B で、のように都度コンテナを切り替えるのは嫌なので、必要なもの全部入りのコンテナを作ります。

# Dockerfile

以下のような `Dockerfile` を書きました。

```Dockerfile:Dockerfile
FROM ubuntu:21.10

# 使用するユーザーやバージョンを指定
ARG USER_NAME=kyamadahoge
ARG USER_ID=1000
ARG GROUP_ID=1000
ARG NODE_VERSION=14.18.3
ARG GO_VERSION=1.16.10

# apt で入れたいものをインストール
RUN apt update && apt install -y \
  zsh \
  git \
  vim \
&& apt clean \
&& rm -rf /var/lib/apt/lists/*

# rootパスワードの設定と一般ユーザーを作成
SHELL ["/bin/bash", "-c"]
RUN echo "root:root" | chpasswd && \
  addgroup --gid ${GROUP_ID} ${USER_NAME} && \
  adduser --shell /bin/zsh --uid ${USER_ID} --gid ${GROUP_ID} --disabled-password --gecos "" ${USER_NAME} && \
  echo "${USER_NAME}:${USER_NAME}" | chpasswd && \
  echo "%${USER_NAME} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/${USER_NAME} && \
  chmod 0440 /etc/sudoers.d/${USER_NAME}

# Node.jsインストール
WORKDIR /usr/local
RUN curl -L https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz -o node.tar.gz \
  && mkdir node \
  && tar zxf node.tar.gz -C node --strip-components 1 \
  && rm node.tar.gz \
  && ln -s /usr/local/node/bin/node /usr/bin/node
ENV PATH="/usr/local/node/bin:${PATH}"

# Goインストール
RUN curl -L https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz -o go.tar.gz \
  && tar -xzf go.tar.gz \
  && rm go.tar.gz
ENV PATH="/usr/local/go/bin:${PATH}"

# 一般ユーザーに切り替え
USER ${USER_NAME}
```

* イメージのサイズをなるべく減らせるように、apt キャッシュを削除しておくと良いそうです
* RUN 命令をなるべく少なくすることで、イメージを構成するレイヤーが減ってパフォーマンスが向上するそうなので、コマンドを連結して記述しました
  * ここでは見やすさのために RUN を分けて実行していますが、一発でやる方がより良いと思います
* パーミッション問題対策で、Docker コンテナで使用するユーザーと Ubuntu のユーザーの ID を合わせます
  * Dockerfile 内の `USER_ID`、`GROUP_ID` の値を Ubuntu で使用するユーザーの ID と合わせます

# docker-compose.yml

最低限しか書いてないため、このファイルは無くても良いかもしれません。
コンテナの起動が楽なので作りました。

```yaml:docker-compose.yml
version: '3.3'
services:
  dev:
    build: .
    volumes:
      - type: bind
        source: ./apps
        target: /apps
    tty: true
```

* Docker コンテナ内の `/apps` と Ubuntu の docker-compose.yml を置いたディレクトリ(*1)内の `apps` ディレクトリとが同期されます
  * ここにプロジェクトで使用するソースファイル等を配置します
  * 例えば (*1) が `/home/kyamadahoge/proj` なら
    * Ubuntu の `/home/kyamadahoge/proj/apps` と
    * Docker コンテナの `/apps` が同期します
  * VSCode で `/home/kyamadahoge/proj/apps` 以下のファイルを編集し、Docker コンテナで `/apps` 以下に対しビルド等をするということです
* 起動したコンテナをすぐに終了させないために `tty: true` を記述しています

# コンテナ起動

上記 2 ファイルを同じディレクトリに置いて

```shell
docker-compose up -d
```

でイメージが仕立てられて、コンテナが起動します。続いて

```shell
docker-compose exec dev zsh
```

でコンテナに入れるので、ここでビルドなど開発作業を行います。

# 肝心のビルド時間は？

Windows と Docker コンテナで同じリビジョンのソース一式をビルドしました。

|   | Windows | Docker | 速度 |
|:-:|:-:|:-:|:-:|
| プロジェクトA | 69秒 | 31秒 | 2.23倍 |
| プロジェクトB | 142秒 | 50秒 | 2.84倍 |

Windows よりもかなり改善されました！やった :tada: 

# 所感

同じ PC でこんなに違うのかと驚きました。ビルド速度が改善できて良かったです。
ミドルウェアのバージョンを切り替えて開発する場合には、別バージョンを入れたコンテナを別途作れば簡単に切り替えができて良いなと思いました。
もっと使い込んでいきたいと思います。