灰度发布环境搭建
spring cloud alibaba 微服务在k8s中实现灰度发布
环境说明
本示例使用简单的spring cloud alibaba工程的发布作为示例,演示在k8s环境中如何进行灰度发布
工程包含hello-service服务端,使用nacos作为服务注册发现和配置中心组件
另外包含consumer-feign客户端,使用openfeign方式访问hello-service服务端
微服务源码下载(免费的)
nacos服务端部署
从github中下载nacos服务端进行部署
下载后解压,并启动
[root@host125 nacos]# ls
bin conf LICENSE NOTICE target
[root@host125 nacos]# pwd
/opt/install/nacos
[root@host125 nacos]# cd bin
[root@host125 bin]# sh startup.sh -m standalone
可以登录控制台查看http://192.168.220.125:8848/nacos/index.html#/login nacos/nacos
登录nacos控制台,创建namespace并新增配置文件
consumer-feign.yml
server:
port: 8765
spring:
application:
name: consumer-feign
cloud:
nacos:
config:
server-addr: ${nacos.server-addr}
file-extension: yml
group: ${deploy.env}
namespace: ${deploy.namespace}
discovery:
server-addr: ${nacos.server-addr}
metadata:
deploy.env: ${deploy.env} #服务注册时会在元数据中添加该项配置
namespace: ${deploy.namespace}
hello-service.yml
server:
port: 8763
spring:
# main:
# allow-bean-definition-overriding: true
application:
name: hello-service
cloud:
nacos:
config:
server-addr: ${nacos.server-addr}
file-extension: yml
group: ${deploy.env}
namespace: ${deploy.namespace}
discovery:
server-addr: ${nacos.server-addr}
metadata:
deploy.env: ${deploy.env} #服务注册时会在元数据中添加该项配置
namespace: ${deploy.namespace}
zipkin:
base-url: http://localhost:9411
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.220.125:3306/mall?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: Root123
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
微服务代码说明
consumer-feign服务
@RestController
public class FeignHelloController {
@Autowired
FeignHelloServiceClient helloService;
@GetMapping(value = "/hi")
public String hi(@RequestParam String name) {
return helloService.hi( name );
}
}
@FeignClient(value = "hello-service",fallback = FeignHelloServiceImpl.class)
public interface FeignHelloServiceClient {
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String hi(@RequestParam(name = "name") String name);
}
配置自定义负载均衡策略
1、获取当前服务元数据中deploy.env配置的值
2、根据@FeignClient中的服务名获取nacos中所有实例
3、过滤所有实例中元数据deploy.env配置的值与当前服务一致的实例集合
4、调用nacos随机权重策略选取一个实例
public class EnvLoadbalancerRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
String deployEnv = "deploy.env";
// 获取当前服务的集群名称
String currentClusterName = nacosDiscoveryProperties.getClusterName();
// 获取当前服务所处环境
String env = nacosDiscoveryProperties.getMetadata().get(deployEnv);
// 获取被调用的服务的名称
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer();
String serviceName = baseLoadBalancer.getName();
// 获取nacos clinet的服务注册发现组件的api
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
// 获取所有被调用服务
List<Instance> allInstances = namingService.getAllInstances(serviceName);
// 过滤出相同环境且相同集群下的所有服务
List<Instance> sameVersionAndClusterInstances = allInstances.stream()
.filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get(deployEnv), env)
&& StringUtils.equalsIgnoreCase(x.getClusterName(), currentClusterName)
).collect(Collectors.toList());
Instance chooseInstance;
if(sameVersionAndClusterInstances.isEmpty()) {
// 过滤出所有相同环境的服务
List<Instance> sameVersionInstances = allInstances.stream()
.filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get(deployEnv), env))
.collect(Collectors.toList());
if(sameVersionInstances.isEmpty()) {
System.out.println("跨集群调用找不到对应合适的服务:env:"+env);
throw new RuntimeException("找不到相同环境的微服务实例");
}
else {
// 随机权重
chooseInstance = ExtendBalancer.getHostByRandomWeight2(sameVersionInstances);
}
}
else {
chooseInstance = ExtendBalancer.getHostByRandomWeight2(sameVersionAndClusterInstances);
}
return new NacosServer(chooseInstance);
} catch (NacosException e) {
e.printStackTrace();
return null;
}
}
}
@SpringBootApplication
@EnableFeignClients
@RibbonClients(defaultConfiguration = FeignConsumerApplication.class)
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
@Bean
public IRule myRule(){
//需要什么算法就new对应的类
return new EnvLoadbalancerRule();
}
}
hello-service服务
@RestController
public class HelloService {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private UserMapper userMapper;
@Value("${deploy.env}")
String env;
@RequestMapping("/hi")
public String home(@RequestParam(value = "name", defaultValue = "prod") String name) throws UnknownHostException {
logger.info("===== hello-service/hi ============"+env);
// User user = new User();
// user.setId(2);
// user.setName(name);
// user.setPwd("123455");
// userMapper.addUser(user);
return "hi " + name + " ,i am at env:" + env+", ip:"+ InetAddress.getLocalHost().getHostAddress();
}
}
制作镜像,并push阿里云
打包微服务成jar包,并上传到虚拟机中,然后编写Dockerfile文件
[root@host15 dockerImage]# vi Dockerfile_helloService
FROM openjdk:8-jre-alpine
COPY hello-service-1.0-SNAPSHOT.jar /hello-service.jar
ENTRYPOINT ["java","-jar","/hello-service.jar"]
[root@host15 dockerImage]# docker build -f Dockerfile_helloService -t hello-service-image .
Sending build context to Docker daemon 74.25MB
Step 1/3 : FROM openjdk:8-jre-alpine
8-jre-alpine: Pulling from library/openjdk
e7c96db7181b: Pull complete
f910a506b6cb: Pull complete
b6abafe80f63: Pull complete
Digest: sha256:f362b165b870ef129cbe730f29065ff37399c0aa8bcab3e44b51c302938c9193
Status: Downloaded newer image for openjdk:8-jre-alpine
---> f7a292bbb70c
Step 2/3 : COPY hello-service-1.0-SNAPSHOT.jar /hello-service.jar
---> 4876489f0135
Step 3/3 : ENTRYPOINT ["java","-jar","/hello-service.jar"]
---> Running in 293d314ccadb
Removing intermediate container 293d314ccadb
---> 15be60565b5c
Successfully built 15be60565b5c
Successfully tagged hello-service-image:latest
[root@host15 dockerImage]#
将制作好的镜像推送至镜像仓库,由于我本地没有搭建私有仓库,所以先用阿里云的镜像仓库吧
$ docker login --username=at36*****@aliyun.com registry.cn-hangzhou.aliyuncs.com
$ docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/demo-ns-p/demo:hello-service-v1.0
$ docker push registry.cn-hangzhou.aliyuncs.com/demo-ns-p/demo:hello-service-v1.0
docker login命令输入密码时需要输入容器镜像服务的固定密码,而不是阿里云登陆的密码,否则会报错
Error response from daemon: Get https://registry.cn-hangzhou.aliyuncs.com/v2/: unauthorized: authentication required
固定密码在容器镜像服务-》访问凭证中设置
使用相同的方式制作出consumer-feign镜像并push到镜像仓库,push完成之后就可以在阿里云中看到
编写k8s资源文件并发布
在k8s中先创建两个命名空间 gray 和 blue,代表灰度环境和蓝组正式环境,服务部署时这两个namespace下的yaml文件是不一样的,主要是部署的资源所属的namespace和pod中设置的环境变量不一样
hello-service-gray.yaml
apiVersion: apps/v1 # 版本号
kind: Deployment # 类型
metadata: # 元数据
name: hello-service-deploy # deployment的名称
namespace: gray # 命名类型
spec: # 详细描述
replicas: 2 # 副本数量
selector: # 选择器,通过它指定该控制器可以管理哪些Pod
matchLabels: # Labels匹配规则
app: hello-service
template: # 模块 当副本数据不足的时候,会根据下面的模板创建Pod副本
metadata:
labels:
app: hello-service
spec:
containers:
- name: hello-service
image: registry.cn-hangzhou.aliyuncs.com/demo-ns-p/demo:hello-service-v1.0
ports:
- containerPort: 8763 # 容器所监听的端口
env:
# 部署环境,指定是灰度还是正式
- name: deploy.env
value: GRAY
- name: deploy.namespace
value: prod
- name: nacos.server-addr
value: 192.168.220.125:8848
---
apiVersion: v1
kind: Service
metadata:
name: hello-service-svc
namespace: gray
spec:
type: NodePort
selector:
app: hello-service
ports:
- port: 8081 #service(对内)的端口
protocol: TCP
targetPort: 8763 #pod的端口
nodePort: 32001 #service(对外)的端口
创建hello-service-blue.yaml文件,内容copy上面的文件内容,注意将文件中的gray改为blue,特别是deploy.env的值要改为BLUE, nodePort的端口需要改下,否则会出现端口冲突
consumer-feign-gray.yaml
apiVersion: apps/v1 # 版本号
kind: Deployment # 类型
metadata: # 元数据
name: consumer-feign-deploy # deployment的名称
namespace: gray # 命名类型
spec: # 详细描述
replicas: 2 # 副本数量
selector: # 选择器,通过它指定该控制器可以管理哪些Pod
matchLabels: # Labels匹配规则
app: consumer-feign
template: # 模块 当副本数据不足的时候,会根据下面的模板创建Pod副本
metadata:
labels:
app: consumer-feign
spec:
containers:
- name: consumer-feign
image: registry.cn-hangzhou.aliyuncs.com/demo-ns-p/demo:consumer-feign-v1.0
ports:
- containerPort: 8765 # 容器所监听的端口
env:
# 部署环境,指定是灰度还是正式
- name: deploy.env
value: GRAY
- name: deploy.namespace
value: prod
- name: nacos.server-addr
value: 192.168.220.125:8848
---
apiVersion: v1
kind: Service
metadata:
name: consumer-feign-svc
namespace: gray
spec:
type: NodePort
selector:
app: consumer-feign
ports:
- port: 8082 #service(对内)的端口
protocol: TCP
targetPort: 8765 #pod的端口
nodePort: 32088 #service(对外)的端口
创建consumer-feign-blue.yaml文件,内容copy上面的文件内容,注意将文件中的gray改为blue,特别是deploy.env的值要改为BLUE, nodePort的端口需要改下,否则会出现端口冲突
创建k8s资源并查看
[root@host15 dockerImage]# kc apply -f hello-service-gray.yaml
[root@host15 dockerImage]# kc apply -f hello-service-blue.yaml
[root@host15 dockerImage]# kc apply -f consumer-feign-gray.yaml
[root@host15 dockerImage]# kc apply -f consumer-feign-blue.yaml
[root@host15 dockerImage]# kc get pod -n gray
NAME READY STATUS RESTARTS AGE
consumer-feign-deploy-7d59497fbf-9l4cf 1/1 Running 0 6s
consumer-feign-deploy-7d59497fbf-tq6gr 1/1 Running 0 7s
hello-service-deploy-54c56cdbcc-dldpg 1/1 Running 0 82s
hello-service-deploy-54c56cdbcc-hfqqf 1/1 Running 0 82s
[root@host15 dockerImage]# kc get pod -n blue
NAME READY STATUS RESTARTS AGE
consumer-feign-deploy-649cb7bbb4-cjngj 1/1 Running 0 3m49s
consumer-feign-deploy-649cb7bbb4-jjd69 1/1 Running 0 3m49s
hello-service-deploy-64cc64d84d-gq6ft 1/1 Running 0 31m
hello-service-deploy-64cc64d84d-t9wh4 1/1 Running 0 31m
nacos中查看服务
测试验证
[root@host15 dockerImage]# kc get svc -n gray -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
consumer-feign-svc NodePort 10.1.69.46 <none> 8082:32088/TCP 10m app=consumer-feign
hello-service-svc NodePort 10.1.215.162 <none> 8081:32001/TCP 11m app=hello-service
[root@host15 dockerImage]# kc get svc -n blue -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
consumer-feign-svc NodePort 10.1.75.91 <none> 8082:32089/TCP 14m app=consumer-feign
hello-service-svc NodePort 10.1.156.240 <none> 8081:32021/TCP 41m app=hello-service
[root@host15 dockerImage]#
使用32088端口访问consumer-feign-svc时,请求只会在gray命名空间中找consumer-feign服务处理,然后consumer-feign调用hello-service时,走的是ribbon负载均衡调用,由于应用中定义了新的负载均衡策略,由此决定了在灰组中只会找到元数据中deploy.env=GRAY的实例进行调用,页面多刷新几次也只会在灰组的两个实例中随机选取一个实例调用,达到了灰度发布的效果
同样的,使用32089端口访问时,请求只会在blue组中选取实例
总结
1、k8s创建2个命名空间,gray 和 blue
2、2个命名空间中各自定义k8s资源描述文件,且文件中定义环境变量
灰组
name: deploy.env
value: GRAY
蓝组
name: deploy.env
value: BLUE
3、微服务注册到nacos中时,需要读取环境变量中deploy.env的值,将其写到元数据中
spring:
cloud:
nacos:
discovery:
metadata:
deploy.env: ${deploy.env} #服务注册时会在元数据中添加该项配置
4、微服务中自定义负责均衡策略,实现按deploy.env值选择实例
更多推荐
所有评论(0)