Springboot业务迁移到Kubernates环境小结
目前将视频会员服务从阿里云ECS服务器迁移到IDC机房的Kubernates环境,在迁移的过程中遇到一些或大或小的问题,因此将解决的方式及一些心得记录下来,以方便大家进行参考。
大纲
对应用本身的要求 -->无状态
会话同步
锁机制
外部服务基于域名方式
配置走环境变量
应用探针*(保证应用的无缝迁移)
docker打包
k8s部署相关
服务编排
Redis集群的配置
一. 应用本身的要求
由于K8S内部是分布式环境,因此服务本身必须支持分布式环境下的部署要求。
1.1 会话同步
服务需要是无状态的,如果服务自身保存了会话信息,请求转移到其余节点时会因为丢失会话而报错。
因此对于web服务,涉及到会话保持的部分,需要采用外部方式进行会话保持。常见的方式有:会话粘滞,分布式session
【推荐阅读】 session会话保持原理
我的应用采用的是基于Redis的spring session实现方式。
【推荐阅读】 Spring Boot项目利用Redis实现session管理
(文章采用的是单机模式的Redis部署,生产中多采用集群方式,在后面的内容会讲解视频服务采用的哨兵方式)
1.2 锁机制
对于扫库逻辑,应当支持锁机制,多采用分布式锁、数据库乐观锁(版本号、状态机)、悲观锁等的实现方式。保证同一时刻只有单个服务在实际提供服务,其余未获取锁的服务只能自旋。
【推荐阅读】 分布式锁的几种实现方式
【推荐阅读】 乐观锁与悲观锁
1.3 外部服务基于域名方式
由于服务本身已经无状态,因此类似于ip等的硬编码等配置应当剔除,转而使用基于名字的方式进行服务的访问,K8S的DNS服务会对服务和实ip之间做映射,这样我们的服务可以任意伸缩及转义而不需要频繁更改配置。
1.4 配置走环境变量
配置使用环境变量,在容器实际部署的时候由外部环境设值,docker命令为
docker -e xxxx=xxxxx
这也是围绕着无状态服务的。在k8s中的使用方式为基于Secret,服务生成时会根据对应的key取到保密字典中的value。后续会讲解。
1.5 应用探针
应用本身需要提供HTTP(推荐)方式的服务探针,k8s会依据探针的响应情况决定是否对服务进行负载伸缩。
二. docker打包等
2.1 打包方式
视频会员服务目前采用的方式是线下环境打包,push到仓库,运维在线上拉取镜像进行部署。前提是保证线下测试通过。
后续该方式会更加的流程化,尽量减少人员的介入。
2.1.1 打包
打包之前需要准备
- 应用二进制包,springboot项目为jar包,web后台为war等。
2.Dockerfile
3.启停脚本
在docker环境下执行打包操作,命令为(以视频会员举例)
打包
docker build -t development1/video-member-api:1.2 .
打标签,描述镜像属于哪个部门哪个业务线
docker tag development1/video-member-api:1.2 172.20.36.229:5000/development1/video-member-api:1.2
将镜像推送到仓库
docker push 172.20.36.229:5000/development1/video-member-api:1.2
删除本地镜像
docker rmi development1/video-member-api:1.2
三. K8S部署相关
3.1 服务编排
【推荐阅读】 视频会员Gitlab主页
3.2 外部服务接入
外部服务在K8S内部是以一个service的形式部署的,部署方式如下,以数据库为例
video-member-db.yaml
apiVersion: v1
kind: Service
metadata:
name: video-member-db
namespace: video-member
spec:
ports:
- port: 3306 -- 集群内监听端口
protocol: TCP
targetPort: 3306 -- 需要继续端口转换的目标端口,指向真实端口
---
apiVersion: v1
kind: Endpoints
metadata:
name: video-member-db
namespace: video-member
subsets:
- addresses:
- ip: 172.30.61.12 -- 外部服务的真实ip
ports:
- port: 3306 -- 外部服务端口
3.3 Redis集群搭建
由于视频会员是基于三节点的集群方式部署,需要进行会话同步,采用了基于Redis方式的spring session技术实现,因此需要依赖高可靠的Redis集群方式实现,此处采用的是哨兵模式的Redis集群方式。
由于之前已经有一套现成的Redis服务,因此直接将外部的哨兵集群映射到服务内部,在集群内采用域名方式加载哨兵集群。
spring-application-prod.properties
#########################################################################
#
# redis配置
#
#########################################################################
spring.redis.database=0
#spring.redis.host=172.30.66.78
spring.redis.port=6379
spring.session.store-type=redis
# name of Redis server 哨兵监听的Redis server的名称
spring.redis.sentinel.master=mymaster
# comma-separated list of host:port pairs 哨兵的配置列表
spring.redis.sentinel.nodes=redis-sentinel-1:26379,redis-sentinel-2:26379,redis-sentinel-3:26379
# REDIS-PASSWD
spring.redis.password=${redis_password}
可以看到,集群节点处是以名字方式指向了外部的redis哨兵集群
配置文件如下:
video-member-redis.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-sentinel-1
namespace: video-member
spec:
ports:
- port: 26379
protocol: TCP
targetPort: 26379
---
apiVersion: v1
kind: Endpoints
metadata:
name: redis-sentinel-1
namespace: video-member
subsets:
- addresses:
- ip: 172.30.61.12
ports:
- port: 26379
---
apiVersion: v1
kind: Service
metadata:
name: redis-sentinel-2
namespace: video-member
spec:
ports:
- port: 26379
protocol: TCP
targetPort: 26379
---
apiVersion: v1
kind: Endpoints
metadata:
name: redis-sentinel-2
namespace: video-member
subsets:
- addresses:
- ip: 172.30.61.12
ports:
- port: 26379
---
apiVersion: v1
kind: Service
metadata:
name: redis-sentinel-3
namespace: video-member
spec:
ports:
- port: 26379
protocol: TCP
targetPort: 26379
---
apiVersion: v1
kind: Endpoints
metadata:
name: redis-sentinel-3
namespace: video-member
subsets:
- addresses:
- ip: 172.30.61.12
ports:
- port: 26379
3.4 启动顺序的问题
先加载环境变量secret服务,加载数据库服务,加载外部系统映射服务,外部系统构建完毕之后最后启动主业务系统。
这些操作都可以通过服务编排脚本一键实现。