oauth2_proxyとNginxのauth_requestを組み合わせると便利

oauth2_proxy、便利でググると導入手順も結構出てくるのだけど、今回紹介するのは(環境によっては)さらに便利に使えるナウい方法です。

なにをするのかというとREAMDEに書いてあります。

Configuring for use with the Nginx auth_request directive The Nginx auth_request directive allows Nginx to authenticate requests via the oauth2_proxy's /auth endpoint, which only returns a 202 Accepted response or a 401 Unauthorized response without proxying the request through. For example:

サンプル設定もあるので、なんとなく雰囲気は伝わるんだけど、はっきり言ってこれだけ見てすんなり出来る人はなかなかいないだろう(自分がそうだった)し、何が便利なのかもイマイチわからんので、もうちょい丁寧に説明してみようと思います。


まず以前からQuipperでもやっていた一般的な構成はこうです。

  1. nginxで特定locationやserver全体でproxy_passにoauth2_proxyサーバを指定する
  2. oauth2_proxyがGoogle Appsの認証ページにリダイレクトする
  3. 認証成功したら、oauth2_proxyのupstreamで設定しているbackendのアプリケーションにproxyされる

しかし、これだと下記のような面倒が発生することがあります。

  • 認証を導入したいbackendのアプリケーションが複数ある場合、その分複数のoauth2_proxy設定を用意し、複数のプロセスを起動しなくてはいけない
    • nginx側のproxy_passも変えないといけない
  • nginx側の設定だけを見るとどこに最終的にproxyされるのかわからん
  • 特定pathのみ認証をいれたいような場合に、設定が煩雑になる

それがこうなります。

  1. nginxのauth_requestディレクティブ使って、oauth2_proxyに対して認証(ログイン)済みかどうか確認する
  2. 未認証の場合はoauth2_proxyに自動的に認証を依頼する
  3. 認証がすんだらnginxのproxy_passに設定しているbackendアプリケーションにproxyされる

ポイントは「oauth2_proxyには認証だけしてもらってbackendへのproxyには一切ノータッチ」になるってとこです。これによって上述した問題は全て解決されます。oauth2_proxy沢山起動する必要もないし、nginxの設定も見通しが良くなります。

また、auth_requestはsatisfyに対応しているので、allowディレクティブと組み合わせることで、「特定のクライアントのみ認証不要にする」ということも簡単にできるようになります。

設定例

前置きが長くなった。ここからそれをやるための設定例です。尚、設定を作るにあたって、同じような仕組みで動作するクックパットさんのnginx_omniauth_adapterの紹介エントリが大変参考になりました。ありがとうございます。

oauth2_proxy側の設定についてはauth_requstを使わない場合とほぼ変わらないので省きますが、2点だけ注意点があります。

  • まだauthエンドポイントがリリースされてない(releasesにあるバイナリでは未サポート)ので、masterからbuildすること
  • oauth2_proxyでは一切proxyしないけど、configのupstreamsにはなんでもいいので値をsetしとくこと。しないと起動しない

です。

common/oauth2_proxy.conf

oauth2_proxyの各種エンドポイントへproxyするための設定。

# for checking login status.
# only returns a 202 Accepted response or a 401 Unauthorized response;
location = /oauth2/auth {
  internal;
  proxy_pass http://127.0.0.1:4180;
  proxy_set_header Host $host;
  proxy_pass_request_body off;
  proxy_set_header Content-Length "";
}

# to start oauth cycle
location = /oauth2/start {
  internal;
  proxy_pass http://127.0.0.1:4180;
  proxy_set_header Host $host;
  proxy_pass_request_body off;
  proxy_set_header Content-Length "";
}

# to complete oauth cycle
location = /oauth2/callback {
  auth_request off;
  proxy_pass http://127.0.0.1:4180;
  proxy_set_header Host $host;
}

common/oauth2_enable.conf

locationコンテクストでincludeして、認証を有効化する設定。

satisfy any;

# 認証済みかどうか確認
auth_request /oauth2/auth;

# ^で401がかえってきたら認証を開始
# rdパラメータにpathをセットしないと、最終的に/にredirectされちゃうよ
error_page 401 = /oauth2/start?rd=$uri;

# オフィスなど認証が不要なクライアントIPをallow
allow 119.172.242.131;
deny all;

virtual host

上記の2つの設定をincludeして一部のpathのみ認証を有効にする設定例。

