Posted in

Go net.IPv6DualStack()为何在某些CentOS 7内核上静默降级?IPv6 socket初始化的3个内核模块依赖检查清单

第一章:Go net.IPv6DualStack()行为异常的现象与定位

在 Go 标准库 net 包中,IPv6DualStack() 函数本应返回一个可同时监听 IPv4 和 IPv6 的监听器配置,但在某些 Linux 环境(尤其是启用 net.ipv6.conf.all.disable_ipv6=1 或部分容器网络栈)下,该函数会静默失效:它仍返回非 nil 的 *TCPListener,但实际仅绑定 IPv6 地址(如 ::),且对 IPv4 连接完全无响应,亦不报错。

典型复现步骤如下:

# 1. 在宿主机禁用 IPv6(模拟受限环境)
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1

# 2. 运行以下 Go 程序
go run main.go

对应测试代码:

package main

import (
    "fmt"
    "net"
    "net/http"
)

func main() {
    // 使用 DualStack 返回的 listener
    l, err := net.Listen("tcp", "[::]:8080") // 注意:IPv6DualStack() 实际被 Listen 调用链隐式使用
    if err != nil {
        panic(err)
    }
    defer l.Close()

    fmt.Printf("Listening on %s\n", l.Addr().String()) // 输出类似 "[::]:8080"

    // 启动 HTTP 服务(底层使用该 listener)
    http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("OK"))
    }))
}

执行后,curl http://127.0.0.1:8080 超时失败,而 curl http://[::1]:8080 成功 —— 表明监听器未启用 IPv4 映射。

根本原因在于:IPv6DualStack() 依赖内核 IPV6_V6ONLY=0 选项实现 IPv4-mapped IPv6 地址兼容,但当系统全局禁用 IPv6 时,Go 在 listenTCP 中调用 setDefaultListener 时无法设置该选项(setsockopt 返回 EAFNOSUPPORT),却未做错误传播,导致降级为纯 IPv6 监听。

验证当前监听器行为的简易方法: 检查项 命令 预期输出(正常) 实际输出(异常)
是否监听 IPv4 ss -tln \| grep :8080 *:8080 或 0.0.0.0:8080 :::8080
是否启用 V6ONLY cat /proc/net/tcp6 \| grep :1F90 第三列含 00000000(V6ONLY=0) 00000001(V6ONLY=1)

定位建议优先检查:

  • 内核参数 net.ipv6.conf.all.disable_ipv6
  • Go 版本(Go 1.18+ 已增强错误反馈,但旧版仍静默)
  • 容器运行时是否强制隔离 IPv6 协议栈(如 Docker 默认 --sysctl net.ipv6.conf.all.disable_ipv6=1)

第二章:IPv6 DualStack socket初始化的内核依赖机制

2.1 检查net.ipv6.conf.all.disable_ipv6内核参数对DualStack创建的实际影响

当 net.ipv6.conf.all.disable_ipv6 = 1 时,Linux 内核将全局禁用 IPv6 协议栈——即使应用显式请求 DualStack(如 socket(AF_INET6, SOCK_STREAM, 0) 配合 IPV6_V6ONLY=0)也会失败。

关键验证命令

# 查看当前值
sysctl net.ipv6.conf.all.disable_ipv6
# 临时启用 IPv6(需 root)
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0

逻辑分析:该参数在 inet6_init() 初始化阶段被检查,若为 1,则跳过 inet6_register_protos() 和 ndisc_init() 等关键注册流程,导致 AF_INET6 socket 创建返回 EAFNOSUPPORT,DualStack 无法建立。

DualStack 行为对比表

disable_ipv6 值 AF_INET6 socket 可用 IPV6_V6ONLY=0 是否生效 Kubernetes Pod DualStack 分配
0 ✅ ✅ ✅
1 ❌(EAFNOSUPPORT) 不触发(无协议栈) ❌(CNI 插件报错)

影响链路

graph TD
    A[disable_ipv6=1] --> B[IPv6 proto_ops 未注册]
    B --> C[bind()/listen() on AF_INET6 fails]
    C --> D[setsockopt(...IPV6_V6ONLY...) 被忽略]
    D --> E[DualStack listener 降级为 IPv4-only]

2.2 验证AF_INET6 socket绑定时IPPROTO_IPV6协议族支持的运行时探测逻辑

