ASP站长网Dubbo是阿里开源的一套服务治理与rpc框架,服务的提供者通过zookeeper把自己的服务发布上去,然后服务调用方通过zk获取服务的ip和端口,dubbo客户端通过自己的软负载功能自动选择服务提供者并调用,整个过程牵涉到的三方关系如下图所示。
 
Dubbo应用迁移到Kubernetes
 
在正常的情况下,这三方都在同一个互通的网段,provider提供给zk的就是获取到的本机地址,consumer能访问到这个地址。
 
但是假如服务放在docker容器中,而调用者并不在docker中,它们的网段是不一样的。
 
Dubbo应用迁移到Kubernetes
 
这个时候就出现问题了,consumer无法访问到provider了。
 
Dubbo提供的解决方案
新版的Dubbo提供了四个配置来指定与注册服务相关的地址和端口。
 
DUBBO_IP_TO_REGISTRY: 要发布到注册中心上的地址
DUBBO_PORT_TO_REGISTRY: 要发布到注册中心上的端口
DUBBO_IP_TO_BIND: 要绑定的服务地址(监听的地址)
DUBBO_PORT_TO_BIND: 要绑定的服务端口
以IP地址为例,Dubbo先找是不是有DUBBO_IP_TO_BIND这个配置,如果有使用配置的地址,如果没有就取本机地址。然后继续找DUBBO_IP_TO_REGISTRY,如果有了配置,使用配置,否则就使用DUBBO_IP_TO_BIND。具体代码如下:
 
        /**
         * Register & bind IP address for service provider, can be configured separately.
         * Configuration priority: environment variables -> Java system properties -> host property in config file ->
         * /etc/hosts -> default network address -> first available network address
         *
         * @param protocolConfig
         * @param registryURLs
         * @param map
         * @return
         */
        private static String findConfigedHosts(ServiceConfig<?> sc,
                                                ProtocolConfig protocolConfig,
                                                List<URL> registryURLs,
                                                Map<String, String> map) {
            boolean anyhost = false;
 
            String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
            if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {
                throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind);
            }
 
            // if bind ip is not found in environment, keep looking up
            if (StringUtils.isEmpty(hostToBind)) {
                hostToBind = protocolConfig.getHost();
                if (sc.getProvider() != null && StringUtils.isEmpty(hostToBind)) {
                    hostToBind = sc.getProvider().getHost();
                }
                if (isInvalidLocalHost(hostToBind)) {
                    anyhost = true;
                    try {
                        logger.info("No valid ip found from environment, try to find valid host from DNS.");
                        hostToBind = InetAddress.getLocalHost().getHostAddress();
                    } catch (UnknownHostException e) {
                        logger.warn(e.getMessage(), e);
                    }
                    if (isInvalidLocalHost(hostToBind)) {
                        if (CollectionUtils.isNotEmpty(registryURLs)) {
                            for (URL registryURL : registryURLs) {
                                if (MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) {
                                    // skip multicast registry since we cannot connect to it via Socket
                                    continue;
                                }
                                try (Socket socket = new Socket()) {
                                    SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                    socket.connect(addr, 1000);
                                    hostToBind = socket.getLocalAddress().getHostAddress();
                                    break;
                                } catch (Exception e) {
                                    logger.warn(e.getMessage(), e);
                                }
                            }
                        }
                        if (isInvalidLocalHost(hostToBind)) {
                            hostToBind = getLocalHost();
                        }
                    }
                }
            }
 
            map.put(BIND_IP_KEY, hostToBind);
 
            // registry ip is not used for bind ip by default
            String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY);
            if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {
                throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
            } else if (StringUtils.isEmpty(hostToRegistry)) {
                // bind ip is used as registry ip by default
                hostToRegistry = hostToBind;
            }
 
            map.put(ANYHOST_KEY, String.valueOf(anyhost));
 
            return hostToRegistry;
        }
然后我们看这个getValueFromConfig(),它调用了下面的函数,可以看到,它是先找环境变量,再找properties。
 
    public static String getSystemProperty(String key) {
        String value = System.getenv(key);
        if (StringUtils.isEmpty(value)) {
            value = System.getProperty(key);
        }
        return value;
    }
所以我们通过环境变量,就能修改Dubbo发布到zookeeper上的地址和端口。假如我们通过docker镜像启动了一个dubbo provider,并且它的服务端口是8888,假设主机地址为192.168.1.10,那么我们通过下面的命令,
 
docker run -e DUBBO_IP_TO_REGISTRY=192.168.1.10 -e DUBBO_PORT_TO_REGISTRY=8888 -p 8888:8888 dubbo_image
就能让内部的服务以192.168.1.10:8888的地址发布。
 
我们通过官方的实例来演示一下,因为官方提供的案例都很久了,所以我自己重新搞了一个示例,代码在https://github.com/XinliNiu/dubbo-docker-sample.git 。
 
