微服务不同环境到底该如何部署?最佳实践是什么?
你们是怎么部署微服务的?
如何处理不同的环境部署的?
今天给大家带来我们的一些实践。
    
      
  
1
微服务部署的挑战一句话来说,微服务部署的最大挑战是如何保证不同的环境部署代码的一致性,以及如何区分不同的部署环境。
作为开发,可能我们大多数时间都在开发代码,却忽略了发布的重要性,实际发布才是到用户手里的最后一步,这一步做的不好,那么你的软件价值则不能很好交付到用户手里,作为开发,你自己的体验也不好。
Docker正式因为改变了部署交付的方式,才引起了革命,产生了如此广泛的影响。
所以我们需要重视并做好交付这一步。
    
      
  
2
微服务部署的方式微服务的部署方式有多种,传统的比如打个Jar包,通过 java -jar 来启动,在云计算时代,更多的则是通过构建Docker镜像的方式,通过docker run 来启动。
这里的一个主要问题是,如何在不同的环境中去部署相应的服务。
对于一般公司来讲,我们会有开发、沙箱、预生产、生产环境,不同的环境,会有不同的配置,当然微服务背景下,我们都会引入配置中心,但不同环境下,还是需要不同的配置中心配置。
那么如何在不同的环境下去部署?
让我们来看看几种解决方案。(说明:以下都是通过构建Docker镜像的方式来部署的方案。)
方案一:不同的环境打不同的镜像
此种方案通过配合maven profile,maven-antrun-plugin插件,在不同的环境下,将不同环境的配置文件覆盖resources目录下的配置最后在打成Jar包,然后通过docker-maven-plugin插件,将应用Jar包构建成Docker镜像,推送到不同环境的Docker镜像仓库。
不同环境的部署,只需要到不同环境的Docker镜像仓库中拉取镜像,然后启动就行了。
关键步骤信息如下:
1)pom.xml中提前设定好不同的profile,不同的profile将使用不同的配置文件进行打包并构建镜像。
      
                 
      
      
                  
      
      
                    sand 
      
      
                     
      
      
                         
      
      
                             
      
      
                                org.apache.maven.plugins 
      
      
                                maven-antrun-plugin 
      
      
                                1.8 
      
      
                                 
      
      
                                     
      
      
                                        validate 
      
      
                                         
      
      
                                             
      
      
                                                
       
      
                                                      tofile="./src/main/resources/bootstrap.yml"
      
      
                                                      overwrite="true"/>
      
      
                                                ${revision} 
      
      
                                                
       
      
                                                         value="${revision}"/>
      
      
                                            
      
      
                                        
      
      
                                         
      
      
                                            run 
      
      
                                        
      
      
                                    
      
      
                                
      
      
                            
      
      
                             
      
      
                                com.spotify 
      
      
                                docker-maven-plugin 
      
      
                                1.0.0 
      
      
                                 
      
      
                                     
      
      
                                        build-image 
      
      
                                        package 
      
      
                                         
      
      
                                            build 
      
      
                                            push 
      
      
                                        
      
      
                                    
      
      
                                
      
      
                                 
      
      
                                    
      
      
                                    127.0.0.1:5000/${project.artifactId} 
      
      
                                    
      
      
                                     
      
      
                                        ${revision} 
      
      
                                    
      
      
                                    http://127.0.0.1:2375 
      
      
                                ${project.basedir}/src/main/assembly 
      
      
                                     
      
      
                                        ${project.build.finalName} 
      
      
                                    
      
      
                                     
      
      
                                         
      
      
                                            ${project.build.directory} 
      
      
                                             
      
      
                                                ${project.build.finalName}.jar 
      
      
                                            
      
      
                                        
      
      
                                    
      
      
                                
      
      
                            
      
      
                        
      
      
                    
      
      
                
      
    
  2)maven打包阶段指定不同的profile
      
        mvn clean deploy -P sand
      
    
  上述部署方案整体流程如下图所示:
    
      
  
