环境说明

本示例使用简单的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值选择实例

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