Visual Studio Code - "Remote Development" を使って Docker Container on "Vagrant + VirtualBox" のファイルを編集する

## 要約

- Vagrant で構築した仮想環境の中にある Docker へ繋げるときは 多段SSH を使う。
- Docker Container に SSHサーバ を構築する必要がある。
- VS Code の Remote Development は Remote SSH を使用する。

## はじめに

VS Code に Remote Development と呼ばれる拡張機能が追加されました。

- [Remote Development with VS Code - Visual Studio Code](https://code.visualstudio.com/blogs/2019/05/02/remote-development)

数ヶ月遅れでその機能の素晴らしさに気づき、その活用方法をメモしておこうと記事を作成します。

基本的な用途は以下のクラスメソッドさんの記事が分かりやすいので参照ください。

[【設定爆速】VS CodeのRemote Developmentを使ってSSH接続したEC2上のファイルを編集する](https://dev.classmethod.jp/etc/vs-code-remote-development-ec2/)

## 概要

### 書くこと

- VS Code Remote Development の活用方法
    - 多段SSH を用いて、２つ先の仮想環境に繋げる方法
- docker container を作るポイント
- ホストOS の設定（ `.ssh/config` ）

### 書かないこと

- Vagrant で Docker / docker-compose の環境を作る方法

### 想定環境

この記事で実現するシステムの構成は下図の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/2ea24fb5-c3d6-57c3-0e56-5f4a695e8f6d.png)

上図の通り、VS Code の Remote Development を使用して、リモート環境のコードを VS Code の UI で直接編集出来るようにします。

また、多段SSH の仕組みを用いて、１つ目の仮想環境の先に存在する Docker コンテナ に対しても同じ仕組みでコードの編集が出来るようにします。

今回の仕組みを実現した環境の詳細は以下の通りです。

|Item|Version|
|:--|:--|
|HostOS|Windows 10|
|VirtualBox|5.2.18 r124319 (Qt5.6.2)|

```powershell:command
> vagrant -v
Vagrant 2.1.2

> vagrant plugin list
vagrant-disksize (0.1.2)
vagrant-docker-compose (1.5.1)
vagrant-omnibus (1.5.0)
vagrant-proxyconf (1.5.2)
vagrant-vbguest (0.15.2)

> vagrant box list
centos/7         (virtualbox, 1905.1)
```

```bash:command
$ rpm -qa kernel\* | sort
kernel-3.10.0-1062.9.1.el7.x86_64
kernel-3.10.0-957.12.2.el7.x86_64
kernel-devel-3.10.0-1062.9.1.el7.x86_64
kernel-headers-3.10.0-1062.9.1.el7.x86_64
kernel-tools-3.10.0-1062.9.1.el7.x86_64
kernel-tools-libs-3.10.0-1062.9.1.el7.x86_64
```

## Remote Development が実現すること

### 「ローカルPCのコードを修正して、それで十分？」

近年のソフトウェア開発の現状として、次のような傾向があります。

- 特定のコンテナやVMでのみでアプリケーションが動作する。
- ローカルでは実現出来ない実行環境が多くある。

こういった現状の中で、ローカルPCでソースコードのみ修正出来たとしても、以下のようなケースにおいては、手間が増えてしまいます。

- 実行環境へのデプロイが必要である。
- IDEが直接コードを実行することで実現出来るようや動的解析機能のため、ローカルPCにも開発キットの導入が必要なケースがある。

例えば次の図のように、コンテナ上に PHP の実行環境を作って動作出来るようにした場合を考えます。
コンテナ上のアプリケーションは意図した通りに動くでしょうが、 VS Code で静的解析や動的解析を行いたい場合、別途、Host OS や ローカルPC上の VS Code に手を加える必要が出てきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/1de22fff-33b0-a1f4-58f6-caee39cb5421.png)

また、Web Application をチーム開発する場合には、Docker などで共通化した仮想環境を作成して配布することが多いと思いますが、ローカルPCで別途設定が必要となると、全てが共通化出来ているとは言えず、面倒な手間も増えてしまいます。


