基于 Paxos 构建高可用 Kong 控制面并结合 eBPF 与 Prometheus 实现无侵入深度观测


标准的 Kong 网关部署模式,无论是带数据库还是无数据库(DB-less),在管理动态配置时都存在固有的运维痛点。依赖 PostgreSQL 或 Cassandra 引入了沉重的外部依赖,这本身就是一个复杂的分布式系统,构成了潜在的单点故障和性能瓶颈。而 DB-less 模式虽然消除了数据库,但配置分发依赖于 CI/CD 流水线和文件系统,配置变更的实时性和原子性难以保证,在规模化集群中尤其脆弱。我们的目标是构建一个完全自包含、高可用的 Kong 集群,其控制面配置不依赖任何外部存储,而是通过内置的分布式一致性协议进行同步。

为此,我们决定用一个基于 Paxos 协议实现的轻量级分布式 K/V 存储来替换 Kong 的原生配置后端。每个 Kong 节点将内嵌一个 Agent,这个 Agent 同时是 Paxos 集群的参与者。配置变更通过 Paxos 协议在所有 Agent 间达成共识,然后由 Agent 调用本地 Kong 节点的 Admin API 来动态应用配置。这个架构消除了外部数据库依赖,实现了控制面的去中心化和高可用。

然而,这套自定义的控制面本身也成了一个需要被严密监控的黑盒。同时,我们还需要对 Kong 数据面的流量进行细粒度观测,但又不想侵入 Kong 本身(例如通过编写自定义插件),以保持其升级和维护的简洁性。eBPF 是解决这个问题的完美方案。我们可以利用 eBPF 在内核空间无侵入地捕获网络流量和应用函数调用,从而同时观测到 Kong 数据面的请求延迟和我们自研 Paxos 控制面的内部状态。这些观测数据最终被格式化为 Metrics,汇入 Prometheus TSDB 进行存储和告警。

第一阶段:构建基于 Multi-Paxos 的分布式配置存储

我们将使用 Go 语言实现一个简化的 Multi-Paxos 库。Multi-Paxos 通过选举一个稳定的 Leader 来简化流程,所有写请求都由 Leader 处理,然后复制到 Acceptor 节点。这比经典的 Paxos 更易于工程实现。

我们的 K/V 存储需要支持 Set(key, value)Get(key) 操作。Set 操作将作为 Paxos 日志条目进行共识。

核心数据结构

// paxos_node.go

package main

import (
	"encoding/json"
	"log"
	"net"
	"net/rpc"
	"sync"
	"time"
)

// Paxos 日志条目
type LogEntry struct {
	InstanceID int
	Command    string // e.g., "SET key value"
	Accepted   bool
}

// Prepare 请求参数
type PrepareArgs struct {
	InstanceID int
	ProposalID int
}

// Prepare 响应
type PrepareReply struct {
	OK         bool
	ProposalID int
	Value      string // 已接受的提案值
}

// Accept 请求参数
type AcceptArgs struct {
	InstanceID int
	ProposalID int
	Value      string
}

// Accept 响应
type AcceptReply struct {
	OK bool
}

// Learn 通知参数
type LearnArgs struct {
    InstanceID int
    Value      string
}

type LearnReply struct {
    OK bool
}


// Paxos 节点状态
type PaxosNode struct {
	mu           sync.Mutex
	id           int
	peers        []string // 其他节点的 RPC 地址
	log          map[int]*LogEntry // instanceId -> LogEntry
	stateMachine map[string]string // 应用状态机,一个简单的 K/V store
	isLeader     bool
	
	// 每个 instance 的最高承诺 proposalId 和已接受的 proposal
	maxPromisedID map[int]int
	acceptedValue map[int]string
	
	// 用于 RPC 客户端的缓存
	rpcClients map[string]*rpc.Client
}

// NewPaxosNode 创建一个新的 Paxos 节点
func NewPaxosNode(id int, peers []string, isLeader bool) *PaxosNode {
	pn := &PaxosNode{
		id:            id,
		peers:         peers,
		log:           make(map[int]*LogEntry),
		stateMachine:  make(map[string]string),
		isLeader:      isLeader,
		maxPromisedID: make(map[int]int),
		acceptedValue: make(map[int]string),
		rpcClients:    make(map[string]*rpc.Client),
	}
	// 启动 RPC 服务
	go pn.startRPCServer()
	// 在后台尝试连接到其他节点
	go pn.connectToPeers()
	return pn
}

