チラシの裏からうっすら見える外枠の外のメモ書き

新聞に挟まってる硬い紙のチラシの裏からうっすら見える外枠の外に走り書きされたようなものです。思いついたときにふらふらと。

リモートマシンのDockerコンテナにVSCodeで接続して開発するための環境づくり

研究開発などで超高性能なマシン(リモートマシン)を使う場合、いろいろな環境からアクセスする方法には主にSSHがあります。 しかし、SSHでリモートマシンに接続して作業を行う場合、ユーザーごとに環境が完全に分離していないので他のユーザー環境から汚染を受けたり、他のユーザー環境を汚染してしまったりしてしまいます。 この問題の解決策としては様々ありますが、Dockerによる仮想化はコンテナごとに環境を分離できるので開発には向いてます。 しかし、VSCodeでリモートマシン上のDockerコンテナにアクセスするのは設定が難解であったり、VSCodeにバグがあったりして容易ではありません。 そこで、多段SSHを利用することで、超高性能なマシンの上で動作するDockerコンテナにVSCodeのRemote SSHを使用して接続し、開発などが行える環境を作ってみました。

構成

ネットワークの構成図。クライアントマシンからリモートマシンに行った接続をトンネルにコンテナへ接続する。 今回の構成は上の図のようになっています。クライアントマシンからはリモートマシンにSSHで接続することはできますが、コンテナのポートはリモートマシン側やその上流にあるゲートウェイなどで遮断されているため、コンテナのSSHには直接接続できません。 そこで、はじめにリモートマシンに対してSSHで接続し、それをトンネルとして利用することであたかもlocalhostにあるSSHサーバへ接続するかのようにリモートマシン上のコンテナへ接続します。

事前準備

インストール

クライアントマシン

クライアントマシンには予めSSHクライアントとVSCodeをインストールしておく必要があります。 また、VSCodeにはRemote SSH拡張機能をインストールしておく必要があります。

これらをすでにインストールしている人はこの手順は必要ありません。 azure.microsoft.com marketplace.visualstudio.com

なお、Windowsに同梱されているSSHクライアントは現時点(Windows 10 20H2)でバグがあるため、以下のGitHubリポジトリで配布されているSSHクライアントをダウンロードしてCドライブ直下などに展開しておく必要があります。 github.com

リモートマシン

サーバには、SSHサーバとDocker、そしてDocker Composeをインストールしておく必要があります。Rootless Dockerでも動作することを確認しています。

これらをすでにインストールしている人はこの手順は必要ありません。

公開鍵/秘密鍵の作成

クライアントマシンで、今後使用する公開鍵/秘密鍵を作成します。

すでに使いたい公開鍵/秘密鍵を持っている人はこの手順は必要ありません。 git-scm.com

ここで用意した公開鍵はリモートマシンのSSHサーバにログインするため、そしてDockerコンテナ内のSSHサーバにログインするために使用します。

リモートマシンへの公開鍵の登録

リモートマシンにパスワードを入力せずにSSH接続できるようにするため、公開鍵をリモートマシンに転送します。 公開鍵が~/.ssh/id_rsa.pubの場合、クライアントマシン上で次のようにコマンドを入力します。

ssh-copy-id -i ~/.ssh/id_rsa.pub [リモートマシンのユーザ名]@[リモートマシンのホスト名]

これにより、リモートマシン上の~/.ssh/authorized_keysに公開鍵が追加されます。

手順

  1. DockerコンテナにSSHサーバを導入する
  2. Docker Composeの設定を行う
  3. Docker Composeで立ち上げる
  4. SSHのクライアント設定を行う
  5. SSHでコンテナに接続してみる
  6. VSCodeのRemote SSHで接続する

1. DockerコンテナにSSHサーバを導入する

DockerコンテナにSSHサーバを導入します。 Dockerfileに次のように記述します。

# イメージは使いたい好きなイメージを選択
FROM ubuntu:focal-20201008
RUN apt-get update && \
    # SSHサーバをインストール
    apt-get install -q -y ssh && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    # SSHサーバが動作するために必要なsockファイルが配置されるディレクトリを用意
    mkdir /var/run/sshd && \
    # rootでログインできるようにするため、パスワードを設定(ただしこのパスワードは使いません)
    echo 'root:password' | chpasswd && \
    # パスワードでのログインをできないようにする
    sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config # 

# SSHで使用する公開鍵をここでコピーする
WORKDIR /root/.ssh
COPY id_rsa.pub authorized_keys
# SSHポートを公開する(Docker Composeで別のポートにバインドするので22番ポートのまま)
EXPOSE 22

# その他開発に必要なプログラムのインストールなど

CMD ["/usr/sbin/sshd", "-D"]

Dockerfileにも記載されていますが、公開鍵をコンテナ内にコピーしています。そのため、このDockerfileがあるディレクトリにid_rsa.pubをコピーしておきます。 また、rootアカウントにパスワードを設定していますが、SSHの設定でパスワードでのログインを禁止しているので使うことはありません。 コンテナのSSHへのログインには、公開鍵暗号方式を使用します。 また、SSHのポートを標準の22番ポートから変更していませんが、これもDocker Composeで別のポートにバインドするため気にする必要はありません。*1

2. Docker Composeの設定を行う

Docker Composeの設定を行います。 docker-compose.ymlに次のように記述します。

version: "3.8"

services:
    worker:
        build:
            context: ./
        ports:
            # ホストマシン側のポート番号は使えるポートであればたとえ外部に公開していなくてもOK
            - "8022:22"
# 他に必要な設定(volumesとか)を記述