server {
  listen 80;
  server_name my.domain.com;

  include conf.d/common/oauth2_proxy.conf;

  location /secret/path {
    include conf.d/common/oauth2_enable.conf;
    # 認証こけたらproxyされないよ
    proxy_pass http://backend;
  }

  location / {
    proxy_pass http:/backend;
  }
}

おわり。

Puppet人がAnsible界にきてみて

この記事はPuppet Advent Calendar 2015の14日目の記事です。 昨日はおっくんのPuppetの予約語 $name と $titleでした。


書いている人

  • Puppetはペパボで6年間、複数のサービスインフラ用に書いてきた。社内で一番書いたかも
    • 新規やら導入やら引き継ぎやらいろいろ
    • オンプレもクラウドもある
  • AnsibleはQuipperで2ヶ月ちょい。既ににあるのに書き足したりリファクタリングしたり
    • 当然だけど2ヶ月ずっとそれだけやってたわけではないです
    • EC2オンリー

経験値が全然違うけど、できるだけ公平に評価してみるつもり。 尚、Puppet、Ansible以外は語れるほど使ったこと無いので言及しません。


結論

  • クラウド(IaaS)にConfiguration Management Toolを導入したいならAnsibleがいいと思う
    • ただし、クラウドクラウドらしくつかっていること
    • Puppet等他のツールに慣れてたり既存の資産があるなら、そっちでよい
  • オンプレ、特に歴史ある複雑なシステムに対して使うならPuppet
    • どんだけ慣れてたとしてもAnsibleを選択するのはドMだと思う
    • Ansible超人なら大丈夫かも

普通の結論だと思う。

初期導入コストについて

これは、さんざん言われているけど、やはりエージェントレスなAnsibleの方が始めやすいと思います。Ansibleの方は最初から導入したことないけど、途中から触るようになった自分でもそう思います。

puppetは慣れててもマスターとエージェントのバージョン不整合とか認証まわりでハマることがしばしばあるので、その辺考えなくていいのはとても楽です。(あと巷で得れる情報量が…)

ただし、Ansibleの方もある程度初期の頃から、ベストプラクティスに従って設計しないと後々辛いことになるので、その辺のこととかトレンドを学習した上で始めた方がいいと思います。

学習コストについて

DSL(Ansibleのあれを単なるYAMLだと思ってはいけない)の学習コストは似たようなもんだと思います。

Ansibleの方がYAMLで簡潔に定義できていいよねぇ〜という意見を見ることが多いような気がしますが、自分はどっちかといえばAnsibleの方が癖があって馴染みにくいように感じています。

Puppetの方はまぁゆうても普通のLLぽいです。なので、多分こんか感じかなぁで書いても大体うまく行く感じがします。対してAnsibleの方は「え!マジで!それそう書くの?」みたいなことが多いです(短くは書けるので普通に書いててもGolfぽい)。

とはいえ、どちらも基本的な文法についてはすぐに慣れます。

表現力について

ここが全然違う。これについてはPuppetの圧勝だと思います。

上述したように基本的な文法で単純なリソース定義をする分には大差はないように思いますが、複雑な環境や構成を表現するにはAnsibleではきついと思います。

複雑というのは例えばこういうの

  • 複数のロケーションや、ハードウェアに対応したり最適化する必要がある
  • 同一roleで複数のOSやミドルウェアのバージョンが混在している
  • 同じホストに複数のroleが乗っている

オンプレだと、こういったことは望む望まざるとにかかわらず発生します。

Ansibleにもroleの概念などは有りますが、変数のscopeがざっくりしてたり、条件分岐や依存関係の表現が分かりにくかったりして、上記のような複雑なものを綺麗にレイヤーをわけて適切に抽象化し、メンテナンス性の高い状態を維持するのは困難であろうと感じています(Puppetでも大変ではあるけど、うまく設計すれば可能)。

逆にいうと扱う対象がシンプルであればAnsibleで十分というか、コンパクトに記述できる分、全体の把握もしやすいので、Puppetより適していると思っています。

クラウドクラウドらしくということ

上記のような複雑な状態をつくらないということです。

H/Wレイヤーの複雑性はそもそも発生しないので、いいとして、オンプレの時の感覚で単一ホストにどっちゃりいろいろつめ込んだりしないで、1ホストにつき1つのことだけをやるようにするのが肝要かと思います。