// startRPCServer 启动节点的 RPC 服务
func (pn *PaxosNode) startRPCServer() {
	rpc.Register(pn)
	listener, err := net.Listen("tcp", pn.peers[pn.id])
	if err != nil {
		log.Fatalf("PaxosNode %d: listen error: %v", pn.id, err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Printf("PaxosNode %d: accept error: %v", pn.id, err)
			continue
		}
		go rpc.ServeConn(conn)
	}
}

// connectToPeers 预先建立与其他节点的 RPC 连接以提高效率
func (pn *PaxosNode) connectToPeers() {
	for i, addr := range pn.peers {
		if i == pn.id {
			continue
		}
		// 循环尝试连接,直到成功
		for {
			client, err := rpc.Dial("tcp", addr)
			if err == nil {
				pn.mu.Lock()
				pn.rpcClients[addr] = client
				pn.mu.Unlock()
				log.Printf("Node %d connected to peer %s", pn.id, addr)
				break
			}
			time.Sleep(1 * time.Second)
		}
	}
}

Proposer 和 Acceptor 逻辑

PrepareAccept 是 Paxos 的核心。在我们的 Multi-Paxos 实现中,只有 Leader 才能发起提案。

// paxos_logic.go

// ... (在 PaxosNode 结构体中)

// Prepare RPC handler
func (pn *PaxosNode) Prepare(args PrepareArgs, reply *PrepareReply) error {
	pn.mu.Lock()
	defer pn.mu.Unlock()

	// 真实项目中,需要处理 proposal ID 的持久化和恢复
	if args.ProposalID > pn.maxPromisedID[args.InstanceID] {
		pn.maxPromisedID[args.InstanceID] = args.ProposalID
		reply.OK = true
		reply.ProposalID = pn.maxPromisedID[args.InstanceID]
		reply.Value = pn.acceptedValue[args.InstanceID]
	} else {
		reply.OK = false
		reply.ProposalID = pn.maxPromisedID[args.InstanceID]
	}
	return nil
}

// Accept RPC handler
func (pn *PaxosNode) Accept(args AcceptArgs, reply *AcceptReply) error {
	pn.mu.Lock()
	defer pn.mu.Unlock()

	if args.ProposalID >= pn.maxPromisedID[args.InstanceID] {
		pn.maxPromisedID[args.InstanceID] = args.ProposalID
		pn.acceptedValue[args.InstanceID] = args.Value
		reply.OK = true
	} else {
		reply.OK = false
	}
	return nil
}

// Learn RPC handler
func (pn *PaxosNode) Learn(args LearnArgs, reply *LearnReply) error {
    pn.mu.Lock()
    defer pn.mu.Unlock()

    if _, exists := pn.log[args.InstanceID]; exists {
        reply.OK = true
        return nil // 已经学习过
    }

    log.Printf("Node %d is learning instance %d with value %s", pn.id, args.InstanceID, args.Value)
    pn.log[args.InstanceID] = &LogEntry{InstanceID: args.InstanceID, Command: args.Value, Accepted: true}
    
    // 简单的命令解析和状态机应用
    // 生产环境中需要更健壮的解析和错误处理
    var commandData map[string]string
    if err := json.Unmarshal([]byte(args.Value), &commandData); err == nil {
        if cmd, ok := commandData["cmd"]; ok && cmd == "SET" {
            key := commandData["key"]
            value := commandData["value"]
            pn.stateMachine[key] = value
            log.Printf("Node %d applied to state machine: SET %s = %s", pn.id, key, value)
            
            // 此处触发调用 Kong Admin API 的逻辑
            go pn.applyToKong(key, value)
        }
    }
    reply.OK = true
    return nil
}


