地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Nginx】小ネタ、denyを大量に書く際はCIDR 形式でまとめた方がパフォーマンス(レイテンシ、メモリ効率)が上がる

mogile.web.fc2.com

例えば

deny 192.168.1.0;
deny 192.168.1.1;
(snip)
deny 192.168.1.255;
deny 192.168.1.256;

deny 192.168.1.0/24;

だと後者の方がレスポンスタイムが短くなるようです。(メモリ使用量も抑えられる)

実験してみる

二つのconfigでそれぞれ計測してみます

# configにdenyの192.168.1.1/20を設定
$ for i in $(seq 100); do curl localhost -s -o /dev/null -w  "%{time_starttransfer}\n"; done | awk '{sum+=$1} END {print sum}'
0.34399

# configにdenyの192.168.1.1 ~ 192.168.15.255を設定
for i in $(seq 100); do curl localhost -s -o /dev/null -w  "%{time_starttransfer}\n"; done | awk '{sum+=$1} END {print sum}'
39.6531

すごい差が出ました!!こんな例はあんまないと思いますがわかりやすくするためにやってます。。。w

なぜ

実際にdenyをした際に動くソースは以下。リクエストごとにアクセス元IPとdenyで設定し内容が一致するかを見るわけだが探索自体は線形探索を用いて実装されておりconfigで設定したdenyの行数分ループしていく実装となっている。denyが10000行あったら毎回この探索が実施されるという。CIDR形式の指定だとアクセス元IPとconfigに記載したCIDRのビット演算とルールのIPの一回の比較で済むので早いという仕組み。

static ngx_int_t
ngx_http_access_inet(ngx_http_request_t *r, ngx_http_access_loc_conf_t *alcf, in_addr_t addr)
{
    ngx_uint_t               i;
    ngx_http_access_rule_t  *rule;

    rule = alcf->rules->elts;

    // allow, denyのルールの行数ごとに探索を実施する(netltsは配列内に格納された要素の数)
    for (i = 0; i < alcf->rules->nelts; i++) { 
        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "access: %08XD %08XD %08XD",
                       addr, rule[i].mask, rule[i].addr);

        if ((addr & rule[i].mask) == rule[i].addr) { // 「アクセス元IPとconfigに設定されたCIDRとのビットAND」とconfigに設定されたIPの比較
            return ngx_http_access_found(r, rule[i].deny); // 一致していたらdenyフラグを立てて検出関数を呼び出す
        }
    }

    return NGX_DECLINED; // 一致するものがなければ処理継続を呼び出し元に戻す
}

// アクセスルールの構造体
typedef struct {
    in_addr_t         mask; // configのCIDR
    in_addr_t         addr; // configのアドレス
    ngx_uint_t        deny;      /* unsigned  deny:1; */
} ngx_http_access_rule_t;

// ルール格納用の配列
typedef struct {
    ngx_array_t      *rules;     /* array of ngx_http_access_rule_t */
    ngx_array_t      *rules6;    /* array of ngx_http_access_rule6_t */
    ngx_array_t      *rules_un;  /* array of ngx_http_access_rule_un_t */
} ngx_http_access_loc_conf_t;

実際にNGX_HTTP_FORBIDDENを返すのはこっちの関数

static ngx_int_t
ngx_http_access_found(ngx_http_request_t *r, ngx_uint_t deny)
{
    ngx_http_core_loc_conf_t  *clcf;

    // 今回のケースだとdenyフラグは有効化されて呼び出される
    if (deny) {
        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

        if (clcf->satisfy == NGX_HTTP_SATISFY_ALL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "access forbidden by rule");
        }

        // クライアントへは403が返る
        return NGX_HTTP_FORBIDDEN;
    }

    return NGX_OK;
}

まとめ

CIDR 形式の方が計算量も少ないので良さそうというのとあとはメモリ効率も違うのでまとめれるならCIDR形式でガットかけると良さそうでした。ちなみに線形探索なのでよくアクセスしてくるIPを上に書くことでループ回数を抑えるテクニックがあることにも気づきました(そこまでやる必要がある機会はよくわからないけど...)

eBPFへ入門していく その1

背景

eBPFに興味はあったもののそれとなくしか理解してなかったのでせっかくなので概要だけでも掴もうと思って書く記事。とりあえず概要とかとよく読むカーネル関数とかをattachしてみて引数を見るみたいなのまでやってみました。