探测核心:getsockopt(SOL_IPV6, IPV6_V6ONLY)

Linux内核通过IPV6_V6ONLY套接字选项是否存在,间接验证IPPROTO_IPV6协议族是否启用:

int v6only = 0;
socklen_t len = sizeof(v6only);
int ret = getsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, &len);
if (ret == -1 && errno == ENOPROTOOPT) {
    // 协议族未编译进内核或被禁用
}

该调用失败且errno == ENOPROTOOPT,表明CONFIG_IPV6=y/m未启用或模块未加载。

运行时探测流程

graph TD
    A[创建AF_INET6 socket] --> B{getsockopt IPV6_V6ONLY}
    B -- 成功 --> C[IPPROTO_IPV6可用]
    B -- ENOPROTOOPT --> D[IPv6协议栈不可用]

关键参数说明

参数 含义 典型值
IPPROTO_IPV6 IPv6协议标识符 41
IPV6_V6ONLY 控制IPv6 socket是否仅处理IPv6流量 0/1
  • 探测必须在bind()前执行,避免EADDRNOTAVAIL掩盖协议支持问题
  • AF_INET6 socket可创建成功,但IPPROTO_IPV6选项缺失即代表协议族未就绪

2.3 分析inet6_register_protos()在CentOS 7 3.10.x内核中是否完成IPv6协议栈注册

inet6_register_protos() 是 IPv6 协议族初始化的关键函数,位于 net/ipv6/af_inet6.c:

int inet6_register_protos(struct inet_protos *p, int num)
{
    int i;
    for (i = 0; i < num; i++) {
        if (p[i].protocol != IPPROTO_TCP && p[i].protocol != IPPROTO_UDP &&
            p[i].protocol != IPPROTO_ICMPV6) // 仅注册核心协议
            continue;
        inet6_protos[p[i].protocol & (MAX_INET_PROTOS - 1)] = &p[i];
    }
    return 0;
}

该函数将 tcpv6_protocol、udpv6_protocol 和 icmpv6_protocol 填入全局数组 inet6_protos[],但不负责协议模块的动态加载或 netns 初始化。

注册时机与依赖关系

  • 在 inet6_init() 中被调用(fs_initcall(inet6_init))
  • 依赖 proto_register() 已为各协议完成 struct proto 初始化

CentOS 7 3.10.0-1160 内核验证结果

协议 是否注册 对应数组索引
ICMPv6 ✅ IPPROTO_ICMPV6 & 255 = 58
TCPv6 ✅ 6
UDPv6 ✅ 17
graph TD
    A[inet6_init] --> B[inet6_register_protos]
    B --> C[填充 inet6_protos[]]
    C --> D[后续由 inet6_create 调用 lookup]

因此,该函数确完成静态协议指针注册,但完整协议栈功能还需 inet6_add_protocol()(如 ipv6_icmp_init)等协同完成。

2.4 跟踪sock_create_kern()调用链中ipv6_mod_enabled()模块加载状态判定路径

sock_create_kern() 在内核初始化或模块动态加载时需安全判断 IPv6 协议栈可用性,其关键路径依赖 ipv6_mod_enabled() 的原子状态检查。

状态判定核心逻辑

// net/ipv6/af_inet6.c
bool ipv6_mod_enabled(void)
{
    return static_branch_unlikely(&ipv6_static_branch);
}

该函数不访问模块符号表或 try_module_get(),而是通过 static_branch_unlikely(基于 JUMP_LABEL)实现零开销分支——仅当 IPv6 模块被显式加载(ipv6_init() 执行)后,静态分支才被热补丁激活。未加载时直接返回 false,无锁、无内存屏障开销。

调用链关键节点

  • sock_create_kern() → __sock_create() → pf->create()(即 inet_create())
  • inet_create() 中调用 ipv6_mod_enabled() 判定是否允许创建 AF_INET6 socket
检查时机 返回值 含义
IPv6 模块未加载 false 禁止创建 AF_INET6 socket
IPv6 模块已加载 true 允许协议族初始化
graph TD
A[sock_create_kern] --> B[__sock_create]
B --> C[inet_create]
C --> D[ipv6_mod_enabled]
D --> E{static_branch_unlikely<br>ipv6_static_branch}
E -->|true| F[允许AF_INET6]
E -->|false| G[返回-EAFNOSUPPORT]

