Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Nginx Hacking Guide

Nginx Hacking Guide

nginx の実装の説明(@ KMC 春合宿 2015)

Yusuke Nojima

March 15, 2015
Tweet

More Decks by Yusuke Nojima

Other Decks in Programming

Transcript

  1. 自己紹介 • nojima (野島 裕輔) • 30代(たぶん) 会長 • サイボウズ株式会社のインフラチームに所属

    • ロードバランサとして Apache を使っていた状態から nginx に 完全移行する仕事をした。nginx 改造スキルが身についた。 2015/3/12 Nginx Hacking Guide 2
  2. nginx • 高速で高機能な HTTP サーバ • static なファイルの配信 • リバースプロキシ

    • ロシア製 • 同時接続処理にイベント駆動を利用しており、同時接続 が多い場合でも高いパフォーマンスを発揮する。 • 最近は Apache よりも nginx を採用するケースが増えて きている。 2015/3/12 Nginx Hacking Guide 3
  3. demo • configure • make / make install • 起動

    • server / location • proxy_pass • if • internal redirect • addon 2015/3/12 Nginx Hacking Guide 4
  4. マルチプロセスモデル • コネクション一つにつき一つの プロセスを割り当てるモデル。 • 実装が簡単。 • オーバーヘッドが大きい。 • メモリ

    • コンテキストスイッチ • Apache の Prefork MPM は このモデルを使っている。 Master Process Worker Process Worker Process Worker Process Conn 1 Conn 2 Conn 3 2015/3/12 Nginx Hacking Guide 8
  5. マルチスレッドモデル • コネクション一つにつき一つの スレッドを割り当てるモデル。 • 実装が簡単。 • マルチプロセスモデル程ではな いが、オーバーヘッドが大きい。 •

    メモリ • コンテキストスイッチ • Apache の Worker MPM はこ のモデルを使っている。 Main Thread Worker Thread Worker Thread Worker Thread Conn 1 Conn 2 Conn 3 2015/3/12 Nginx Hacking Guide 9
  6. 比較 モデル サーバ マルチプロセスモデル Apache (perfork) passenger unicorn マルチスレッドモデル Apache

    (worker) イベント駆動モデル nginx node.js ※ 待ち受けだけをイベント駆動で行い、その後の処理を マルチスレッドで行うようなものもある (Apache Event MPM) 2015/3/12 Nginx Hacking Guide 11
  7. Master - Worker Master Process Worker Process Worker Process Worker

    Process Worker Process • マルチプロセスモデルと違って、普通はプロセッサ数以上には Worker Process を立てない。 • 各 Worker Process はシングルスレッド。 • Worker 間で listening socket を共有しており、全ての Worker が accept を呼び出す。 2015/3/12 Nginx Hacking Guide 14
  8. Master Process の役割 • Master Process はリクエスト処理は行わない。 (Single process mode

    の場合を除く) • Master は Worker を産んだり殺したりできる。 • この仕組みを使って Graceful Reload (設定再読み込み) や Graceful Restart が実装されている。 2015/3/12 Nginx Hacking Guide 15
  9. Graceful Reload Master Process Worker Process (old) Worker Process (old)

    Worker Process (new) Worker Process (new) SIGHUP SIGQUIT spawn 2015/3/12 Nginx Hacking Guide 16
  10. Graceful Restart Master Process (old) Worker Process (old) Worker Process

    (old) Worker Process (new) Worker Process (new) SIGUSR2 SIGQUIT Master Process (new) exec SIGQUIT SIGQUIT 2015/3/12 Nginx Hacking Guide 17
  11. read(2) char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf)); •

    read はファイルやソケットからデータを読む関数。 • 以下では fd が TCP ソケットである場合のみを扱う。 • 意外と OS のレイヤーで色々隠蔽されているので、実際 に何が起こるのかが微妙に分かりにくい。 2015/3/12 Nginx Hacking Guide 19
  12. read(2) char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf)); ソケットに割り当てられたバッファ

    buf read カーネル空間 ユーザー空間 2015/3/12 Nginx Hacking Guide 20
  13. 非同期IO • 普通、read は fd からデータを1バイト以上読み取るか、 エラーになるまで返ってこない(ブロックする)。 • fd がノンブロッキングモードに設定されている場合、

    データが直ちに fd から読めないときは、n = -1, errno = EAGAIN ですぐに返ってくる。 • 正確には EAGAIN or EWOULDBLOCK だけど、いちいち断るのが 面倒なので、以降 EAGAIN で統一します。 char buffer[1024]; ssize_t n = read(fd, buffer, sizeof(buf)); 2015/3/12 Nginx Hacking Guide 21
  14. 非同期 IO ssize_t n = read(fd, buf, sizeof(buf)); if (n

    < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { このコネクションの処理は一旦中断して、別のコネクションを処理する。 return; } エラー処理を行う。 return; } 通常系の処理を行う。 2015/3/12 Nginx Hacking Guide 23
  15. write(2) char buf[1024]; size_t len = ...; ssize_t n =

    write(fd, buf, len); ソケットの送信用バッファ buf write カーネル空間 ユーザー空間 2015/3/12 Nginx Hacking Guide 24
  16. write(2) • write はカーネルのバッファに書き込むだけ。 • バッファ内のデータをいつ送信するのかは OS が決める。 • すぐに送りたい場合はプラットフォーム依存な方法で

    フラッシュするしかない。 • 普通はバッファに空きがなければ空きができるまで ブロックする。 • ソケットがノンブロッキングモードな場合、空きが 全くなければ EAGAIN が返ってくる。 2015/3/12 Nginx Hacking Guide 25
  17. ソケットの監視 • ノンブロッキング IO では、ソケットが読み書き可能に なったタイミングを自力で検出して、処理を再開させな いといけない。 • そこで epoll

    などの API を使ってソケットの監視を行う。 1. epoll に使っているソケットを登録しておく。 2. epoll_wait を呼び出すと、登録されたソケットの中で、読み書 き可能なものを教えてくれる。 3. それらのソケットに対して、中断していた処理を再開させる。 2015/3/12 Nginx Hacking Guide 26
  18. イベントループ (epoll の場合) for (;;) { struct epoll_event events[MAX_EVENTS]; //

    入出力可能になった FD の集合を得る。 int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (n == -1) エラー処理。 for (int i = 0; i < n; ++i) { ここで events[i].data.fd を使って処理を進める。 } } ソケットの FD は予め epoll_fd に登録して あるものとする 2015/3/12 Nginx Hacking Guide 27
  19. イベントループ • epoll は Linux 特有。 • *BSD 系だと kqueue

    とかが使えるらしい。 (よく知らない) • select や poll は様々なプラットフォームで使えるが、 遅い。 • したがって、マルチプラットフォームなアプリケーショ ンではこれらを使い分ける必要がある。 2015/3/12 Nginx Hacking Guide 28
  20. event module • nginx はイベント処理を行うための独自のフレームワー クを持っている。 • プラットフォームごとの API の違いを吸収

    • これにより、プラットフォームが提供する API がレベルトリ ガーである場合でも上のレベルではエッジトリガーな API が使 える。 2015/3/12 Nginx Hacking Guide 29
  21. ngx_event_t typedef void (*ngx_event_handler_pt)(ngx_event_t *ev); struct ngx_event_s { // コールバックで使用するデータ

    void *data; ... // イベントが発生したときに呼び出されるコールバック ngx_event_handler_pt handler; }; 2015/3/12 Nginx Hacking Guide 30
  22. ngx_add_event ngx_event_t *rev, *wev; ... // イベントを登録する。 // rev->data, wev->data

    は ngx_connection_t である必要がある。 // 登録したコネクションで読み書きが可能になったらコールバック関数が // 呼ばれる。 ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT); ngx_add_event(wev, NGX_WRITE_EVENT, NGX_CLEAR_EVENT); 2015/3/12 Nginx Hacking Guide 31
  23. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 33
  24. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 34
  25. ngx_connection_t typedef struct ngx_connection_s ngx_connection_t; // socket を wrap した構造体

    struct ngx_connection_s { void *data; // 何か ngx_event_t *read; // read イベントハンドラ ngx_event_t *write; // write イベントハンドラ ngx_socket_t fd; // socket ngx_log_t *log; // エラーログ用 ngx_pool_t *pool; // メモリプール ... }; 2015/3/12 Nginx Hacking Guide 35
  26. ngx_connection_t • ngx_connection_t はクライアントと nginx の間の接続 や、nginx とバックエンドサーバの間の接続を表す。 • socket

    の fd と event handler と memory pool とその他 色々が組み合わさったものだと思えばよい。 • ngx_connection_t のメモリ割り当てを省くために、 未使用の ngx_connection_t はフリーリストにつな がっていて、新しい接続が来たらフリーリストから ngx_connection_t を取ってくる実装になっている。 2015/3/12 Nginx Hacking Guide 36
  27. init_connection (抜粋) void ngx_http_init_connection(ngx_connection_t *c) { hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));

    c->data = hc; // address:port に対応する server の設定を取ってくる port = c->listening->servers; hc->conf_ctx = hc->addr_conf->default_server->ctx; // read イベントハンドラの設定 rev = c->read; rev->handler = ngx_http_wait_request_handler; // write イベントハンドラの設定 c->write->handler = ngx_http_empty_handler; // タイマーイベントの設定 ngx_add_timer(rev, c->listening->post_accept_timeout); // read イベントを登録 ngx_handle_read_event(rev, 0); } 2015/3/12 Nginx Hacking Guide 37
  28. static void ngx_http_wait_request_handler( ngx_event_t *rev) { c = rev->data; //

    タイムアウトしてたらエラー if (rev->timedout) { ngx_http_close_connection(c); return; } // 既に close されていたらエラー if (c->close) { ngx_http_close_connection(c); return; } // バッファが確保されていない場合は確保する b = c->buffer; if (b == NULL) { b = ngx_create_temp_buf(c->pool, size); c->buffer = b; } n = c->recv(c, b->last, size); if (n == NGX_AGAIN) { // タイマがセットされていなければセットする if (!rev->timer_set) ngx_add_timer(rev, c->listening->post_accept_timeout); ngx_handle_read_event(rev, 0); return; } if (n == NGX_ERROR) { ngx_http_close_connection(c); return; } if (n == 0) { // EOF ngx_http_close_connection(c); return; } // 正常に読めたとき b->last += n; // リクエスト構造体の作成 c->data = ngx_http_create_request(c); // ハンドラのセット rev->handler = ngx_http_process_request_line; // 次のハンドラを呼ぶ ngx_http_process_request_line(rev); } 2015/3/12 Nginx Hacking Guide 38
  29. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 41
  30. ngx_http_request_t struct ngx_http_request_s { ngx_connection_t *connection; // コネクション void **ctx;

    // 設定依存のデータ void **main_conf; // 設定 (main) void **srv_conf; // 設定 (server) void **loc_conf; // 設定 (location) ngx_http_event_handler_pt read_event_handler; ngx_http_event_handler_pt write_event_handler; ngx_pool_t *pool; // メモリプール ngx_http_headers_in_t headers_in; // リクエストヘッダ ngx_http_headers_out_t headers_out; // レスポンスヘッダ ngx_http_request_body_t *request_body; // リクエストボディ ... 2015/3/12 Nginx Hacking Guide 42
  31. ngx_http_request_t • ngx_http_request_t は、一対のリクエストとレスポンス を表す。 • HTTP では一回のコネクションで複数個のリクエストを送れるこ とに注意。 •

    リバースプロキシする場合はバックエンドへのリクエス トとかの情報も持つ。 • read/write イベントハンドラを持つ • ngx_event_t よりもちょっと高レベル(HTTP より) 2015/3/12 Nginx Hacking Guide 43
  32. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 44
  33. ヘッダの処理 • おおまかな流れ • ソケットから読めるだけ読んでバッファに貯める。 • ブロックされる直前まで読んだら(あるいは EOF まで読んだ ら)パースする。(実際には一行ずつ)

    • パースできたら次のステップにすすむ。リクエストが読み切れ てなくてパースできなかったら次に読めた時にもう一回パース する。 2015/3/12 Nginx Hacking Guide 45
  34. static void ngx_http_process_request_line(ngx_event_t *rev) { c = rev->data; // ngx_connection_t

    r = c->data; // ngx_http_request_t if (rev->timedout) { ... } rc = NGX_AGAIN; for ( ;; ) { // recv してバッファに突っ込む n = ngx_http_read_request_header(r); if (n == NGX_AGAIN || n == NGX_ERROR) return; // パースしてみる rc = ngx_http_parse_request_line(r, r->header_in); if (rc == NGX_OK) { // NGX_OK: リクエストラインの処理は終わった ... } if (rc != NGX_AGAIN) エラー処理 // NGX_AGAIN: リクエストライン処理はまだ終わってないので // もう一度 ngx_http_read_request_header を呼ぶ if (r->header_in->pos == r->header_in->end) バッファが少ないので増やす } } 2015/3/12 Nginx Hacking Guide 46
  35. static ssize_t ngx_http_read_request_header(ngx_http_request_t *r) { c = r->connection; // ngx_connection_t

    rev = c->read; // ngx_http_request_t // バッファの中に未読がある場合はそれを返す n = r->header_in->last - r->header_in->pos; if (n > 0) return n; n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last); if (n == NGX_AGAIN) { if (!rev->timer_set) タイマをセット ngx_handle_read_event(rev, 0); return NGX_AGAIN; } if (n == 0 || n == NGX_ERROR) { // EOF かエラーの場合は BAD REQUEST にする c->error = 1; ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } r->header_in->last += n; return n; } 2015/3/12 Nginx Hacking Guide 47
  36. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 49
  37. フェイズ処理 • HTTP Header を読んだ後はフェイズ処理に入る。 • フェイズは Rewrite Phase, Access

    Phase, Content Phase など色々ある。 • 各フェイズでは、設定ファイルの内容に応じて色々な ことが起こる。 • Rewrite Phase では URI の書き換え、Access Phase では認証、 Content Phase ではレスポンスの作成など 2015/3/12 Nginx Hacking Guide 50
  38. フェイズ処理 location /hoge/ { if ($http_user_agent ~ ‘curl’) { return

    200 “You are curl¥r¥n”; } allow 127.0.0.1; deny all; proxy_pass http://localhost:3000/; } rewrite phase access phase content phase 2015/3/12 Nginx Hacking Guide 51
  39. フェイズと http modules • src/http/modules/ 以下に http モジュールが大量に定義 されている •

    これらのモジュールはフェイズごとに必要なハンドラを 突っ込むことで実装されている。 • 自分で独自のモジュールを定義することで新しいディレ クティブを追加できる。 2015/3/12 Nginx Hacking Guide 52
  40. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 53
  41. finalize • finalize は魔界 • void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);

    • static void ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc); • void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc); • static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); • エラーページの処理とか…。 2015/3/12 Nginx Hacking Guide 54