模拟容器顺序启动场景并实践解决

模拟容器顺序启动场景并实践解决

之前讲 docker-compose 的时候有提到过容器顺序启动的问题,现在来模拟然后实践解决一下。

准备环境

直接用 socket 的服务器和客户端来模拟一下场景,先快速整一下相关代码。

java

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Server {
public static void main(String[] args) throws IOException, InterruptedException {
TimeUnit.SECONDS.sleep(3);
ServerSocket serverSocket = new ServerSocket(11010);
System.out.println("开始监听11010端口");
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
StringBuilder stringBuilder = new StringBuilder();
int pass;
while ((pass = inputStream.read()) != -1) {
stringBuilder.append((char) pass);
}
System.out.println("收到消息: " + stringBuilder);
serverSocket.close();
}
}

为什么 Server 要启动的时候先睡觉 3 秒,这是为了模拟大型应用在启动时经历的初始化耗时,初始化结束后才监听 port。

Client

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) throws IOException {
String host = System.getenv("host");
Socket socket = new Socket(host, 11010);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello server".getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
socket.close();
System.out.println("消息发送完成");
}
}

dockerfile

server-dockerfile

1
2
3
4
5
6
7
FROM openjdk:11

RUN mkdir /data01
COPY out/artifacts/Server_jar/Server.jar /data01/
WORKDIR /data01/

ENTRYPOINT ["java", "-jar", "Server.jar"]

client-dockerfile

1
2
3
4
5
6
7
8
FROM openjdk:11

RUN mkdir /data01
COPY out/artifacts/Client_jar/Client.jar /data01/
WORKDIR /data01/
ENV host=localhost

ENTRYPOINT ["java", "-jar", "Client.jar"]

在 dockerfile 目录下执行以下命令构建镜像

docker build -f server-dockerfile -t test-server .

docker build -f client-dockerfile -t test-client .

docker-compose

1
2
3
4
5
6
7
services:
server:
image: test-server
client:
image: test-client
environment:
- host=server

重现启动顺序问题

OK,直接跑docker-compose up就出现下面的情况

image-20230315020333871

那么如何解决?

事件解决方案

FROM server

这是我一个朋友觉得可行的方法,OK,来试试。我已经看到了失败了未来

来魔改一下 client-dockerfile

1
2
3
4
5
6
7
8
9
# 改变FROM
FROM test-server

RUN mkdir /data02
COPY out/artifacts/Client_jar/Client.jar /data02/
WORKDIR /data02/
ENV host=127.0.0.1

ENTRYPOINT ["java", "-jar", "Client.jar"]

然后敲敲命令进行构建、运行

docker build -f client-dockerfile -t test-client .

docker run --rm -it test-client

202303130900309

预料之中的失败,为什么呢,因为 server 还在睡觉吗?不,它根本不存在。

来魔改一下 server-dockerfile

1
2
3
FROM openjdk:11

ENTRYPOINT ["/bin/echo", "i am server"]

然后是 client-dockerfile

1
2
3
FROM test-server

ENTRYPOINT ["/bin/echo", "i am client"]

运行docker run --rm -it test-server-client看看结果

202303130859677

命令被覆盖掉了。

就算不被覆盖掉,那启动起来也是一样的结果,Java -jar Server.jar又不会造成阻塞。

docker 推荐的项目

wait-port是基于 nodejs 的,哎,还得安装环境,开始写 dockerfile

server-dockerfile

1
2
3
4
5
6
7
FROM openjdk:11

RUN mkdir /data01
COPY out/artifacts/Server_jar/Server.jar /data01/
WORKDIR /data01/

ENTRYPOINT ["java", "-jar", "Server.jar"]

client-dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
FROM openjdk:11

RUN mkdir /data01
COPY out/artifacts/Client_jar/Client.jar /data01/
WORKDIR /data01/
ENV host=localhost

# 开始装nodejs环境与wait-port
RUN apt update && apt install nodejs npm -y && npm install -g wait-port
ENV waitServer=localhost
COPY ./start.sh /data01/
ENTRYPOINT ["/bin/bash", "start.sh"]

start.sh

1
2
wait-port $waitServer
java -jar Client.jar

docker-compose.yml

1
2
3
4
5
6
7
8
services:
server:
image: test-server
client:
image: test-client
environment:
- waitServer=server:11010
- host=server

OK,使用docker-compose up启动看看

202303130859983

它是用 socket 尝试连接,由于我们的 server 只监听一次 socket,好吧,加一次循环监听两次 socket

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Server {
public static void main(String[] args) throws IOException, InterruptedException {
TimeUnit.SECONDS.sleep(3);
for (int i = 0; i < 2; i++) {
ServerSocket serverSocket = new ServerSocket(11010);
System.out.println("开始监听11010端口");
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
StringBuilder stringBuilder = new StringBuilder();
int pass;
while ((pass = inputStream.read()) != -1) {
stringBuilder.append((char) pass);
}
System.out.println("收到消息: " + stringBuilder);
serverSocket.close();
}
}
}

docker compose up启动

202303130857972

终于舒服了,中间的那个 client 构建,我这里可是花了 300 秒…

shell 脚本

我之前有说过一个 shell 脚本来实现的,大概看了一下他的代码,一句话概括就是,用 netcat 进行 socket 连接并循环直到连通为止。

OK,那 server 还是得监听两次 socket,但是他的代码还要魔改一下,方便我们使用 docker-compose 进行传参。

主要魔改它的 main 函数:

1
2
3
4
5
6
7
main() {
if [ ! "${port:+x}" ]; then
die "No port was specified."
fi
wait_for "$host" "$port" "$service"
java -jar Client.jar
}

再来改改 docker-compose 配置

1
2
3
4
5
6
7
8
9
services:
server:
image: test-server
client:
image: test-client
environment:
- host=server
- port=11010
- service="server"

还有 client-dockerfile

1
2
3
4
5
6
7
8
9
10
11
FROM openjdk:11

RUN mkdir /data01
COPY out/artifacts/Client_jar/Client.jar /data01/
WORKDIR /data01/
ENV host=localhost

COPY ./wait.sh /data01/
# 上一步也可以按照他的说明用curl下下来,不过我在本地修改了他的代码所以直接COPY了
RUN apt update && apt install -y netcat
ENTRYPOINT ["/bin/bash", "wait.sh"]

docker compose up 跑起来

202303130856964

舒服了,比 docker 推荐的方案要轻得多。

不过 emmmmmm,那我直接启动的时候让 client 直接睡得比 server 久,不也是个方法吗,哈哈哈,虽然可靠性不怎么样就是了。

PS

如果需要等待多个服务怎么办?那就改进一下 shell 呗,加个循环,全部循环成功才执行下一步命令。


模拟容器顺序启动场景并实践解决
https://blog.gugu.dev/2024-02-21/模拟容器顺序启动场景并实践解决/
作者
MinMin
发布于
2024年2月21日
许可协议