先启动一个zookeeper,暴露2181端口。
 
docker run --name zkserver --rm -p 2181:2181  -d zookeeper:3.4.9
看一下zk起来了
 
niuxinli@niuxinli-B450M-DS3H:~/dubbo-samples-docker$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                         NAMES
5efc1f17fba0        zookeeper:3.4.9     "/docker-entrypoint.…"   4 seconds ago       Up 2 seconds        2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp   zkserver
把代码导入IDE,修改dubbo-docker-provide.xml,把地址改成刚发布到zk的地址和端口,我的地址是192.168.1.8。
 
运行DubboApplication,这时候可以看到在zk上注册了服务。
 
Dubbo应用迁移到Kubernetes
 
修改dubbo-docker-consumer.xml里的zk地址,执行单元测试,能正常访问。
 
把DubboApplication导出成可以执行的jar包,名字叫app.jar,创建如下Dockerfile
 
FROM openjdk:8-jdk-alpine
ADD app.jar app.jar
ENV JAVA_OPTS=""
ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar
创建dubbo-demo镜像,在同样的目录里执行docker build。
 
docker build --no-cache -t dubbo-demo .
正常启动镜像
 
docker run  -p 20880:20880  -it --rm dubbo-demo
发现是172.16.0.3的地址,这个是访问不了的。
 
Dubbo应用迁移到Kubernetes
 
传入环境变量重新启动,
 
docker run  -e DUBBO_IP_TO_REGISTRY=192.168.1.8 -e DUBBO_PORT_TO_REGISTRY=20880 -p 20880:20880  -it --rm dubbo-demo
这时候就变成主机地址了。
 
Dubbo应用迁移到Kubernetes
 
在Kubernetes中使用Dubbo
当在Kubernetes中启动多个副本的时候,指定具体的IP和具体的端口,都是不可行的,因为每个机器的IP都不一样,不能写很多个yaml文件,而且一旦指定了具体端口,那这台主机的这个端口就被占用了。
 
我们可以通过创建Service,使用NodePort的方式,把端口固定住,这样端口的问题就解决了。因为是对外服务,所以使用ClusterIP肯定是不行了,IP有两种解决办法:
 
(1)使用Kubernetes的downward api动态的传入主机的ip。
 
(2)传固定的loadbalancer的地址,例如在所有的node之外有一个F5。
 
不管哪种方法,都是一种妥协的办法,很不“云原生”,我演示一下使用downward api动态传入主机地址,并使用nodeport固定端口的方式。
 
我的kubernetes集群如下:
 
角色 地址
master 192.168.174.50
node1 192.168.174.51
node2 192.168.174.52
node3 192.168.174.53
zk的地址是192.168.1.8,它与集群的主机互通。
 
我没有建private镜像仓库,把我之前打好的dubbo-demo直接push到docker-hub上了,名字是nxlhero/dubbo-demo。
 
创建Service,使用的NodePort为30001,创建4个副本,这样3台机器上正好有一台起两个pod。
 
apiVersion: v1
kind: Service随着Kubernetes的遍地开花,Kubernetes的优势可以说是深入人心,很多企业也是利用Kubernetes,来实现更高效的交付和更好地提高我们的资源使用率,推动标准化,适应云原生。
 
随着Kubernetes和云原生加速企业产品落地,现在总结以下几点
 
1)更快的应用开发与交付
2)天然适合微服务,是微服务和Devops的桥梁
3)可移植性,支持公有云,私有云,裸机,虚拟机
4)标准化的应用开发与发布:声明式API和Operator
5)自动化运维:弹性伸缩(HPA),故障自愈,负载均衡,配置管理等
另外就是交付spring cloud到k8s之前说一下微服务的概念
 
什么是微服务?
早在2011年的5月份在威尼斯的一个架构研讨会上微服务的概念就被人提起了,当时的参会者对它的描述只是一种通用的软件并没给出明确的定义是什么,之后随着技术的不断发展,在2014年詹姆斯里维斯以及它的伙伴马丁福勒在它的微博中发表了一篇有关于微服务特点的文章,对微服务进行了全面的阐述,之后微服务就走进了我们的视野
https://martinfowler.com/articles/microservices.html
这是关于网站的描述
在这个文章中并给出微服务一个明确的定义,微服务其实是一种软件的架构风格,是一种将单体架构
拆分为小的服务进行去开发,每个服务都运行在自己的进程中,采用的是轻量级的restful或者http进行通信,并且都是独立开发独立部署和测试的,可以使用多种语言进行开发
 
对于微服务有一个关键点叫化整为零,把一个大的应用却成一个小的不同的应用
比如嘀嘀打车,早期在一个互联网应用上基本上都是单体架构,不是分布式的
单体情况下把很多程序都写一个程序中,然后一台服务器对所有服务进行运行,但是随着并发的提高,这种单体架构显然承受不了了,这样的话就需要我们对我们软件的指责进行慢慢的划分,将其剥离出来,形成一个一个的微服务,也就是多个微服务的模块形成一个完整的应用,这些都是独立部署独立运行的,一个微服务也会运行在一个虚拟机里面。
 