(私の事前の知識レベルとしては「eBPFを使えば、システムコール、パケットの受信など、カーネルで発生する様々なイベントに対して動かしたいコードを動かして遊べる機能だ!」くらいの感じで始めました)

eBPFとは

eBPF(extended Berkeley Packet Filter)とは任意の様々なタイミングでバイトコードを実行できるLinuxカーネルの機能です(BPFはLinux3.15に最初に追加された新機能)。カーネルソースコードを変更したり、カーネルモジュールをロードしたりすることなく、カーネルの機能を安全かつ効率的に拡張するために使用されたりする(独自の命令セット を持つカーネル内部の仮想マシン上で実行できる機能)。カーネル自体に手を入れることや、kprobeを利用するカーネルモジュールを書くことに比べ遥かに簡単に追うことができたりするという印象を持ちました。

ebpf.io

ユーザが定義したプログラムをカーネルランドで実行するための仕組みで。パフォーマンスの計測、セキュリティ、ネットワーク処理などの利用用途があります

また、BPF Compiler Collection (BCC)という、eBPFプログラムの作成や実行を手助けしてくれる便利なツールキットがあります。(ちなみにBCCが使用するものの多くは、Linux4.1以降を必要とするのでCentOS7をそのまま使うのは若干厳しいのでカーネルを上げるなり違うディストロを用意するなどが必要です)

github.com

Brendan Gregg's Blogでの言及もとても面白かったです。

www.brendangregg.com

学ぶ順番も記されており

あたりを行なっていきます。

BPF

歴史的にeBPFの前にBPFがあるとのことですが今はeBPFのことを「BPF」古いBPFのことを「cBPF」と呼ぶことが多いらしいです。BPFの初版はwikiを見てたらなんと 1992年12月とのこと。(年上だったとは...!!!)

ja.wikipedia.org

BPFのドキュメントは以下。BPF設計Q&Aは見ておいても良いかもと思った。(が、見れていないので後で見る)

www.kernel.org

eBPF Foundation

Linux Foundationをスポンサーとした、eBPFプロジェクトのための「eBPF Foundation」という団体もあるらしい。FacebookGoogle、Isovalent、MicrosoftNetflixという名だたる企業が並んでいる。

japan.zdnet.com

昨年はeBPF Summitというイベントも開催されていたらしい。(界隈の盛り上がりをとても感じる。)、eBPFのいくつかの主要なユースケースとして、可観測性、負荷分散の話なんかがされていた模様。

ebpf.io

使っていく

環境構築は以下の記事を参考にBCCでeBPFのコードを書いていく感じで進める。

gihyo.jp

何はともあれhello worldをやってみる。

#!/usr/bin/python3

from bcc import BPF

bpf_text = """
int trace_sys_clone(struct pt_regs *ctx) {
  bpf_trace_printk("Hello, World!\\n");
  return 0;
}
"""

b = BPF(text=bpf_text)
b.attach_kprobe(event="__x64_sys_clone", fn_name="trace_sys_clone")

b.trace_print()

カーネル内部で __x64_sys_clone関数が呼ばれたときに、自分で定義した trace_sys_clone関数を呼び出すという流れです。bpf_trace_printkで中身を出力しているように見えますが実際は/sys/kernel/debug/tracing/trace_pipeへ出力が行われていてb.trace_print()で中身を見にいてっています。

システムコールの入口を知る: get_syscall_fnname

github.com

b = BPF(text=bpf_text)
print b.get_syscall_fnname("inotify_init")

# sys_inotify_init

気になるbccツール

biotop.py

block device (disk) I/O をプロセスごとに表示してくれるツール

PID    COMM             D MAJ MIN DISK       I/O  Kbytes  AVGms
14501  cksum            R 202 1   xvda1      361   28832   3.39
6961   dd               R 202 1   xvda1     1628   13024   0.59
13855  dd               R 202 1   xvda1     1627   13016   0.59
326    jbd2/xvda1-8     W 202 1   xvda1        3     168   3.00
1880   supervise        W 202 1   xvda1        2       8   6.71
1873   supervise        W 202 1   xvda1        2       8   2.51
1871   supervise        W 202 1   xvda1        2       8   1.57
1876   supervise        W 202 1   xvda1        2       8   1.22
1892   supervise        W 202 1   xvda1        2       8   0.62
1878   supervise        W 202 1   xvda1        2       8   0.78
1886   supervise        W 202 1   xvda1        2       8   1.30
1894   supervise        W 202 1   xvda1        2       8   3.46
1869   supervise        W 202 1   xvda1        2       8   0.73
1888   supervise        W 202 1   xvda1        2       8   1.48

