Table of Contents
なんかテックブログができたらしいので
今回の記事は、ちょうど季節の変わり目なので、放送の開始されたアニメの1話の感想を1行ずつ延々と書こうかと思っていました。 ただ、初回くらいは真面目に書くかと、ネタを探したら意外といいのがあってさくっと実装もできたので、わざわざ技術ブログっぽいことでも書こうかと思います。基本、最近読んだ記事・リファレンス・本の感想文がメインになることの方が多い予定です。
Dockerで sshd
についてよく言われるアレ
Dockerでは差し迫った要求がない限り sshd
を入れてはいけないとはいいますが、 docker exec
は毎回 -ti
オプションを付けないといけなかったりでなかなかめんどくさいです。
systemd
の運用に慣れている人は、とりあえず systemd
入った仮想マシンとあまり変わらないコンテナ作りがちでそのついでで sshd
が入ってることが多い気がします。
GolangだとSSHサーバーが簡単に作れると知る
読書感想文をLTで発表しに行った、Fukuoka.go #12でたまたまSSHのプロキシーを作る話を聞きました:
言語の標準パッケージというわけでもないようですが、どうやらGolangではcrypto/ssh
という准・標準ライブラリくらいのSSHパッケージがあるらしいです。
パッケージのリファレンスやサンプルをパラっと見た感じ、簡単にSSHのクライアントとサーバーを作れるみたいで、何かできないかと頭の片隅にありました。
dockerと crypto/ssh
をくっつける
基本説明書は後で読むタイプなので、元ネタの伝聞で「簡単そう」という印象だけで実装をはじめました。 目標として:
- Dockerクライアントライブラリで
exec
で接続する - SSHのユーザー名をdockerのコンテナ名に対応させる
- 起動するシェルは
/bin/bash
に決め打ち(Alpine Linuxなどは考えない)
を掲げて実装しました。
成果物はGitHubにあげてあります。
最初はリファレンス読んで、それっぽく実装しようかと思ったんですが、サンプルのコードがかなり使い物になりそうだったのでそれをベースに実装する方針でやったらうまくいきました。
数時間程度で、 ssh
コマンドでコンテナに exec
することができるようになって驚きました。
ちなみに、今回ちゃんと書いた部分はとても少なくて全体で数百行で、更に実際にスクラッチで書いた部分が数十行程度でした。
基本的に、Dockerのクライアントライブラリで取ってきたexec
の入出力をsshの入出力とつなげてるだけです:
exec, err := dockerClient.CreateExec(docker.CreateExecOptions{
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true,
Cmd: []string{"/bin/bash"},
Container: conn.User(),
})
if err != nil {
return err
}
err = dockerClient.StartExec(exec.ID, docker.StartExecOptions {
InputStream: channel,
OutputStream: channel,
ErrorStream: channel.Stderr(),
Tty: true,
// Detach: true,
RawTerminal: true,
})
if err != nil {
return err
}
channel
という変数は、多重化された接続の一つを表すssh.Channel
型の変数です。新しい多重化された接続が増える度に生成されるため、その度にDockerでシェルを起動します。
ssh.Channel
は標準入出力に、そのStderr()
メソッドの戻り値は標準エラー出力に対応するのでStartExec
に渡す構造体の対応するフィールドにそれぞれ入れてつなげてます。
起動例
Go 1.11のModulesに対応しているのでGo 1.11をインストールしてリポジトリルート下で:
go build && ./go-ssh-docker-proxy
コマンドを実行するとデフォルトのTCPポート2022で起動します。
そして、起動して適当なDockerコンテナをrun
してその名前をSSHのユーザー名にしてこんな風に起動できます:
SSHのRFCを読む
一通り実装が終わったあと、SSHでどんな内部コマンドが投げられているのかの興味でRFC4254を読みながら、ssh
コマンドがどんなリクエストを送っているのか実験してました。
サンプルだと最小限しかハンドリングしていないので、機能によっては新しくハンドラを実装する必要があったりで、今後単発のシェルコマンドを投げる時に必要そうです。
今回、X11の仕様がSSHの仕様に書いてあって驚きました。 X11 Forwardingはたまに使うので、こうゆう風に動いていたのかと感心します。 ここら辺の仕様を理解するとmoshでX forwardingを動かすとかもできそうな気がしてきました。
今後
コードをあげたリポジトリのトラッカーにタスクだけ書き出してます。 自動テスト化なんですが、よく考えるとCIサービスでDockerデーモンが立ち上がるのかとか考えるとイマイチで、こうゆうミドルウェアのテストのめんどくささを感じます。
独創性とか考えると、ssh
コマンドでつながるDockerシェルとかやると楽しいのかもしれません。
watanabe
技術開発部門所属 一番好きな言語はC++です。