May 8, 2025
本文主要解决在服务重构过程中如何保证新旧服务行为一致性的问题。
场景描述
#
现有一个 python 开发的 gRPC 微服务提供了一些 数据查询 接口 供 上层应用使用,随着业务流量的增加运维这个服务的成本也逐渐增加,为了降低运维成本和提高性能 (木有擅长 python 高性能的开发),因此选择了使用 go 语言对这个服务进行重写。在开发完成之后,需要对新服务的 gRPC 接口进行验证。
这种场景对测试开发人员来说,实在是太熟悉了吧?典型的 重放验证,马上能想到的验证手段就是:
- 如果有存量的单元测试,那么直接重新跑一遍单元测试就能快速的完成验证。
- 没有单元测试的情况,那么可以将新服务部署起来,通过流量复制的方式将旧服务的流量复制到新服务上,然后对比两个服务的返回结果是否一致。
flowchart LR
%% 定义布局方向和间距
subgraph s1["方案一: 单元测试验证"]
direction TB
UT[单元测试] -->|执行| NS1[新服务]
end
subgraph s2["方案二: 流量复制验证"]
direction TB
C[客户端] -->|请求| OS[旧服务]
OS -->|响应| C
OS -->|复制流量| NS2[新服务]
NS2 -->|对比响应| OS
end
%% 设置布局方向和对齐方式
s1 ~~~ s2
但是很遗憾 😭,并没有成熟的单元测试;测试人员也都是人肉测试,对于内部服务的接口验证帮助不大,因此这里采用第二种方式进行验证。
...
February 28, 2025
本文记录了在 ubuntu 22.04 上配合 minikube 搭建的 k8s 集群,搭建 doris 存算分离集群的过程。
0. 环境信息
#
软件 |
版本 |
OS |
Ubuntu 24.04.1 LTS x86_64 |
kernel |
6.8.0-52-generic |
minikube |
v1.35.0 |
kubernetes |
v1.32.0 |
doris |
v3.0.3 |
这里默认已经准备好了基础的 kubernetes 集群,所以也不再阐述如何通过 minikube 或者其他方式搭建 kubernetes 集群。
1. 安装 FoundationDB
#
参考文档:https://doris.apache.org/zh-CN/docs/3.0/install/deploy-on-kubernetes/separating-storage-compute/install-fdb
1.1 安装 FoundationDB CRD 资源
#
kubectl apply -f https://raw.githubusercontent.com/FoundationDB/fdb-kubernetes-operator/main/config/crd/bases/apps.foundationdb.org_foundationdbclusters.yaml
kubectl apply -f https://raw.githubusercontent.com/FoundationDB/fdb-kubernetes-operator/main/config/crd/bases/apps.foundationdb.org_foundationdbbackups.yaml
kubectl apply -f https://raw.githubusercontent.com/FoundationDB/fdb-kubernetes-operator/main/config/crd/bases/apps.foundationdb.org_foundationdbrestores.yaml
1.2 部署 FoundationDB Operator
#
wget https://raw.githubusercontent.com/apache/doris-operator/master/config/operator/fdb-operator.yaml
kubectl apply -f fdb-operator.yaml
1.3 部署 FoundationDB 集群
#
wget https://raw.githubusercontent.com/foundationdb/fdb-kubernetes-operator/main/config/samples/cluster.yaml -O fdb-cluster.yaml
kubectl apply -f fdb-cluster.yaml
# 查看集群状态
kubectl get fdb
# 预期输出(启动需要时间,需要等待几分钟)
NAME GENERATION RECONCILED AVAILABLE FULLREPLICATION VERSION AGE
test-cluster 1 1 true true 7.1.26 3m30s
2. 安装 Doris Operator
#
2.1 安装 CRD 部署 Doris 相关资源定义
#
kubectl create -f https://raw.githubusercontent.com/apache/doris-operator/master/config/crd/bases/crds.yaml
2.2 部署 Doris Operator
#
wget https://raw.githubusercontent.com/apache/doris-operator/master/config/operator/disaggregated-operator.yaml -O disaggregated-operator.yaml
kubectl apply -f disaggregated-operator.yaml
# 查看部署状态
kubectl get pod -n doris
# 预期输出
NAME READY STATUS RESTARTS AGE
doris-operator-5fd65d8d69-rgqlk 1/1 Running 0 79s
3. 部署存算分离集群
#
3.1 下载示例配置
#
wget https://raw.githubusercontent.com/apache/doris-operator/master/doc/examples/disaggregated/cluster/ddc-sample.yaml -O ddc-sample.yaml
3.2 配置 ConfigMap
#
对于 ddc-sample.yaml 配置进行调整配置。这三个都需要分别配置 ConfigMap 并修改集群中的配置挂载。
...
November 17, 2023
前提
#
本文假设你已经对 Kubernetes、istio 和 Envoy 有一定的了解,如果你还不了解,可以先阅读下面的文章:
当然不仅限于知道这些,还需要对其有一定的实践经验,这样才能更好的理解本文的内容。当然本文也不会涉及太深,只是作为 istio 的一个入门扩展教程。
为什么要扩展功能?#背景
#
用通俗的话去理解 istio 的作用就是,对我们部署在 kubernetes 上的应用进行流量控制(代理),给我们提供了:流量控制、流量监控、流量安全等功能。但是在实际的使用中,我们还是会遇到一些特殊的场景,需要我们自己基于自己的业务场景去扩展一些功能,比如:ip 白名单、ip 黑名单、统一认证等。这些功能往往和具体公司的业务场景有关,因此 istio 无法直接提供这些功能,需要我们自己去扩展。
传统的扩展方式
#
在传统架构中,常常通过 API 网关这一组件来实现这一些扩展能力,常用的 API 网关有:kong、apisix、openresty,而扩展的原理就是插件,或者像 nginx/openresty 在请求链中的不同阶段提供了不同的 hook,我们可以基于这些 hook 来实现扩展功能。
就我个人的经历来说,网关要么自己定制开发,要么基于openresty来实现,又或者使用一些开源的网关,如:kong、apisix,在此基础上进行二次开发。不过这些方式都是在传统API网关中去实现的,随着 Service Mesh 的发展,API网关的某些功能也被 Sidecar 代理所取代,比如:流量控制、流量监控、流量安全等。目前 Mesh 发展的趋势也是进一步在 Sidecar 代理中实现更多的功能,而不是在 API 网关中实现。
Envoy 的扩展能力
#
envoy 提供了丰富的扩展能力,Access Logger, Access Log Filter, Clusters, Listen Filter, Network Filter, HTTP Filter 等等。这一块内容非常多,如果想要了解更多,请参考官方文档。
...
September 22, 2020
背景
#
第一次,线上遇到大量接口RT超过10s触发了系统告警,运维反馈k8s集群无异常,负载无明显上升。将报警接口相关的服务重启一番后发现并无改善。但是开发人员使用链路追踪系统发现,比较慢的请求总是某个gRPC服务中的几个POD导致,由其他POD处理的请求并不会出现超时告警。
第二次,同样遇到接口RT超过阈值触发告警,从k8s中查到某个gRPC服务(关键服务)重启次数异常,查看重启原因时发现是OOM Killed
,OOM killed
并不是负载不均衡直接导致的,但是也有一定的关系,这个后面再说。前两次由于监控不够完善(于我而言,运维的很多面板都没有权限,没办法排查)。期间利用pprof分析了该服务内存泄漏点,并修复上线观察。经过第二次问题并解决之后,线上超时告警恢复正常水平,但是该 deployment 下的几个POD占用资源(Mem / CPU / Network-IO),差距甚大。
第二张图是运维第一次发现该服务OOM killed 之后调整了内存上限从 512MB => 1G,然而只是让它死得慢一点而已。
从上面两张图能够石锤的是该服务一定存在内存泄漏。Go项目内存占用的分析,我总结了如下的排查步骤:
1. 代码泄漏(pprof)(可能原因 goroutine泄漏;闭包)
2. Go Runtime + Linux 内核(RSS虚高导致OOM)https://github.com/golang/go/issues/23687
3. 采集指标不正常(container_memory_working_set_bytes)
2,3 是基于第1点能基本排除代码问题的后续步骤。
解决和排查手段:
1. pprof 通过heap + goroutine 是否异常,来定位泄漏点
运行`go tool pprof`命令时加上--nodefration=0.05参数,表示如果调用的子函数使用的CPU、memory不超过 5%,就忽略它。
2. 确认go版本和内核版本,确认是否开启了MADV_FREE,导致RSS下降不及时(1.12+ 和 linux内核版本大于 4.5)。
3. RSS + Cache 内存检查
> Cache 过大的原因 https://www.cnblogs.com/zh94/p/11922714.html
// IO密集:手动释放或者定期重启
查看服务器内存使用情况: `free -g`
查看进程内存情况: `pidstat -rI -p 13744`
查看进程打开的文件: `lsof -p 13744`
查看容器内的PID: `docker inspect --format "{{ .State.Pid}}" 6e7efbb80a9d`
查看进程树,找到目标: `pstree -p 13744`
参考:https://eddycjy.com/posts/why-container-memory-exceed/
通过上述步骤,我发现了该POD被OOM killed
还有另一个元凶就是,日志文件占用。这里就不过多的详述了,搜索方向是 “一个运行中程序在内存中如何组织 + Cache内存是由哪些部分构成的”。这部分要达到的目标是:一个程序运行起来它为什么占用了这么些内存,而不是更多或者更少。
...