k8s部署前后分离架构微服务——跨域和缓存问题
前面k8s环境部署的差不多了,现在尝试把自己开发的微服无程序部署到k8s环境中。
首先是打包镜像,前端是用了Ant Design Vue Pro框架开发的,使用cnpm run build 编译,会在工程目录下生成一个dist文件目录,再将此目录和nginx打包为docker镜像,上传到私有harbor仓库中就可以进行k8s的部署了。
1.我这里前端的镜像和要调用api服务不在同一个pod下,就遇到了CORS(跨域资源共享)问题,前端是禁止跳转访问api的服务的,即便在api服务上做了类似下面的cors设置也还是解决不了问题。
//go代码 中间件设置
handlers.CORS(handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization", "Kt"}), //这里需要添加自定义的头部信息如Kthandlers.AllowedMethods([]string{"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE"}),handlers.AllowedOrigins([]string{"*"}),handlers.AllowCredentials(), // 允许携带凭证(如 cookies))
问了deepseek,我采用了使用nginx代理方案。即在Ant Design Vue Pro框架的工程目录下有deploy/nginx.conf的配置文件,将其配置文件的注释部分打开,设置代理跳转地址,为了方便过滤需要跳转的url,我在.env文件中VUE_APP_API_BASE_URL=/api统一为跳转的url添加了一个/api/ 前缀,这样在nginx代理时,就会将所有有/api/前缀的url拦截,进入代理步骤,当然你的前缀也可以自定义,只要和nginx.conf配置文件中过滤的前缀设置一致就行,如下
location /api/ {proxy_pass http://roles-manage.eqim.svc.cluster.local:8002/; #这是需要跳转的地址proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}
}
# 跳转的时候,将/api/前缀自动去除。
这样再重新编译工程,打包前端镜像,部署后,cors跨域问题就可以解决了。
2. 遇到的第二个问题是缓存问题。之前api服务使用单个redis测试开发的,现在k8s环境中部署的是redis集群,它和单个redis部署的不同点是:在写入和读取时,数据是通过hash分片保存到某一个集群的pod节点上,但为服务的pod此时要访问的redis的pod不一定是存储数据的pod,这样redis集群会发出重定向指令,但是正常使用单个redis测试开发的服务程序是不支持这个重定向的,就会出现,写入的数据获取失败的问题。一开始想通过不修改代码的方式解决,于是选择部署一个redis-cluster-proxy代理服务,应用服务连接代理服务,由代理服务协调redis集群的读写问题。方案是可行的,但我没有搞成功。原因是我没找到现成的redis-cluster-proxy的镜像,是通过下载源码编译自己打包的镜像,不知是什么原因,每次连接代理服务的pod总重启,导致没法使用。
于是我只好退回来选择修改代码的方式来解决缓存问题。
这里也遇到点坑,就是github.com/go-redis/redis/v8 库不支持域名或ip地址的问题,好像只支持类似localhost或主机名的地址方式,不适合在k8s中使用,经过多次尝试,我换了github.com/redis/go-redis/v9库,官网地址
修改代码只需要修改连接redis的代码部分就行,后面的读写操作代码不用修改。代码修改类似下面所示:
// go语言代码//config中的c.Redis.Addr设置:redis-cluster-0.redis-headless.redis.svc.cluster.local:6379,redis-cluster-1.redis-headless.redis.svc.cluster.local:6379,redis-cluster-2.redis-headless.redis.svc.cluster.local:6379var (rdb redis.CmdablesingleClient *redis.ClientclusterClient *redis.ClusterClient)redis_addr := strings.Split(c.Redis.Addr, ",")redis_addr_num := len(redis_addr)if redis_addr_num == 0 {panic("redis连接失败!!!\n 请检查配置地址信息。")}if redis_addr_num == 1 {singleClient = redis.NewClient(&redis.Options{Addr: redis_addr[0],Password: c.Redis.Passwd,DialTimeout: c.Redis.DialTimeout.AsDuration(),WriteTimeout: c.Redis.WriteTimeout.AsDuration(),ReadTimeout: c.Redis.ReadTimeout.AsDuration(),})err = redisotel.InstrumentTracing(singleClient)if err != nil {log.Fatal(err)}rdb = singleClient}if redis_addr_num > 1 {clusterClient = redis.NewClusterClient(&redis.ClusterOptions{Addrs: redis_addr,Password: c.Redis.Passwd,DialTimeout: c.Redis.DialTimeout.AsDuration(),WriteTimeout: c.Redis.WriteTimeout.AsDuration(),ReadTimeout: c.Redis.ReadTimeout.AsDuration(),})//log.Info(fmt.Printf("redis集群地址:%v \n", clusterClient.ClientList(context.Background())))//log.Info(fmt.Printf("redis集群信息:%v \n", clusterClient.ClusterInfo(context.Background())))// 添加集群健康检查ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()log.Info("执行Redis集群Ping测试...")if err := clusterClient.Ping(ctx).Err(); err != nil {log.Fatal("Redis集群连接失败: %v", err)// 可以在这里降级到单节点模式或抛出更明确的错误} else {log.Info("Redis集群连接成功")}err = redisotel.InstrumentTracing(clusterClient)if err != nil {log.Fatal(err)}rdb = clusterClient}......defer func() {if singleClient != nil {err := singleClient.Close()if err != nil {log.Error(err)}}if clusterClient != nil {err := clusterClient.Close()if err != nil {log.Error(err)}}
}()
重新编译打包部署,就可以解决redis集群缓存读写数据不一致问题了。
这里的数据库使用的是greatsql, 在MySQL和MariaDB 迁移过程中,遇到的问题不大,大部分是在创建数据表时,字符集和默认数据格式问题,比如在MariaDB 中 datetime类型 的默认值可以设置为‘0000-00-00 00:00:00’ ,而在greatsql中较为严格,只支持'1970-01-01 00:00:00' 格式。建议在迁移数据表的时候,将表结构单独部署,使用sql语句一个表一个表的执行,方便查错。迁移过程中一般情况批量导入都会报错。另外数据库工具也非常关键,目前还是没找到太好用的替代Navicat的工具。使用开源的Dbeaver 有时会出现莫明其妙的数据导入后,应用服务读取数据错误的问题。具体原因还没太搞明白,可能和字符集有关,具体还待进一步研究。