OSについては、クラウドでもいつかは切り替える日が来ますが、マシン調達のためのリードタイムが無いに等しいので、オンプレのように長い期間をかけてOSをリプレースせざるを得ないといった理由で複数のOSが混在するという状況は避けやすいと思います。

構成管理ツールの方では複数のOSに対応させるような書き方は最初から諦めて、切り替えのタイミングでそれように全部新たに書いてしまったほうが安上がりです。古い環境はすっぱり捨ててしまえばよい。


以上、文字ばかりのこのエントリをここまで読んでいただきありがとうございました。

お前のAnsible評価は間違っておるぞ。という方、突っ込お待ちしています。

明日はあの人です。

第3回ペパボテックカンファレンスでPrivate S3の作り方を発表してきた

第3回ペパボテックカンファレンス - connpass で発表してきました。

スライドはこちら。

www.slideshare.net

前後編に分かれていて、後半は @hiboma が発表しました。続けて見た方が良いです。

MogileFS をバックエンドとしたPrivate S3の作り方 【後半】API 編


残念ながらYAPC::Asiaの方では採択されなかったのですが、話すことは大分前からあれこれ考えていたので、アウトプットできてほっとしています。

内容は、細かいノウハウや工夫はあるものの、基本的には枯れたソフトウェアやセオリーの積み上げです。なので「ふーん割と普通だなぁ」と思うかもしれません。

でもその普通ぽいことをしっかりと積み上げることで、「国内最大級の写真共有サービス」のインフラ上に更に「国内最大級のショッピングカートASP」の画像をまるごと移行出来てしまう。という事例を示せたのは、結構意味のあることでは無いかと思ってます。

  • 「事実を積み上げて判断する」
  • 「古典的でも地道にやる」

ってことを、以前@myfinderさんが言ってて、これすごく好きなんですよね。今後もこの気持ちでよい物をつくっていきたいです。

もうすぐ1ペタバイトに届くMogileFSクラスタを運用してみたい方はこちらまで。

js01.jposting.net

ngx_mrubyを使って特定ホスト以外からのアクセスをメンテナンス画面にする

こんな感じ。(ほんとはもうちょいifが多い)

error_page 503 /maintenance.html;
location = /maintenance.html { internal; }

mruby_access_handler_code '
  c = Nginx::Connection.new
  r = Nginx::Request.new

  Nginx.return -> do
    if File.exists?("/var/tmp/maint_ignore_office") && c.remote_ip == "192.168.1.3"
      return Nginx::DECLINED
    end

    if !File.exists?("/var/tmp/maint")
      return Nginx::DECLINED
    end

    # 503
    return Nginx::HTTP_SERVICE_UNAVAILABLE
  end.call
';

これで、

  • /var/tmp/maintが存在したらメンテナンス画面になって
  • /var/tmp/maint_ignore_officeが存在して、且つアクセス元が192.168.1.3であれば、そこは通常通りレスポンス

になる。

元々はひろせさんのこの方式で変数をsetしてコントロールしてたんだけど nginxで特定ホスト以外からのアクセスをメンテナンス画面にする方法 (2) - (ひ)メモ

その後ふじわらさんのフラグファイルみて切り替えるのがよいなぁと思って nginxで特定ホスト以外からのアクセスをメンテナンス画面にする方法 - 酒日記 はてな支店

でも特定ホスト(今回だとオフィス)からのみ開放するのもフラグファイルの有る無しでコントロールするとなると、またNginxの「複数条件できん」がネックで、うーんってなって、

あーそうだ!このNginxはngx_mrubyつかえるようになってるからそれでやろーっと。てなって上記のコードのようになった次第。

ちなみに、最初はこんな感じでハンドラのコードを書いたんだけど、これはうまくくいかなくて

if File.exists?("/var/tmp/maint_ignore_office") && c.remote_ip == "192.168.1.3"
  Nginx.return Nginx::DECLINED
end

if !File.exists?("/var/tmp/maint")
  Nginx.return Nginx::DECLINED
end

Nginx.return Nginx::HTTP_SERVICE_UNAVAILABLE

弊社は、同僚に @ がいるという便利な環境なので、聞いたら

Nginx.returnはコードからreturnするわけではないので 全部実行されちゃいます。

と教えてもらって、なるほどん!ってなって、次にこんな感じにしたら