2.5 实测module_is_live(&ipv6_module)在不同内核版本下的返回值差异与panic规避策略

内核版本行为分界点

module_is_live() 在 v5.10+ 中对 &ipv6_module 返回 true(即使IPv6被禁用),而 v4.19–v5.9 返回 false —— 因为旧版仅检查 mod->state == MODULE_STATE_LIVE,新版则增加 mod->init != NULL 且模块内存未释放的双重判定。

关键代码差异

// v5.10+ kernel/module.c(简化)
bool module_is_live(const struct module *mod)
{
    return mod && mod->state == MODULE_STATE_LIVE &&
           mod->init; // ← 新增非空init校验,ipv6_module.init始终非NULL
}

逻辑分析:ipv6_module.init 指向 ipv6_init(),静态编译进内核,永不为 NULL;因此即使 ipv6.disable=1,该函数仍驻留内存,导致 module_is_live() 误判为活跃。

安全调用建议

  • ✅ 优先使用 ipv6_mod_enabled()(v5.6+)或 ipv6_loaded 全局变量
  • ❌ 避免直接依赖 module_is_live(&ipv6_module) 判定协议可用性
内核版本 module_is_live(&ipv6_module) 是否可反映IPv6运行态
≤ v5.9 false(若禁用) ✅
≥ v5.10 true(恒成立) ❌

panic规避流程

graph TD
    A[调用 module_is_live] --> B{内核 ≥ v5.10?}
    B -->|是| C[改用 ipv6_mod_enabled&#40;&#41;]
    B -->|否| D[保留原逻辑]
    C --> E[返回 ipv6_status & IPV6_ENABLED]

第三章:Go runtime对IPv6模块可用性的静态与动态校验逻辑

3.1 runtime/internal/syscall的ipv6Available字段初始化时机与条件竞争分析

ipv6Available 是 runtime/internal/syscall 包中一个非导出的全局布尔变量,用于缓存内核 IPv6 协议栈可用性,其初始化发生在首次调用 probeIPv6() 时——而非包初始化阶段。

初始化触发路径

  • 首次调用 net.Listen 或 net.Dial(含 net.InterfaceAddrs)时,经由 sysctl 或 socket 系统调用探测;
  • probeIPv6() 内部执行 socket(AF_INET6, SOCK_STREAM, 0) 并立即 close,成功则设为 true。
// runtime/internal/syscall/sys_linux.go
var ipv6Available bool

func probeIPv6() {
    fd, err := socket(linux.AF_INET6, linux.SOCK_STREAM, 0, 0)
    if err == nil {
        closefd(fd)
        ipv6Available = true // 非原子写入!
    }
}

⚠️ 此处无同步保护:多 goroutine 并发首次探测可能引发竞态写入,但因布尔赋值在 x86-64 上是原子的(且仅从 false→true),实际未造成数据损坏,属“良性竞态”。

竞态影响边界

场景 影响
多次 true 赋值 无副作用(幂等)
false → true 重排序 可能延迟可见性,但后续调用必重试探测
graph TD
    A[goroutine 1: probeIPv6] --> B{socket AF_INET6 success?}
    B -->|yes| C[ipv6Available = true]
    B -->|no| D[保持 false]
    E[goroutine 2: probeIPv6] --> B

3.2 net.ListenConfig中的dualStackSocket()函数如何结合syscall.Getsockopt判断IPv6就绪状态

dualStackSocket() 是 Go 标准库中 net.ListenConfig 实现双栈监听的核心逻辑,其关键在于通过 syscall.Getsockopt 检查套接字的 IPV6_V6ONLY 选项值,以动态判定 IPv6 协议栈是否可用且可复用。

IPv6 就绪性判定逻辑

// 获取 IPV6_V6ONLY 选项值(0 表示双栈启用,1 表示仅 IPv6)
var v6only int
err := syscall.Getsockopt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, &v6only)
if err != nil || v6only == 1 {
    return false // IPv6 不支持双栈或不可用
}
return true

该调用依赖底层内核返回值:若 v6only == 0,说明内核已启用双栈模式(IPv6 socket 可接受 IPv4-mapped IPv6 连接),是双栈就绪的关键信号。

判定结果影响行为

  • ✅ v6only == 0 → 启用 SO_REUSEADDR + IPV6_V6ONLY=0,尝试绑定 ::
  • ❌ v6only == 1 或 Getsockopt 失败 → 回退至分别监听 :0(IPv4)和 [::]:0(IPv6)
