2020-10-14に投稿

LVS のセッション数上限についてメモ

Qrunch から引っ越し

conntrack については nf_conntrack_max の話題を時々聞くが、
ipvs のセッション数はリミットを気にしなくていいんだっけ。

ちょうど手元にあった kernel-ml-4.13.5-1.el7.elrepo.nosrc.rpm のソースで確認

conntrack

そもそも nf_conntrack_max はどうなっているのか確認

net/netfilter/nf_conntrack_core.c

static struct nf_conn *
__nf_conntrack_alloc(struct net *net,
             const struct nf_conntrack_zone *zone,
             const struct nf_conntrack_tuple *orig,
             const struct nf_conntrack_tuple *repl,
             gfp_t gfp, u32 hash)
{
    struct nf_conn *ct;

    /* We don't want any race condition at early drop stage */
    atomic_inc(&net->ct.count);

    if (nf_conntrack_max &&
        unlikely(atomic_read(&net->ct.count) > nf_conntrack_max)) {
        if (!early_drop(net, hash)) {
            if (!conntrack_gc_work.early_drop)
                conntrack_gc_work.early_drop = true;
            atomic_dec(&net->ct.count);
            net_warn_ratelimited("nf_conntrack: table full, dropping packet\n");
            return ERR_PTR(-ENOMEM);
        }
    }

    /*
     * Do not use kmem_cache_zalloc(), as this cache uses
     * SLAB_TYPESAFE_BY_RCU.
     */
    ct = kmem_cache_alloc(nf_conntrack_cachep, gfp);
    if (ct == NULL)
        goto out;

対象の netns の conntrack の数が nf_conntrack_max より多ければログを出して抜ける。
nf_conntrack_max 以下の場合はその次の処理 kmem_cache_alloc() で nf_conn 構造体の領域が割り当てられるようだ。

ipvs

続いて ipvs の場合はどうか確認

net/netfilter/ipvs/ip_vs_conn.c

truct ip_vs_conn *
ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af,
           const union nf_inet_addr *daddr, __be16 dport, unsigned int flags,
           struct ip_vs_dest *dest, __u32 fwmark)
{
    struct ip_vs_conn *cp;
    struct netns_ipvs *ipvs = p->ipvs;
    struct ip_vs_proto_data *pd = ip_vs_proto_data_get(p->ipvs,
                               p->protocol);

    cp = kmem_cache_alloc(ip_vs_conn_cachep, GFP_ATOMIC);
    if (cp == NULL) {
        IP_VS_ERR_RL("%s(): no memory\n", __func__);
        return NULL;
    }

ip_vs_conn_new() にそれらしきものは無い。呼び出し元を見ても、特に何かのパラメータによる制限は無さそう。

スラブアロケータは全然分かってないけど、inode のキャッシュでめっちゃメモリ食われてたという話を聞くし、メモリが空いている限り使えるのだろう。

そういえば inode のキャッシュって drop_caches で消せるけど、これは大丈夫なんだろうか。多分何か識別するものがあるはずなので xfs の inode あたりを確認してみる。

fs/xfs/xfs_super.c

    xfs_inode_zone =
        kmem_zone_init_flags(sizeof(xfs_inode_t), "xfs_inode",
            KM_ZONE_HWALIGN | KM_ZONE_RECLAIM | KM_ZONE_SPREAD |
            KM_ZONE_ACCOUNT, xfs_fs_inode_init_once);

fs/xfs/kmem.h

#define KM_ZONE_RECLAIM SLAB_RECLAIM_ACCOUNT

/* 勝手に省略 */

static inline kmem_zone_t *
kmem_zone_init_flags(int size, char *zone_name, unsigned long flags,
             void (*construct)(void *))
{
    return kmem_cache_create(zone_name, size, 0, flags, construct);
}

include/linux/slab.h

/* The following flags affect the page allocator grouping pages by mobility */
#define SLAB_RECLAIM_ACCOUNT    0x00020000UL        /* Objects are reclaimable */

kmem_cache_create() する時に SLAB_RECLAIM_ACCOUNT のフラグ渡しているようだ。

ipvs で kmem_cache_create() しているところはこのフラグが無かったので大丈夫なのだろう

net/netfilter/ipvs/ip_vs_conn.c

int __init ip_vs_conn_init(void)
{
    int idx;

    /* Compute size and mask */
    ip_vs_conn_tab_size = 1 << ip_vs_conn_tab_bits;
    ip_vs_conn_tab_mask = ip_vs_conn_tab_size - 1;

    /*
     * Allocate the connection hash table and initialize its list heads
     */
    ip_vs_conn_tab = vmalloc(ip_vs_conn_tab_size * sizeof(*ip_vs_conn_tab));
    if (!ip_vs_conn_tab)
        return -ENOMEM;

    /* Allocate ip_vs_conn slab cache */
    ip_vs_conn_cachep = kmem_cache_create("ip_vs_conn",
                          sizeof(struct ip_vs_conn), 0,
                          SLAB_HWCACHE_ALIGN, NULL);
    if (!ip_vs_conn_cachep) {
        vfree(ip_vs_conn_tab);
        return -ENOMEM;
    }

サービス毎のセッション数制限

nf_conntrack_max のような全体の (network namespace 毎の) 制限値は無さそうだが、ググったらサービス毎に上限を設定できるらしい。

ipvsadm -h から抜粋すると以下の部分のようだ。

  --u-threshold  -x uthreshold        upper threshold of connections
  --l-threshold  -y lthreshold        lower threshold of connections

lower threshold はどういう時に使うのかよく分からない。気にしないでおく。
upper threshold もそうだが、明示的に指定しなければ制限しないようだ。
ipvsadm -ln --thresholds した時に Uthreshold が 0 になっていれば制限されていない。

ちょっと試してみたところ、すべての real server が upper threshold に達した状態で新規セッションの通信がきた場合は ICMP Port Unreachable を返すようだ。

それはいいのだが、セッションの数として inactive なセッションもカウントされているので、セッションを使い続けるのではなく接続・切断が頻繁に起こる場合などでは TIME_WAIT が多くなってしまい、制限方法として採用しづらい。

net/netfilter/ipvs/ip_vs_conn.c

static inline int ip_vs_dest_totalconns(struct ip_vs_dest *dest)
{
    return atomic_read(&dest->activeconns)
        + atomic_read(&dest->inactconns);
}

/*
 *  Bind a connection entry with a virtual service destination
 *  Called just after a new connection entry is created.
 */
static inline void
ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
{
/* 勝手に省略 */
    if (dest->u_threshold != 0 &&
        ip_vs_dest_totalconns(dest) >= dest->u_threshold)
        dest->flags |= IP_VS_DEST_F_OVERLOAD;
}

結論

lower/upper threshold を使っていなければ、ipvs セッション上限は気にしない。メモリの量は気にする。

ツイッターでシェア
みんなに共有、忘れないようにメモ

albatross0

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント