ebpf を使用してロード バランサーを最適化する

I.はじめに

久しぶりに記事を書きました.最近決断に追われてとても疲れています.どの方法が自分にとって最善なのかわかりません.リスクとメリットが共存しています.安全にプレイできますか.選択する方法を知っていると、右派と左派の考えが戦ってきました。

身近なところで、私はebpfをしばらく学んでいて、まだ少し知っているように感じますが、ギャップはまだ少し大きいです. この記事はebpfコースを学ぶための実験的な記事です. 主にebpfに基づいています.ネットワークプログラムは、以前よりも難しくなり、新しい学習と相まって、模倣実験からのみ開始できます.実験は、Ni Pengfei氏がGeek Timeに書いた「ebpfコアテクノロジーと実際の戦闘」から来ています.

第 2 環境の準備

2.1 テスト環境のインストール

次のように、ネットワーク アーキテクチャ図全体を展開します。450432a48435bf14be257236548511a1.png

Docker 環境のインストール スクリプト:

# Webserver (响应是hostname,如 http1 或 http2)
docker run -itd --name=http1 --hostname=http1 feisky/webserver
docker run -itd --name=http2 --hostname=http2 feisky/webserver
# Client
docker run -itd --name=client alpine
# Nginx
docker run -itd --name=nginx nginx

説明:

docker alpine とは何ですか? Alpine オペレーティング システムは、セキュリティ指向の軽量 Linux ディストリビューションです。通常の Linux ディストリビューションとは異なり、Alpine は Musl libc と busybox を使用してシステム サイズ (5M サイズ) と実行時のリソース消費を削減していますが、その機能は busybox よりもはるかに充実しているため、オープンな支持を得ています。ソースコミュニティ。Alpine はスリムな状態を保ちながら、https://pkgs.alpinelinux.org/packages Web サイトからパッケージ情報をクエリしたり、apk コマンドを使用してさまざまなソフトウェアを直接クエリしてインストールしたりできる独自のパッケージ管理ツール apk も提供しています。

Docker コンテナーの IP アドレス情報を確認します。

root@ubuntu-lab:/home/miao# IP1=$(docker inspect http1 -f '{
    
    {range .NetworkSettings.Networks}}{
    
    {.IPAddress}}{
    
    {end}}')
root@ubuntu-lab:/home/miao# IP2=$(docker inspect http2 -f '{
    
    {range .NetworkSettings.Networks}}{
    
    {.IPAddress}}{
    
    {end}}')
root@ubuntu-lab:/home/miao# echo $IP1
172.17.0.2
root@ubuntu-lab:/home/miao# echo $IP2
172.17.0.3
root@ubuntu-lab:/home/miao# IP3=$(docker inspect nginx -f '{
    
    {range .NetworkSettings.Networks}}{
    
    {.IPAddress}}{
    
    {end}}')
root@ubuntu-lab:/home/miao# echo $IP3
172.17.0.5
root@ubuntu-lab:/home/miao

2.2 nginx 構成の更新

# 生成nginx.conf文件
cat>nginx.conf <<EOF
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
   include       /etc/nginx/mime.types;
   default_type  application/octet-stream;

    upstream webservers {
        server $IP1;
        server $IP2;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://webservers;
        }
    }
}
EOF

構成の更新:

# 更新Nginx配置
docker cp nginx.conf nginx:/etc/nginx/nginx.conf
docker exec nginx nginx -s reload

三原則

3.1 コンテナ間のネットワーク送信

5e919c987fb000db09ce1570100973ed.png
コンテナ間でパケットを送信する

上の図に示すように、通常の状況では、ロード バランサーはソケットに関連付けられたキューにメッセージを送信し、プロトコル スタックを通過してから仮想ネットワーク カード 1 を通過し、それを仮想ネットワーク カード 2 に転送してから、再度プロトコルスタックを通過処理、ヘッダー情報を削除、データパケットはソケット2に送信、2回のプロトコルスタック処理後、実は全く不要、紫の処理のようにプロトコルスタックをバイパスできる図の矢印で、同じホスト マシンをアップグレードします。コンテナ間のネットワーク フォワーディング パフォーマンスの問題。

3.2 プログラムの原則の説明

私の理解によると、簡単に言えば、最初に新しく作成したソケットを BPF_MAP_TYPE_SOCKHASH タイプと呼ばれるマッピング テーブルに保存します。下の図に示すように、キーは 5 倍、値はソケットのファイル記述子です。キーは次のように定義されます。

struct sock_key
{
    __u32 sip;    //源IP
    __u32 dip;    //目的IP
    __u32 sport;  //源端口
    __u32 dport;  //目的端口
    __u32 family; //协议
};
95e56c72b6ba71d4eeb395f71bdcbdde.png
マッピング図

このデータを取得した後、新しく送信されたデータの 5 タプル情報を転送します。つまり、送信元 IP と宛先 IP が交換され、送信元ポートと宛先ポートが交換されるため、反対側が取得され、関数 bpf_msg_redirect_hash を介して完了します。簡単に言えば、現在のソケットのメッセージは、魔法のようにプロトコル スタックをバイパスするソケット マッピングのソケットに転送されます。

long bpf_msg_redirect_hash(struct sk_msg_buff *msg, struct bpf_map *map, void *key, u64 flags)

Description
This helper is used in programs implementing policies at the socket  level.  If  the
message  msg  is allowed to pass (i.e. if the verdict eBPF program returns SK_PASS),
redirect it to the socket referenced by map (of  type  BPF_MAP_TYPE_SOCKHASH)  using
hash  key.  Both  ingress  and  egress  interfaces  can be used for redirection. The
BPF_F_INGRESS value in flags is used to make the distinction (ingress  path  is  se‐
lected  if  the  flag is present, egress path otherwise). This is the only flag sup‐
ported for now.

Return SK_PASS on success, or SK_DROP on error.

3.3 ebpf タイプを使用する

異なる ebpf タイプのプログラムには、使用できる異なるヘルパー関数があります. 操作の便宜上、ここでは 2 つの異なるタイプの ebpf プログラムが使用されています:

  1. BPF_PROG_TYPE_SOCK_OPS このタイプは、クインタプルとソケットのマッピングを構築するための ebfp プログラム タイプです。(ソケット操作イベントが実行をトリガーします)

  2. BPF_PROG_TYPE_SK_MSG このタイプは、ソケットで送信されたデータ パケットをキャプチャし、上記のソケット マッピングに従ってそれらを転送するために使用されます。(sendmsg システムコールが実行のトリガー)

さまざまなタイプの ebpf プログラム フック ポイントの説明:f1e70a384b8945cc6ed8f168a00fcc09.png

4 つのコードのまとめ

4.1 ソケットマッピングデータの保存

ヘッダー ファイルは sockops.h を定義します

#ifndef __SOCK_OPS_H__
#define __SOCK_OPS_H__

#include <linux/bpf.h>

struct sock_key {
 __u32 sip;
 __u32 dip;
 __u32 sport;
 __u32 dport;
 __u32 family;
};

struct bpf_map_def SEC("maps") sock_ops_map = {
 .type = BPF_MAP_TYPE_SOCKHASH,
 .key_size = sizeof(struct sock_key),
 .value_size = sizeof(int),
 .max_entries = 65535,
 .map_flags = 0,
};

#endif    /* __SOCK_OPS_H__ */

ソケットとソケット マッピングを作成するためのプログラム。ファイル名は sockops.bpf.c です。

#include <linux/bpf.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <sys/socket.h>
#include "sockops.h"