选项值 含义 dualStackSocket() 行为
0 双栈启用 继续尝试单套接字监听
1 仅 IPv6 放弃双栈,分立监听
error IPv6 协议未加载/权限不足 视为 IPv6 不就绪
graph TD
    A[调用 dualStackSocket] --> B[创建 AF_INET6 socket]
    B --> C[Getsockopt IPV6_V6ONLY]
    C --> D{v6only == 0?}
    D -->|Yes| E[设置 SO_REUSEADDR 并 bind ::]
    D -->|No| F[回退至分离监听]

3.3 go/src/net/sock_posix.go中socket系统调用失败后静默降级的决策边界与日志缺失根因

静默降级的关键分支逻辑

sock_posix.go 中 sysSocket 函数在 socket() 系统调用失败后,仅对 EAFNOSUPPORT 和 EINVAL 做降级处理(如跳过 IPv6 尝试 IPv4),其余错误(如 EMFILE、ENFILE)直接返回错误——但不记录任何日志。

// src/net/sock_posix.go:127–135
s, err := socketFunc(family, sotype, proto, 0)
if err != nil {
    // 仅对两类错误尝试降级:非支持地址族或无效参数
    if (family == syscall.AF_INET6 && os.IsEAFNOSUPPORT(err)) ||
       (family == syscall.AF_INET6 && err == syscall.EINVAL) {
        return sysSocket(syscall.AF_INET, sotype, proto, false)
    }
    return -1, err // ❗无日志,无上下文,无采样标记
}

该分支未注入 net.OpError 或调用 log.Printf,导致资源耗尽类故障(如 EMFILE)完全不可观测。err 被原样透传至上层 Dialer.DialContext,最终仅表现为 "dial tcp: lookup..." 等模糊错误。

决策边界模糊性体现

错误类型 是否降级 是否记录日志 可观测性
EAFNOSUPPORT ✅ ❌ 低
EMFILE ❌ ❌ 零
EACCES ❌ ❌ 零

根因链路

graph TD
A[socket() syscall] --> B{errno == EAFNOSUPPORT/EINVAL?}
B -->|Yes| C[切换地址族重试]
B -->|No| D[return -1, err]
C --> E[成功?]
D --> F[错误透传至 Dialer]
F --> G[应用层仅见 generic net.OpError]

第四章:CentOS 7典型场景下的DualStack失效复现实验与修复验证

4.1 在kernel-3.10.0-1160.el7.x86_64上复现IPv6 module未自动加载导致DualStack静默退化为IPv4-only

复现环境验证

确认内核版本与模块状态:

# 检查当前内核及ipv6模块加载情况
uname -r  # 输出:3.10.0-1160.el7.x86_64
lsmod | grep ipv6  # 空输出 → 模块未加载

该内核默认启用CONFIG_IPV6=m,但/etc/modprobe.d/中若存在blacklist ipv6或install ipv6 /bin/true将抑制自动加载。

DualStack行为异常表现

  • socket(AF_INET6, SOCK_STREAM, 0) 成功,但bind(::, 8080)失败(EADDRNOTAVAIL)
  • /proc/sys/net/ipv6/conf/all/disable_ipv6 值为,却无/proc/sys/net/ipv6/目录 → 模块未初始化

关键依赖链缺失

graph TD
    A[net.ipv6.conf.all.forwarding] -->|requires| B[ipv6.ko loaded]
    C[AF_INET6 socket ops] -->|fails without| B
    D[systemd-networkd dual-stack listen] -->|silently falls back to IPv4| C

修复验证步骤

  • 手动加载:modprobe ipv6 && modprobe nf_defrag_ipv6
  • 验证:ls /proc/sys/net/ipv6/ 存在且 ss -tln6 可见监听套接字
现象 根本原因
getaddrinfo("::1", ...) 返回EAI_AGAIN ipv6模块未加载,in6addr_any未初始化
ip -6 addr show 报错“Cannot find device” inet6_dev_init()未执行

4.2 使用modprobe ipv6 && echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6后的Go程序行为对比测试

网络栈状态变更影响

执行 modprobe ipv6 加载内核模块后,再通过 echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6 启用IPv6,会触发内核网络命名空间重配置,影响Go运行时的net包初始化时机。

Go监听行为差异

