SSHの多重接続について

tl;dr

デプロイツールによっては 同じサーバに SSH で何回もコマンドを実行することがある(ansible とか)。
コマンドごとに毎回コネクション(トンネル)を確立するとオーバーヘッドも大きくなる。
OpenSSH は $HOME/.ssh/config に次の設定を書くことで、接続を多重化(multiplex)することができる。

# $HOME/.ssh/config
Host machine1
  HostName machine1.example.org
  ControlPath ~/.ssh/controlmasters/%r@%h:%p
  ControlMaster auto
  ControlPersist 1h

multiplex 前後の速度差

SSH を使ってリモートサーバで echo コマンドを実行した時の速度差を計測

before multiplex

$ time ssh REMOTE -C echo

real    0m0.259s
user    0m0.012s
sys     0m0.004s

after multiplex

$ time ssh REMOTE -C echo

real    0m0.012s
user    0m0.000s
sys     0m0.004s

多重化接続してみる

多重化接続の流れ

SSH で多重化接続するには

  1. ControlMaster=yes で多重化のためのコントロールソケットを作成し
  2. 以降の接続では上で作成されたソケットを利用して接続する

多重化接続の設定

必要な設定は以下の3つ

  • ControlMaster 多重化接続方法
  • ControlPath 多重接続のソケットファイルのパス
  • ControlPersist 多重化接続の永続化設定

~/.ssh/ssh_config で設定することも、接続のたびにコマンドラインで渡すことも可能。

多重化接続する

動きを確認しやすいように、設定ファイルを利用せずに毎回オプションを渡して多重接続してみる。

まずは ControlMaster=yes にしてコントロールソケットを作成し、 ControlPath でコントロールソケットのパスを指定する。

$ ssh -o "ControlMaster=yes" -o "ControlPath=~/.ssh/controlmasters/%r@%h:%p" REMOTE

ソケットファイルが作成されることを確認

$ file ~/.ssh/controlmasters/jsmith@REMOTE:22
/home/jsmith/.ssh/controlmasters/jsmith@REMOTE:22: socket

以降の接続では作成したコントロールソケットファイルを ControlPath で指定し、モード ControlMaster=no(デフォルト) で接続すると、多重化される。

$ ssh -o "ControlMaster=no" -o "ControlPath=~/.ssh/controlmasters/%r@%h:%p" REMOTE

LISTEN 状態のソケットを確認

$ lsof -iLISTEN 状態の ssh ソケットを確認。

通常の設定では SSH 接続するたびに、クライアントとサーバの接続が確立される。

同じリモートサーバに3並列で接続すると

$ lsof -i | grep ssh
ssh     21909 jsmith    3r  IPv4 577301      0t0  TCP LOCAL:40531->REMOTE:ssh (ESTABLISHED) # REMOTE:ssh (ESTABLISHED)
ssh     21912 jsmith    3r  IPv4 577324      0t0  TCP LOCAL:40532->REMOTE:ssh (ESTABLISHED) # REMOTE:ssh (ESTABLISHED)
ssh     21912 jsmith    3r  IPv4 577324      0t0  TCP LOCAL:40532->REMOTE:ssh (ESTABLISHED)
ssh     21914 jsmith    3r  IPv4 577345      0t0  TCP LOCAL:40533->REMOTE:ssh (ESTABLISHED) # REMOTE:ssh (ESTABLISHED)
ssh     21976 jsmith    3u  IPv4 577588      0t0  TCP LOCAL:40534->REMOTE:ssh (ESTABLISHED) # REMOTE:ssh (ESTABLISHED)
ssh     21976 jsmith    3u  IPv4 577588      0t0  TCP LOCAL:40534->REMOTE:ssh (ESTABLISHED) # REMOTE:ssh (ESTABLISHED)
ssh     21976 jsmith    3u  IPv4 577588      0t0  TCP LOCAL:40534->REMOTE:ssh (ESTABLISHED) # <- multiplexing control socket

リモートサーバで sshd のログレベルを DEBUG にすると、同じコネクションに対して接続が増えるたびに channel 番号が 0 から採番されていることが確認できる。

# egrep 'channel [0-9]' /var/log/auth.log
Nov 30 17:04:53  REMOTE sshd[20480]: debug1: channel 0: new [server-session]
...
Nov 30 17:06:58  REMOTE sshd[20480]: debug1: channel 1: new [server-session]
...
Nov 30 17:12:51  REMOTE sshd[20480]: debug1: channel 2: new [server-session]
...

多重化接続の終了

多重接続用に作成下コントロールソケットのとじ方について

永続化させた場合

ControlPersist=yes の場合、コネクションは張られたままとなる。
コネクションが残り続けてほしくない場合、明示的に切断する必要がある。

graceful disconnect