if File.exists?("/var/tmp/maint_ignore_office") && c.remote_ip == "192.168.1.3"
  return Nginx.return Nginx::DECLINED
end

if !File.exists?("/var/tmp/maint")
  return Nginx.return Nginx::DECLINED
end

return Nginx.return Nginx::HTTP_SERVICE_UNAVAILABLE

こんどは、LocalJumpError が出てしまって、あーなるほどmrubyのハンドラって関数なわけじゃないので途中で抜けれないのねぇってなって、lambdaにしてreturnする冒頭のコードに落ち着いたのでした。

褒めてもらった。

このラムダな書き方は、mruby_setディレクティブとかでも同様につかえて、なかなかシャレオツ便利です。

尚、今回は大したロジックでもないので、直接nginxの設定にコードを埋め込んでいるけど、複雑なのは、外部ファイルに切り出して、テストも書いた方がいいです。

テストのあたりは@さんのこれがわかりやすい。

How to test code with mruby

構成管理(puppet)リポジトリrake mtestすると全ファイルのテストが実行されるようにしてます。

ngx_mruby大変便利なので、今後もどんどん使っていく所存です。

はてなブログに引っ越した

あまり更新してないのだけど、昨日中の人と話しててダイアリーで続ける理由も無いなぁと思って引っ越した。

3ポチくらいでブクマも全部引っ越し終わって楽ちんだった。

あとやっぱりずっと使いやすそうだ(それでも多分更新しないのだけど)。