### リモート環境のコンテクストで、リッチなローカル開発環境の提供

VS Code の Remote Development を使用することで ローカルPC 上の VS Code を使用して、リモート環境にあるコードを違和感なく編集することが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/b8d5ff88-cc16-14ae-6564-0d8a64c49858.png)
cited from [Remote Development with VS Code - Visual Studio Code](https://code.visualstudio.com/blogs/2019/05/02/remote-development)

### 3つの拡張機能

**Remote Development** は、次の３つリモート環境に対応しています。

- remote workspaces running in WSL
- remote workspaces running in Docker containers
- remote workspaces running in physical and virtual machines over SSH

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/933a27a0-6058-f990-5f71-fdf1fcfed0d4.png)
cited from [Remote Development with VS Code - Visual Studio Code](https://code.visualstudio.com/blogs/2019/05/02/remote-development)

### Architecture and extension

Remote Development は、大きく別けて以下の２つの extension から構成されています。

- UI Extensions
- Workspace Extensions

この２つは、下図のようなアーキテクチャで構成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/d8cc4068-3f19-adc3-3de7-80aaccfdc649.png)
cited from [Supporting Remote Development and Visual Studio Online - Visual Studio Code](https://vscode-eastus.azurewebsites.net/api/advanced-topics/remote-extensions)

### 実現のイメージ

今回の記事では、次のように、ホストOS の VS Code から 仮想環境のコードを編集出来るようにすることを目指します。
特に、仮想環境の上にある Docker Container を、同じ仕組みでどのように実現するかがポイントです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/11cf0210-941f-8773-7394-f370c3d083f5.png)


## 設定手順

設定を行うにあたって、 Docker や docker-compose の導入以外で特徴的な作業としては次の３つがあります。

1. Docker Container に sshd サーバを構築し、 root ユーザでログイン出来るようにする。
2. Docker Container 起動時に Port Forward 設定を行う。
3. Host OS に `ssh/config` 設定を追加する。

これらを図で示すと以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/5bc85cbd-22b2-f984-5e59-7e1a2401d953.png)

この設定を行うことで、 HostOS に存在する VS Code の Remote Development を使用して、２つ先の Docker Container に対して Remote SSH 接続を行うことが出来ます。

### 1. Vagrant 環境の構築

