0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JupyterHub + jupyter-sshd-proxy + websocat 経由 SSH 接続

0
Last updated at Posted at 2026-02-02

この記事はネットワーク上にある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.pubNotebook の作業ディレクトリにドラッグ&ドロップでアップロード。


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

  1. 拡張機能 Remote - SSH をインストール
  2. コマンドパレットを開く
    Remote-SSH: Connect to Host
  3. jupyter (設定例で指定した名前) を選択

ユーザコンテナ再構築時の注意

ユーザコンテナ再構築後は、tokenの再発行と登録、それとsshのknown hostsの再設定が必要

1. JupyterHub API Token の再作成

ユーザコンテナ再作成後や 403 / 認証失敗時は、まず token を疑うのが定石。

作成方法(GUI)

  1. JupyterHub にブラウザでログイン
  2. 右上「Control Panel」
  3. 「API Tokens」
  4. 新規トークンを作成
    • 権限: default(十分)
  5. 表示された 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. 接続トラブル時の即チェック順

  1. token は最新か?
  2. ssh config に token が反映されているか?
  3. known_hosts を消したか?
  4. 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. 切り分けの基本フロー(重要)

  1. WebSocket 単体確認

    websocat -vv --binary -H="Authorization: token <TOKEN>" \
      wss://<host>/user/<user>/sshd/
    
  2. SSH 単体確認

    ssh -vvv jupyter
    
  3. Notebook 側ログ確認

    kubectl logs <notebook-pod>
    

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?