模拟容器顺序启动场景并实践解决
之前讲 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
就出现下面的情况

那么如何解决?
事件解决方案
FROM server
这是我一个朋友觉得可行的方法,OK,来试试。我已经看到了失败了未来
来魔改一下 client-dockerfile
1 2 3 4 5 6 7 8 9
| 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

预料之中的失败,为什么呢,因为 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
看看结果

命令被覆盖掉了。
就算不被覆盖掉,那启动起来也是一样的结果,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
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
启动看看

它是用 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
启动

终于舒服了,中间的那个 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/
RUN apt update && apt install -y netcat ENTRYPOINT ["/bin/bash", "wait.sh"]
|
再 docker compose up
跑起来

舒服了,比 docker 推荐的方案要轻得多。
不过 emmmmmm,那我直接启动的时候让 client 直接睡得比 server 久,不也是个方法吗,哈哈哈,虽然可靠性不怎么样就是了。
PS
如果需要等待多个服务怎么办?那就改进一下 shell 呗,加个循环,全部循环成功才执行下一步命令。