spring cloud微服务体系的组成
服务发现 (Eureka,Cousul,zookeeper)
也就是注册中心最为微服务必不可少的中央管理者,服务发现的主要职责就是将其它的微服务模块进行登记与管理,这就相当于生活中来了一家公司,去工商局进行登记一样的道理,在服务发现中它主包含了三个子模块,分别是eureka,cousul,zookeeper,这些spring cloud底层都支持的注册中心,一般常用的是eureka和consul,那么微服务构建好了之后,那么微服务与微服务直接怎么进行服务直接的通信,或者微服务遇到了故障无法达到请求的话(hystrix/ribbon/openfeign)
另外就是路由与过滤主要是针对对外接口的暴露的,这里主要涉及zuul,spring cloud gateway,这两个组件主要为外部的调用者比如其他的系统和我们的微服务进行通信的时候,由外到内是怎么彼此进行访问的,那么这些就是由这两个组件进行完成的
配置中心就是存放我们应用程序配置的地方,可能我们有上百个应用程序,那么每个应用程序都是一个微服务,那么就会产生一个很严重的问题,就是这些配置文件放在什么地方比如每个服务下都放一个xml,或者yml,维护起来是非常不方便的,因为改一个参数,就要对所有的应用进行调整,为了解决这个问题配置中心就出现了,相当于又提供了一个微服务把我们应用中所有的配置文件,都放在了配置中心中,那么其他应用都是通过配置中心来获取到这些配置文件的而不是我们要这个这个配置文件放到每个程序中,这样的好处就是可以将我们的配置文件进行集中的管理,只需要改一个地方所有地方都能生效
 
spring cloud微服务组成
消息总线,spring cloud stream或者spring cloud bus就跟我们的消息mq差不多就是我们发布一个信息,到我们队列里面由其他的微服务或者其他的应用进行获取提供了系统与系统之间或者微服务与微服务之间的消息传递过程,这个中间增加了一个额外的东西叫做消息总线,具体的消息总线可以是mq或者是Redis,不同的厂商实现了不同的实现
安全控制是针对我们安全的管理,在我们传统网站开发的时候,应用的访问控制有授权的可以使用这个功能,没有授权的就无法进行访问,安全控制在spring cloud中也是存在的提供了AUTH2.0方案的支持,链路监控就是对我们消息传递的过程要进行统筹和监控,比如系统中有10个微服务,而这10个微服务是彼此依赖的,第一个微服务它是底层最基础的用户管理而第二个微服务是基于用户管理开发一个权限管理,在往上是应用管理,应用系统的扩展,每一个微服务之间彼此之间进行依赖在顶层我们进行调用的时候会安装微服务的调用顺序一级一级消息往下传递,这样做有一个问题来了,如果中间有个环节出现了问题,没有响应服务我们在使用的角度当前我们的请求失败了,但是具体的环节不知道是在那一块出现问题,那么链路监控就是让我们快递定位消息传递过程哪个阶段进行出错,有助于我们问题的排查
spring cloud cli命令行工具,来实现我们开发来实现的一些功能,spring cloud cluster是对我们集群管理的一个辅助工具
 
现在去交付微服务到k8s中举个demo仅供参考
 
一、发布流程设计
二、准备基础环境
三、在Kubernetes中部署jenkins
四、jenkins pipeline及参数化构建
五、jenkins在k8s中动态创建代理
六、自定义构建jenkins-slave镜像
七、基于kubernetes构建jenkins ci系统
八、pipeline集成helm发布spring cloud微服务
metadata:
  name: dubbo-docker
  labels:
    run: dubbo
spec:
  type: NodePort
  ports:
  - port: 20880
    targetPort: 20880
    nodePort: 30001
  selector:
    run: dubbo-docker
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-docker
spec:
  selector:
    matchLabels:
      run: dubbo
  replicas: 4
  template:
    metadata:
      labels:
        run: dubbo
    spec:
      containers:
      - name: dubbo-docker
        image: nxlhero/dubbo-demo
        env:
        - name: DUBBO_IP_TO_REGISTRY
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: DUBBO_PORT_TO_REGISTRY
          value: "30001"
        tty: true
        ports:
        - containerPort: 20880
这个yaml最关键的地方就是环境变量,主机IP通过downward apid传入,端口使用固定的nodeport。
 
        env:
        - name: DUBBO_IP_TO_REGISTRY
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: DUBBO_PORT_TO_REGISTRY
          value: "30001"
创建Service,启动后可以看到zookeeper上的地址都是主机的地址和nodeport。

dawei

【声明】:九江站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。