中に書いたとおり、コンテナのSSHで利用するポートは使われていないポートであればどのポートでも構いません。 たとえ外部に公開していないポートでも、リモートマシン自体のSSHに接続できればそこを通過していくので問題ありません。

3. Docker Composeで立ち上げる

ここでリモートマシンに移ります。 Dockerfiledocker-compose.yml、公開鍵などをリモートマシンに用意したら、コンテナを起動します。

docker-compose up -d

問題がなければビルドされ、コンテナ上にSSHサーバが立ち上がります。 以下のコマンドで確認した際に、Upになっていなければ立ち上がっています。

docker-compose ps

4. SSHのクライアント設定を行う

ここでクライアントマシンに戻り、~/.ssh/にあるconfigというファイルに次を追記します。

# リモートマシンの名前(1)
Host remote_machine
    # リモートマシンのホスト名(2)
    HostName remote.machine.address
    # ポート番号(3)
    Port 22
    # リモートマシンでのユーザー名(4)
    User user
    # リモートマシンのSSHに接続するための秘密鍵のパス(5)
    IdentityFile ~/.ssh/id_rsa

# コンテナの名前(1)
Host container
    # コンテナのホスト名、そのままでOK(6)
    HostName localhost
    # ポート番号(7)
    Port 8022
    # コンテナでのユーザー名(8)
    User root
    # コンテナのSSHに接続するための秘密鍵のパス(9)
    IdentityFile ~/.ssh/id_rsa
    # トンネルの設定(10)
    ProxyCommand ssh -W %h:%p remote_machine

ここが重要なポイントです。

  1. 接続先名
    リモートマシンの名前とコンテナの名前については、自由に決めることができます。 これは、SSHコマンドでアクセスするときに使えるので、わかりやすくて短い名前をおすすめします。 SSHコマンドで使う場合はssh remote_machineと入力します。
  2. リモートマシンのホスト名
    リモートマシンのホスト名はSSHでリモートマシンに接続するときのIPアドレスFQDNを記述します。
  3. リモートマシンのポート番号
    ポート番号は22番以外でリモートマシンに接続している場合は変更してください。
  4. リモートマシンユーザー名
    リモートマシンのユーザー名はSSHでリモートマシンに接続するときに使うユーザー名を記述します。
  5. リモートマシンの秘密鍵のパス
    リモートマシンに登録した公開鍵に対応する秘密鍵のパスを記述します。
  6. コンテナのホスト名
    コンテナにSSHでログインするためのホスト名です。ここはlocalhost127.0.0.1を指定します。 (10)でリモートマシンにトンネルを作った場合、それ以降のクライアントマシンからのSSHはリモートマシンから実行されます。 クライアントマシンから見ればコンテナはリモートマシンの上にありますが、リモートマシンから見ればコンテナは自分の上にあります。 つまり、クライアントマシンから見てコンテナのホスト名はリモートマシンと同じホスト名ですが、リモートマシンから見てコンテナのホスト名はローカルホストになるわけです。 アドレスの関係図。クライアントマシンから見たコンテナのアドレスはremote.addressだが、リモートマシンから見たコンテナのアドレスはlocalhostになる。
  7. コンテナのポート番号
    コンテナのポート番号はdocker-compose.ymlでバインドした番号です。 (6)に書いたようにリモートマシンからアクセスするときに使うので外部からアクセスできなくても問題ありません。
  8. コンテナのユーザー名
    コンテナのユーザー名です。ほとんどのコンテナはデフォルトのユーザー名がrootなので大体このままで問題ないと思います。
  9. コンテナの秘密鍵のパス
    コンテナに登録した公開鍵に対応する秘密鍵のパスを記述します。
  10. トンネルの設定
    このコンテナにSSHで接続しようとすると、この項目が実行されます。 ここにはSSHのトンネルを作成するコマンドが記述してあるため、自動的にトンネルが作られ、そこの中を通ってコンテナへSSH接続をするようになります。 一番うしろのremote_hostはリモートマシンの接続先名なので、(1)のリモートマシンの接続先名を変更した場合はここも変更してください。

また、冒頭の事前準備にも記載しましたが、Windows 10のSSHクライアントにはバグがあり秘密鍵の認証がうまくいかないので、予めダウンロードして展開しておいたWin32-OpenSSHを使用します。 例えば、ssh.exeC:\OpenSSH-Win64\ssh.exeにある場合

    ProxyCommand ssh -W %h:%p remote_machine

    ProxyCommand C:\OpenSSH-Win64\ssh.exe -W %h:%p remote_machine

と書き換えてください。

5. SSHでコンテナに接続してみる

設定を保存したら、SSHコマンドでコンテナに接続できるか確かめてみます。 以下のSSHコマンドでコンテナに接続します。

ssh container

初めての接続の場合は接続先が正しいか確認されるので、問題なければyesと答えます。 ここまでの設定が問題なければコンテナに直接接続できます。

もしエラーが発生する場合は、リモートマシン上のコンテナが起動しているか(docker-compose psを確認してUpになっているか)、SSHクライアントのconfigが間違っていないかを確認してください。

6. VSCodeのRemote SSHで接続する

クライアントマシンでVSCodeを起動します。 VSCodeの右下に<>マーク(Remote拡張機能のメニュー)をクリックし、Remote-SSH: Connect to Hostを選びSSHクライアントのconfigで設定した接続先名containerを選択します。 接続後、開発したいフォルダを選択すればそのディレクトリをVSCodeで開くことができます。 また、一度開くことで2回目以降はRecentに同じディレクトリが表示されるため、1クリックで接続することが可能になります。

*1:ただし、22番ポートを別のプログラムに使用させたい場合は別です。