この記事はネットワーク上にあるJupyterHub / Notebook 開発環境を、手元のVSCode の Remote SSH を利用して使いたいエンジニア向けの記事である。
前提として、構築済みのJupyterHubの設定を変更可能なユーザが利用するものとする。
概要
本記事では、JupyterHub 上で起動している Notebook コンテナに対して、VSCode の Remote SSH 機能で接続する方法を記述する
この方式の特徴は以下。
- JupyterHub の認証トークン(API token)
- SSH の公開鍵認証
- WebSocket 経由の SSH(jupyter-sshd-proxy + websocat)
という 二重の認証を用いる
仕組みの概要
VSCode
|
| (ssh)
| ProxyCommand: websocat
v
WebSocket (wss://<JupyterHub>/user/<user>/sshd/)
|
| JupyterHub Token 認証
v
Notebook コンテナ
|
| sshd (jupyter-sshd-proxy)
v
シェル / 開発環境
-
JupyterHub 側
- トークンで「そのユーザの Notebook にアクセスしてよいか」を検証
-
SSHD 側
- 公開鍵で「誰としてログインするか」を検証
事前設定
以下、ユーザが手元で利用する端末を クライアント 、JupyterHubのユーザPod側を ノートブック とする。
クライアント側の追加要件
1. websocat のインストール
websocat は WebSocket を stdin/stdout にブリッジするために使用する。
以下のリポジトリからクライアントに合わせて pre-built binary を取得。
例(Linuxの場合):
curl -LO https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl
chmod +x websocat.x86_64-unknown-linux-musl
sudo mv websocat.x86_64-unknown-linux-musl /usr/local/bin/websocat
2. JupyterHub のトークン発行
以下にアクセスし、API トークンを発行。
https://<JupyterHub>/hub/token
このトークンは後述の SSH 設定で使用する。
3. SSH 鍵の生成(クライアント側)
秘密鍵はクライアントにのみ保持し、Notebook には公開鍵のみを配置。
Linux / Mac
KEY_DIR="${HOME}/.ssh"
KEY_NAME="id_ed25519_jupyter"
mkdir -p "${KEY_DIR}"
chmod 700 "${KEY_DIR}"
if [ ! -f "${KEY_DIR}/${KEY_NAME}" ]; then
ssh-keygen -t ed25519 -N "" -f "${KEY_DIR}/${KEY_NAME}"
fi
chmod 600 "${KEY_DIR}/${KEY_NAME}"
chmod 644 "${KEY_DIR}/${KEY_NAME}.pub"
echo "公開鍵: ${KEY_DIR}/${KEY_NAME}.pub をJupyterにアップロードしてください"
echo "秘密鍵: ${KEY_DIR}/${KEY_NAME} はクライアントに保持(アップロードしない)"
Windows
コマンドプロンプト
@echo off
REM ============================================
REM このファイルは、クライアント側(Windows PC)で実行する
REM ============================================
REM スクリプト自身のあるディレクトリへ移動
cd /d %~dp0
REM 鍵設定
set KEY_DIR=%USERPROFILE%\.ssh
set KEY_NAME=id_ed25519_jupyter
set KEY_FILE=%KEY_DIR%\%KEY_NAME
REM .ssh ディレクトリ作成
if not exist "%KEY_DIR%" (
mkdir "%KEY_DIR%"
)
REM (安全のため)権限注意喚起
echo.
echo NOTE:
echo Windows では chmod は不要ですが、
echo SSH が鍵を拒否する場合は ACL を確認してください。
echo.
REM 公開鍵が存在しない場合のみ再生成(通常は不要)
if not exist "%KEY_FILE%.pub" (
ssh-keygen -y -f "%KEY_FILE%" > "%KEY_FILE%.pub"
)
echo.
echo 公開鍵: %KEY_FILE%.pub を Jupyter にアップロードしてください
echo 秘密鍵: %KEY_FILE% はクライアントに保持(アップロードしない)
echo.
4. ~/.ssh/config の設定
以下を クライアントの ~/.ssh/config に追記。
Host jupyter
HostName <ホスト名>
User <ユーザ名: エスケープ処理有>
ProxyCommand websocat --binary -H="Authorization: token <トークン>" asyncstdio: wss://%h/user/<ユーザ名:エスケープ処理無>/sshd/
IdentitiesOnly yes
IdentityFile ~/.ssh/<鍵名>
PreferredAuthentications publickey
PasswordAuthentication no
設定例
JupyterHub上のユーザ名が h.hoge である場合
Host jupyter
HostName example.com
User h-2ehoge
ProxyCommand websocat --binary -H="Authorization: token トークン" asyncstdio: wss://%h/user/h.hoge/sshd/
IdentitiesOnly yes
IdentityFile ~/.ssh/id_ed25519_jupyter
PreferredAuthentications publickey
PasswordAuthentication no
ノートブック側の追加要件
1. jupyter-sshd-proxy の導入
Notebook イメージに以下を組み込む(Dockerfile)。
RUN pip install jupyter-sshd-proxy
2. SSH サーバ・クライアントのインストール(Dockerfile で事前に)
Notebook 起動後の後付けは不可のため、Dockerfile に含める。
RUN apt-get update && \
apt-get install -y openssh-server openssh-client && \
rm -rf /var/lib/apt/lists/*
3. 公開鍵のアップロード
クライアントで生成した id_ed25519_jupyter.pubを Notebook の作業ディレクトリにドラッグ&ドロップでアップロード。
4. 公開鍵登録スクリプトの実行
Notebook 側で、以下のようなスクリプトを用意して実行。
PUBKEY_FILE="ここに.pub鍵ファイルパスを書く"
SSH_DIR="${HOME}/.ssh"
AUTH_KEYS="${SSH_DIR}/authorized_keys"
mkdir -p "${SSH_DIR}"
chmod 700 "${SSH_DIR}"
touch "${AUTH_KEYS}"
chmod 600 "${AUTH_KEYS}"
PUB="$(cat "${PUBKEY_FILE}")"
# 上書き(注意: 既存のauthorized_keysは上書きされます。既存の公開鍵は削除されます。)
echo "${PUB}" > "${AUTH_KEYS}"
# 追記の場合
# grep -qxF "${PUB}" "${AUTH_KEYS}" || echo "${PUB}" >> "${AUTH_KEYS}"
# 念のため(権限が壊れると認証失敗する)
chmod 700 "${SSH_DIR}"
chmod 600 "${AUTH_KEYS}"
echo "[OK] installed into ${AUTH_KEYS}"
接続方法(VSCode)
VSCode
- 拡張機能 Remote - SSH をインストール
- コマンドパレットを開く
Remote-SSH: Connect to Host -
jupyter(設定例で指定した名前) を選択
ユーザコンテナ再構築時の注意
ユーザコンテナ再構築後は、tokenの再発行と登録、それとsshのknown hostsの再設定が必要
1. JupyterHub API Token の再作成
ユーザコンテナ再作成後や 403 / 認証失敗時は、まず token を疑うのが定石。
作成方法(GUI)
- JupyterHub にブラウザでログイン
- 右上「Control Panel」
- 「API Tokens」
- 新規トークンを作成
- 権限: default(十分)
- 表示された token を 控える(再表示不可)
ssh config への反映
~/.ssh/config
Host jupyter
...
ProxyCommand websocat --binary -H="Authorization: token <NEW_TOKEN>"
...
※ token を変えたら 必ず ssh 再実行
※ Cursor / VSCode は内部 ssh を使うので再起動推奨
2. SSH known_hosts の更新(最重要)
ユーザコンテナ再作成後は 必ず起きる
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
これは 異常ではない(sshd 実体が変わったため)。
既存エントリ削除
再設定例(ドメインは適宜変更)
ssh-keygen -f ~/.ssh/known_hosts -R example.com
手動で一度だけ接続(必須)
ssh -v jupyter
- 指紋が表示されたら yes
-
Authentication succeeded (publickey)まで行けばOK - ここで known_hosts が再登録される
3. 接続トラブル時の即チェック順
- token は最新か?
- ssh config に token が反映されているか?
- known_hosts を消したか?
-
ssh -v jupyterは通るか?
VSCode で失敗しても、ssh が通れば設定は正しい
(オプション) .bash_profile
ssh接続した時、Dockerfileで定義した 環境変数が読み込まれない仕様 となっている。
そのため、SSH接続前に、予めJupyterHubのユーザPodの ~/.bash_profile に必要な環境変数を定義したファイルを作成しておく必要がある。
エラー対応
1. websocat: command not found
症状
exec: websocat: not found
原因
- クライアントに websocat がインストールされていない
- PATH が通っていない
対処
which websocat
で見つからない場合、公式バイナリを配置。
curl -LO https://github.com/vi/websocat/releases/latest/download/websocat.x86_64-unknown-linux-musl
chmod +x websocat.x86_64-unknown-linux-musl
sudo mv websocat.x86_64-unknown-linux-musl /usr/local/bin/websocat
2. WebSocketError: Redirected (302 Found)
症状
WebSocketError: Redirected (302 Found) to /user/<user>/sshd
原因
- WebSocket エンドポイントの末尾
/の有無が不一致 - nginx / JupyterHub のリダイレクトが発生
対処
必ず /sshd/(末尾スラッシュ付き)を使用する
ProxyCommand websocat --binary -H="Authorization: token <TOKEN>" asyncstdio: wss://%h/user/<user>/sshd/
3. Received unexpected status code (404 Not Found)
症状
WebSocketError: Received unexpected status code (404 Not Found)
原因
-
jupyter-sshd-proxyが Notebook に入っていない - Jupyter Server 拡張がロードされていない
- SSHD Proxy が有効化されていない
対処
Notebook 内で確認:
python -c "import jupyter_sshd_proxy, jupyter_server_proxy; print('ok')"
jupyter server extension list
Dockerfile に含める:
RUN pip install jupyter-sshd-proxy
4. option requires an argument -- V(ssh-keygen)
症状
ssh-keygen -V
option requires an argument -- V
原因
-
-Vはバージョン表示オプションではない
対処
ssh -V
で OpenSSH のバージョン確認を行う。
5. WARNING: UNPROTECTED PRIVATE KEY FILE!
症状
Permissions 0664 for 'id_ed25519' are too open.
This private key will be ignored.
原因
- 秘密鍵の権限が緩すぎる
対処(Linux / macOS)
chmod 600 ~/.ssh/id_ed25519_jupyter
chmod 700 ~/.ssh
対処(Windows / PowerShell)
icacls $env:USERPROFILE\.ssh\id_ed25519_jupyter /inheritance:r
icacls $env:USERPROFILE\.ssh\id_ed25519_jupyter /grant:r "$($env:USERNAME):(R)"
6. 公開鍵認証が通らずパスワードを要求される
症状
Authentications that can continue: publickey,password
...
<user>@<host>'s password:
原因
-
authorized_keysに公開鍵が登録されていない - 改行・空白の混入
-
.ssh/authorized_keysの権限不正 - ユーザ名の不一致
対処
Notebook 側で確認:
ls -ld ~/.ssh
ls -l ~/.ssh/authorized_keys
正しい権限:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
7. entry_point sshd was unable to be loaded
症状
entry_point sshd was unable to be loaded: No such file or directory: 'ssh-keygen'
原因
- Notebook イメージに
openssh-client/openssh-serverが入っていない
対処(Dockerfile)
RUN apt-get update && \
apt-get install -y openssh-server openssh-client && \
rm -rf /var/lib/apt/lists/*
8. SSH 接続はできるが VSCode が失敗する
症状
-
ssh jupyterは通る - VSCode / Cursor の Remote SSH が失敗
原因
- ProxyCommand の quoting ミス
- token に改行や空白が含まれている
-
%h/%pの誤用
対処
まず CLI で検証:
ssh -vvv jupyter
ProxyCommand を単純化して再確認。
9. WebSocketError: I/O failure
症状
websocat: WebSocketError: I/O failure
原因
- JupyterHub Proxy / nginx のタイムアウト
- WebSocket Upgrade が遮断されている
対処
- nginx で
proxy_http_version 1.1 -
Upgrade/Connectionヘッダの確認 -
proxy_read_timeoutを十分長く設定
10. 切り分けの基本フロー(重要)
-
WebSocket 単体確認
websocat -vv --binary -H="Authorization: token <TOKEN>" \ wss://<host>/user/<user>/sshd/ -
SSH 単体確認
ssh -vvv jupyter -
Notebook 側ログ確認
kubectl logs <notebook-pod>