// Propose 发起一个提案,只有 Leader 能调用
func (pn *PaxosNode) Propose(command string) bool {
	pn.mu.Lock()
	if !pn.isLeader {
		pn.mu.Unlock()
		return false
	}
	
	// 找到下一个可用的 instance ID
	instanceID := len(pn.log)
	pn.mu.Unlock()

	// 简单的 proposal ID 生成策略,生产环境需要更可靠的唯一ID生成器
	proposalID := int(time.Now().UnixNano())

	// Phase 1: Prepare
	prepareCount := 0
	quorum := len(pn.peers)/2 + 1
	var highestProposalID int
	var valueToPropose string = command

	wg := sync.WaitGroup{}
	for i, addr := range pn.peers {
		wg.Add(1)
		go func(peerIndex int, peerAddr string) {
			defer wg.Done()
			args := PrepareArgs{InstanceID: instanceID, ProposalID: proposalID}
			var reply PrepareReply

			var err error
			if peerIndex == pn.id {
				// 本地调用
				err = pn.Prepare(args, &reply)
			} else {
				client, ok := pn.rpcClients[peerAddr]
				if !ok || client == nil {
					log.Printf("Leader %d: RPC client for %s not ready", pn.id, peerAddr)
					return
				}
				err = client.Call("PaxosNode.Prepare", args, &reply)
			}

			if err == nil && reply.OK {
				pn.mu.Lock()
				prepareCount++
				if reply.ProposalID > highestProposalID {
					highestProposalID = reply.ProposalID
					if reply.Value != "" {
						valueToPropose = reply.Value // 发现之前有已接受的提案,必须采用它的值
					}
				}
				pn.mu.Unlock()
			}
		}(i, addr)
	}
	wg.Wait()

	if prepareCount < quorum {
		log.Printf("Leader %d: Prepare phase failed for instance %d. Quorum not reached.", pn.id, instanceID)
		return false
	}

	// Phase 2: Accept
	acceptCount := 0
	wg = sync.WaitGroup{}
	for i, addr := range pn.peers {
		wg.Add(1)
		go func(peerIndex int, peerAddr string) {
			defer wg.Done()
			args := AcceptArgs{InstanceID: instanceID, ProposalID: proposalID, Value: valueToPropose}
			var reply AcceptReply

			var err error
			if peerIndex == pn.id {
				err = pn.Accept(args, &reply)
			} else {
				client, ok := pn.rpcClients[peerAddr]
				if !ok || client == nil {
					return
				}
				err = client.Call("PaxosNode.Accept", args, &reply)
			}
			
			if err == nil && reply.OK {
				pn.mu.Lock()
				acceptCount++
				pn.mu.Unlock()
			}
		}(i, addr)
	}
	wg.Wait()

	if acceptCount < quorum {
		log.Printf("Leader %d: Accept phase failed for instance %d. Quorum not reached.", pn.id, instanceID)
		return false
	}

	// Phase 3: Learn (广播学习)
	// 这里的坑在于,如果Learn消息丢失,节点状态会不一致。生产系统需要重试或拉取机制。
	for i, addr := range pn.peers {
		go func(peerIndex int, peerAddr string) {
			args := LearnArgs{InstanceID: instanceID, Value: valueToPropose}
			var reply LearnReply
			
			var err error
			if peerIndex == pn.id {
				err = pn.Learn(args, &reply)
			} else {
				client, ok := pn.rpcClients[peerAddr]
				if !ok || client == nil {
					return
				}
				// 异步广播,不关心结果
				client.Go("PaxosNode.Learn", args, &reply, nil)
			}
		}(i, addr)
	}

	log.Printf("Leader %d: Proposal for instance %d with value '%s' succeeded.", pn.id, instanceID, valueToPropose)
	return true
}

第二阶段:与 Kong 集成

Agent 的主要职责是将 Paxos 集群中达成共识的配置,通过本地 Kong Admin API 应用到 Kong 实例。这里我们只演示对 services 的配置。

// kong_adapter.go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
)

const KongAdminURL = "http://localhost:8001"

// 示例:key 的格式为 "service:my-service",value 是 service 的 JSON 定义
func (pn *PaxosNode) applyToKong(key string, value string) {
	parts := strings.SplitN(key, ":", 2)
	if len(parts) != 2 {
		return
	}
	resourceType := parts[0]
	resourceName := parts[1]

	switch resourceType {
	case "service":
		pn.applyService(resourceName, value)
	// case "route": ...
	default:
		log.Printf("Unknown resource type: %s", resourceType)
	}
}

