dubbo服务在docker容器中进行服务编排

问题起源

最近在对现有业务系统进行Dubbo服务化重构,部署方式采用Docker部署,注册中心采用nacos。当部署完成之后会发现消费者服务能正常启动,服务在注册中心显示正常,但是进行服务调用的时候就会报错,对应的服务无响应信息。

1
com.alibaba.dubbo.remoting.RemotingException: message can not send, because channel is closed

问题追溯

报错信息显示comsumer地址是172.18.*.*,这是docker容器的ip地址,猜想应该是容器之间无法进行调用。把docker服务启动参数修改成宿主机的内网地址,依然还是报错。是否应该是服务端口号不一致?查看dubbo端口绑定源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Register port and bind port for the provider, can be configured separately
* Configuration priority: environment variable -> java system properties -> port property in protocol config file
* -> protocol default port
*
* @param protocolConfig
* @param name
* @return
*/
private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name, Map<String, String> map) {
Integer portToBind = null;

// parse bind port from environment
String port = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_BIND);
portToBind = parsePort(port);

// if there's no bind port found from environment, keep looking up.
if (portToBind == null) {
portToBind = protocolConfig.getPort();
if (provider != null && (portToBind == null || portToBind == 0)) {
portToBind = provider.getPort();
}
final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
if (portToBind == null || portToBind == 0) {
portToBind = defaultPort;
}
if (portToBind == null || portToBind <= 0) {
portToBind = getRandomPort(name);
if (portToBind == null || portToBind < 0) {
portToBind = getAvailablePort(defaultPort);
putRandomPort(name, portToBind);
}
logger.warn("Use random available port(" + portToBind + ") for protocol " + name);
}
}

// save bind port, used as url's key later
map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind));

// registry port, not used as bind port by default
String portToRegistryStr = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_REGISTRY);
Integer portToRegistry = parsePort(portToRegistryStr);
if (portToRegistry == null) {
portToRegistry = portToBind;
}

return portToRegistry;
}

整个方法最核心的代码:

1
String port = getValueFromConfig(protocolConfig, Constants.DUBBO_PORT_TO_BIND);

通过读取系统环境变量:DUBBO_PORT_TO_BIND获取暴露端口。如何修改这个参数呢?想到了docker三剑客之docker-compose。现在多个服务要逐个启动,正好使用compose可以解决这个问题。

compose简介

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 OpenStack 中的 Heat 十分类似。

其代码目前在 https://github.com/docker/compose 上开源。

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。

通过第一部分中的介绍,我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

Compose 中有两个重要的概念:

服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。

项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose 来进行编排管理。

编写docker-compose.yml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
version: '3'
services:
visit:
image: provider-visit:1.0.0
restart: always
ports:
- "9203:9003"
# 前面为注册到注册中心的端口,后面为docker监听的端口
- "20883:20883"
environment:
# 注册到注册中心的IP,这里我们选择宿主机的IP
DUBBO_IP_TO_REGISTRY: 172.31.138.159
# 注册到注册中心的端口
DUBBO_PORT_TO_REGISTRY: 20883
DUBBO_PORT_TO_BIND: 20883
theme:
image: provider-theme:1.0.0
restart: always
ports:
- "9204:9004"
# 前面为注册到注册中心的端口,后面为docker监听的端口
- "20884:20884"
environment:
# 注册到注册中心的IP,这里我们选择宿主机的IP
DUBBO_IP_TO_REGISTRY: 172.31.138.159
# 注册到注册中心的端口
DUBBO_PORT_TO_REGISTRY: 20884
DUBBO_PORT_TO_BIND: 20884

启动过程中碰到一个问题

1
found character '\t' that cannot start any token while scanning for the next token at line

查询得知是yml文件中有使用tab进行空格,yml是不允许使用tab的。修改之后正常启动。
服务启动之后,消费者调用恢复正常。