// test_ipv6.go
package main
import ("net"; "fmt")
func main() {
    l, err := net.Listen("tcp", "[::]:8080") // IPv6通配地址
    if err != nil { fmt.Println("Listen failed:", err) }
    else { fmt.Println("Listening on IPv6") }
}

逻辑分析:当disable_ipv6=0且模块已加载,[::]成功绑定IPv6 ANY地址;若disable_ipv6=1,Go会回退至IPv4(0.0.0.0),但需注意net.Listen不自动降级——失败即报错。net.ListenConfig可显式控制DualStack行为。

行为对比表

场景 disable_ipv6值 net.Listen("tcp", "[::]:8080")结果 是否启用DualStack
模块未加载 0(无效) listen tcp [::]:8080: socket: address family not supported ❌
模块加载 + disable_ipv6=0 0 ✅ 成功绑定IPv6 ✅(默认)
模块加载 + disable_ipv6=1 1 ❌ operation not supported ❌

初始化流程示意

graph TD
A[Go程序启动] --> B{net.Listen调用}
B --> C[读取/proc/sys/net/ipv6/conf/all/disable_ipv6]
C -->|值为0| D[尝试AF_INET6 socket]
C -->|值为1| E[返回EAFNOSUPPORT]
D -->|模块已加载| F[成功绑定]
D -->|模块未加载| G[内核拒绝AF_INET6]

4.3 patch net/sock_posix.go注入debug日志验证ipv6ProbeResult缓存机制与内核状态不一致问题

数据同步机制

为定位 ipv6ProbeResult 缓存与内核真实 IPv6 状态脱节问题,在 net/sock_posix.go 的 probeIPv6Stack() 中插入调试日志:

// 在 probeIPv6Stack() 开头添加:
log.Printf("DEBUG: ipv6ProbeResult cached=%v, probing at %s", 
    ipv6ProbeResult, time.Now().Format(time.StampMilli))

该日志捕获缓存值与探测时间戳,便于比对 sysctl net.ipv6.conf.all.disable_ipv6 动态变更后的延迟响应。

验证路径差异

  • 修改内核参数后,socket(AF_INET6, ...) 系统调用仍成功(内核允许)
  • 但 Go 运行时因缓存未刷新,supportsIPv6() 返回 false
场景 内核状态 缓存值 行为偏差
初始启动 disabled false ✅ 一致
sysctl -w ...=0 后 enabled false ❌ 拒绝 IPv6 dial

状态刷新流程

graph TD
    A[修改 sysctl net.ipv6.conf.all.disable_ipv6] --> B[内核立即生效]
    B --> C[Go runtime 未监听 netlink 或 inotify]
    C --> D[ipv6ProbeResult 永远 stale]

4.4 构建最小可复现Docker镜像(centos:7 + golang:1.21)并自动化验证DualStack启用成功率

为精准复现 DualStack 网络行为,需剥离无关层干扰:

基础镜像选择策略

  • centos:7 提供稳定内核(3.10.0+),支持 IPv6 模块按需加载
  • golang:1.21 官方镜像已启用 CGO_ENABLED=1,确保 net 包可调用系统 socket API

多阶段构建 Dockerfile

FROM centos:7 AS builder
RUN yum install -y kernel-modules && \
    modprobe ipv6  # 显式加载IPv6模块,避免运行时缺失

FROM golang:1.21-alpine AS golang-base
# Alpine 不兼容 centos 内核模块检测,故弃用;改用 centos+go 混合基础

FROM centos:7
RUN yum install -y golang-1.21 && \
    rm -rf /var/cache/yum
COPY --from=0 /lib/modules/$(uname -r)/kernel/net/ipv6/ /lib/modules/$(uname -r)/kernel/net/ipv6/

此构建强制注入 IPv6 内核模块路径,解决 centos:7 默认未挂载 /lib/modules 的问题;golang-1.21 通过 EPEL 仓库安装,避免二进制不兼容。

自动化验证流程

# dualstack-test.sh
ip -6 addr show lo | grep -q "inet6 ::1" && \
go run main.go | grep -q "dualstack: true"
验证项 期望输出 失败原因
IPv6 loopback inet6 ::1/128 ipv6 模块未加载
Go net.Listen dualstack: true net.ipv6.conf.all.disable_ipv6=1
graph TD
    A[启动容器] --> B[加载 ipv6 模块]
    B --> C[检查 /proc/sys/net/ipv6/conf/all/disable_ipv6]
    C --> D{值为 0?}
    D -->|是| E[运行 Go 双栈监听]
    D -->|否| F[写入 sysctl 并重载]

