引用元(勉強の為に引用しました。):
http://matope.hatenablog.com/entry/2014/09/28/031155
1024以下の番号のポートでサーバーをListenするには、rootで実行する必要がある。それはもちろん嫌なので、GoのWebサーバーを80番ポートでサービスするためにどういう方法があるのか調べた。
root権限で起動してListenしてからSetuidで権限降格
最初はroot権限で起動して、80番ポートをListenしてからsetuidで権限降格するやりかた。 Node.jsでもそんな感じだったし、まあそういう感じだろうと当たりをつけて調べたら、どうもLinuxではうまくいかないらしい。
コメント欄でのid:methaneさんの指摘によると、Linuxのsetuidシステムコールは、実行したスレッドにしか効力が無い。Goは自動的に複数スレッドに分散されるので、権限降格されないまま実行されるGoroutineが出てくる事になり、望ましくない。さらにGo 1.4ではLinuxでのSetuid/Setgidが禁止されるらしい。Oh...
という訳で他の方法を探す。
rootで起動した起動スクリプトからポートのfdをもらってExec
id:methaneさんの実装。外部スクリプトで80番をListenして、そのポートのfdをコマンドラインオプションで与えてGoのサーバーをexecする。サーバの方は、ポートで起動する場合は
goprogram -port 8080
、fd受け渡しの場合はgoprogram -fd 4
みたいな感じで起動できるように実装を変更する必要がある。外部スクリプトの方はCircusのようなツールを利用することもできるようだ。
トリッキーな起動オプションの実装が追加されるのは若干気持ち悪い気がするが、それさえ気にしなければ実装コストも少ないしパフォーマンスペナルティも多分ない。ソケット管理ツールを書くなり、既存のツールを学習するコストはそこそこ面倒くさそうで気になる。
iptablesでポートフォワーディングする
この方法であればサーバー側実装には全く手を入れる必要は無い。起動用のツールをrootで立ち上げる必要が無いのでクリーンな感じもする。ひとつのサーバーを動かすためにシステム設定に手を入れるのは気が引けるとか、運用コストが上がりそうとか思うが、Chefでiptablesの書き換えすればOKみたいな環境であれば手軽なのでは。
Deploying Go servers with Docker - The Go Blogとかを読んでも、Dockerを使うならDockerにポートフォワーディングを任せればいいみたいな感じがするので、環境側でなんとかする方向はありっぽい。
ローカルにHTTP層のReverse Proxyを使うという手もある。この場合プロキシを挟むのでパフォーマンスペナルティや、プロキシに使うサーバとの相性などが気になる。
実際、Nginxを挟む事によるパフォーマンスペナルティはちょっと無視できない。
あるいは今気づいたけど、もともとReverse Proxy用のサーバを別に立てるつもりなら、そもそも気にしなくてよい問題ではある。
GoバイナリにウェルノウンポートをListenするケーパビリティを設定する
ケーパビリティとは、ファイルまたはスレッドに対して、root権限の一部を付与したり剥奪したりできるもの。実行ファイルに対して
setcap
コマンドで任意の権限を与えられる。GoWebの人たちはこの方法を使っているらしい。この場合は以下のようになる。
簡単にcapabilityを試してみた。通常のlsでは/root配下が覗けないが、読み込み時パーミッションチェックをスキップするCAP_DAC_READ_SEARCHケーパビリティをlsのコピーに付与したところ、sudoもなしに一般ユーザーで/root配下が参照できる。
ただ、このcapability、手元で検証した限りではcpなどで複製すると権限は失われてしまうようなので、デプロイ先の環境で
sudo setcap
してやる必要があるようだ。まとめ
今回候補に挙げた手法は以下の通り。
- Listen後にsetuidで権限降格する
- Linuxでは問題がある
- 起動スクリプト(カスタムメイド or Circus)からfdをもらう
- ソケット管理ソフトウェアを用意する必要がある
- サーバー側にもfdによるListenに対応するコードを追加する必要がある
- iptablesなど、OSやコンテナ側でポートフォワーディングする
- システム側に手を加える必要がある
- ローカルでProxyを立てる
- パフォーマンスペナルティがある
- ケーパビリティを設定する
- デプロイ先ホストにバイナリを配置後、
sudo setcap
する必要がある。
- デプロイ先ホストにバイナリを配置後、
この中では、ポートフォワーディングかケーパビリティが筋が良さそうな気がしている。せっかくシングルバイナリデプロイができるGoなのだし、できればバイナリで完結する手法でなんとかしたいが、そういう方法は今は無いようだ。
0 コメント:
コメントを投稿