実録、ほぼ無停止なMHAによるマスターフェイルオーバー(動画もあるよ

タイトルはこちらのインスパイアです。

 

これはPepabo Advent Calendar 2014 - Qiita18日目のエントリーです。 昨日は@社内での活動履歴でした。なかじーーー。

 

1年半ぶりのblog更新ですが、そこそこ元気にやってます。もじゃもじゃは辞めました。 で、MHAですが、MHAってあれです。これです。

MHA for MySQL の概要 - Gosuke Miyashita

すごいです。すごいので今更ですが導入しようとおもいました。

ありがたいことにMHAはドキュメントが非常に充実しているので、大抵のことはそれを読めば把握できます。

mysql-master-ha/Wiki

 が、賢明なインフラエンジニアのみなさんは次のように思うはずです。

  • 実際に構築して動かして検証しないことには!
  • 実運用を想定した意地悪を色々やってみたい!
  • 色々やって環境をぶっ壊して、再現してまたぶっ壊したい!
  • ip_failoverスクリプトを実装して、動作検証したい!

だがしかし、MHAは最低でも3台のホストが必要なのです。カジュアルに3台もオンプレサーバ調達できないし、同じような設定、構築を複数台で何度も行うのは辛いし、そろそろ四十路だ。

 というわけで、つくったのがこちらでです。*1

lamanotrama/vagrant-mha · GitHub

 

デモ

https://cloud.githubusercontent.com/assets/329120/4963945/e8a0eab2-6736-11e4-8823-1616f8033ff7.gif

 

細かいことは置いといて、このデモ動画でやってることはざっくりこんな感じです。

  1. muxを使ってtmuxのpaneを複数開き、vagrant(virtaulbox)のvm4台にログイ
  2. 左上: managerノード上でvipに対して接続テストするスクリプトを実行
  3. 左下: 同じくmanagerノード上でシェル操作
  4. 右側: mysqlノード3台でそれぞれmysqlのログをtail
  5. 一旦mhaマネージャデーモンを停止 -- 起動しているとオンラインmaster切り替えが実行できない + オンラインマスター切り替えを2回実行
  6. マネージャデーモンを起動 + 右上の現master(node001)のmysqldを停止
  7. マネージャによる自動マスター切り替え

 

左下のシェル操作以外はREADMEにある、setup手順とdemoの開始コマンドだけで勝手に始まります(多分)。 便利そうじゃないですか?

 

くわしく

というか想定問答集的な。

 

provisioning 

vmのprovisioningにはpuppetを使っています。 MHA周りの定義はpuppetモジュールとして切り出してあるので、librarian-puppetを使って本番でもさくさくーと同様の環境を構築できるという寸法です。もうちょいブラッシュアップしたら、PuppetForgeにもupします。

lamanotrama/puppet-mha · GitHub

 

mysqlのバージョン(パッケージ名)はこの辺で定義しています。 https://github.com/lamanotrama/vagrant-mha/blob/master/roles/mha_node/manifests/init.pp#L15

 ここを変更すれば別のバージョンで試したり、node毎に別々のバージョンにしたりってのも出来ます。vmにはmysql本家やpercona、epelといったyumリポジトリは登録済です。 OSは全部CentOS6にしていますが、それはVagrantfileで変更してください。多分el5、6とそのクローンOSでなら動くんじゃないかなと思います(試してない)。

 

mysql_online_switch ってなんぞ

 オンラインでのmaster切り替えには /usr/bin/masterha_master_switch を使うことになっているんですが、オプションの指定が多くて面倒なので、よく指定する(であろう)オプションを面倒みてくれる簡単なラッパースクリプトを作りました。デモで実行しているのはそれです。

puppet-mha/mysql_online_switch at master · lamanotrama/puppet-mha · GitHub

 

接続でなくて更新のテストもしたい

 そんなこともあろうかと、単純な更新テストできるスクリプトも置いておきました。

vagrant-mha/roles/mha_manager/templates/usr/local/bin at master · lamanotrama/vagrant-mha · GitHub

 

ip failover どうなってんの

MHAのリポジトリにサンプル実装があるので、それにちょちょいと手を加えて、単純なエイリアスIP付け替えを行うようにしました。 この辺に置いてます。 vagrant-mha/roles/mha_manager/files/usr/local/libexec at master · lamanotrama/vagrant-mha · GitHub

尚、初回vagrant up直後はnode001にvipを付けていますが、vmを停止して再度起動してもアタッチされないので、都度vagrant provisonを実行してvipをくっつけてください。手打ちでifconfigでもいいです。

 

環境のリセット 

いろいろ試してレプリケーションをぶっ壊わした後に、全nodeでmysql_install_db実行したりするのは面倒臭かろう。ということでdbをresetするスクリプトを入れておきました。

vagrant-mha/reset_dbs.sh at master · lamanotrama/vagrant-mha · GitHub

ホストマシン(Mac)上で実行すると、各vmvagrant sshしてあれこれやってくれます。その後レプリケーションとかMHA用にGRANT入れるのは、vagrant provison実行でよろ。

 

まとめ 

  • コマンド数発でMHAを試せる環境をセットアップできるものを作った
  • 検証するときに便利だと思うヘルパースクリプトもつくった
  • 一応動くip failoverスクリプトのサンプル実装もおいといた

 

どうぞご利用ください。

 

明日は@です。

*1:便利そうなプロジェクトを見つけたので試したんだけど、まず動かないし、色々微妙なとこがあったのでforkして変えていったら原型が全くなくなった。

WEB+DB PRESS Vol.75 に寄稿しました

自分を含む5人のペパボメンバー(@, @, @, @)で第一特集の「複雑性の増大と環境の変化に対応する継続的Webサービス改善ガイド」ってのをやらせて頂きました。その内自分が書いたのは4章の「インフラ構成管理の改善」の半分くらいです。

WEB+DB PRESS Vol.75

WEB+DB PRESS Vol.75

3月のある日のぽよん会(ってのが会社であるんです)で柴田さん、あんちぽさんに「お前も当然かくよな!」って言われた時は、正直「小学校以来、作文宿題の全てを友達に書いてもらってた俺に8ページも書けだとぉぉ!」とナーバスになったんですが、隣の席のつね様こと常松さんに「いっしょに書かない?」って誘ったら「もちろんさ!」と快諾してもらい、なんとか協力して書き上げることができました。やはり持つべきものはイケメンの同僚です。

内容としては、歴史あるサービスってインフラでも技術的負債が結構たまってくるよね。ヘテムルではこんな感じで運用を改善して、負債を返済していったよ。って感じです。

改善と題してはいますが、例えばこれからサービスを立ちあげていくってフェーズでも、後々負債を産まない為に気をつけることってなんだろ?って視点で読んでもらえたら、それはそれで幾つかヒントになる事が書いてあるんじゃないかなぁと思います。

んで、そのヘテムルでは現在、自分や、イケメン、あとターミナルマニアの@とかとかと、一緒にインフラ周りのお仕事をしてくれる仲間を募集中です。https://js01.jposting.net/paperboy/u/recruit/job.phtml?job_code=66
ちょっとだけ興味あるんだけどって人はtwitterのDMとかでも全然OKです。お待ちしてまーす。