新規に多重化コネクションは受け付けなくし、コントロールソケットを利用したチャンネルがひとつもなくなったタイミングでコネクションは切断する。
ssh -O stop hostname とする。

$ ssh -O check REMOTE
Master running (pid=16124)

check コマンドから多重化が ACTIVE 状態とわかる。

stop コマンドを実行

$ ssh -O stop REMOTE
Stop listening request sent.
$ ssh -O check REMOTE
Control socket connect(/home/jsmith/.ssh/controlmasters/jsmith@REMOTE:22): No such file or directory
$ lsof -i  | grep ssh
ssh     22331 jsmith    3r  IPv4 555740      0t0  TCP LOCAL:38772->REMOTE:ssh (ESTABLISHED)

コントロールソケットを利用した最後のチャンネルが切断されると、このソケットも閉じられる。

forceful disconnect

コントロールソケットを利用したアクティブなチャンネルが残っていても、強制的に切断するには $ ssh -O exit hostname とする。

$ ssh -O check REMOTE
Master running (pid=16260)

check コマンドから多重化が ACTIVE 状態とわかる。
exit コマンドを実行

$ ssh -O exit REMOTE
Exit request sent.
$ ssh -O check REMOTE
Control socket connect(/home/jsmith/.ssh/controlmasters/jsmith@REMOTE:22): No such file or directory
$ lsof -i | grep ssh
$

リモートサーバーにログインしていると、強制的にログアウトさせられる

jsmith@REMOTE:~$ Shared connection to REMOTE closed.
[jsmith@LOCAL ~]$

タイムアウト設定した場合

ControlPersist=1m のようにすると、コントロールソケットを利用した接続が1分以上一つも無ければ、自動的に切断される。

$ ssh  -C echo REMOTE # 新規にコネクションをはる
$ lsof -i | grep ssh
ssh     16447 jsmith    3u  IPv4 556983      0t0  TCP LOCAL:38776->REMOTE:ssh (ESTABLISHED)
$ ssh -O check REMOTE
Master running (pid=16447)

1分間放置する

$ ssh -O check REMOTE
Control socket connect(/home/jsmith/.ssh/controlmasters/jsmith@REMOTE:22): No such file or directory
$ lsof -i | grep ssh
$

ローカルサーバの SSH の LogLevelDEBUG にしていると、タイムアウトとともにマスター接続をしたターミナルには次のようなログが出力される。

debug1: ControlPersist timeout expired
debug1: channel 0: free: /home/jsmith/.ssh/controlmasters/jsmith@REMOTE:22, nchannels 1
Transferred: sent 2664, received 1888 bytes, in 71.8 seconds
Bytes per second: sent 37.1, received 26.3
debug1: Exit status -1
debug1: compress outgoing: raw data 147, compressed 134, factor 0.91
debug1: compress incoming: raw data 101, compressed 86, factor 0.85

多重化の接続数制限

多重化の接続数を制限したい場合、サーバー側(sshd)の MaxSessions で制御する。

In sshd_config(5), the directive MaxSessions specifies the maximum number of open sessions permitted per network connection. This is used when multiplexing ssh sessions over a single TCP connection. Setting MaxSessions to 1 disables multiplexing and setting it to 0 disables login/shell/subsystem sessions completely. The default is 10. The MaxSessions directive can also be set under a Match conditional block.
http://www.openbsd.org/cgi-bin/man.cgi?query=ssh_config&apropos=0&sektion=0&manpath=OpenBSD+Current&arch=i386&format=html

例えば sshd_configMaxSessions 3 の時に 4 接続目を行うと、多重化されず新たに接続が確立される。

$ ssh REMOTE
mux_client_request_session: session request failed: Session open refused by peer
ControlSocket /home/jsmith/.ssh/connections/jsmith@REMOTE:22 already exists, disabling multiplexing
...

リモートサーバには以下のようなログが出力される

Nov 30 18:56:21 REMOTE sshd[14885]: error: no more sessions

~/.ssh/config 設定

これらをまとめて SSH の設定ファイルは次のようにする良い

# $HOME/.ssh/config
Host machine1
  HostName machine1.example.org
  ControlPath ~/.ssh/controlmasters/%r@%h:%p
  ControlMaster auto
  ControlPersist 1h

ControlPath

  • %r はリモートサーバのユーザー名(%u はローカルサーバのユーザー名)
  • %h はリモートサーバのホスト名
  • %p はポート番号

ControlMaster

1回目の接続は ControlMaster=yes, 2回目以降の接続は ControlMaster=no で接続する必要がある。
auto とすると、マスター接続がない場合は yes、ある場合は no で接続してくれる。

yes/no 以外にも X 環境でパスワードを入力する ask/autoask モードもある。

ControlPersist

コネクションが残ってもかなわなければ yes で、定期的に掃除したい場合は 1h のように接続判定用のタイムアウト期間を設定する。

References

Leave a comment