在之前的文章《Apollo学习(三): 分布式部署》中,我简单介绍了在Windows环境下,实现Apollo配置中心的分布式部署。本篇博文我将对如何实现Apollo的docker容器化分布式部署的步骤进行记录总结。
要达到以上的目的,我们需要根据官方的分布式部署指南对Apollo的启动脚本,配置文件等进行修改,获取安装包。同时,因为使用了docker部署,在与MySQL数据库和Eureka注册中心的通信,涉及到了容器间的通信问题。最后,要使用docker compose对容器进行编排,需要编写docker-compose.yml文件。
根据需求从GitHub Release页面下载所需版本的source code包或者直接clone源码。
如果需要项目的默认端口,日志输出路径、等级等配置信息,需要对apllo-configservice, apollo-adminservice, apollo-portal三个项目进行修改。
修改端口:原来默认端口分别是8070 8080 8090,若需修改监听端口,可直接修改三个项目的scripts/startup.sh中的SERVER_PORT。
注意,由于采用的是docker部署,可以不修改原有端口,可采用端口映射来实现默认值的修改。此时,要注意docker-compose.yml文件中各服务的端口映射ports的值。
修改日志配置:
调整日志输出路径可分别修改三个项目scripts/startup.sh文件和apollo-{project}.conf文件中LOG_DIR的值。为方便查询日志,可在docker-compose.yml文件中,使用volumes进行文件的挂载。
调整日志等级分别修改三个项目的logback.yml配置文件。
修改JVM参数:可以修改scripts/startup.sh的JAVA_OPTS参数值
由于Apollo的apollo-configservice项目本身就是配置中心,要使用独立的配置中心,需要禁止自带的配置中心。修改apollo-configsevice项目的bootstrap.yml文件,添加以下配置:
apollo:
eureka:
server:
enabled: false
分布式部署时,apollo-configsevice和apollo-adminservice需要把直接的ip和port注册到注册中心,再由Meta Server返回给客户端和apollo-portal。
在docker部署时,如果没有使用独立的容器化部署的注册中心,不需进行这样的容器间通信。这里注意,需要避免将某些客户端和Portal无法访问的网卡IP注册到eureka,因此要在apollo-configservice和apollo-adminservice中做先关的限制。
修改两个项目的application.yml文件,把要忽略的网卡加进去,如:docker0,这个是docker默认的bridge network,当创建容器时,没有特别指network,则会默认加入到此bridge network中。
spring:
application:
name: apollo-configservice
profiles:
active: ${apollo_profile}
cloud:
inetutils:
ignoredInterfaces:
- docker0
- veth.*
如果是使用单独容器化部署的注册中心,则容器间需要进行通信(这里指的都是同一个docker daemon进程下的容器通信)。需要使用特定的bridge network。注意,因为容器间的通信,所以apollo-configservice和apollo-adminservice在注册时会将特定的网卡ip注册到注册中心,这样会导致客户端和Portal无法访问。这里,就需要指定要注册的IP,由于apollo-configservice和apollo-adminservice是基于内网可信的网络设计的,我们可以直接指定内网机器的内网ip,此时访问时就需要内网IP:映射的Port, 而不是通过expose暴露的端口。
指定注册的IP,修改apollo-configsevice和apollo-adminservice项目的bootstrap.yml文件。其他方式详见wiki。
eureka:
instance:
ip-address: 内网ip
这里我使用的是独立的容器化部署的eureka集群,需要修改ApolloConfigDB数据库中ServerConfig表的eureka.service.url字段的值。容器间的通信,直接指定container_name:port,多个地址用逗号分隔:
http://eureka-server1:3030/eureka/,http://eureka-server2:3031/eureka/
这里我同样使用的是容器化部署的MySQL数据库,ip也为container_name。直接编辑项目中的scripts/build.sh文件,配置ApolloConfigDB和ApolloPortalDB的连接信息:
# apollo config db info
apollo_config_db_url=jdbc:mysql://mysql5.7:3306/ApolloConfigDB?characterEncoding=utf8
apollo_config_db_username=root
apollo_config_db_password=
# apollo portal db info
apollo_portal_db_url=jdbc:mysql://mysql5.7:3306/ApolloPortalDB?characterEncoding=utf8
apollo_portal_db_username=root
apollo_portal_db_password=
由于meta service和config service是在同一个jvm部署的,所以这个地址就是apollo-configservice项目的地址,又因为apollo-portal项目同为docker部署,这里要进行容器间的通信。ip即为apollo-configservice的容器名称,若之前修改了项目的监听端口,这里的端口也要与其一致。
注意,在实际使用中,在不同环境中,apollo-configservice和apollo-adminservice要重新部署,而apollo-portal只要部署一套就可管理各个环境,不同的环境中可能并不需要容器间的通信。要依据实际来配置meta service的地址。
编辑scripts/build.sh文件
dev_meta=http://apollo-configservice:9003
根据需求修改后,进行编译打包,执行项目的build.sh脚本,该脚本会重新打包项目的安装包。
由于不同环境所用的数据库信息不同,所以针对不同的环境apollo-configservice和apollo-adminservice要重新打包,apollo-portal只需要打包一次。
在脚本执行完成后,分别在三个项目的target文件夹下获取安装包,名称分别为apollo-configservice-x.x.x-github.zip, apollo-adminservice-x.x.x-githup.zip, apollo-portal-x.x.x-github.zip。
官方已经在源码中提供了Dockerfile,注意文件中的版本号要与安装包的版本号一致。
这里我使用的是云服务器,将Dockerfile和安装包上传到服务器后,执行docker build -t container_name . 构建镜像。
注意,这里连接MySQL,Eureka以及Portal需要从meta service获取服务地址信息都涉及到了容器间的通信,又因为这些容器我都部署在了同一个docker daemon进程下,所以使用 User-defined bridge network。
这里我使用了两个bridge network,分别为mysql_net,eureka_net。
eureka_net为eureka容器化部署集群时创建,apollo-configservice和apollo-adminservice需要与eureka进行通信,需要加入到此网络中,又因为apollo-portal需要从meta service中获取服务地址,即需要和apollo-configservice通信,也需要加入到此网络中。
mysql_net为单独创建,为了与MySQL通信,apollo-configservice,apollo-adminservice和apollo-portal都需要进入到此网络中
创建bridge network可使用以下命令:
docker network create [network_name]
连接正在运行的容器到network中可使用以下命令:
dockcer network connect [network_name] [container_name]
注意,这里的bridge network都是提前创建的,在docker-compose.yml文件中需要将networks的external参数值设置为true
version: '3'
services:
apolloconfigservice:
image: apollo-configservice
container_name: apollo-configservice
networks:
- mysql
- eureka-net
volumes:
- /opt/docker/apollo/logs/config:/opt/logs/configservice
ports:
- 9003:9003
apolloadminservice:
image: apollo-adminservice
container_name: apollo-adminservice
depends_on:
- apolloconfigservice
networks:
- mysql
- eureka-net
volumes:
- /opt/docker/apollo/logs/admin:/opt/logs/adminservice
ports:
- 9004:9004
apolloportalservice:
image: apollo-portal
container_name: apollo-portal
depends_on:
- apolloconfigservice
- apolloadminservice
networks:
- mysql
- eureka-net
volumes:
- /opt/docker/apollo/logs/portal:/opt/logs/apolloportal
ports:
- 9005:9005
networks:
eureka-net:
external: true
mysql:
external: true
将docker-compose.yml上传到服务器,使用命令docker-compose up -d 启动服务。
注意,在文件中使用depends_on来确定容器的启动顺序,确保apollo-configservice和apollo-adminservice服务在apollo-portal服务前启动。
我在实际操作时,使用的是个人云服务器,由于服务器的限制,无法同时启动三个服务,我将apollo-configservice和apollo-adminservice使用一个docker-compose.yml文件管理,apollo-portal单独使用一个docker-compose.yml文件管理。
但是在服务启动后发现,apollo-portal服务对apollo-adminservice 和 apollo-configservice的某个http请求一直会出现connection refused错误,但是这些容器都运行正常。
在刚开始时,以为是端口的问题,换了多次端口,通过docker-compose重启多次,但依然存在http connection refused的问题。查了很多资料,都很确定容器内监听的端口设置没有问题,如果正常监听的话,不会出现connection refused的问题。那么问题就在 容器是否正常监听了设置的端口??
通过命令 docker exec -it container_name /bin/sh 进入到无法连接的服务容器中,再通过命令 netstat -tunlp 查看端口占用的情况。果然,端口没有正常监听,导致apollo-portal的http请求一直connection refused,整个Apollo配置服务一直异常。
通过命令 docker restart container_name 重启容器后,容器监听端口正常,服务恢复正常。
源码地址:
https://github.com/Edenwds/apollo_docker
参考资料:
https://github.com/idoop/docker-apollo
https://docs.docker.com/network/bridge/
https://github.com/moby/moby/issues/23849
https://blog.csdn.net/Third_Week/article/details/89145583