SEC("sockops")
int bpf_sockmap(struct bpf_sock_ops *skops)
{
 /* 包如果不是ipv4的则忽略*/
 if (skops->family != AF_INET) {
  return BPF_OK;
 }

 /* 只有新创建的主动连接或被动连接才更新 */
 if (skops->op != BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB
     && skops->op != BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB) {
  return BPF_OK;
 }

 struct sock_key key = {
  .dip = skops->remote_ip4,
  .sip = skops->local_ip4,
  /* convert to network byte order */
  .sport = bpf_htonl(skops->local_port),
  .dport = skops->remote_port,
  .family = skops->family,
 };

 bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST);
 return BPF_OK;
}

char LICENSE[] SEC("license") = "Dual BSD/GPL";

キーは次のとおりです。

bpf_sock_hash_update(skops, &sock_ops_map, &key, BPF_NOEXIST);

4.2 ソケットデータの転送

保存されたソケット マッピング データを使用し、bpf ヘルパー関数を組み合わせてメッセージを転送します。ファイル名: sockredir.bpf.c

#include <linux/bpf.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include <sys/socket.h>
#include "sockops.h"



SEC("sk_msg")
int bpf_redir(struct sk_msg_md *msg)
{
    // 源和目标要反转,因为我们先对端发的
    struct sock_key key = {
        .sip = msg->remote_ip4,
        .dip = msg->local_ip4,
        .dport = bpf_htonl(msg->local_port),
        .sport = msg->remote_port,
        .family = msg->family,
    };
    // 将套接字收到的消息转发
    bpf_msg_redirect_hash(msg, &sock_ops_map, &key, BPF_F_INGRESS);
    return SK_PASS;
}

char LICENSE[] SEC("license") = "Dual BSD/GPL";

コンパイル コマンド:

clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -I/usr/include/x86_64-linux-gnu -I. -c sockops.bpf.c -o sockops.bpf.o

clang -g -O2 -target bpf -D__TARGET_ARCH_x86 -I/usr/include/x86_64-linux-gnu -I. -c sockredir.bpf.c -o sockredir.bpf.o

2 行のコマンドで bpf プログラムを bpf バイトコードに変換します。

4.3 ebpf プログラムのロード

以前は、BCC の python コードまたは libbpf ライブラリによって提供されていた機能、今回は bpftool を使用して ebpf プログラムをロードおよびマウントするというエキサイティングな方法で、ebpf プログラムを長時間実行する方法がようやくわかりました。フロントエンドで実行すると、プログラムが停止するとドロップしますが、そうではありません。

sockops プログラムをロードします。

sudo bpftool prog load sockops.bpf.o /sys/fs/bpf/sockops type sockops pinmaps /sys/fs/bpf

sockops.bpf.o をカーネルにロードし、BPF ファイル システムに修正します。コマンドの終了後、ebpf プログラムはバックグラウンドで実行され続けます。見られます:

root@ubuntu-lab:/home/miao/jike-ebpf/balance# bpftool prog show 
992: sock_ops  name bpf_sockmap  tag e37ef726a3a85a2e  gpl
        loaded_at 2022-06-12T10:43:09+0000  uid 0
        xlated 256B  jited 140B  memlock 4096B  map_ids 126
        btf_id 149

上記は ebpf プログラムのみをロードしますが、カーネル イベントにバインドされていません. sockops プログラムは cgroup サブシステムにマウントできるため、cgroup で実行されているすべてのプログラムに対して有効になります. これは本当に魔法のようなものです. 2 つの手順: 1. 現在のシステムのマウントされた cgroup パスを表示します。

root@ubuntu-lab:/home/miao/jike-ebpf/balance# mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
  1. マウント:

sudo bpftool cgroup attach /sys/fs/cgroup/ sock_ops pinned /sys/fs/bpf/sockops

フォワーダーのロードとマウント:

sudo bpftool prog load sockredir.bpf.o /sys/fs/bpf/sockredir type sk_msg map name sock_ops_map pinned /sys/fs/bpf/sock_ops_map
sudo bpftool prog attach pinned /sys/fs/bpf/sockredir msg_verdict pinned /sys/fs/bpf/sock_ops_map