cachetop.py

読み取りを含むLinuxページキャッシュのヒット/ミス統計を表示するツール。異常に高い数値が出てるかどうかはすぐにでも見れそうなツール

13:01:01 Buffers MB: 76 / Cached MB: 114 / Sort: HITS / Order: ascending
PID      UID      CMD              HITS     MISSES   DIRTIES  READ_HIT%  WRITE_HIT%
       1 root     systemd                 2        0        0     100.0%       0.0%
     680 root     vminfo                  3        4        2      14.3%      42.9%
     567 syslog   rs:main Q:Reg          10        4        2      57.1%      21.4%
     986 root     kworker/u2:2           10     2457        4       0.2%      99.5%
     988 root     kworker/u2:2           10        9        4      31.6%      36.8%
     877 vagrant  systemd                18        4        2      72.7%      13.6%
     983 root     python                148        3      143       3.3%       1.3%
     981 root     strace                419        3      143      65.4%       0.5%
     544 messageb dbus-daemon           455      371      454       0.1%       0.4%
     243 root     jbd2/dm-0-8           457      371      454       0.4%       0.4%
     985 root     (mount)               560     2457        4      18.4%      81.4%
     987 root     systemd-udevd         566        9        4      97.7%       1.2%
     988 root     systemd-cgroups       569        9        4      97.8%       1.2%
     986 root     modprobe              578        9        4      97.8%       1.2%
     287 root     systemd-journal       598      371      454      14.9%       0.3%
     985 root     mount                 692     2457        4      21.8%      78.0%
     984 vagrant  find                 9529     2457        4      79.5%      20.5%

cpudist.py

タスクごとのオンCPU時間をヒストグラムとして表示するツール

Tracing on-CPU time... Hit Ctrl-C to end.
^C
     usecs               : count     distribution
         0 -> 1          : 0        |                                        |
         2 -> 3          : 1        |                                        |
         4 -> 7          : 1        |                                        |
         8 -> 15         : 13       |**                                      |
        16 -> 31         : 187      |****************************************|
        32 -> 63         : 89       |*******************                     |
        64 -> 127        : 26       |*****                                   |
       128 -> 255        : 0        |                                        |
       256 -> 511        : 1        |                                        |

vfscount.py

vfs_*の関数ごとのコール数を表示

ADDR             FUNC                          COUNT
ffffffff87325781 b'vfs_symlink'                    1
ffffffff8739f971 b'vfs_test_lock'                  2
ffffffff87351fd1 b'vfs_fsync_range'                2
ffffffff87354d21 b'vfs_statfs'                     3
ffffffff87323871 b'vfs_rename'                     3
ffffffff87325aa1 b'vfs_mkdir'                      4
ffffffff87323561 b'vfs_unlink'                     4
ffffffff873287b1 b'vfs_readlink'                  15
ffffffff8731a671 b'vfs_fstat'                    260
ffffffff87310c61 b'vfs_open'                     330
ffffffff873124b1 b'vfs_write'                    404
ffffffff87319601 b'vfs_statx'                    491
ffffffff873a1261 b'vfs_lock_file'                523
ffffffff873195b1 b'vfs_getattr'                  594
ffffffff873194f1 b'vfs_getattr_nosec'            594
ffffffff87312201 b'vfs_read'                    1304

vfsstat.py

vfscountをvmstatみたいに表示してくれるツール

TIME         READ/s  WRITE/s  FSYNC/s   OPEN/s CREATE/s
22:52:16:       813      372        0       66        0
22:52:17:        41       42        0       13        0
22:52:18:         3        2        0        0        0
22:52:19:        23        7        0        8        0
22:52:20:         5        6        0        0        0

memleak.py

解放されなかった未処理のメモリ割り当てをトレースツール

Attaching to pid 5193, Ctrl+C to quit.
[11:16:33] Top 2 stacks with outstanding allocations:
        addr = 948cd0 size = 16
        addr = 948d10 size = 16
        addr = 948d30 size = 16
        addr = 948cf0 size = 16
        64 bytes in 4 allocations from stack
                 main+0x6d [allocs]
                 __libc_start_main+0xf0 [libc-2.21.so]