上述方案的问题是,需要提前将所有的配置文件都放在代码仓库中,在环境多的时候不便于维护,特别是在多数据中心的情况下。另外不同的环境实际部署的是不同的镜像,不符合Docker的一次构建,处处部署的思想,当然还需要不同的环境部署不同的镜像仓库,也有一定的资源浪费。
方案二:将所有环境打进一个镜像,通过spring.profiles.active来生效不同的环境
此种方案是将所有环境都打包到一个镜像中,然后在启动的时候通过Spring的 spring.profiles.active 来生效不同的环境配置,关键步骤如下:
1)将所有环境的配置放到一个配置文件中,或者是放在不同环境的配置文件中(下面是将所有的配置都放到/src/main/resources/bootstrap.yml文件中):
      
        server:
      
      
          port: 8888
      
      
        spring:
      
      
          application:
      
      
            name: my-app
      
      
          profiles:
      
      
            # 默认启用dev环境
      
      
            active: dev   
      
      
        
          
      
      
        # 开发环境
      
      
        ---
      
      
        spring:
      
      
          config:
      
      
            activate:
      
      
              on-profile: dev
      
      
          cloud:
      
      
            nacos:
      
      
              discovery:
      
      
                server-addr: http://localhost:8848
      
      
                metadata:
      
      
                  version: '1.0.0-SNAPSHOT'
      
      
              config:
      
      
                server-addr: ${spring.cloud.nacos.discovery.server-addr}
      
      
                file-extension: yaml
      
      
                name: ${spring.application.name}
      
      
        
          
      
      
        # 沙箱环境
      
      
        ---
      
      
        spring:
      
      
          config:
      
      
            activate:
      
      
              on-profile: sand
      
      
          cloud:
      
      
            nacos:
      
      
              discovery:
      
      
                server-addr: http://sand.nacos:8840
      
      
                metadata:
      
      
                  version: '1.0.0-SNAPSHOT'
      
      
              config:
      
      
                server-addr: ${spring.cloud.nacos.discovery.server-addr}
      
      
                file-extension: yaml
      
      
                name: ${spring.application.name}
      
      
        
          
      
      
        # 生产环境
      
      
        ---
      
      
        spring:
      
      
          config:
      
      
            activate:
      
      
              on-profile: prod
      
      
          cloud:
      
      
            nacos:
      
      
              discovery:
      
      
                server-addr: http://prod.nacos:8848
      
      
                metadata:
      
      
                  version: '1.0.0-SNAPSHOT'
      
      
              config:
      
      
                server-addr: ${spring.cloud.nacos.discovery.server-addr}
      
      
                file-extension: yaml
      
      
                name: ${spring.application.name}
      
    
  2)启动的时候,通过启动脚本指定不同的active:
      
        JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=sand"
      
    
  上述部署方案整体流程如下图所示:
    
上述方案可以确保不同环境的镜像一致性,符合Docker的构建一次,处处部署的思想,当然问题是需要提前将所有环境都放在项目里,这会导致在环境变化时需要修改代码的问题(代码和配置没有完全分离),也会有安全问题,因为你的生产环境地址也暴露无遗。
方案三:将所有环境抽离出环境变量,在应用启动的时候通过环境变量注入
此种方案利用了Spring通过命令行来指定相关配置的特性,真正实现了代码与配置的完全分离。代码库里不存放任何的环境地址(可以只存放本地开发环境地址),所有的环境地址都通过环境变量的方式注入进去。这样可以真正做到,一套代码,处处运行,即使在新增数据中心,需要重新部署的时候,也不需要修改任何代码,直接通过环境变量指定即可。
关键步骤如下:
1)Docker部署脚本在启动容器时将环境变量注入:
      
        docker run -e SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR="localhost:8840" -d --network=host -v /log/:/log/ ${SERVER_DOCKER}/${SERVICE_NAME}:latest
      
    
  2)应用启动脚本通过环境变量,获取相应的地址:
      
        # 指定nacos地址
      
      
        JAVA_OPTS="$JAVA_OPTS -Dspring.cloud.nacos.discovery.server-addr=${SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR}"
      
      
        # 指定日志文件
      
      
        JAVA_OPTS="$JAVA_OPTS -DLOGBACK_LOG_HOME=${LOGBACK_LOG_HOME}"
      
      
        JAVA_OPTS="$JAVA_OPTS -jar ${DEPLOY_DIR}/${SERVER_NAME}.jar"
      
    
  
    上述部署方案整体流程如下图所示:
    
    
上述方案实现了代码与配置的真正分离,代码库里无需存放任何地址(可以指保留开发环境地址),不同的部署环境只要注入不同的环境配置即可。可以称得上是最佳实践。
    
      
        
    
  
写在最后
作为开发,不光光要关心写代码,更要重视发布的重要性。
微服务的发布有多种方式,云计算时代通过Docker镜像去发布是你的最优选择。
要把打包一次,处处运行作为发布目标。
想要实现上述目标,需要做到真正意义上的代码、配置分离,通过Docker环境变量注入,配合应用启动脚本,你可以做到真正意义上的代码、配置分离。
希望今天的文章对大家有所帮助。
推荐阅读: 《Spring Cloud Gateway如何优雅地进行feign调用》
    
聊技术,不止于技术。
    
    
      
  