1 つは sockops タイプで、もう 1 つは sk_msg タイプという異なるタイプの bpf を含め、上記のマウント コマンドにはまだ多くの違いがあります。2 つのプログラムは、パス マッピングによってバインドされる sock_ops_map を介して通信します。

5 回実行の最適化されたロード バランサーのパフォーマンス比較

5.1 最適化前

改善があるかどうかを確認するには、変更を加えずに元の負荷分散アーキテクチャでパフォーマンスをテストする必要があります。テスト ツールをダウンロードして、クライアント側でテストします。

# 进入client容器终端,安装curl之后访问Nginx
docker exec -it client sh 

# 安装和验证
/ # apk add curl wrk --update

/ # curl "http://172.17.0.5"

正常と判断された場合は、性能テストツールwrkをインストールし、以下のようにテストしてください。

/ # apk add wrk --update
/ # wrk -c100 "http://172.17.0.5"

出力は次のとおりです。

/ #  wrk -c100 "http://172.17.0.5"
Running 10s test @ http://172.17.0.5
  2 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    32.81ms   28.30ms 252.86ms   87.21%
    Req/Sec     1.75k   612.19     3.26k    67.35%
  34406 requests in 10.10s, 5.41MB read
Requests/sec:   3407.42
Transfer/sec:    549.05KB

平均遅延は 32.81 ミリ秒、1 秒あたりの平均リクエスト数は 3407.42、平均リクエスト サイズは 1.75 です。

5.2 最適化後

docker exec -it client sh
 /# wrk -c100 "http://172.17.0.5"

結果は次のとおりです。

Running 10s test @ http://172.17.0.5
  2 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    29.21ms   27.98ms 294.16ms   89.78%
    Req/Sec     2.06k   626.54     3.25k    68.23%
  40389 requests in 10.07s, 6.36MB read
Requests/sec:   4010.77
Transfer/sec:    646.27KB

比較すると、遅延は 32.81 ミリ秒から 29.21 ミリ秒に減少し、1 秒あたりの平均リクエスト数は 3407 から 4010 に増加し、17% 増加しましたが、これはまだ許容範囲内です。

curl スコープも正常です。

/ # curl "http://172.17.0.5"
Hostname: http1

/ # curl "http://172.17.0.5"
Hostname: http2

テストの実行中に、マップに値を表示できます。

root@ubuntu-lab:/home/miao/jike-ebpf/hello# sudo bpftool map dump name sock_ops_map
key:
ac 11 00 05 ac 11 00 03  00 00 c7 60 00 00 00 50
02 00 00 00
value:
No space left on device
key:
ac 11 00 05 ac 11 00 04  00 00 00 50 00 00 e0 86
02 00 00 00
value:
No space left on device
key:
ac 11 00 05 ac 11 00 04  00 00 00 50 00 00 e0 88
02 00 00 00

無視 デバイスにスペースが残っていません。これは ebpf バージョンの問題です。キーの値は 5 倍の値に対応しており、テスト後には表示されません。

6つのデータクリーニング

# cleanup skops prog and sock_ops_map
sudo bpftool cgroup detach /sys/fs/cgroup/ sock_ops name bpf_sockmap
sudo rm -f /sys/fs/bpf/sockops /sys/fs/bpf/sock_ops_map

# cleanup sk_msg prog
sudo bpftool prog detach pinned /sys/fs/bpf/sockredir msg_verdict pinned /sys/fs/bpf/sock_ops_map
sudo rm -f /sys/fs/bpf/sockredir

元のマウント ポイントをアンインストールし、いくつかのファイルを削除して ebpf プログラムを削除します。

Docker コンテナーを削除します。

docker rm -f http1 http2 client nginx

おすすめ

転載: blog.csdn.net/mseaspring/article/details/125252762