VSCodeのRemote Developmentを使ってDocker上のNode.js、Redis環境で作業する


2019年5月に公開された、Visual Studio Code でリモートマシン、コンテナ、Windows Subsystem for Linux (WSL) 上のワークスペースを開くことができるようになる拡張機能群「Remote Development Extension Pack」を使ってみました。

>![remote-development0.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264338/cf839461-18e5-cf16-c22a-5634fd1fdf84.jpeg)
> https://code.visualstudio.com/docs/remote/remote-overview

この拡張パックを導入すると、手元の「Visual Studio Code」からリモート環境へ接続し、そこでアプリ開発等を行うことが可能になります。

今回は、[Microsoftのレポジトリ VSCode Remote Try Node](https://github.com/microsoft/vscode-remote-try-node)を元に、Node.jsからRedisを扱うDockerコンテナに接続する設定をやってみます。



##1  Visual Studio Codeのバージョン確認
Stable 版では (version 1.35) リリース以降で Remote Development 拡張が利用可能です。
バージョンが古い場合は、Visual Studio Code の [ヘルプ]->[更新の確認] または https://code.visualstudio.com/updates 等から最新版にアップデートしてください。


##2  拡張機能Remote Developmentのインストール

Visual Studio Codeで拡張機能"Remote Development"を検索し、インストールしてください。
![vscode0.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264338/7afbad75-006c-16c2-6ae9-5ac89963271c.jpeg)

##3 プロジェクトの構成
[Microsoftのレポジトリ VSCode Remote Try Node](https://github.com/microsoft/vscode-remote-try-node)をクローンし、最低限のディレクトリ構成を揃えた後、必要なファイルを加除していきます。

<pre>
[Project Home]
|_ .devcontainer
|    |_  devcontainer.json 
|    |_ ubuntu-bionic-core-cloudimg-amd64-root.tar.gz
|  
|_ .vscode
|    |_  launch.json
|  
|_  build
|    |_  server.js
|_  src
|    |_  server
|         |_  main.ts
|_  store
|    |_  redis
|         |_  .gitkeep
| 
|_  Dockerfile
|_  docker-compose.yml
|_  package.json
|_　tsconfig.json
|_　tslint.json
|_  webpack.config.js
</pre>

公式ではDockerfileを .devcontainer 以下に配置していますが、 ホームディレクトリ上のローカルフォルダ store/redis とDockerコンテナ上のディレクトリを同期してデータ永続化したいので、ホームディレクトリ直下に配置しています。

また、公式では、node:10イメージをベースを使っていますが、 今回は、Linuxイメージ上に npm, node.jsをインストールしたコンテナを作成し、redis コンテナとリンクする構成をやってみたいと思います。

当初、軽量Linuxの代表格[alpine](https://hub.docker.com/_/alpine)をベースにしようとしましたが、どうやら Stable版のVSCode上で動くRemote Developmentではalpineをサポートしていないようなので、やむなく[Minimum Ubuntu](https://ubuntu.com/blog/minimal-ubuntu-released)を使うことにしました。

MInimum Ubuntuのイメージファイルは以下のレポジトリからダウンロードし、.devcontainer以下に配置しました。
> https://github.com/tianon/docker-brew-ubuntu-core/blob/59aa7dfef17153ecc812adbf26516675ce67e8aa/bionic/Dockerfile



##4 Remote Development の設定

### devcontainer.json

```json:devcontainer.json
{
    "name": "node.js and redis sample ",
    "dockerComposeFile": [
        "../docker-compose.yml"
    ],
    "service": "node",
    "appPort": 3000,
    "extensions": [
        "VisualStudioExptTeam.vscodeintellicode",
        "dbaeumer.vscode-eslint",
        "eg2.tslint"
    ],
    "settings": {
        "terminal.integrated.shell.linux": "/bin/bash"
    },
    "shutdownAction": "none" 
}
```

主要な設定項目としては、
>
- service で node.jsを稼働するコンテナのサービス名を指定
- appPort で node.js上でlistenするportを指定
- dockerComposeFileでdocker-compose.ymlの相対パスを指定
- extensions でリモート上のVSCodeで使う拡張機能を追加
 
ちなみに、普段使っているVSCodeの拡張機能一覧は

```sh
code --list-extensions | xargs -L 1 echo code --install-extension
```
で取得できます。

##5 Docker の設定

### Dockerfile
 ```dockerfile:Dokerfile
FROM scratch
ADD .devcontainer/ubuntu-bionic-core-cloudimg-amd64-root.tar.gz /
RUN set -xe \
    \
    && echo '#!/bin/sh' > /usr/sbin/policy-rc.d \
    && echo 'exit 101' >> /usr/sbin/policy-rc.d \
    && chmod +x /usr/sbin/policy-rc.d \
    \
    && dpkg-divert --local --rename --add /sbin/initctl \
    && cp -a /usr/sbin/policy-rc.d /sbin/initctl \
    && sed -i 's/^exit.*/exit 0/' /sbin/initctl \
    \
    && echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \
    \
    && echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean \
    && echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean \
    && echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean \
    \
    && echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages \
    \
    && echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes \
    \
    && echo 'Apt::AutoRemove::SuggestsImportant "false";' > /etc/apt/apt.conf.d/docker-autoremove-suggests

RUN rm -rf /var/lib/apt/lists/*

RUN sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list

RUN mkdir -p /run/systemd && echo 'docker' > /run/systemd/container

# Configure apt and install packages
RUN apt-get update && \
    yes | apt-get install \
    curl \
    git \
    npm \
    nodejs
WORKDIR /home
SHELL ["/bin/bash", "-c"]
```

基本的に、https://github.com/tianon/docker-brew-ubuntu-core/blob/59aa7dfef17153ecc812adbf26516675ce67e8aa/bionic/Dockerfile のままです。追加項目として、\#Configure apt and install packages以下で、必要なモジュールをインストールするようにします。


### docker-compose.yml
 ```yml:docker-compose.yml
version: '3'
services:
  node:
    build:
       context: .
       dockerfile: Dockerfile  
    container_name: node-container
    volumes:
      - .:/home
    ports:     
       - 3000:3000
    command: sh -c 'npm install & node build/server.js'
    links:
      - "redis"
  redis:
    restart: always
    container_name: redis-container
    image: redis:latest
    volumes:
        - ./store/redis:/data
    ports:
        - "6379:6379"
    command: redis-server --appendonly yes
 ```

上記のDockerfileを走らせて作成するnodeイメージと、Official Imageの[redis:latest](https://hub.docker.com/_/redis)イメージをリンクします。
特記事項として、redis側でローカルのstore/redisとコンテナ側のdataフォルダを同期し、データの永続化を行っています。


##6 package.jsonの設定
今回は、Webpackを使ってTypeScriptをトランスパイルする例です。
> 2019年7月現在の、webpackの最新バージョン 4.29.0 では Maximum Call Stackのエラーが出てしまうため、旧バージョンを指定しています。

```json:package.json
{
  "name": "node.js and redis sample",
  "version": "1.0.0",
  "devDependencies": {
    "@types/app-root-path": "",
    "@types/express": "",
    "@types/node": "",
    "@types/redis": "",
    "path": "",
    "ts-loader": "",
    "tslint": "",
    "tslint-loader": "",
    "typescript": "",
    "webpack": "4.17.1",
    "webpack-cli": "",
    "webpack-node-externals": ""
  },
  "dependencies": {
    "app-root-path": "",
    "express": "",
    "redis": ""
  },
  "private": true
}

```
##7 Node.jsによるサーバ設定

expressでWebサーバを立ち上げ、redisでPOSTの回数をカウントするという例です。

```ts:main.ts

'use strict';
import * as root from 'app-root-path';
import * as express from 'express';
import * as redis from 'redis';

/**
 * Configure Redis
 */
const client: redis.RedisClient = redis.createClient(6379, 'redis');
client.on('connect', () => console.log('Connected to Redis'));
client.set('visit', '0');

/**
 * Configure Web Server
 */
const app: express.Application = express();
app.use(express.static('.'));
app.post('/api',  (_req: express.Request, res: express.Response) => {
    client.get('visit', (_err: Error|null , visit: string) => {
      res.send('POST request received. Number of Visits:' + visit);
      client.set('visit', String(parseInt(visit, 10) + 1));
  });
});

// tslint:disable-next-line:typedef
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server running on ${port}`));
```

##8 実行


 F1で拡張機能の一覧を表示するか、Visual Studioコードの左下の緑色のブラケットマークからRemoteデスクトップの機能一覧を表示します。
![Inkedremote-development1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264338/482a1e2f-9ce8-fadc-c0e5-d78198b9ff99.jpeg)

`docker-compose up --build` を既に一度走らせている場合は、「Attach to Running Container（水色）」を選択し、まだの場合は「Open Folder in Container（赤色）」で現在のホームディレクトリを指定すれば`docker-compose up --build` が自動的に走ります。ビルドに成功すると、Remote Development用のVSCodeのウィンドウが起動します。

![Inkedremote-development2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264338/17d5db71-8e45-869d-6df9-fcf71f1db3d2.jpeg)


後は開いたウィンドウ上で「フォルダを開く」から任意のフォルダを開くと、コンテナ上のファイルをVS Codeで編集することができます。


![remote-development4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264338/94421fa6-2da0-1efb-4e71-e488838e25cf.jpeg)


##9 Redisの動作確認

　Remote Developmentとは直接関係ありませんが、Redisがきちんと動作しているか確認するには、`curl -X POST`(ローカル、コンテナ側のどちらからでも結構です) で以下のAPIを叩き、

```sh
 curl -X POST http://localhost:3000/api
```

以下のようにPOSTの通算回数が返ってくれば成功です。

```sh
POST request received. Number of Visits:[POSTの回数]
```

##10 参考にさせていただいたサイト

下記のページを参考にさせていただきました。

> https://code.visualstudio.com/updates/v1_35
https://qiita.com/yoskeoka/items/01c52c069123e0298660
https://qiita.com/mizuhof/items/7bc20538c9fe1edcba40
https://katsu-tech.hatenablog.com/entry/2017/10/11/233024
https://blog.manabusakai.com/2018/08/minimal-ubuntu-dockerfile/


なお、今回の構成はGithubで公開していますので、ご指摘などあればよろしくお願いいたします:neutral_face:

> https://github.com/snst-lab/vscode-remote-try-node-typescript-redis