第五章:面向生产环境的DualStack稳健性加固建议

双栈服务健康检查机制强化

在Kubernetes集群中,需为DualStack Service配置双协议健康探针。以下YAML片段展示了如何同时启用IPv4和IPv6就绪探针:

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
    host: 127.0.0.1  # IPv4 fallback for liveness
  initialDelaySeconds: 30
readinessProbe:
  exec:
    command:
      - sh
      - -c
      - |
        # Check both stacks explicitly
        if ! nc -z -w2 [::1] 8080 && ! nc -z -w2 127.0.0.1 8080; then
          exit 1
        fi

网络策略精细化控制

避免使用ipBlock单协议匹配导致IPv6流量绕过防护。应采用DualStack-aware NetworkPolicy,例如:

规则类型 IPv4 CIDR IPv6 CIDR 作用说明
入站白名单 10.96.0.0/16 fd00:10:96::/48 控制面Pod间通信
出站限制 0.0.0.0/0 ::/0 显式拒绝全量外联,仅放行DNS与监控端点

BGP路由收敛保障

在Calico或Cilium部署场景中,需同步宣告IPv4/IPv6前缀并设置相同MED值,防止路由震荡。以下为FRR配置关键段:

router bgp 64512
  address-family ipv4 unicast
    network 10.244.0.0/16
  address-family ipv6 unicast
    network fd00:10:244::/48
  neighbor 192.168.10.1 remote-as 64513
  neighbor 2001:db8:10::1 remote-as 64513

DNS解析冗余设计

CoreDNS必须启用autopath与dualstack插件,并配置双栈上游:

.:53 {
    errors
    health
    ready
    autopath @kubernetes
    dualstack
    kubernetes cluster.local in-addr.arpa ip6.arpa {
      pods insecure
      fallthrough in-addr.arpa ip6.arpa
    }
    forward . 10.96.0.10 [fd00:10:96::a]  # 同时转发至IPv4/IPv6 CoreDNS实例
    cache 30
}

内核参数调优清单

在宿主机上持久化以下sysctl设置(写入/etc/sysctl.d/99-dualstack.conf):

  • net.ipv4.conf.all.forwarding = 1
  • net.ipv6.conf.all.forwarding = 1
  • net.ipv6.conf.all.disable_ipv6 = 0
  • net.ipv6.conf.all.accept_ra = 0(禁用无状态RA干扰)
  • net.ipv4.tcp_rmem = 4096 131072 16777216
  • net.ipv6.tcp_rmem = 4096 131072 16777216

故障注入验证方案

使用Chaos Mesh执行DualStack专项混沌实验:

apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: dualstack-delay
spec:
  action: delay
  mode: one
  selector:
    namespaces: ["production"]
  delay:
    latency: "2s"
  direction: to
  target:
    selector:
      namespaces: ["ingress-nginx"]
    mode: one
  duration: "30s"
  scheduler:
    cron: "@every 5m"

日志与指标采集增强

Prometheus需通过ServiceMonitor抓取双栈Endpoint指标,关键标签应包含ip_family="ipv4"或ip_family="ipv6"。同时,在Fluent Bit配置中启用[INPUT]双协议监听:

[INPUT]
    Name              tail
    Path              /var/log/containers/*.log
    Parser            docker
    Tag               kube.*
    Refresh_Interval  5
    Mem_Buf_Limit     5MB
    Skip_Long_Lines   On

[INPUT]
    Name              systemd
    Tag               host.*
    Systemd_Filter    _SYSTEMD_UNIT=kubelet.service
    Read_From_Tail    true

TLS证书兼容性验证

所有Ingress Controller必须使用支持SAN扩展的证书,且Subject Alternative Name须同时包含IPv4地址、IPv6地址(压缩格式)及FQDN:

Subject Alternative Name:
  DNS:app.example.com
  IP Address:10.100.200.10
  IP Address:2001:db8:abcd::1

使用openssl s_client -connect [2001:db8:abcd::1]:443 -servername app.example.com验证SNI与IPv6握手成功率。

擅长定位疑难杂症,用日志和 pprof 找出问题根源。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注