func (pn *PaxosNode) applyService(name string, serviceJSON string) {
	// 检查服务是否存在
	url := fmt.Sprintf("%s/services/%s", KongAdminURL, name)
	req, _ := http.NewRequest("GET", url, nil)
	resp, err := http.DefaultClient.Do(req)
	
	var method string
	if err == nil && resp.StatusCode == 200 {
		// 服务存在,使用 PUT/PATCH 更新
		method = "PATCH"
	} else {
		// 服务不存在,使用 POST 创建
		method = "POST"
		url = fmt.Sprintf("%s/services/", KongAdminURL)
	}
	if resp != nil {
		resp.Body.Close()
	}

	// 执行创建或更新
	req, _ = http.NewRequest(method, url, bytes.NewBufferString(serviceJSON))
	req.Header.Set("Content-Type", "application/json")

	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		log.Printf("Failed to apply service '%s' to Kong: %v", name, err)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		log.Printf("Successfully applied service '%s' to Kong (Method: %s)", name, method)
	} else {
		log.Printf("Error applying service '%s' to Kong (Method: %s), Status: %s", name, method, resp.Status)
	}
}

第三阶段:使用 eBPF 实现零侵入观测

现在,我们有了自定义的控制面和标准的 Kong 数据面。我们需要同时监控两者。

  1. 数据面监控: 监控通过 Kong 的 HTTP 请求延迟。我们将使用 kprobe 挂载到内核网络函数上。
  2. 控制面监控: 监控我们 Go 语言实现的 Paxos Agent 的关键函数调用情况和延迟,如 ProposeLearn。我们将使用 uprobe 挂载到 Go 程序的用户空间函数上。

我们将使用 libbpf-go 和 CO-RE (Compile Once – Run Everywhere) 的方式来构建 eBPF 应用。

eBPF 内核态程序 (C)

// bpf/monitor.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

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

// 用于存储函数开始时间戳
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 10240);
    __type(key, u64); // key: goroutine id or socket cookie
    __type(value, u64); // value: start timestamp
} start_times SEC(".maps");

// 用于向用户空间发送事件数据
struct event {
    u64 duration_ns;
    u32 pid;
    char comm[16];
    char type[16]; // "http_req" or "paxos_propose"
};

struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 256 * 1024); // 256 KB
} events SEC(".maps");

// --- 数据面: Kong HTTP 请求延迟 ---
// 使用 kprobe 监控 TCP 连接上的数据发送
// 这是一个简化模型,真实场景需要关联请求和响应
SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(trace_tcp_sendmsg, struct sock *sk) {
    u64 id = (u64)sk;
    u64 ts = bpf_ktime_get_ns();
    bpf_map_update_elem(&start_times, &id, &ts, BPF_ANY);
    return 0;
}

SEC("kretprobe/tcp_sendmsg")
int BPF_KRETPROBE(trace_tcp_sendmsg_ret, struct sock *sk) {
    u64 id = (u64)sk;
    u64 *start_ts = bpf_map_lookup_elem(&start_times, &id);
    if (!start_ts) {
        return 0;
    }

    u64 duration = bpf_ktime_get_ns() - *start_ts;
    bpf_map_delete_elem(&start_times, &id);

    // 过滤掉非 Kong 进程的事件
    u64 pid_tgid = bpf_get_current_pid_tgid();
    char current_comm[16];
    bpf_get_current_comm(&current_comm, sizeof(current_comm));

    // kong 的 master 进程名是 "nginx", worker 进程也是 "nginx"
    // 实际项目中需要更精确的过滤
    if (bpf_strncmp(current_comm, sizeof(current_comm), "nginx") != 0) {
        return 0;
    }

    struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
    if (!e) {
        return 0;
    }

    e->duration_ns = duration;
    e->pid = pid_tgid >> 32;
    __builtin_memcpy(e->comm, current_comm, sizeof(e->comm));
    __builtin_memcpy(e->type, "http_req", sizeof(e->type));

    bpf_ringbuf_submit(e, 0);
    return 0;
}


