文章目录
-
- [方案:Init Container + 动态注册------我的最终方案](#方案:Init Container + 动态注册——我的最终方案)
- [实例授权 vs VCPU授权------容器场景下的选型建议](#实例授权 vs VCPU授权——容器场景下的选型建议)
- PVC挂载与授权文件的持久化
- LAC服务端的部署位置
- 生产环境跑了一年半的监控配置
- 容器环境下心跳参数的特殊考量
- 幽灵授权的清理
- 容器化部署的完整清单
- 回过头来想想

兼容 是对前人努力的尊重 是确保业务平稳过渡的基石 然而 这仅仅是故事的起点
上篇把问题讲清楚了------Pod IP会变、MAC是虚拟的、资源弹性跟授权刚性有冲突。这篇聊怎么在实际项目里把这些坑填上。
先声明,我试过的方案有好几个,有些能用有些不能用,有些能凑合用但得加一堆补丁。没有一个方案是完美的,但有至少一条路能让你在生产环境里睡着觉。
方案:Init Container + 动态注册------我的最终方案
折腾了一圈之后,我最后用的是一个"曲线救国"的方案------让lac_agent启动前先搞清楚自己该叫什么名字,而不是依赖自动获取的IP。
思路是这样的:用StatefulSet的序号(ordinal index)作为客户端的唯一标识,启动时通过一个Init Container把自己的"逻辑身份"注册到LAC服务端的一个映射表里,然后把这个逻辑身份对应的某个固定IP(比如Headless Service解析到的IP)写进 lac_agent.conf。
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kes-db
spec:
serviceName: kes-db-headless
replicas: 5
template:
spec:
initContainers:
- name: lac-register
image: busybox:1.36
command:
- sh
- -c
- |
# 获取Pod的序号,作为稳定标识
ORDINAL=${HOSTNAME##*-}
# 生成逻辑IP(用10.99.x.x段作为标识段,不参与实际网络通信)
LOGIC_IP="10.99.1.${ORDINAL}"
# 写lac_agent.conf
cat > /lac-config/lac_agent.conf << EOF
lac_host = lac-server.lac-system.svc.cluster.local
lac_port = 11234
lac_type = Ent
local_ip = ${LOGIC_IP}
lac_interval = 10
use_vcpu_limit = 0
enable_auto_refresh = 1
db_paths = /data/kingbase/data
EOF
echo "Registered kes-db-${ORDINAL} with logic_ip=${LOGIC_IP}"
volumeMounts:
- name: lac-config
mountPath: /lac-config
containers:
- name: kes
image: kingbase/kes:v9r2c14
volumeMounts:
- name: lac-config
mountPath: /opt/Kingbase/ES/V9/Server/share
- name: data
mountPath: /data/kingbase
volumes:
- name: lac-config
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: data-kes-db-0 # 每个Pod用自己的PVC
这里的关键点是 local_ip = 10.99.1.${ORDINAL}。这个IP不是Pod的真实网络IP,而是一个逻辑标识。LAC服务端心跳来的时候,看到的就是 10.99.1.0、10.99.1.1 这种格式的地址,它们跟Pod的实际网络位置无关,只跟StatefulSet的序号绑定。
Pod重建了?没关系,序号没变,local_ip 还是同一个值。Pod漂移到另一个节点了?也没关系,逻辑IP跟节点无关。
但这个方案有个前提------LAC服务端得认这个逻辑IP。也就是说,服务端那边的路由表里不需要真的能路由到 10.99.1.x,因为lac_agent和服务端之间的通信用的是K8s的Service DNS(lac-server.lac-system.svc.cluster.local),跟 local_ip 无关。local_ip 只是lac_agent上报给服务端的"我叫什么",服务端不会尝试回访这个IP。
我反复验证过这一点------lac_agent.conf 里的 local_ip 确实只是个身份标识字段,不是通信地址。通信地址是 lac_host 和 lac_port。
实例授权 vs VCPU授权------容器场景下的选型建议
回到授权类型选择的问题。经过几个项目的实践,我的建议是:
如果你的K8s集群规模不大(30个KES Pod以内),而且实例数量比较固定,选实例授权。
理由很简单:实例授权的逻辑最清晰,一个Pod对应一个授权,跟裸机时代的思维一致。
PVC挂载与授权文件的持久化
不管选哪种方案,PVC的配置都有讲究。KES的数据目录必须挂PVC,这没商量。但 lac_agent.conf 和 license.dat 放在哪?
我的做法是分开挂------数据用PVC持久化,配置文件用ConfigMap+emptyDir:
yaml
# ConfigMap存放lac_agent.conf
apiVersion: v1
kind: ConfigMap
metadata:
name: kes-lac-config
data:
lac_agent.conf: |
lac_host = lac-server.lac-system.svc.cluster.local
lac_port = 11234
lac_type = Ent
lac_interval = 10
use_vcpu_limit = 0
enable_auto_refresh = 1
log_level = INFO
---
# Secret存放license.dat(如果有的话)
apiVersion: v1
kind: Secret
metadata:
name: kes-license
type: Opaque
data:
license.dat: <base64-encoded-license-content>
---
# StatefulSet里的挂载
spec:
template:
spec:
containers:
- name: kes
volumeMounts:
- name: data
mountPath: /data/kingbase
- name: lac-config
mountPath: /opt/Kingbase/ES/V9/Server/share/lac_agent.conf
subPath: lac_agent.conf
- name: license
mountPath: /data/kingbase/license.dat
subPath: license.dat
volumes:
- name: data
persistentVolumeClaim:
claimName: data-{{.PodName}}
- name: lac-config
configMap:
name: kes-lac-config
- name: license
secret:
secretName: kes-license
这样 lac_agent.conf 通过ConfigMap管理,改配置只需要 kubectl edit configmap 然后重启Pod。license.dat 放在PVC的数据目录下,这样即使Pod重建,文件也在。
但要注意------如果你用的是上面方案二的逻辑IP方案,ConfigMap就不能写死 local_ip 了,得用Init Container动态生成。所以实际上ConfigMap里只放通用配置,local_ip 由Init Container追加:
bash
# Init Container里的逻辑
cat /config-map/lac_agent.conf > /runtime-config/lac_agent.conf
echo "local_ip = 10.99.1.${ORDINAL}" >> /runtime-config/lac_agent.conf
LAC服务端的部署位置
这个也很重要------LAC服务端放哪?
放在K8s集群内?放在集群外的裸机上?放在另一个K8s集群里?
我的建议是放在K8s集群外,最好是一台稳定的裸机或虚拟机。原因有三:
第一,LAC服务端是基础设施级别的服务,它的稳定性不应该依赖业务集群。如果K8s集群出了问题(比如etcd挂了、控制面故障),LAC也跟着挂了,那所有KES客户端都拿不到授权、心跳也续不上,整个就死锁了。
第二,LAC服务端的授权文件绑MAC地址。如果服务端也跑在K8s里,你用的是虚拟MAC,前面说过了,这东西不稳定。
第三,运维职责分离。LAC是授权系统,归安全或运维团队管;K8s是业务平台,归开发或平台团队管。两个系统放在同一个集群里,权限边界就模糊了。
bash
# LAC服务端部署在独立虚拟机上
# /opt/KingbaseLAC/bin/lac_server.conf
port = 11234
max_workers = 16 # 大规模客户端场景调大
receive_timeout = 15 # 容器网络延迟可能更大,给宽一点
heart_offline_times = 5 # 配合lac_interval=10,判定窗口50分钟
K8s集群内的Pod通过NodePort或LoadBalancer Service访问集群外的LAC服务端:
yaml
apiVersion: v1
kind: Service
metadata:
name: lac-server-external
spec:
type: ExternalName
externalName: lac-server.internal.company.com # LAC服务端的可达地址
---
# 或者直接用IP
# lac_agent.conf里写
lac_host = 192.168.1.100 # LAC服务端物理机IP
lac_port = 11234
生产环境跑了一年半的监控配置
最后说说监控。LAC在容器环境里跑,你不能只靠肉眼看WEB管理界面。必须把监控接进去。
我的做法是写个sidecar容器,定期调lac_agent的status命令,把结果暴露成Prometheus metrics:
bash
#!/bin/bash
# lac-metrics-exporter.sh
while true; do
STATUS=$(lac_agent status 2>&1)
# 解析状态
if echo "$STATUS" | grep -q "running"; then
LAC_RUNNING=1
else
LAC_RUNNING=0
fi
# 检查license有效性
VALID_DAYS=$(ksql -U system -d test -c "select get_license_validdays();" -t 2>/dev/null | head -1)
# 输出Prometheus格式
cat > /metrics/lac_metrics.prom << EOF
# HELP lac_agent_running Whether lac_agent is running (1=running, 0=stopped)
# TYPE lac_agent_running gauge
lac_agent_running ${LAC_RUNNING}
# HELP lac_license_valid_days Remaining valid days of license
# TYPE lac_license_valid_days gauge
lac_license_valid_days ${VALID_DAYS:-0}
EOF
sleep 60
done
yaml
# Sidecar容器配置
- name: lac-metrics
image: busybox:1.36
command: ["/bin/sh", "/scripts/lac-metrics-exporter.sh"]
ports:
- containerPort: 9100
name: metrics
volumeMounts:
- name: metrics
mountPath: /metrics
- name: scripts
mountPath: /scripts
然后在Grafana里配几个关键告警:
lac_agent_running == 0:lac_agent挂了,立刻告警lac_license_valid_days < 30:授权快到期了,提前一个月告警lac_license_valid_days < 7:授权即将到期,紧急告警- 心跳中断持续时间 > 15分钟:可能掉授权了
这套东西搞完之后,至少我不用再半夜被叫起来看授权有没有掉了。虽然不能说100%无忧,但至少能睡个囫囵觉。
容器环境下心跳参数的特殊考量
容器网络跟裸机网络有个本质区别------延迟和抖动更大。裸机上两台服务器在同一个VLAN里,ping一下0.2ms。容器里跨节点通信,经过veth pair、iptables、overlay网络(如果是Flannel之类的隧道模式),一个来回可能要2~5ms,网络抖动的时候能到50ms以上。
这对LAC的心跳机制有什么影响呢?首先 receive_timeout 不能卡太死。默认5秒在裸机上绰绰有余,但容器里如果赶上节点负载高或者overlay网络拥堵,5秒可能不够lac_agent把一个心跳包发完整。特别是当多个Pod同时向服务端发心跳的时候,服务端的 max_workers 如果不够大,处理队列会积压,某些请求就会因为超时而失败。
我在一个20个KES Pod的项目里调过这个参数。开始用的默认值 max_workers=8,结果每隔几个小时就会有1~2个Pod的心跳超时。后来把 max_workers 调到16,同时把 receive_timeout 从5调到10,问题就消失了。
ini
# 容器环境推荐配置(LAC服务端 lac_server.conf)
max_workers = 16 # 至少是Pod数量的50%~80%
receive_timeout = 10 # 容器网络延迟补偿
heart_offline_times = 5 # 配合lac_interval=10,50分钟判定窗口
客户端那边的 lac_interval 也要考虑容器的资源限制。如果Pod的CPU request只有500m,lac_agent本身要占一点CPU,KES主进程也要占CPU,在资源紧张的时候lac_agent可能被cgroup限流导致心跳延迟。这时候 lac_interval=5 就太紧了,因为5分钟一次心跳,留给延迟恢复的窗口太小。调到10~15分钟,给lac_agent更多的喘息空间。
ini
# 容器环境推荐配置(客户端 lac_agent.conf)
lac_interval = 10 # 别太激进,容器里CPU资源可能紧张
enable_auto_refresh = 1 # 这个永远是1,不要动
幽灵授权的清理
用了一年多容器化LAC之后,我发现一个很烦人的问题------幽灵授权会慢慢累积。
什么是幽灵授权?就是一个Pod被删了、重建了、IP变了,老IP上的授权没有被及时回收,一直挂在服务端的已发放列表里。虽然心跳超时后服务端会把状态标成offline,但offline的授权并不会自动从池子里删掉。它在WEB管理界面上一直显示着,只是标了个灰色的"离线"。
时间一长,服务端可能积累了上百条offline记录,而激活文件里的授权总数是有限的。这些幽灵记录虽然不直接占license配额(offline的授权理论上可以被重新分配),但它们会干扰你的判断------你在WEB界面上看,100个客户端里只有30个在线,你以为授权池有70个富余,但其实那70个offline记录对应的Pod可能随时重新上线(比如之前只是网络抖动,Pod进程其实还在)。
bash
# 通过lac_manager命令行清理offline的授权记录
# 先查看所有客户端状态
./lac_manager list-clients
# 手动回收某个offline客户端的授权
./lac_manager revoke --client-ip 10.244.2.15
# 批量清理(写个脚本)
for ip in $(./lac_manager list-clients --status offline | awk '{print $1}'); do
echo "Revoking $ip ..."
./lac_manager revoke --client-ip $ip
done
我后来写了个定时清理脚本,每周跑一次,把所有offline超过7天的记录清掉。这个脚本放在LAC服务端的crontab里:
bash
# /opt/KingbaseLAC/scripts/cleanup-ghost.sh
#!/bin/bash
LAC_BIN=/opt/KingbaseLAC/bin
LOG=/opt/KingbaseLAC/log/cleanup.log
echo "[$(date)] Starting ghost license cleanup..." >> $LOG
# 获取offline超过7天的客户端IP列表
OFFLINE_CLIENTS=$($LAC_BIN/lac_manager list-clients --status offline --days-since-heartbeat 7 | awk 'NR>2 {print $1}')
for ip in $OFFLINE_CLIENTS; do
$LAC_BIN/lac_manager revoke --client-ip $ip 2>>$LOG
echo "[$(date)] Revoked $ip" >> $LOG
done
echo "[$(date)] Cleanup done. Processed $(echo "$OFFLINE_CLIENTS" | wc -l) clients." >> $LOG
cron
# 每周日凌晨3点清理
0 3 * * 0 /opt/KingbaseLAC/scripts/cleanup-ghost.sh
这个不算是多高级的方案,但确实帮我避免了授权池被幽灵记录占满的问题。如果LAC能加一个"自动回收offline超过N天的授权"的配置项就好了,但目前没有。
容器化部署的完整清单
搞了这么久,我把容器化环境下部署LAC的checklist整理一下,给后面的人省点时间:
□ LAC服务端部署在K8s集群外的物理机或虚拟机上
□ LAC服务端授权文件绑定的是物理机MAC,不是虚拟MAC
□ lac_server.conf中max_workers≥Pod数量×60%
□ lac_server.conf中receive_timeout≥10
□ lac_server.conf中heart_offline_times≥3(根据网络质量调整)
□ 客户端lac_agent.conf中local_ip使用逻辑标识而非Pod IP
□ 客户端lac_agent.conf中enable_auto_refresh=1
□ lac_agent通过Init Container动态注入local_ip
□ license.dat通过Secret或PVC持久化
□ lac_agent.conf通过ConfigMap管理,支持热更新
□ 配置Prometheus监控lac_agent运行状态
□ 配置授权到期提前30天告警
□ 配置offline授权定期清理(每周)
□ 预留授权池余量≥实际需求的30%(应对Pod重建期间的双重占用)
□ HPA的最大副本数×单实例授权数 < 激活文件总授权数
□ 确认CNI插件类型,评估MAC地址变化对服务端的影响
这份清单不完美,但能覆盖大部分坑了。
上面这些脚本和配置,说难听点都是在打补丁。我其实一直想写个更完善的LAC容器化运维工具------自动处理Pod漂移的授权迁移、自动清理幽灵记录、自动适配CNI插件的MAC变化......但一直没抽出时间。前两天看到金仓社区在办"2026智能运维工具开发大赛",感觉这个方向还挺对路的,正好把我这一年多踩的坑沉淀成工具代码交上去。不管参不参赛,这个方向确实值得有人去做
传送门:
回过头来想想
容器化环境下搞授权管理,本质上是在两种完全不同的设计哲学之间找平衡。K8s说"一切都是临时的",LAC说"一切都是固定的"。你不可能让其中一个完全迁就另一个,只能在中间找到一条勉强能走的路。
我的方案二(Init Container+逻辑IP)不是最优解,但它是在"不改LAC底层代码"的前提下能做到的最稳的方案了。如果LAC团队未来能支持用自定义标识(比如hostname或UUID)替代IP作为客户端身份,那容器化授权的问题就能从根本上解决。
但在那之前,我们只能在夹缝中求生存。