DockerとSSHを用いた、柔軟性と管理性を両立する開発環境
そして、現在の開発環境は図3のようになりました。
ありきたりの構成になってしまったとは思いますが、やはりSSHというソリューションが最も汎用的で、かつ自由度が高いことを実感しました。ただし、SSHをインターネット上に開放することはセキュリティ的にあまり好ましくはありません。それらの問題の改善も後述します。
Portainerの環境構築
今やDockerは開発者にとって必要不可欠になりつつあり、サーバソリューションを含む開発であれば必須のツールです。
しかし、Dockerの基本的な操作方法がわかることと、実際に環境を構築することには差があります。誰もが同じDocker環境を作れるように定義するのは難しく、またその事に注力することは開発をする上で本質的な作業とも言えません。
そこで、Docker環境を社内で使い回せるように複数の同じDockerコンテナを作成します。そして、各開発者はWebインターフェースを通じて各Dockerコンテナにアクセスできるようにすれば、開発業務として十分なはずです。そのような要件を実現するのがPortainerというツールです。
Portainer自体がDocker上で動作するので、Dockerがすでにインストールされていればリスト1のようにインストールは簡単です。
#docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer-ce
そして、以下のようなURLですぐに利用できます。
http://<host>:9000/
各コンテナは図4のように表示されます。ローカル上のコンテナであれば特別な設定はいりません。
コンテナの停止や起動などもできますが、各コンテナのコンソールにも図5のように簡単にアクセスできます。
Dockerコマンドを使うのは面倒だなと思う方も多く、筆者もコーディング中であればDockerを意識した作業は面倒にすら感じます。そのときに、Docker DesktopのようにUIを用いて簡単に操作したいという方も多いと思います。Portainerを使えば、そのような非常に使いやすい環境がWebシステムとして構築できます。
また、今回は詳しくは説明しませんが、Portainerはローカル上のDockerだけではなくリモートにあるDockerコンテナも管理できます。従って、Dockerコンテナの数が必要になっても拡張しやすいはずです。
SSHでのセキュリティ対策
SSHポートをインターネットに公開することはあまり好ましくありません。開発者がリモート作業用に固定のグローバルアドレスを持っていればアクセス制限は簡単ですが、自宅からアクセスする場合には事前にどのIPからアクセスしてくるかは大概わかりません。
そこで、図6のようなシステムで、許可された人だけがSSH接続できるようなシステムにしています。実際に利用しているシステムはもう少し複雑ですが、今回は概要と主となる部分のみについて説明します。
(1)Web画面を通じて、メールアドレスを入力し利用申請をします。この際、あらかじめ許可されたメールアドレスやドメインのみしか許さないようなシステムを作っておけば、誰でも利用できるようなことにはなりません。
(2)Webシステムを通じて取得できるグローバルアドレスを取得します。
(3)利用されていないDockerコンテナとホストで共有しているディレクトリを開発者ごとの$HOME(※)にシンボリックリンクなどを使って切り替えます。
ここでのディレクトリ構造は実際に作成するシステムによって異なると思いますので詳細は割愛しますが、Dockerコンテナ側から見えるディレクトリをプロジェクトごとに決まった構成でユーザごとに割り当てたディレクトリにします。Dockerのコンテナと利用者を固定にするのであれば、このような仕組みは必要ありません。
(4)(2)で取得したグローバルアドレスからのみ接続できるSSHポートをポートフォーワードにより新たに作成します。
(5)取得したポート番号を申請者にメールで伝えます。
(6)開発者はそのメールに記載されているポート番号を使ってSSHでアクセスします。
※ここでは厳密な$HOMEではなく、開発者ごとに利用するディレクトリ程度の意味です。
このような流れで開発が開始できます。
リスト3は、(4)で利用する指定されたアドレスに一定時間、SSH接続を許可するシェルのサンプルです。
#!/bin/sh # $1 : HOST min=${2:-2} sec=`expr $min '*' 60 - 30`; # (1) 動的に空いているポート番号を取得する port=`php -r '$s = socket_create_listen(0); socket_getsockname($s,$addr,$port); print($port);'`; # (2) redirを使って22番ポートへのポートフォーワード指定を行う redir -n -t $sec :$port 127.0.0.1:22 & # (3) プロセスIDの取得 PID=$! # (4) ファイアーウォール(ufw)でアクセスを許可する ufw allow from $1 to 0.0.0.0/0 port $port # (5) 時間が来たら切断する echo "/usr/local/bin/close.sh $1 $port $PID" | at now + $min min echo $port
(1)では、PHPを使って現在利用可能なポート番号を取得しています。PHP以外でも空いているポート番号を調べる方法は他にもありますので、PHPがインストールされていない場合にはこの部分を書き換えてください。
また、(2)ではredirというポートフォーワードするコマンドを使っています。このコマンドは特定のポートで受信したパケットを他のポートへ簡単に転送することができます。「-t」オプションで指定しているのは最大接続秒数です。これでつないだままになるという状態をなくしています。
そして、今回の例ではローカルホストに転送していますが、他のサーバに転送することもできます。従って、クライアントからSSHで接続するサーバとして踏み台サーバを用意するような場合でも対応可能です。
(4)はufwを使ってファイアーウォールで指定されたポート番号への接続を許可するようにしています。0.0.0.0/0を指定しているのはIPv4のみを対象とするためです。ufwはデフォルトでIPv4とv6の設定の2つの指定が行われるので、意図的にv4のみにして、削除する指定も1つにして処理を単純にしています。
そして、最後に利用期限の時間が来たら(5)のようにatコマンドを使って切断する処理を自動で実行するようにしています。
このシェルプログラムはリスト4のようにして利用する想定です。ただし、前提としてはUbuntu上で動作することを想定しています。
./open.sh <グローバルアドレス> 60
このような処理を利用申請ごとに実行するようにしています。ただし、実行にはRoot権限が必要なため、Webサーバ上で実行することは避けたいものです。そのため、例えば申請データをDBに保存しておき、一定間隔のcronなどで実行するような方法もあります。
また、先ほどの(5)で実行する切断処理スクリプトはリスト5のようになります。
#!/bin/sh # (1) 許可した指定の行を取得する LINE=`ufw status numbered | grep ALLOW | grep $1 | grep $2` if [ "x$LINE" = "x" ]; then echo "not found" else # (2) N=`echo $LINE | cut -f2 -d[ | cut -f1 -d]` if [ "x$N" != "x" ]; then # (3) ファイアーウォールの指定削除 ufw --force delete $N # (4) redirのプロセス停止 kill $3 fi fi
(1)は接続時に登録したファイアーウォールでの対象の行を取得しています。(2)ではその指定の行番号を取得します。そして(3)で対象のファイアーウォールでの指定を削除したら、(4)で起動時に立ち上げたredirのプロセスを停止しています。
このように少々面倒な方法を採るのはSSHへの接続制限という面もありますが、セキュリティ面にも理由があります。リモート開発環境では実際に誰がどの程度使っているのかがわからなくなりやすいということがよくあります。そのため、プロジェクトを抜けたエンジニアや退職した社員などが接続できる状態のままになってしまうという状態になりやすい傾向があります。そこで、SSHの接続履歴をシステムのログから見るだけではなく、明確な申請ベースにすることで、リモート環境を誰がいつから作業をしたのか履歴が作れるようにしています。
また、このような管理はSSHだけではなく、他のサービスであっても同様の手法で許可された時間に許可された人だけという仕組みが作りやすいはずです。
特に開発時には、厳しい制限をしてしまうと実用性が下がってしまいます。そのため、できることの制限は緩くし、一方で時間と利用者の制限を厳格にすることでこの両立を図っていくようにしています。
さいごに
現在の開発環境には管理と柔軟性が求められています。これらの点に関して、オフィスのような環境ではある程度実現ができつつあります。しかしまだ十分とは言えません。
そんな状況のなかリモート開発での環境作りとなると、まだまだ手探りという現場も多いのではないでしょうか。そのとき、既存のサービスやシステムを組み合わせつつ、足りない部分をほんの少しだけ開発者が補えば、それぞれのプロジェクトによって良い環境ができるのではないかと思っています。
そこで今回は、オフィスで使っている、もしくは使える開発環境をできるだけそのままリモート開発環境にも適用できるように考え、構築した事例を紹介してみました。
次回は、VPN環境があることを前提にこのリモート開発環境を補足していった事例を紹介します。