// --- 控制面: Paxos Agent 函数延迟 ---
// 挂载到我们的 Go 程序的 Propose 函数
// 需要知道二进制文件的路径和函数符号
SEC("uprobe/paxos_agent:main.(*PaxosNode).Propose")
int BPF_UPROBE(trace_paxos_propose) {
    u64 id = bpf_get_current_pid_tgid(); // 用 pid+tgid 作为 key
    u64 ts = bpf_ktime_get_ns();
    bpf_map_update_elem(&start_times, &id, &ts, BPF_ANY);
    return 0;
}

SEC("uretprobe/paxos_agent:main.(*PaxosNode).Propose")
int BPF_URETPROBE(trace_paxos_propose_ret) {
    u64 id = bpf_get_current_pid_tgid();
    u64 *start_ts = bpf_map_lookup_elem(&start_times, &id);
    if (!start_ts) {
        return 0;
    }

    u64 duration = bpf_ktime_get_ns() - *start_ts;
    bpf_map_delete_elem(&start_times, &id);

    struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
    if (!e) {
        return 0;
    }

    e->duration_ns = duration;
    e->pid = id >> 32;
    bpf_get_current_comm(&e->comm, sizeof(e->comm));
    __builtin_memcpy(e->type, "paxos_propose", sizeof(e->type));

    bpf_ringbuf_submit(e, 0);
    return 0;
}

用户态 Go 程序 (数据收集与导出)

这个程序负责加载 eBPF 程序、从 ring buffer 读取事件,并将其转换为 Prometheus 指标。

// ebpf_monitor/main.go
package main

import (
	"bytes"
	"encoding/binary"
	"errors"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/ringbuf"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall" bpf ../bpf/monitor.bpf.c -- -I../bpf/headers

// Event mirrored from C code
type Event struct {
	DurationNs uint64
	Pid        uint32
	Comm       [16]byte
	Type       [16]byte
}

var (
	httpReqDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
		Name:    "kong_http_request_duration_seconds",
		Help:    "Latency of HTTP requests processed by Kong (observed via eBPF).",
		Buckets: prometheus.DefBuckets,
	})
	paxosProposeDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
		Name:    "paxos_propose_duration_seconds",
		Help:    "Latency of Paxos Propose calls in the agent (observed via eBPF).",
		Buckets: prometheus.DefBuckets,
	})
)

func init() {
	prometheus.MustRegister(httpReqDuration)
	prometheus.MustRegister(paxosProposeDuration)
}

func main() {
	stopper := make(chan os.Signal, 1)
	signal.Notify(stopper, os.Interrupt, syscall.SIGTERM)

	objs := bpfObjects{}
	if err := loadBpfObjects(&objs, nil); err != nil {
		log.Fatalf("loading objects: %v", err)
	}
	defer objs.Close()
	
	// Attach Kprobes for Kong (nginx)
	kp, err := link.Kprobe("tcp_sendmsg", objs.TraceTcpSendmsg, nil)
	if err != nil {
		log.Fatalf("attaching kprobe: %v", err)
	}
	defer kp.Close()

	krp, err := link.Kretprobe("tcp_sendmsg", objs.TraceTcpSendmsgRet, nil)
	if err != nil {
		log.Fatalf("attaching kretprobe: %v", err)
	}
	defer krp.Close()
	
	// Attach Uprobes for Paxos Agent
	// 这里的 "/path/to/paxos_agent" 必须是 Paxos Agent 二进制文件的绝对路径
	ex, err := link.OpenExecutable("/path/to/paxos_agent")
	if err != nil {
		log.Fatalf("opening executable: %v", err)
	}
	
	up, err := ex.Uprobe("main.(*PaxosNode).Propose", objs.TracePaxosPropose, nil)
	if err != nil {
		log.Fatalf("attaching uprobe: %v", err)
	}
	defer up.Close()

	urp, err := ex.Uretprobe("main.(*PaxosNode).Propose", objs.TracePaxosProposeRet, nil)
	if err != nil {
		log.Fatalf("attaching uretprobe: %v", err)
	}
	defer urp.Close()


	// Start Prometheus metrics server
	go func() {
		http.Handle("/metrics", promhttp.Handler())
		log.Println("Metrics server listening on :9091")
		if err := http.ListenAndServe(":9091", nil); err != nil {
			log.Fatalf("Failed to start metrics server: %v", err)
		}
	}()


	rd, err := ringbuf.NewReader(objs.Events)
	if err != nil {
		log.Fatalf("opening ringbuf reader: %s", err)
	}
	defer rd.Close()

	go func() {
		<-stopper
		rd.Close()
	}()

	log.Println("Waiting for events...")

	var event Event
	for {
		record, err := rd.Read()
		if err != nil {
			if errors.Is(err, ringbuf.ErrClosed) {
				log.Println("Received signal, exiting...")
				return
			}
			log.Printf("reading from reader: %s", err)
			continue
		}

		if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
			log.Printf("parsing ringbuf event: %s", err)
			continue
		}

		durationSec := float64(event.DurationNs) / 1e9
		eventType := string(bytes.TrimRight(event.Type[:], "\x00"))
		comm := string(bytes.TrimRight(event.Comm[:], "\x00"))

		log.Printf("eBPF Event: Type=%s, Comm=%s, PID=%d, Duration=%.6fs\n", eventType, comm, event.Pid, durationSec)

		switch eventType {
		case "http_req":
			httpReqDuration.Observe(durationSec)
		case "paxos_propose":
			paxosProposeDuration.Observe(durationSec)
		}
	}
}

