P8面试
-
date_range 23/01/2023 09:09
点击量:次infosort网随云动label
一、架构设计与全局视野(10题)
A01. 你主导的 Elevate-SaaS 多租户平台,40+ 租户的隔离方案是怎么演进的?一开始就是四层隔离吗?
参考回答:
不是一步到位的,经历了三个阶段:
第一阶段:逻辑隔离(0-10 租户)。 只用了数据层隔离——MyBatis 拦截器自动注入 tenant_id 条件,所有租户共享同一套 Namespace 和数据库。优点是成本低、交付快;问题是一个租户的慢查询会拖垮整个数据库,而且网络层完全没有隔离,租户之间可以互相访问 Service。
第二阶段:三层隔离(10-25 租户)。 引入了 Namespace 隔离 + RBAC 隔离 + ResourceQuota。每个租户一个 Namespace,RBAC 限制只能访问自己的 Namespace,ResourceQuota 限制 CPU/内存上限。解决了资源争抢问题,但网络层仍然只靠 NetworkPolicy 做基础隔离,对于金融级客户(民生银行)不够。
第三阶段:四层隔离(25+ 租户)。 引入 Kube-OVN VPC 做网络层强隔离,每个大客户一个独立 VPC,子网、路由表、安全组完全独立。同时用 OPA Gatekeeper 做准入策略——强制要求所有资源必须带 tenant-id 标签,没有标签的资源直接拒绝创建。对于合规要求最高的金融客户(民生银行),通过 Karmada 纳管其自建集群,实现物理级隔离。
取舍判断: 四层隔离的代价是运维复杂度显著上升——每新增一个 VPC 租户,需要配置独立的子网段、路由规则、Ingress 入口。我们通过 CRD + Operator 自动化了这个流程:运维只需要创建一个 TenantConfig CRD,Operator 自动创建 Namespace、VPC、RBAC、ResourceQuota、NetworkPolicy 全套资源。
数据支撑: 新租户 onboard 从人工配置 2 周缩短到 3 天(大部分时间在业务对接而非基础设施),资源利用率从 35% 提升到 65%(共享集群的中小租户可以弹性复用资源)。
追问方向:
- MyBatis 拦截器的租户透明隔离具体怎么实现的?JSqlParser 解析 SQL 有什么坑?
- Karmada 纳管自建集群时,跨集群服务发现怎么做?
- OPA 策略有多少条?怎么管理策略的版本和灰度?
A02. DetNet Controller 的”云网双向联动”具体怎么实现?”网络能力反向影响调度决策”是什么意思?
参考回答:
先说正向联动(云→网):K8s 集群里 VM 的生命周期变化需要同步到物理网络。
我们在 DetNet Controller 里用 K8s Watch 监听 VirtualMachineInstance(KubeVirt 的 VMI)的事件。当一个 VM 被创建或迁移时:
- Controller 感知到 VMI 事件,拿到 VM 新的节点位置和 IP
- 调 OVN API 在 VM 所在节点的 OVS 上配置 DSCP 标记规则(比如 EF=46 标记确定性流量)和分流策略(把标记流量导向物理网络出口)
- 调 SDN Controller 的北向 RESTful API,传入源/目的节点信息,SDN Controller 用 CSPF 算法在物理拓扑上计算最优 SRv6 路径,然后通过 Netconf 南向接口逐设备下发流表
再说反向联动(网→云),这是核心创新点:
物理网络的能力应该影响 K8s 的调度决策。举个例子:某条链路拥塞导致时延超标,DetNet Controller 通过 OAM 监控发现后,不仅要重算路径(正向),还要把”这条链路的可用带宽下降了”这个信息反馈给 K8s 调度器——如果有新的 VM 需要创建,就不应该调度到这条链路覆盖的节点上。
具体实现:DetNet Controller 维护一个”网络能力视图”(从 SDN Controller 定期拉取链路带宽、时延、丢包数据),然后通过 K8s 的 Extended Resource 或者自定义 Scheduler Plugin 把网络能力注入调度决策。比如给节点打上 detnet.io/available-bandwidth: 8Gbps 的扩展资源,当用户创建 VM 时声明 requests: {detnet.io/bandwidth: 2Gbps},调度器就只会选择剩余带宽足够的节点。
四个闭环:
| 触发事件 | 动作 | 闭环 |
|---|---|---|
| VM 创建 | OVN 标记 + SDN 建路径 | 创建→路径建立 |
| VM 迁移 | 感知新位置→重算路径 | 迁移→重算 |
| 链路拥堵 | OAM 告警→CSPF 重算→SDN 更新 | 拥堵→自愈 |
| 带宽不足 | 更新节点扩展资源→影响调度 | 能力→调度 |
追问方向:
- CSPF 算法的约束条件有哪些?和 Dijkstra 的区别?
- VM 迁移过程中(live migration),确定性路径会断多久?怎么做到无缝切换?
- 网络能力视图多久更新一次?延迟超标的判断阈值怎么设?
A03. 你说用 CNCF 体系替代 OpenStack,具体替代映射关系是什么?为什么不直接用 OpenStack?
参考回答:
替代映射关系:
| OpenStack 组件 | CNCF 替代方案 | 替代理由 |
|---|---|---|
| Nova(计算) | KubeVirt | VM 作为 K8s Pod 调度,统一容器和 VM 的生命周期管理 |
| Neutron(网络) | Kube-OVN | 支持 VPC、子网、安全组、QoS,且天然融入 K8s NetworkPolicy 体系 |
| Cinder(块存储) | Longhorn | 声明式 PVC/StorageClass,自动副本复制,运维复杂度远低于 Ceph |
| Glance(镜像) | Harbor | 不仅管 VM 镜像,还管容器镜像,统一制品管理 |
| Keystone(认证) | K8s RBAC + Dex | OIDC 标准集成,不需要单独维护一套认证系统 |
| Heat(编排) | Helm + Kustomize | 声明式编排,GitOps 管理 |
为什么不用 OpenStack:
核心原因是运维成本和人才成本。OpenStack 是一个庞大的 Python 体系,每个组件都有自己的数据库、消息队列、API 服务——一个最小化部署也需要 MySQL + RabbitMQ + Keystone + Glance + Nova + Neutron + Cinder,加起来几十个服务进程。升级一次大版本(比如 Yoga→Zed)要做大量兼容性测试。
而 K8s + CNCF 的方案,底座就是一个 K8s 集群,所有上层能力(计算、网络、存储)都是 CRD + Operator 的模式,统一用 kubectl/Helm 管理,运维工具链收敛。更重要的是,K8s 人才市场远大于 OpenStack——招一个懂 K8s 的比招一个懂 OpenStack 的容易得多。
取舍和代价: KubeVirt 在 VM 管理能力上确实还不如 Nova 成熟——比如 GPU 直通、SR-IOV 网卡绑定、在线磁盘扩容等场景,KubeVirt 要么不支持要么需要额外适配。我们的方案是先覆盖 80% 的常规场景,特殊场景通过自定义 Device Plugin 逐步补齐。
A04. Dapr Sidecar 替代 SDK 直连中间件,具体解决了什么问题?性能损耗怎么样?
参考回答:
解决的核心问题:中间件锁定。 传统微服务中,业务代码直接依赖具体中间件的 SDK——用 Redis 就 import jedis,用 Kafka 就 import kafka-clients。迁移公有云时,阿里云的 Redis 可能需要换 SDK,消息队列从 Kafka 换成 RocketMQ,每个服务都要改代码、改依赖、重新测试。
Dapr 的做法是在业务代码和中间件之间插入一个 Sidecar 代理。业务代码只跟 Dapr 的标准 API 交互(HTTP/gRPC),具体用什么中间件由 Dapr 的 Component YAML 配置决定。迁移时只改 Component 配置,业务代码零修改。
实际效果: 我们有一个客户从自建 IDC 迁移到阿里云,涉及 Redis→阿里云 Redis、Kafka→RocketMQ、MinIO→OSS 三个中间件替换。用了 Dapr 之后,20+ 微服务的代码完全没改,只改了 3 个 Component YAML 文件,迁移周期从预估的 2 个月缩短到 2 周。
性能损耗: Sidecar 模式不可避免有额外开销。实测数据:
- 延迟增加:P99 从 12ms 增加到 15ms(增加约 3ms,主要是 localhost loopback + 序列化开销)
- 吞吐量:下降约 8-10%
- 内存:每个 Sidecar 约 50-80MB
取舍: 对于延迟敏感的场景(比如实时风控),我们没有用 Dapr,而是直接 SDK 调用。对于绝大多数 CRUD 业务场景,3ms 的额外延迟完全可接受,换来的可移植性远超代价。
A05. Karmada 多云联邦的实际使用场景是什么?跨集群调度的策略怎么设计?
参考回答:
核心场景:合规隔离 + 成本优化。
金融客户(民生银行、中石化)有监管合规要求——数据不能出自有机房、计算资源必须在指定地域。但他们又想用我们的 PaaS 平台能力。所以方案是:客户自建 K8s 集群,通过 Karmada 纳管到我们的联邦控制面,我们的 PaaS 管理平台可以统一管理所有集群,但数据和计算始终留在客户的集群里。
普通中小客户没有合规要求,直接调度到公有云集群,按需弹性伸缩,成本更低。
调度策略设计:
# PropagationPolicy 示例
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
spec:
placement:
clusterAffinity:
# 金融客户绑定指定集群
clusterNames: ["minsheng-private"]
# 或者按标签选择
clusterAffinity:
labelSelector:
matchLabels:
compliance: "financial"
# 溢出策略:自建集群资源不足时溢出到公有云
failover:
application:
decisionConditions:
tolerationSeconds: 300
purgeMode: Graciously
三种策略模式:
- 绑定模式:金融客户→固定集群,不允许溢出
- 弹性溢出:普通客户→优先自建集群,资源不足时溢出到公有云
- 灾备模式:主集群故障→自动 Failover 到备集群,
tolerationSeconds: 300表示等待 5 分钟后触发迁移
踩坑经历: Karmada 的跨集群服务发现依赖 ServiceExport/ServiceImport(MCS API),但实际使用中发现跨集群 DNS 解析延迟较高(首次解析 200-500ms)。最终对于高频跨集群调用,我们在应用层做了服务注册中心(Nacos 跨集群同步),绕过了 MCS API。
A06. 你简历提到 Cilium eBPF 做 L7 流分类,”同一服务不同 API 走不同网络路径”具体怎么实现?
参考回答:
场景: 一个视频会议服务,它的 /api/video/stream(实时音视频流)需要走 DetNet 确定性路径保证低延迟,而 /api/video/metadata(元数据查询)走普通 Best-Effort 路径就行。传统 DSCP 标记只能到 L3/L4(按源IP/目的IP/端口),区分不了同一个服务的不同 API。
实现方式: Cilium 的 eBPF 程序挂载在 Pod 的 veth 出口(TC egress hook),可以解析到 L7——看到 HTTP 请求的 URL、Header、Method。我们利用这个能力做细粒度分流:
# CiliumNetworkPolicy 的 L7 规则
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
spec:
endpointSelector:
matchLabels:
app: video-service
egress:
- toEndpoints:
- matchLabels:
detnet-gateway: "true"
toPorts:
- ports:
- port: "443"
rules:
http:
- method: "POST"
path: "/api/video/stream.*"
# 匹配到的流量被标记为 DSCP EF(46)
eBPF 程序识别到匹配的 HTTP 请求后,在 IP 头设置 DSCP=46,然后 OVN 的分流规则会根据 DSCP 值把这个包导向 DetNet 物理网关。其余不匹配的流量走默认路径(DSCP=0)。
和纯 OVN 方案的区别: OVN 的 QoS 规则只能到 L3/L4(按 IP 段、端口匹配),做不到按 HTTP URL 分流。Cilium eBPF 在 L7 做判断后再打 DSCP 标记,OVN 只需要根据 DSCP 值分流,两者配合实现了 L7→L3 的标记传递。
性能影响: eBPF 在内核态执行,不走用户态代理(不像 Istio 的 Envoy),实测每包处理延迟增加 < 50μs,对于确定性网络场景可以接受。
A07. 你的 IaaS 平台是怎么做容灾的?Karmada Failover 的 RTO/RPO 是多少?
参考回答:
容灾分三层:
集群内容灾(单集群高可用):
- etcd 三节点部署,每 6 小时快照备份到 MinIO
- 控制面组件(API Server、Controller Manager、Scheduler)每个 3 副本
- 工作负载通过 PodAntiAffinity 打散到不同节点
- Longhorn 存储默认 3 副本,跨节点复制
跨集群容灾(Karmada Failover):
- 配置
tolerationSeconds: 300,即主集群失联 5 分钟后触发 Failover - Karmada 在备集群重新创建工作负载
- 实测 RTO ≈ 8-12 分钟(5 分钟等待 + 3-7 分钟 Pod 重建和就绪)
- RPO 取决于数据同步策略:
- 无状态服务:RPO=0(没有数据丢失)
- 有状态服务:RPO = 最后一次数据同步的时间差。我们对关键数据用 MySQL 主从同步(异步复制,RPO ≈ 秒级),对于不关键的缓存数据接受丢失
数据层容灾(最难的部分):
- MySQL:基于 GTID 的异步主从复制,RPO ≈ 1-3 秒
- Redis:Sentinel 哨兵模式,主从自动切换
- PV 数据:Longhorn 跨集群备份到 S3/MinIO,定时增量快照
取舍: 我们没有追求同步复制(RPO=0),因为跨数据中心同步复制会显著增加写入延迟(每次写操作都要等远端确认)。金融客户对 RPO 要求高的场景,用 Percona XtraDB Cluster 做多主同步复制,代价是写入吞吐下降约 40%。
A08. “新租户交付从 2 周缩短至 3 天”,这个过程中自动化了哪些环节?还有哪些是手动的?
参考回答:
之前 2 周的时间花在哪里:
| 阶段 | 耗时 | 内容 |
|---|---|---|
| 基础设施 | 3-4 天 | 创建 Namespace、配置 RBAC、设置 ResourceQuota、配置网络策略 |
| 中间件 | 2-3 天 | 部署租户专属的 MySQL schema/Redis 实例/Kafka topic |
| 应用部署 | 2-3 天 | 修改配置文件、构建镜像、部署服务、配置 Ingress |
| 联调测试 | 3-4 天 | 端到端功能验证、性能压测、安全检查 |
自动化后 3 天:
基础设施(从 3-4 天 → 10 分钟):Operator 监听 TenantConfig CRD,自动创建全套资源。
中间件(从 2-3 天 → 30 分钟):Helm Chart 模板化,一个 values.yaml 描述租户的中间件需求,Argo CD 自动部署。
应用部署(从 2-3 天 → 2 小时):Kustomize overlay 按租户生成配置差异,Flux GitOps 自动同步。
还是手动的部分: 联调测试仍然需要 2-3 天。因为每个行业客户的业务逻辑不同,需要人工验证业务流程正确性。我们正在做的优化是建立租户验收测试套件(基于 Playwright 的 E2E 测试模板),目标是把联调测试从 3 天缩短到 1 天。
A09. 你怎么评估一个技术方案的好坏?有没有你主导过的方案后来证明是错误的?
参考回答:
评估框架(四个维度):
- 解决问题的有效性:方案是否真正解决了核心问题,而不是表面问题
- 系统性代价:引入了多少额外复杂度、运维成本、性能损耗
- 团队可执行性:团队能不能 own 住这个方案,学习曲线是否可接受
- 演进空间:方案是否支持未来 2-3 年的业务增长,还是很快就要推倒重来
一个失败案例: 早期我们在 SaaS 平台的配置管理上选择了自研配置中心(基于 ZooKeeper + Spring Cloud Config),理由是”完全可控”。后来发现三个问题:
- ZooKeeper 的 watch 机制在大量 key 变更时有性能瓶颈(session timeout 频繁)
- 自研的配置版本管理、灰度发布、回滚机制远不如成熟方案完善
- 维护成本太高,团队有 2 个人全职维护配置中心
最终切换到了 Nacos,迁移花了 3 周,但之后的维护成本降低了 80%。这个教训让我学到:在非核心竞争力的领域,优先选择社区成熟方案,不要重复造轮子。 只有在核心差异化的地方(比如 DetNet Controller 的云网联动逻辑)才值得自研。
A10. 如果让你从零设计一个多云 PaaS 平台,你会怎么做技术选型?和现在的 Elevate-SaaS 有什么不同?
参考回答:
如果重来,最大的三个变化:
第一,控制面和数据面彻底分离。 现在的 Elevate-SaaS,控制面(管理平台、API Gateway、配置中心)和数据面(业务微服务)跑在同一个 K8s 集群里。应该把控制面独立成一个管理集群,数据面按租户/地域拆成多个业务集群。管理集群挂了不影响业务运行,业务集群挂了不影响其他租户。
第二,从 Dapr 换成更轻量的方案。 Dapr 的 Sidecar 内存占用(50-80MB/pod)在大规模场景下成本可观(1000 个 Pod 就是 50-80GB 额外内存)。如果重来,对于只需要服务发现和配置管理的简单场景,用 K8s 原生能力(Service + ConfigMap + Secret)就够了;只在确实需要中间件抽象的场景用 Dapr。
第三,一开始就设计好租户计量和计费。 现在的平台缺少精细化的资源计量——知道每个租户用了多少 CPU/内存,但不知道每个 API 调用消耗了多少资源。如果重来,会在 API Gateway 层就植入计量逻辑,为后续的按量计费打好基础。
二、K8s 与云原生深度(10题)
B01. K8s HPA 的底层工作原理是什么?你在项目中做过哪些自定义 HPA?
参考回答:
HPA Controller 每 15 秒(默认)从 Metrics Server 拉取 Pod 的资源指标,用公式 期望副本数 = ceil(当前副本数 × (当前指标值 / 目标指标值)) 计算是否需要扩缩容。
自定义 HPA 场景: 默认的 CPU/内存指标在 SaaS 平台中不够用。比如一个租户管理服务,CPU 使用率只有 20% 但并发租户数已经到了阈值,需要扩容。我们通过 Prometheus Adapter 把业务自定义指标(每个租户的活跃连接数、工作流排队深度)暴露为 K8s Custom Metrics API,让 HPA 基于业务指标做扩缩容。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
metrics:
- type: Pods
pods:
metric:
name: active_tenant_connections # 自定义指标
target:
type: AverageValue
averageValue: "50" # 每个 Pod 平均承载 50 个活跃租户连接
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容等 5 分钟稳定期,防止抖动
policies:
- type: Percent
value: 10 # 每次最多缩 10%
periodSeconds: 60
scaleUp:
policies:
- type: Pods
value: 4 # 每次最多扩 4 个 Pod
periodSeconds: 60
踩坑: 缩容太激进导致服务抖动。一次流量高峰过后,HPA 立刻把副本数从 20 缩到 5,结果下一波流量来了来不及扩容。解决方案是 stabilizationWindowSeconds: 300 + 每次最多缩 10%。
B02. Kube-OVN 和 Calico 的核心区别是什么?为什么选 Kube-OVN?
参考回答:
| 维度 | Calico | Kube-OVN |
|---|---|---|
| 数据面 | iptables/eBPF | OVN/OVS |
| 网络模型 | L3 纯路由(BGP) | L2/L3 混合(支持 VLAN、Overlay) |
| 多租户 | NetworkPolicy(L3/L4) | VPC 级隔离(独立子网、路由表、安全组) |
| QoS | 基础限速 | DSCP 标记、流量整形、带宽保证 |
| 固定 IP | 不原生支持 | 原生支持 IP 预分配 |
选 Kube-OVN 的原因:
- VPC 级隔离是刚需。 金融客户要求网络层完全隔离——不仅是 L3/L4 的 ACL,而是独立的 L2 广播域、独立的路由表。Calico 的 NetworkPolicy 做不到这个粒度。
- DSCP 标记能力。 DetNet Controller 需要在 OVS 上配置 DSCP 标记规则,Kube-OVN 底层就是 OVN/OVS,可以直接调 OVN NB API 设置 QoS 规则。Calico 没有这个能力。
- 与 SDN 控制面的协同。 我们的 DetNet Controller 需要读取 OVN 的逻辑拓扑来做分流决策,Kube-OVN 的 OVN 是天然的数据源。
代价:
- Kube-OVN 的学习曲线比 Calico 陡峭得多
- OVS 的故障排查比 iptables 复杂(需要用
ovs-ofctl dump-flows看流表) - 社区生态和文档不如 Calico 成熟
B03. KubeVirt 的 VM 是怎么在 K8s 里运行的?和传统 libvirt/QEMU 有什么关系?
参考回答:
KubeVirt 的核心思路是 把 VM 包在 Pod 里。
当你创建一个 VirtualMachineInstance(VMI)CRD 时,KubeVirt 的 virt-controller watch 到事件后,会创建一个普通的 K8s Pod。这个 Pod 里跑的不是你的业务容器,而是 virt-launcher 进程。virt-launcher 内部调用 libvirt API 启动 QEMU-KVM 虚拟机。
所以本质上还是 QEMU-KVM 在跑 VM,libvirt 还是那个 libvirt。KubeVirt 做的是在上面包了一层 K8s 的调度和生命周期管理:
K8s 调度器 → 选节点
↓
Pod(virt-launcher 容器)→ 利用 K8s 的资源管理
↓
libvirt → 管理 VM 生命周期
↓
QEMU-KVM → 实际的虚拟化
好处: VM 和容器统一调度、统一监控、统一网络(VM 的网卡通过 Pod 网络暴露,可以用 K8s Service 做服务发现)。
实际问题:
- Live Migration(热迁移)需要共享存储(Longhorn ReadWriteMany 或 NFS),否则只能冷迁移
- 性能开销:Pod 网络多一层 veth 转发,网络延迟增加约 100-200μs
- GPU 直通需要额外配置 Device Plugin,不是开箱即用
B04. Longhorn vs Ceph 你怎么选?在什么场景下会选 Ceph?
参考回答:
我们选 Longhorn 的原因是运维简单、安装简单、适合中小规模。Longhorn 是纯 K8s 原生的——CRD + Operator + DaemonSet,用 kubectl 就能管理。Ceph 是独立于 K8s 的分布式存储系统,需要单独部署和运维(即使用 Rook 部署,底层还是 Ceph 的复杂性)。
选择 Ceph 的场景:
- 集群规模超过 50 个节点,存储容量超过 100TB
- 需要块存储(RBD) + 对象存储(RGW) + 文件存储(CephFS)三合一
- 需要极致的存储性能(Ceph 的 BlueStore 直接操作裸设备,跳过文件系统)
- 已有 Ceph 运维团队
我们选 Longhorn 的理由:
- 集群只有 3-10 个节点,存储容量 < 10TB
- 只需要块存储(PVC),不需要对象存储和文件存储
- 团队没有 Ceph 运维经验,学习成本太高
- Longhorn 的增量快照和跨集群备份(到 S3/MinIO)满足我们的容灾需求
B05. Helm Chart 的 values.yaml 怎么管理多租户差异?
参考回答:
用 Helm + Kustomize 两层叠加:
Helm Chart 定义应用的通用模板,values.yaml 定义默认值。每个租户一个 Kustomize overlay 目录,只包含该租户的差异配置。
base/
kustomization.yaml # 引用 Helm Chart 渲染结果
helm-values-default.yaml # 通用默认值
overlays/
tenant-minsheng/ # 民生银行
kustomization.yaml
patch-resources.yaml # 资源配额:CPU 8核, 内存 32G
patch-network.yaml # 网络策略:VPC 隔离
patch-replicas.yaml # 副本数:关键服务 3 副本
tenant-sinopec/ # 中石化
kustomization.yaml
patch-resources.yaml # 资源配额:CPU 16核, 内存 64G
tenant-small-01/ # 中小客户
kustomization.yaml
patch-resources.yaml # 资源配额:CPU 2核, 内存 4G
Flux GitOps 监听每个 overlay 目录的变更,自动渲染并同步到对应集群。新增租户只需要复制一个 overlay 目录,修改差异值,push 到 Git 即可。
B06. OPA Gatekeeper 在你的平台里具体拦截了哪些操作?有没有误拦截的情况?
参考回答:
核心策略大约 15 条,分三类:
安全合规类:
- 禁止使用
latest标签的镜像 - 禁止以 root 用户运行容器(
runAsNonRoot: true) - 禁止使用
hostNetwork、hostPID - 强制设置
resources.requests和resources.limits
租户隔离类:
- 所有资源必须带
tenant-id标签 - Pod 只能拉取 Harbor 私有仓库的镜像(防止拉取外部不可信镜像)
- Ingress 的 host 必须匹配租户的域名前缀
误拦截案例: 有一次紧急修复线上 bug,开发者直接 kubectl apply 一个没有 tenant-id 标签的临时 Pod 做调试,被 OPA 拦截了。紧急情况下需要跳过策略,但 OPA 没有”紧急豁免”机制。后来我们加了一个 emergency-bypass 的 annotation,配合审批流程——打上这个 annotation 可以跳过 OPA 检查,但会触发告警通知安全团队事后审计。
B07. Prometheus + Thanos 的监控体系怎么解决多集群、长期存储的问题?
参考回答:
单集群 Prometheus 两个痛点:数据只保留 15 天(本地存储),多集群需要每个集群装一套 Prometheus 且无法统一查询。
Thanos 的解决方案是 Sidecar 模式:
集群A Prometheus ← Thanos Sidecar → 上传 block 到 S3/MinIO
集群B Prometheus ← Thanos Sidecar → 上传 block 到 S3/MinIO
↓
Thanos Store Gateway
↓
Thanos Query(统一查询入口)
↓
Grafana 大盘
- Thanos Sidecar:挂载在 Prometheus Pod 旁边,定期把数据块上传到对象存储(MinIO)
- Thanos Store Gateway:从对象存储读取历史数据,提供查询接口
- Thanos Query:统一查询入口,自动去重(多个 Prometheus 可能采集同一个指标)
- Thanos Compact:压缩历史数据,降采样(1 个月前的数据从 15s 间隔降到 5m 间隔),节省存储
实测效果:监控数据保留从 15 天延长到 1 年,多集群统一查询延迟 < 3 秒,存储成本通过降采样节省约 70%。
B08. Flux GitOps 和 Argo CD 你都用过,怎么选?
参考回答:
两者都是 CNCF 的 GitOps 工具,核心区别:
| 维度 | Flux | Argo CD |
|---|---|---|
| 架构 | 多个小 Controller,每个负责一个功能 | 单体应用,一个 Server 包含所有功能 |
| UI | 无原生 UI(CLI + API) | 强大的 Web UI |
| 多租户 | 天然支持(每个租户一个 GitRepository + Kustomization) | 需要 AppProject 做隔离 |
| Helm 支持 | 原生支持 HelmRelease CRD | 原生支持 |
我的选择逻辑:
- 平台层的基础设施配置(RBAC、NetworkPolicy、ResourceQuota)用 Flux——因为这些配置变更频率低但数量多(40+ 租户),Flux 的 CRD 驱动模式更适合自动化管理。
- 应用层的业务部署用 Argo CD——因为开发者需要可视化看到部署状态、手动触发回滚,Argo CD 的 UI 体验更好。
两者配合: Flux 管集群级配置,Argo CD 管应用级部署,互不冲突。
B09. Falco 运行时检测具体检测什么?有实际告警的案例吗?
参考回答:
Falco 通过 eBPF(或内核模块)在内核层面监听系统调用,检测异常行为。我们配置的核心规则:
- 容器内执行 shell:
kubectl exec进入生产容器会触发告警 - 读取敏感文件:容器内进程读取
/etc/shadow、/etc/passwd - 异常网络连接:容器发起到非预期 IP/端口的外连(可能是挖矿或数据外泄)
- 特权提升:进程试图用
setuid/setgid提权 - 包管理器执行:生产容器内跑
apt-get/yum install(说明镜像不是 immutable 的)
实际案例: 有一次 Falco 告警某个租户的 Pod 在凌晨 3 点发起大量到境外 IP 的 443 端口连接。排查发现是该租户引入的一个第三方 npm 包有后门,在特定时间窗口把环境变量(包含数据库密码)上报到外部服务器。Falco 的告警让我们在 10 分钟内发现并隔离了该 Pod。
B10. cert-manager 的证书轮换机制是怎么工作的?有没有出过证书过期事故?
参考回答:
cert-manager 通过 CRD(Certificate、Issuer、ClusterIssuer)声明式管理证书。核心流程:
- 创建
CertificateCRD,声明需要的域名和 Issuer - cert-manager 调用 ACME 协议(Let’s Encrypt)或内部 CA 签发证书
- 证书存为 K8s Secret(
tls.crt+tls.key) - Ingress Controller 自动加载 Secret
- cert-manager 在证书到期前 30 天自动触发续期
证书过期事故: 出过一次。原因是 Let’s Encrypt 的 ACME Challenge 方式是 HTTP-01,需要在 /.well-known/acme-challenge/ 路径返回 token。某次 Ingress 规则变更时,开发者加了一条 catch-all 规则把所有路径都转发到后端服务,把 ACME Challenge 路径也覆盖了。cert-manager 续期失败但没有明显告警(默认只写 Event),直到证书过期用户报错才发现。
事后改进:
- 加了 Prometheus 告警:
certmanager_certificate_expiration_timestamp_seconds剩余时间 < 7 天告警 - Ingress 规则里显式保留 ACME Challenge 路径的优先级
- cert-manager 的
Certificate设置renewBefore: 720h(30 天前开始续期),给足重试时间
三、网络与 SDN 深度(8题)
C01. SRv6 和传统 MPLS 的核心区别是什么?为什么 DetNet 选择 SRv6?
参考回答:
| 维度 | MPLS | SRv6 |
|---|---|---|
| 标签位置 | L2 和 L3 之间插入标签栈 | 直接复用 IPv6 扩展头 |
| 协议依赖 | 需要 LDP/RSVP-TE 分发标签 | 不需要额外信令协议,路径编码在 SRH(Segment Routing Header)里 |
| 可编程性 | 标签只表示转发路径 | Segment 可以表示路径、VPN、服务链等多种语义 |
| 设备要求 | 需要 MPLS 专用硬件 | 只要支持 IPv6 的设备就行 |
选 SRv6 的理由:
- Source Routing(源路由):发送端在 IPv6 头中编码完整路径(Segment List),中间设备只需要按列表转发,不需要维护转发状态。这对 DetNet 很重要——确定性路径的每一跳都是预先规划好的。
- 与 IPv6 原生融合:不需要额外的信令协议(RSVP-TE),减少了控制面复杂度。SDN Controller 直接计算路径,编码成 Segment List 下发。
- Network Programming:SRv6 的 SID(Segment Identifier)可以编码 End.DT6(VPN 解封装)、End.X(跨链路转发)等多种指令,在 DetNet 场景下可以用来实现 OAM(操作管理维护)探针注入。
C02. DetNet 的时隙(Timeslot)分配算法是怎么设计的?怎么避免冲突?
参考回答:
DetNet 的核心特性是确定性——通过在每个节点预留固定的发送时隙,保证数据包端到端延迟可预测。
时隙分配本质是一个图着色 + 资源调度问题: 在一条路径的每个节点上,为这条流分配一个不冲突的时隙。约束条件是:同一节点的同一时隙不能分配给两条流;上游节点的时隙必须早于下游节点(因果关系);时隙间隔要满足最大延迟约束。
我们在 DetNet Controller 中用的是贪心 + 回溯算法:按路径顺序,在每个节点上贪心选择最早可用的时隙;如果某个节点找不到满足约束的时隙,回溯到上一个节点重新选择。
资源池管理:StreamId 池 1-16000、PathId 池 1-4000、OamName 池 1-4294967295,通过 CRD 管理,Operator 维护分配状态,避免重复分配。
C03. OAM 监控闭环中,延迟超标自动触发 CSPF 重算,这个”自动”的判断逻辑是什么?
参考回答:
OAM(Operations, Administration, and Maintenance)探针周期性发送测试包,测量每条 DetNet 路径的实时延迟、抖动、丢包率。
判断逻辑:
Kafka 接收 OAM 指标 → Prometheus 存储 → AlertManager 触发规则
告警规则示例:
- 延迟超标:连续 3 个采样周期(每周期 10s),平均延迟 > SLA 阈值的 120%
- 抖动超标:P99 抖动 > 2ms(对于确定性场景,抖动比绝对延迟更重要)
- 丢包超标:任意 60s 窗口内丢包率 > 0.01%
触发后的动作链:
- AlertManager 发 Webhook 到 DetNet Controller
- Controller 从 SDN Controller 拉取最新拓扑和链路状态
- 用 CSPF 算法排除故障链路重新计算路径
- 通过 SDN RESTful API 更新路径(SDN Controller 通过 Netconf 逐设备下发新流表)
- 新路径的 OAM 探针激活,验证延迟恢复正常
防抖设计: “连续 3 个周期”是关键——单次抖动不触发重算,避免网络瞬时波动导致频繁路径切换(路径切换本身也会引入短暂的丢包)。
C04. CSPF 算法和普通 Dijkstra 有什么区别?约束条件有哪些?
参考回答:
CSPF(Constrained Shortest Path First)= Dijkstra + 约束剪枝。
普通 Dijkstra 只考虑链路权重(跳数或代价),找最短路径。CSPF 在计算过程中会剪掉不满足约束条件的链路,然后在剩余拓扑上跑 Dijkstra。
我们的约束条件:
- 带宽约束:链路剩余带宽 ≥ 流的带宽需求
- 延迟约束:端到端路径延迟 ≤ SLA 要求(比如 5ms)
- 亲和/反亲和:某些流必须走/不能走某些链路(比如加密流量必须走国密链路)
- SRLG 约束:Shared Risk Link Group,两条路径不能共享同一个风险组(比如同一条光缆上的两根光纤)
计算时先用约束条件过滤拓扑图,再在剩余图上跑 Dijkstra。如果找不到满足所有约束的路径,按优先级逐步放松约束(先放松亲和约束,再放松延迟约束),并告警通知。
C05. VM 热迁移时,DetNet 路径怎么做到无缝切换?
参考回答:
完全无缝做不到,但可以做到用户无感知(切换期间丢包 < 3 个包)。
流程:
- KubeVirt 发起 Live Migration,virt-handler 在目标节点创建新的 virt-launcher Pod
- 内存脏页开始迭代拷贝(几轮迭代后切换)
- 切换前:DetNet Controller watch 到 VMI 的
migrationState变为PreparingTarget - Controller 提前在目标节点配置好 OVN 标记规则 + 调 SDN API 预建新路径(Make-Before-Break)
- 切换瞬间:VM 停止在源节点,启动在目标节点。此时新旧路径同时存在
- 切换后:OVN 分流规则切到新路径,旧路径延迟几秒后删除
Make-Before-Break(先建后拆) 是关键——新路径在旧路径还在的时候就建好了,切换只是分流规则的更新(一条 OVS 流表的修改,微秒级),所以丢包极少。
C06. 你说 SDN Controller 的南向接口用 Netconf,和 OpenFlow 有什么区别?为什么选 Netconf?
参考回答:
| 维度 | OpenFlow | Netconf |
|---|---|---|
| 操作粒度 | 流表级(match + action) | 设备配置级(接口、路由、QoS 全量配置) |
| 协议 | 自定义二进制协议 | 基于 XML/YANG 的 RPC,走 SSH |
| 适用场景 | 白盒交换机、SDN 流表下发 | 传统设备配置管理、厂商设备 |
| 事务支持 | 无 | 支持 confirmed-commit(先试后确认) |
选 Netconf 的原因: 骨干网的物理设备大多是厂商路由器(华为、中兴),它们原生支持 Netconf + YANG 模型,但不一定支持 OpenFlow。而且 DetNet 的配置不只是流表——还包括接口配置、QoS 策略、时隙参数,这些用 Netconf 的 YANG 模型描述更自然。
Netconf 的 confirmed-commit 很有价值: 下发配置后可以设一个超时(比如 5 分钟),如果 5 分钟内没有 confirm,设备自动回滚到旧配置。这在骨干网场景下是安全网——防止一条错误配置让整条链路中断。
C07. BGP 在你的项目中的角色是什么?
参考回答:
BGP 在两个层面发挥作用:
物理网络层: 骨干网的路由器之间用 iBGP/eBGP 交换路由信息。SDN Controller 通过 BGP-LS(Link State)扩展获取全网拓扑——每个路由器把自己的链路状态通过 BGP-LS 通告给 Controller,Controller 据此构建全局拓扑图,供 CSPF 算法使用。
K8s 网络层(如果用 Calico): Calico 用 BGP 在节点之间通告 Pod 网段路由。但我们选了 Kube-OVN(Overlay 模式),所以 K8s 层面没有用到 BGP。
C08. 你的两项 DetNet 发明专利分别解决什么问题?
参考回答:
专利一:云网双向联动机制。 核心创新点是 DetNet Controller 作为中间层,一侧 Watch K8s 资源变化,另一侧调用 SDN API,实现 VM 生命周期与 DetNet 路径的自动联动。关键技术点包括 Make-Before-Break 路径切换、OVN DSCP 标记与 SDN 路径的语义绑定、基于 OAM 指标的路径自愈。
专利二:基于网络能力的调度决策。 核心创新点是把物理网络的能力(带宽、延迟、可靠性)作为 K8s 调度的输入维度。通过 Extended Resource 把网络能力量化注入节点属性,Scheduler Plugin 在调度决策时考虑网络因素。解决了”计算资源充足但网络能力不足”时仍然调度到该节点的问题。
四、微服务与中间件(8题)
D01. Sentinel 多租户限流怎么设计?不同租户的限流阈值怎么动态调整?
参考回答:
核心设计:每个租户独立的限流规则,通过租户等级动态映射阈值。
// 自定义 Sentinel 资源名:租户ID + 接口路径
String resourceName = tenantId + ":" + request.getRequestURI();
Entry entry = SphU.entry(resourceName);
租户等级和限流阈值的映射:
| 租户等级 | QPS 限制 | 并发限制 | 降级策略 |
|---|---|---|---|
| 铂金(民生银行) | 10000 | 500 | 慢调用比例降级 |
| 黄金 | 5000 | 200 | 异常比例降级 |
| 标准 | 1000 | 50 | 直接拒绝 |
动态调整:限流规则存在 Nacos 配置中心,Sentinel Dashboard 修改后实时推送到各服务节点。运营可以在管理后台调整租户等级,触发 Nacos 配置变更,Sentinel 热加载新规则,无需重启服务。
D02. MyBatis 拦截器实现租户透明隔离的原理是什么?JSqlParser 有什么坑?
参考回答:
核心原理:通过 MyBatis 的 Interceptor 接口拦截 SQL 执行,用 JSqlParser 解析 SQL AST,自动在 WHERE 条件中注入 tenant_id = ?。
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class TenantInterceptor implements Interceptor {
public Object intercept(Invocation invocation) {
// 1. 从 ThreadLocal 获取当前租户ID
String tenantId = TenantContext.getCurrentTenantId();
// 2. JSqlParser 解析原始 SQL
Statement stmt = CCJSqlParserUtil.parse(originalSql);
// 3. 在 WHERE 条件中追加 tenant_id = ?
if (stmt instanceof Select) {
Select select = (Select) stmt;
// ... 遍历 PlainSelect,追加 AndExpression
}
// 4. 替换原始 SQL
metaObject.setValue("delegate.boundSql.sql", stmt.toString());
}
}
JSqlParser 的坑:
- 子查询处理:嵌套子查询需要递归处理,否则子查询里没有 tenant_id 条件会查到其他租户数据
- JOIN 语句:多表 JOIN 时,每个表都要加 tenant_id 条件,不能只加主表
- UNION 语句:每个 UNION 分支都需要独立注入
- INSERT INTO … SELECT:SELECT 部分也需要注入条件
- SQL 方言差异:MySQL 的反引号、PostgreSQL 的双引号,JSqlParser 需要正确配置 parser
最严重的一次事故: 有一个复杂的统计 SQL 用了 3 层子查询 + UNION ALL,拦截器只处理了最外层,导致内层子查询查到了所有租户的数据。修复后加了单元测试——把所有复杂 SQL 模式都作为测试用例。
D03. Dapr 的 Component 配置是怎么实现多租户隔离的?
参考回答:
Dapr 的 Component 是集群级别的 CRD,默认所有 Pod 共享。多租户隔离通过 Component Scoping 实现:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore-tenant-minsheng
spec:
type: state.redis
metadata:
- name: redisHost
value: "redis-minsheng:6379" # 民生银行专用 Redis
- name: redisPassword
secretKeyRef:
name: redis-minsheng-secret
key: password
scopes:
- tenant-minsheng-app1 # 只有这些 appId 能访问
- tenant-minsheng-app2
每个租户有自己的 Component(指向不同的 Redis 实例/Kafka Topic),通过 scopes 限制只有该租户的微服务(按 Dapr appId 匹配)可以访问。
D04. Argo Workflows DAG 模式实现 Saga 事务,补偿失败了怎么办?
参考回答:
分三层处理:
第一层:自动重试。 Argo Workflow 每个补偿步骤配置 retryStrategy:
retryStrategy:
limit: 3
retryPolicy: "Always"
backoff:
duration: "10s"
factor: 2 # 指数退避:10s → 20s → 40s
第二层:人工介入。 3 次重试都失败后,Argo Workflow 进入 Failed 状态,触发 AlertManager 告警(钉钉/飞书通知值班人员)。值班人员通过 Argo UI 查看失败原因,手动修复后点击 Retry 重新执行补偿步骤。
第三层:对账修复。 每天凌晨跑一个对账 Job,比对订单状态和各服务的实际状态。发现不一致的记录进入”异常工单队列”,由运营人工处理。
D05. Emissary 灰度发布的权重路由(10%→50%→100%)怎么做自动化推进?
参考回答:
手动推进 + 自动化守门条件结合:
# Emissary Mapping 配置
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: myservice-canary
spec:
prefix: /api/myservice/
service: myservice-canary:8080
weight: 10 # 10% 流量到新版本
---
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: myservice-stable
spec:
prefix: /api/myservice/
service: myservice-stable:8080
weight: 90 # 90% 流量到旧版本
自动化守门条件(由 CI/CD Pipeline 检查):
- 金丝雀版本的错误率 < 0.1%
- P99 延迟不超过稳定版本的 110%
- 无 Falco 安全告警
三个条件都满足,Pipeline 自动把权重从 10% 调到 50%;持续满足 30 分钟,调到 100%。任一条件不满足,自动回滚到 0%(全部流量回稳定版本)。
D06. 你提到 Disruptor 无锁队列实现 10 万+ QPS,核心原理是什么?
参考回答:
Disruptor 的核心优化有三个:
-
环形数组(RingBuffer):预分配固定大小的数组,避免运行时内存分配和 GC。生产者和消费者通过序号(Sequence)而非锁来协调——生产者 CAS 递增序号,消费者只需要读取序号判断是否有新数据。
-
缓存行填充(Cache Line Padding):Sequence 对象的前后各填充 7 个 long 字段(56 字节),确保 Sequence 独占一个缓存行(64 字节),避免伪共享(False Sharing)。
-
批量处理:消费者不是一条一条取数据,而是一次取出所有可用的事件批量处理,减少同步开销。
在 AI 医学影像项目中的使用场景:DICOM 影像文件通过 Netty 接收后放入 Disruptor RingBuffer,多个消费者并行处理(解码、AI 推理、结果写库)。单节点实测 12 万 QPS(每秒处理 12 万个 DICOM 帧)。
D07. Netty 零拷贝在你的项目中怎么用的?
参考回答:
AI 医学影像平台需要高效传输 DICOM 文件(单个文件 10-100MB)。Netty 的零拷贝体现在三个层面:
- FileRegion(sendfile 系统调用):服务端读取 DICOM 文件发送给客户端时,用
DefaultFileRegion直接把文件内容从内核缓冲区发送到 Socket,不经过用户态拷贝。 - CompositeByteBuf:多个 ByteBuf 组合成一个逻辑 Buffer,避免合并拷贝。DICOM 文件有固定头部(128 字节前导)+ 元数据 + 像素数据,三部分用 CompositeByteBuf 组合,发送时零拷贝。
- 堆外内存(DirectByteBuf):Netty 默认用堆外内存,省去了从堆内存拷贝到堆外内存再发送的步骤。
D08. Flowable 深度二次开发你做了什么?
参考回答:
RPA 低代码平台的核心改造:
-
调度器重写:Flowable 默认的作业执行器是基于数据库轮询的(每隔 N 秒 SELECT 待执行任务),在日均数千任务的场景下数据库压力大。我重写为推模式——基于 Redis Stream 做任务队列,Flowable 引擎产生任务后推入 Redis,Worker 节点消费执行。轮询间隔从 5 秒降到实时。
-
TCC + Saga 混合事务:简单的分支任务用 TCC(Try-Confirm-Cancel),复杂的编排流程用 Saga。在 Flowable 的 ServiceTask 中嵌入事务模式选择逻辑。
-
多租户支持:Flowable 原生不支持多租户隔离。通过自定义
ProcessEngineConfiguration,按租户 ID 隔离流程定义和实例数据。
五、工程效能与团队管理(7题)
E01. 你从 0 到 1 组建 20 人团队,怎么做技术选型和团队分工?
参考回答:
技术选型原则: 团队能力 > 技术先进性。20 人里大部分是 3-5 年经验的工程师,选择团队能快速上手的技术栈,而不是最新最酷的。比如后端选 Spring Boot 而不是 Go(虽然 Go 更适合云原生),因为团队 Java 背景强。
团队分工(按职能):
- 平台组(5人):K8s 运维、CI/CD、基础设施
- 后端组(8人):业务微服务开发
- 前端组(4人):管理后台、工作流设计器
- 测试组(2人):自动化测试、性能测试
- 架构师(1人,我自己):技术方案设计、代码 Review、技术培训
关键动作:
- 第一周:统一开发环境(IDE 配置、Git 规范、代码模板)
- 第二周:搭建 CI/CD Pipeline(GitLab CI + Harbor + K8s)
- 第三周:核心框架 Demo(认证、网关、数据库、消息队列的标准接入方式)
- 第四周起:各组并行开发
E02. CI/CD 流水线的具体设计是什么?从代码提交到生产部署有几个阶段?
参考回答:
代码提交 → GitLab CI 触发
↓
Stage 1: 代码检查(3 分钟)
- SonarQube 静态分析
- 单元测试(覆盖率 > 70% 才通过)
- 依赖安全扫描(Trivy)
↓
Stage 2: 构建(5 分钟)
- Maven/Gradle 编译
- Docker 多阶段构建
- 推送镜像到 Harbor(带 commit hash 标签)
↓
Stage 3: 部署到 Dev 环境(2 分钟)
- Helm upgrade 到 dev namespace
- 自动化冒烟测试
↓
Stage 4: 部署到 Staging 环境(手动触发)
- 集成测试
- 性能回归测试
↓
Stage 5: 生产发布(手动触发 + 审批)
- Flux GitOps 同步配置变更
- Emissary 灰度发布(10% → 50% → 100%)
- Falco 运行时监控
关键指标: 从代码提交到 Dev 部署 < 10 分钟,从 Staging 到生产 < 30 分钟(不含审批时间)。
E03. 代码 Review 你怎么做?有什么标准?
参考回答:
Review 标准分三级:
P0(必须修改):
- 安全漏洞(SQL 注入、XSS、硬编码密码)
- 数据泄露风险(日志打印敏感信息、租户隔离缺失)
- 并发安全问题(竞态条件、死锁风险)
P1(建议修改):
- 性能问题(N+1 查询、大对象 Full GC、未关闭资源)
- 异常处理不当(catch 所有异常、吞掉异常)
- 缺少幂等设计(接口重试会产生重复数据)
P2(可以改善):
- 命名不清晰
- 代码重复
- 注释不足
实际执行: 每个 MR 必须至少 1 个 Senior 工程师 Approve。P0 问题零容忍,必须修复后才能合并。每周抽出 1 小时做团队代码分享——选一个好的 MR 和一个有问题的 MR 匿名展示,讨论改进。
E04. 接口响应提升 30% 是怎么做到的?具体优化了哪些点?
参考回答:
不是一个单点优化,而是系统性地排查和改进。按效果排序:
-
数据库优化(贡献 15%):用 SkyWalking 定位慢 SQL,加索引 + 改写查询。最大的一个优化是把一个 5 表 JOIN 改成两次简单查询 + 应用层组装,从 800ms 降到 120ms。
-
缓存引入(贡献 8%):高频读取的租户配置、权限数据加了 Redis 缓存,TTL 5 分钟。缓存命中率 95%+。
-
连接池优化(贡献 4%):数据库连接池从 HikariCP 默认的 10 调到 30(根据 CPU 核数和并发量计算),Redis 连接池从 8 调到 20。消除了高峰期等待连接的排队时间。
-
序列化优化(贡献 3%):微服务间调用从 JSON 序列化切换到 Protobuf,减少了序列化/反序列化开销。
E05. 你怎么做稳定性治理?有没有经历过严重的线上事故?
参考回答:
稳定性治理三板斧:
- 事前预防:OPA 准入策略强制资源限制、镜像安全扫描、代码 Review P0 检查
- 事中发现:Prometheus 告警(覆盖 200+ 指标)、Falco 运行时检测、链路追踪(Jaeger)
- 事后改进:每次故障写 PostMortem,包含时间线、根因分析、改进 Action、负责人和 Deadline
一次严重事故: 某天下午 3 点,某个租户的批量导入操作触发了大量数据库写入,导致 MySQL 主库 CPU 100%,所有租户的服务都受影响(因为当时还在用共享数据库)。从发现到恢复花了 45 分钟。
根因: 批量导入没有做速率限制,一次性插入 10 万条记录。
改进:
- 批量操作加速率限制(每秒最多 500 条)
- 大客户的数据库物理隔离(独立 MySQL 实例)
- 增加数据库 CPU 使用率告警(> 80% 触发)
- 批量操作走异步队列,不直接写主库
E06. 成本优化你做过哪些?效果如何?
参考回答:
三个方向:
-
资源利用率提升:通过 ResourceQuota 和 HPA 配合,把集群 CPU 利用率从 35% 提升到 65%。核心做法是把 requests 设为实际使用的 80%、limits 设为实际使用的 200%,允许突发但限制上限。
-
存储优化:Longhorn 快照保留策略从”保留所有”改为”保留 7 天 + 每周一份保留 30 天”,存储空间节省 40%。
-
多云弹性:中小租户在业务低峰期(凌晨 0-8 点)自动缩容到最低副本数,节省约 30% 的公有云费用。
E07. 你的博客(https://andrewyghub.github.io/)主要写什么内容?技术影响力怎么样?
参考回答:
博客主要记录三类内容:
- 项目实战经验(K8s 集群搭建、CNCF 组件部署调优、DetNet 架构设计)
- 源码分析(Sentinel 限流原理、MyBatis 拦截器机制、Disruptor 无锁队列)
- 技术方案对比(Calico vs Kube-OVN、OpenStack vs K8s、Flux vs Argo CD)
写博客的目的不是追求流量,而是强迫自己把”以为懂了”的知识真正梳理清楚。很多技术问题是在写博客的过程中才发现自己的理解有偏差,然后回去翻源码验证。这个习惯也帮助了团队——新人入职时可以先看博客快速了解项目技术栈。
六、LLM 与 AI 应用(7题)
F01. AskOS 的 RAG 架构是怎么设计的?检索质量怎么保证?
参考回答:
全链路:Document → Chunking → Embedding → VectorStore → Retrieval → Reranking → LLM
Chunking 策略(最影响检索质量的环节):
- 不是简单按 token 数切分,而是按语义边界切分(标题、段落、列表项)
- 重叠窗口:每个 chunk 和前后 chunk 有 20% 的内容重叠,防止关键信息被切断
- 元数据保留:每个 chunk 携带文档标题、章节标题、页码,检索时用于上下文补充
检索质量保证:
- 混合检索:向量检索(语义相似)+ BM25 关键词检索(精确匹配),RRF(Reciprocal Rank Fusion)融合排序
- Reranking:初筛 Top 20 → CrossEncoder 重排序 → 取 Top 5 送给 LLM
- Query 改写:用户问题可能表述模糊,先用 LLM 改写为更精确的检索查询
F02. 多租户知识库隔离怎么实现?向量数据库层面怎么隔离?
参考回答:
两种模式:
方案一:Collection 级隔离(当前方案)。 每个租户一个 Milvus Collection,物理上独立的向量索引。优点是隔离彻底、删除租户数据简单(直接 drop collection);缺点是 Collection 数量多时内存占用大。
方案二:Partition 级隔离。 同一个 Collection 按 tenant_id 分区。优点是节省内存(共享索引结构);缺点是删除租户数据需要 delete by filter,大量数据时性能差。
当前选方案一,因为租户数量在 40+ 级别,每个 Collection 的内存开销可控。如果租户数量到 1000+,会考虑切换到方案二或混合方案(大租户独立 Collection,小租户共享 Collection + Partition)。
F03. RAG 的 Embedding 模型怎么选?有没有做过 Fine-tuning?
参考回答:
模型选择考量:
| 模型 | 维度 | 中文效果 | 推理速度 | 选择理由 |
|---|---|---|---|---|
| BGE-large-zh | 1024 | 优秀 | 中等 | 中文场景首选,效果和速度的平衡 |
| text-embedding-3-large | 3072 | 良好 | 快(API) | 英文场景或不想自部署 |
| BGE-M3 | 1024 | 优秀 | 较慢 | 支持多语言,跨语言检索场景 |
当前用 BGE-large-zh,部署在 GPU 节点上(单张 A10 24GB 够用)。
Fine-tuning: 正在准备中。核心思路是用企业客户的实际问答对(从客服记录中提取)做对比学习微调——让模型知道”合同违约金条款”和”违约赔偿标准”是语义相近的,即使表面用词不同。预期效果是检索召回率从 85% 提升到 92%+。
F04. Agent 架构在 AskOS 中怎么用?和纯 RAG 有什么区别?
参考回答:
纯 RAG 是”检索-生成”的单一链路,适合简单问答。但企业场景有很多复杂问题需要多步推理:
- “对比我们公司 2023 年和 2024 年的销售政策变化” → 需要检索两份文档,对比后总结
- “帮我根据产品手册生成一份客户方案” → 需要检索 + 生成 + 格式化
Agent 的做法是让 LLM 作为”决策者”,根据问题自主决定:
- 需要调用哪些工具(向量检索、全文搜索、数据库查询、计算)
- 调用顺序是什么
- 中间结果是否足够,是否需要追加检索
我们基于 LangGraph 实现了一个简单的 Agent 框架——定义了 search_knowledge、compare_documents、generate_report 三个工具,LLM 根据用户问题自动编排调用链路。
F05. 文档处理流水线用 Argo Workflows 编排,具体处理哪些步骤?
参考回答:
文档上传
↓
Step 1: 格式转换(PDF/Word/PPT → 纯文本 + 保留结构)
↓
Step 2: 文本清洗(去除页眉页脚、目录、水印、重复内容)
↓
Step 3: 智能分块(按语义边界切分 + 重叠窗口)
↓
Step 4: Embedding 生成(调用 BGE 模型批量向量化)
↓
Step 5: 向量入库(写入 Milvus + 元数据写入 MySQL)
↓
Step 6: 质量检查(随机抽取 chunk 做检索测试,验证可检索性)
每个步骤是 Argo Workflow 的一个独立容器,失败自动重试 3 次。大文件(> 100 页)的 Embedding 步骤会拆成多个并行任务加速。
F06. Dapr 状态管理在 AskOS 中怎么实现”租户透明访问”?
参考回答:
“租户透明”的意思是业务代码不需要关心底层用的是 Redis、MongoDB 还是其他存储,也不需要手动拼接租户前缀。
实现方式:自定义一个 Dapr Middleware,在每次 State Store 操作前自动注入租户前缀。
# 业务代码只需要这样调用
dapr_client.save_state("statestore", key="user_config", value=data)
# 实际存储的 key 变成:"tenant_minsheng:user_config"
# Middleware 伪逻辑
def inject_tenant_prefix(request):
tenant_id = request.metadata["tenant-id"]
request.key = f"{tenant_id}:{request.key}"
return request
好处是业务代码完全不感知多租户,换租户只需要改请求头的 tenant-id,同一套代码服务所有租户。
F07. 你对比过哪些 LLM?在企业场景下怎么选?
参考回答:
| 模型 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| GPT-4o | 综合能力最强,多模态 | 价格高,数据出境合规问题 | 复杂推理、英文场景 |
| Claude Opus | 长上下文(200K),代码能力强 | 价格高 | 长文档分析、代码生成 |
| GLM-4 | 中文优秀,国产合规 | 复杂推理稍弱 | 国内企业合规场景 |
| Qwen-2.5 | 开源可私有部署 | 需要 GPU 资源 | 数据敏感场景,自部署 |
| DeepSeek-V3 | 性价比极高 | 社区支持较新 | 成本敏感场景 |
企业场景选型逻辑:
- 数据合规优先:金融/政府客户 → 国产模型(GLM-4)或开源自部署(Qwen)
- 效果优先:对外产品/客户方案 → GPT-4o 或 Claude
- 成本优先:内部工具/批量处理 → DeepSeek 或 Qwen
AskOS 目前默认用 GLM-4(满足国内客户合规需求),支持模型热切换——管理后台可以按租户配置不同的 LLM Provider。
评论:
技术文章推送
手机、电脑实用软件分享
微信公众号:AndrewYG的算法世界