まず最初に、 Docker と docker-compose をインストールした 仮想環境を構築します。
この手順は本筋ではないので、次の手順で構築してください。
（設定内容は [anfangd/docker-on-vagrant-script - GitHub](https://github.com/anfangd/docker-on-vagrant-script) を参照ください。）

```powershell:command(powershell)
## Vagrant のプラグインをインストールする
> vagrant plugin install vagrant-vbguest
> vagrant plugin install vagrant-proxyconf

## GitHub から Vagrantfile と provision.sh を取得する
> git clone https://github.com/anfangd/docker-on-vagrant-script.git
> cd docker-on-vagrant-script

## Vagrant の起動する
> vagrant up

```

vbguest に関連するエラーが発生した場合は以下を参照してください。
（kernel version のミスマッチによってエラーが発生する可能性があります。）

- [Vagrant 共有フォルダが同期できないときの対処法 /sbin/mount.vboxsf: mounting failed with the error: No such device](https://blog.thingslabo.com/archives/vagrant-vboxsf-mount-failed.html)

### 2. `.ssh/config` の設定（その１）

次のコマンドで、Vagrant で起動している仮想環境の ssh-config を確認することが出来ます。

```powershell:command(powershell@Windows10)
> vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile C:/Users/<username>/work/vagrant/centos-7-docker/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL
```

上記のコマンドで確認した内容を、 `.ssh/config` にコピペします。
なお、Host名は任意の名前に変更してください。

これらは Windows 10 向けの設定のため、macOS などの場合は適宜 PATH の修正などを行ってください。

```config:.ssh/config(Windows10)
Host vagrant-os
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile C:/Users/<username>/work/vagrant/centos-7-docker/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

```

### 3. 秘密鍵の転送

Guest OS から Docker に接続するために、 Vagrant が自動生成した 公開鍵のペアを使用します。
Host OS 上に秘密鍵が存在するので、 Guest OS へ秘密鍵をコピーします。

```powershell:command(powershell@Windows10)
## 注意 `vagrant up` したディレクトリで実行する
> scp .vagrant/machines/default/virtualbox/private_key vagrant-os:/home/vagrant/.ssh/

## Guest OS に SSH接続
> vagrant ssh

## private_key の実行権限を変更（Guest OS 内）
$ chmod 600 ~/.ssh/private_key
```

### 4. Docker Container の起動

Docker を実行出来る仮想環境の構築が出来たら、 Docker Container を構築します。
以下は今回使用する Dockerfile です。

```dockerfile:Dockerfile
# image
FROM php:7.0.15-fpm

ENV LANG C.UTF-8

RUN apt-get update -qq && \
    apt-get install -y \
        zlib1g-dev \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
        && docker-php-ext-install zip \
        && yes "" | pecl install xdebug \
        && docker-php-ext-enable xdebug \
        && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
        && docker-php-ext-install -j$(nproc) gd

RUN apt-get update \
    && apt-get install -y libpq-dev \
    && docker-php-ext-install pdo_mysql pdo_pgsql

RUN apt-get update \
    && apt-get install -my wget gnupg

WORKDIR /opt/

# ここから最後までがポイント
RUN apt-get update && apt-get install -y openssh-server
RUN mkdir /var/run/sshd

## /etc/ssh/sshd_config の設定書き換え
RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config

# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd

ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile

# 公開鍵をコピー
COPY authorized_keys /root/authorized_keys

EXPOSE 22

# Activate authorized_keys & Boot sshd
CMD mkdir ~/.ssh && \
    mv ~/authorized_keys ~/.ssh/authorized_keys && \
    chmod 0600 ~/.ssh/authorized_keys &&  \
    # 最後に ssh を起動
    /usr/sbin/sshd -D
```

上の Dockerfile のうち、後半部分で openssh-server の導入や 公開鍵認証の設定などについて記載しています。

Docker Container の起動は次のコマンドで実行します。

```bash:command(bash@CentOS)
$ pwd
/vagrant/docker-sample

# 公開鍵をコピー
$ cp ~/.ssh/authorized_keys .

$ ls
Dockerfile authorized_keys

# build
$ docker build ./ -t example

# Docker Container を起動
# Port:10000 を Port:22 に転送
$ docker run -d -p 10000:22 example

# Docker Container に SSH 接続
$ ssh root@127.0.0.1 -p 10000 -i ~/.ssh/private_key

```

- [Docker | docker build と Dockerfile でイメージをビルドする基本 #Qiita](https://qiita.com/YumaInaura/items/e7155b309e109bc75cf8)

### 5. `.ssh/config` の設定（その２）

2.) で設定した `.ssh/config` に設定を追加します。

```config:.ssh/config(Windows10)
Host vagrant-os
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile C:/Users/<username>/work/vagrant/centos-7-docker/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

# 以下を追加
Host docker-os
  Hostname 127.0.0.1
  User root
  Port 10000
  ProxyCommand C:\Windows\System32\OpenSSH\ssh.exe -W %h:%p vagrant-os
  IdentityFile C:/Users/<username>/work/vagrant/centos-7-docker/.vagrant/machines/default/virtualbox/private_key
```

上記の設定で Docker Container に接続出来るか確認してみましょう。

```powershell:command(powershell@HostOS)
> ssh docker-os
```

これで接続できれば設定は完了です。

- [Docker | 公開鍵でコンテナにssh接続する出来るようにするDockerfileの例 #Qiita](https://qiita.com/YumaInaura/items/7509061e4b27e03ea538)

### 6. Visual Studio Code の設定

前節までの設定が出来てしまえば、あとは以下の記事の通り extensions をインストールして開けば使用出来ます。

- [【設定爆速】VS CodeのRemote Developmentを使ってSSH接続したEC2上のファイルを編集する](https://dev.classmethod.jp/etc/vs-code-remote-development-ec2/)

以下のように、CentOS へのアクセスを示す `vagrant-os` に加え、その上の Docker Container である `docker-os` も開けるようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/aaa73034-d05c-6e79-d1c6-681d6357d715.png)

### extra-1. 【HostOS←→GuestOS】 のフォルダ同期を停止する

Vagrant で構築した GuestOS のフォルダと HostOS のフォルダを同期する方法は、非常に便利なようで厄介な点がいくつかあります。
私は以下のような事象で時間を取られることがよくあります。

- Vagrant や Box のバージョンアップで頻繁にエラーが発生する。
- GuestOS 内の Webサーバと通信する時、HTTP アクセスが遅くなる。
- `npm install` する時にエラーが発生する。

ただ、今回の記事で使用した VS Code - Remote Development を使用すれば、ファイル同期の必要がなく、仮想環境内のファイルを直接修正出来るのでこのファイル同期の仕組みが不要になります。

同期をしないようにするには、以下のように Vagrantfile を修正して `vagrant reload` すれば良いだけなので非常に簡単です。

```vagrant:Vagrantfile(@HostOS)

＜略＞
  # Synced Folder
  #config.vm.synced_folder "./", "/vagrant", type:"virtualbox", mount_options: ['dmode=777','fmode=777']
  config.vm.synced_folder ".", "/vagrant", disabled: true
＜略＞

```

一方で、これをしてしまうと、これまで当たり前に出来ていたことが一部できなくなってしまいます。
想定されるケースと対策で重要そうなものを以下に記載します。

- **GuestOS にファイルを配置する。**<br>→scp コマンドなどで転送する。
- **仮想環境と同期しているフォルダの git リポジトリを Sourcetree で参照していた場合の対処法**<br>→VSCode の拡張機能で代替する。
- **Vagrant の Box を誤って削除してしまう。**<br>→HostOSとファイル同期してないので、中身は消えてしまいます。<br>　こまめに commit & push して、Vagrant Box の環境は削除される前提で作業しましょう。

## おわりに

多段SSH方式、めちゃくちゃ便利ですね。
Remote Container で実現する方法を模索して時間を浪費してしまったのですが、多段SSH方式でスムーズに実現することが出来ました。

そして何より、 Visual Studio Code さん、どんどん使いやすさが増してゆきます。

### TODO

- ~~活用方法について記事作成（PHP Xdebug の導入など）~~<br>
　→書きました：[Visual Studio Code - Remote Development を使用して PHP Xdebug を使ったデバッグを行う #Qiita](https://qiita.com/anfangd/items/6daccf1d40c84d4e4ced)

## 参考

### Vagrant + VirtualBox で Docker 環境を構築する。

- [docker - ローカル開発環境の構築 / Laravel on [ [Vagrant + VirtualBox] with Docker ] #Qiita](https://qiita.com/anfangd/items/8c57f3d753117ea288b9)

### Vagrant で共有フォルダ設定でエラーが出来た時の対処法

- [Vagrant 共有フォルダが同期できないときの対処法 /sbin/mount.vboxsf: mounting failed with the error: No such device](https://blog.thingslabo.com/archives/vagrant-vboxsf-mount-failed.html)

※ Remote Development 機能の出現によって、この共有をしない方が開発が効率的になる可能性有り。

### 多段SSHの実現方法

- [vscodeの多段SSHをしたい - terratail](https://teratail.com/questions/212441)
- [Docker | 公開鍵でコンテナにssh接続する出来るようにするDockerfileの例 #Qiita](https://qiita.com/YumaInaura/items/7509061e4b27e03ea538)