第四阶段:集成 Prometheus

最后一步是配置 Prometheus 来抓取我们的 eBPF 监控代理暴露的指标。

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'ebpf_kong_monitor'
    static_configs:
      - targets: ['localhost:9091'] # eBPF 监控代理的地址
graph TD
    subgraph "节点 1"
        K1[Kong Data Plane]
        A1[Paxos Agent]
        E1[eBPF Monitor]
    end
    subgraph "节点 2"
        K2[Kong Data Plane]
        A2[Paxos Agent]
    end
    subgraph "节点 3"
        K3[Kong Data Plane]
        A3[Paxos Agent]
    end
    
    AdminAPI[Admin API Request] -- "发起配置变更" --> A1
    A1 -- "Propose()" --> PaxosCluster
    
    subgraph "Paxos 共识"
        A1 <--> A2
        A2 <--> A3
        A1 <--> A3
    end
    
    PaxosCluster -- "达成共识 (Learn)" --> A1
    PaxosCluster -- "达成共识 (Learn)" --> A2
    PaxosCluster -- "达成共识 (Learn)" --> A3
    
    A1 -- "Apply via local Admin API" --> K1
    A2 -- "Apply via local Admin API" --> K2
    A3 -- "Apply via local Admin API" --> K3
    
    subgraph "eBPF 观测"
        E1 -- "kprobes" --> K1
        E1 -- "uprobes" --> A1
    end
    
    Prometheus[Prometheus TSDB] -- "Scrape /metrics" --> E1
    
    Client[Client] -- "API 请求" --> K1
    
    style PaxosCluster fill:#f9f,stroke:#333,stroke-width:2px
    style E1 fill:#bbf,stroke:#333,stroke-width:2px

至此,我们完成了一个闭环:一个高可用的、自管理的 Kong 集群,以及一个对数据面和我们自定义的控制面进行深度、无侵入观测的系统。这套架构虽然复杂,但在需要极致可用性和可控性的场景下,它展示了组合底层技术的强大威力。

这套方案的局限性也显而易见。首先,我们手写的 Paxos 实现相当简化,缺少了生产环境必需的成员变更、日志压缩、快照等高级功能。直接使用成熟的库如 etcd/raft 会是更务实的选择。其次,eBPF 的 kprobe 监控网络延迟模型也过于简单,它无法轻易地将请求和响应关联起来,在长连接和 HTTP/2 场景下会失效;更健壮的方案需要解析应用层协议,这会显著增加 eBPF 程序的复杂性。最后,高基数问题是 Prometheus 的一个常见挑战,如果对每一个 API endpoint 都进行细粒度监控,标签基数可能会爆炸,需要谨慎设计 Metric 的标签。未来的优化路径可以是在 eBPF 中实现更智能的采样和聚合,或者将原始事件数据发送到专门的日志或追踪系统进行处理,而非直接转换为 Prometheus 指标。


  目录