libbpf

bccで抱える課題を解決するべく「libbpf + BPF CO-RE」という開発環境が今後は主流になるらしい。bccのツールの書き換えも実施されていて今後の動向は見守っていく必要がありそう。ただ現段階では私はbccでさっとかけるくらいで良いので方向変えたりはもう少ししばらくはしない。(ポータビリティとかバイナリサイズが減ったりランタイムオーバーヘッドが減るなどのメリットが挙げられている)

github.com

github.com

bccもある程度触ったらlibbpfgoかrustに入門して触り始めるかしても良いのだろうかな(この辺はよくわかってないので別途検討)

github.com

用途とか実例とか

いろんな会社で実例があったので読んだものとか気になってるものをメモ。

ファイアウォールデバイスドライバー、ネットワークパフォーマンス監視などなど色々な文脈で登場する技術のようです。

Cilium

Cilium は、コンテナ ワークロードのスケーラビリティ、セキュリティ、可視性に関する新たな要件に対応するため、eBPF を基礎として設計されたオープンソース プロジェクト。

cilium.io

おまけ

/sys/kernel/debug/kprobes/list

/sys/kernel/debug/kprobes/listを見るとsystem :: probeに登録されているすべてのプローブを一覧表示することができます。

cat  /sys/kernel/debug/kprobes/list
ffffffffaa8af6d0  k  tcp_init_sock+0x0    [FTRACE]
ffffffffaa3cb990  k  do_writepages+0x0    [FTRACE]

参考記事

スクラムに興味を持ち始めた

ありがたいことに話をもらって昨年くらいからスクラムマスター的なことをやり始めた。自分の中でこの手の立場の人間はシステムの理解が深い人がやるべきだみたいな固定観念があったのだが学びながらやるのも楽しそうだなと思い始めてみた。

こういう本とか読んで学んでいる。思ったより楽しく学べているので個人的には新しい発見だった。

生存戦略的な話

社内にはめちゃめちゃすごい人がたくさんいて自分の強みが埋もれてしまう環境にいる。技術を磨くのはもちろんだけど何かしらを組み合わせてニッチ戦略的な考えで生き残っていきたいなって思っていた。そのタイミングで来た話だったのでとてもよかった。

「OS、ミドルウェアからのトラブルシュートを得意とするSREチームのスクラムマスター」みたいなのが目標になるのかな。こう書くと世の中にいっぱいいそうなのでそんなにニッチじゃなかったかも...😅

【Nginx】SO_REUSERPORTが使えた

nginx 1.9.1 からの新機能で、 SO_REUSEPORT というソケットのオプションを有効化することができるようになってたらしい。

qiita.com

この機能は複数のソケットを同じポートにバインドするという機能で443を複数プロセスでバインドして使うみたいに使える。複数プロセスで一つのポートをバインドして何が嬉しいんだって話はNginxみたいにepollでソケットを監視しているケースでSO_REUSERPORTが有効でない場合は全てのプロセスがepoll_waitから戻り早い者勝ちでaccept(2)が成功するみたいな動きになります。SO_REUSERPORTが有効だとカーネルは SO_REUSEPORT が有効なソケットに対してコネクションを一つだけに選択し通知を行うようになります。なのでepoll_wait(2)から戻るプロセスは一つになるという仕組みです。

ちなみにカーネル自体に機能が入ったのはなんとちょうど8年くらい前らしいです。

lwn.net

QUICとかの記事でも登場したるするらしい。

medium.com

とりあえずonしておけばパフォーマンス上がりそうなパラメータに見えますが副作用もあるので注意が必要。(クイズ入りのわかりやすい解説があったので貼っておきます。)

https://linuxplumbersconf.org/event/11/contributions/946/attachments/783/1472/Socket_migration_for_SO_REUSEPORT.pdf

EPOLLEXCLUSIVEが入ったカーネルじゃないとaccept-queueに入ってるコネクションが全て破棄されてしまうというものでEPOLLEXCLUSIVEはちょっと新目のカーネルが必要になる。

dsas.blog.klab.org

さらに見てたらnet.ipv4.tcp_migrate_reqというカーネルパラメータがより新目のカーネルには入るらしい。読んでみた感じ異なるリスナー間のコネクションの移行を有効にするように読めた。(他にも知らないパラメータが多く出てきたので別の機会に読んでみる)

patchwork.kernel.org