支持E2E测试 && Jenkins Build Pod Could not connect to Ryuk

我们目前在使用DolphinScheduler 2.0.5版本搭建大数据调度平台,由于想要提高系统稳定性,尽早能发现bug,所以我们想在项目中引入E2E测试。本文记录启用E2E的过程和遇到Could not connect to Ryuk问题的解决过程。

English version

一、起因

1、E2E测试

社区已经有稳定使用的E2E模块了,但是不支持2.x版本,只有在3.0+才能使用。并且社区的E2E测试是将代码打成纯净的Docker镜像,然后使用Selenium框架进入Docker容器进行测试。

而我们对社区的代码进行过改动,集成了K8s、Flink、Spark等任务的调度,同时需要测试在Flink启动后,是否可以打开Flink UI页面等需求,所以对我们来说更好的使用场景是使用Selenium框架测试线上服务。于是我们打算将社区的E2E模块进行二改,引入我们的项目。

2、代码修改

具体的改动其实不大,社区的E2E是有测试本地服务的逻辑的,我们只需要把需要测试的本地服务连接地址修改为线上服务的地址即可。

1
2
3
4
5
6
7
DolphinSchedulerExtension.java

private void runInLocal() {
Testcontainers.exposeHostPorts(443);
address = HostAndPort.fromParts("https://your.service.com", 443);
rootPath = "/";
}

如果你的服务是https的,那么protocol修改为https即可。

1
2
3
driver.get(new URL("http", address.getHost(), address.getPort(), rootPath).toString());
修改为
driver.get(new URL("https", address.getHost(), address.getPort(), rootPath).toString());

其他的启动参数修改以及E2E的逻辑详见社区文档

其他还需要对登陆流程及其其他需要测试的case进行修改,因为Selenium框架是根据页面上class name去定位各种新增删除按钮,然后模拟浏览器操作,进行测试。3.x版本对前端进行了重构,所以页面的元素class name等跟2.x完全不一致,所以也需要进行修改,这块没有太多的难点,就不赘述了。

3、本地测试

在代码编写完之后,在本地按照文档的步骤进行测试,因为我的电脑是M1芯片,所以需要使用-Dm1_chip=true启动参数,用于配置使用ARM64支持的容器。

本地测试没有任何问题,按照预期可以正常登录我们的线上服务,然后测试租户管理的流程。

我们搭建了Jenkins服务,最终E2E测试会在Jenkins上跑。Jenkins服务我们使用的K8s模式,每新启一个任务,都会在集群中新启动一个Pod,然后在执行完毕后会自动销毁。

在本地测试没问题后,下一步就要上Jenkins进行测试了,当时觉得没有什么问题,因为我们的单测和上线打包部署都在Jenkins上正常运行两三个月了,没想到E2E这边出现了问题。

4、Jenkins测试

在Jenkins上跑E2E出现了问题:

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
2022-08-06 14:49:54,714 org.testcontainers.DockerClientFactory 190 [main] INFO  [] - Connected to docker: 
Server Version: 19.03.10
API Version: 1.40
Operating System: CentOS Linux 7 (Core)
Total Memory: 386685 MB
2022-08-06 14:49:54,877 docker[testcontainers/ryuk:0.3.3] 376 [main] INFO [] - Creating container for image: testcontainers/ryuk:0.3.3
2022-08-06 14:49:54,884 org.testcontainers.utility.RegistryAuthLocator 164 [main] INFO [] - Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: testcontainers/ryuk:0.3.3, configFile: /root/.docker/config.json. Falling back to docker-java default behaviour. Exception message: /root/.docker/config.json (No such file or directory)
2022-08-06 14:49:55,088 docker[testcontainers/ryuk:0.3.3] 440 [main] INFO [] - Container testcontainers/ryuk:0.3.3 is starting: 066d1e071dfc6bce3a71c1321a4331d1b388f50ac25d7c9de3f41f31447500e2
2022-08-06 14:49:55,854 docker[testcontainers/ryuk:0.3.3] 520 [main] INFO [] - Container testcontainers/ryuk:0.3.3 started in PT1.13S
2022-08-06 14:50:00,863 org.testcontainers.utility.RyukResourceReaper 120 [testcontainers-ryuk] WARN [] - Can not connect to Ryuk at 172.17.0.1:32775
java.net.SocketTimeoutException: connect timed out
at java.net.PlainSocketImpl.socketConnect(Native Method) ~[?:1.8.0_212]
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[?:1.8.0_212]
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[?:1.8.0_212]
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[?:1.8.0_212]
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[?:1.8.0_212]
at java.net.Socket.connect(Socket.java:589) ~[?:1.8.0_212]
at org.testcontainers.utility.RyukResourceReaper.lambda$null$0(RyukResourceReaper.java:92) ~[testcontainers-1.17.3.jar:?]
at org.rnorth.ducttape.ratelimits.RateLimiter.doWhenReady(RateLimiter.java:27) ~[duct-tape-1.0.8.jar:?]
at org.testcontainers.utility.RyukResourceReaper.lambda$maybeStart$1(RyukResourceReaper.java:88) ~[testcontainers-1.17.3.jar:?]
at java.lang.Thread.run(Thread.java:748) ~[?:1.8.0_212]
......
2022-08-06 14:50:25,873 org.testcontainers.utility.RyukResourceReaper 131 [main] ERROR [] - Timed out waiting for Ryuk container to start. Ryuk's logs:
2022/08/06 06:49:55 Pinging Docker...
2022/08/06 06:49:55 Docker daemon is available!
2022/08/06 06:49:55 Starting on port 8080...
2022/08/06 06:49:55 Started!

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 31.939 s <<< FAILURE! - in org.apache.dolphinscheduler.e2e.cases.TenantE2ETest
[ERROR] org.apache.dolphinscheduler.e2e.cases.TenantE2ETest Time elapsed: 31.937 s <<< ERROR!
java.lang.IllegalStateException: Could not connect to Ryuk at 172.17.0.1:32775

报错的信息很清晰,无法连接到Ryuk服务。

二、问题排查

1、初步排查

遇到问题第一步是谷歌一下,然后发现这个问题很常见,网上也有很多种解决方案,比如:

  • 很多人反馈重启一下Docker就好了 这个我试过没有用;
  • 将testcontainers-java升级到1.51.1 但是我看本地的版本已经是1.17.3了;
  • docker system prune 查看了一下Docker内存空间是足够的,于是没有尝试这个方案;
  • 配置环境变量TESTCONTAINERS_RYUK_DISABLED=true 有人禁用Ryuk来避免这个问题。经过查阅,Ryuk是用来在任务执行结束后清理容器的,我试了一下这个方案,果然不启动就不报错了,但是遇到了另一个错误:
1
2
3
4
5
6
7
8
9
10
11
2022/08/06 08:51:49,364 docker[testcontainers/sshd:1.1.0] 440 [main] INFO  [] - Container testcontainers/sshd:1.1.0 is starting: a9a5ccc8addcedb374a290675893291214d2123b6e2a5dc5e30b9e54aa01e828
2022/08/06 08:52:50,405 docker[testcontainers/sshd:1.1.0] 529 [main] ERROR [] - Could not start container
org.testcontainers.containers.ContainerLaunchException: Timed out waiting for container port to open (172.17.0.1 ports: [32812] should be listening)
at org.testcontainers.containers.wait.strategy.HostPortWaitStrategy.waitUntilReady(HostPortWaitStrategy.java:102) ~[testcontainers-1.17.3.jar:?]
......
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 62.677 s <<< FAILURE! - in org.apache.dolphinscheduler.e2e.cases.TenantE2ETest
[ERROR] org.apache.dolphinscheduler.e2e.cases.TenantE2ETest Time elapsed: 62.675 s <<< ERROR!
org.testcontainers.containers.ContainerLaunchException: Container startup failed
Caused by: org.rnorth.ducttape.RetryCountExceededException: Retry limit hit with exception
Caused by: org.testcontainers.containers.ContainerLaunchException: Could not create/start container
Caused by: org.testcontainers.containers.ContainerLaunchException: Timed out waiting for container port to open (172.17.0.1 ports: [32812] should be listening)

看错误日志也是无法连接172.17.0.1:32812,说明不管是什么服务,其实都是无法连接的,也就是Ryuk这个服务是不能停止启动的, 必须要解决这个问题。

2、服务无法连接

问题回到Ryuk上来,再次查看日志,看到了Ryuk服务其实已经启动了:

1
2
3
2022-08-06 14:49:55,088 docker[testcontainers/ryuk:0.3.3] 440 [main] INFO  [] - Container testcontainers/ryuk:0.3.3 is starting: 066d1e071dfc6bce3a71c1321a4331d1b388f50ac25d7c9de3f41f31447500e2
2022-08-06 14:49:55,854 docker[testcontainers/ryuk:0.3.3] 520 [main] INFO [] - Container testcontainers/ryuk:0.3.3 started in PT1.13S
2022-08-06 14:50:00,863 org.testcontainers.utility.RyukResourceReaper 120 [testcontainers-ryuk] WARN [] - Can not connect to Ryuk at 172.17.0.1:32775

那登陆到Jenkins pod查看一下,实际上是否启动了服务:

1
2
root@jenkins-pipeline-ln62r:~/agent# docker ps | grep ryuk
f1298662d49f testcontainers/ryuk:0.3.3 "/app" 7 seconds ago Up 6 seconds 0.0.0.0:32772->8080/tcp testcontainers-ryuk-e84b7bc3-00cd-491f-9d47-e2d156e91669

可以看到,实际上是启动了ryuk的,那么问题就在于无法连接,我们通过wget命令来试一下:

1
2
3
root@jenkins-pipeline-ln62r:~/agent# wget 172.17.0.1:32772
--2022-08-06 11:52:48-- http://172.17.0.1:32772/
Connecting to 172.17.0.1:32772...

确实是无法连接,那么由于Jenkins task pod使用的/run/docker.sock文件是宿主机的,所以我们登陆宿主机去访问一下服务看看:

1
2
3
4
[root@k8s-worker-01 ~]# curl 172.17.0.1:32772
ACK
ACK
ACK

在宿主机上服务是可以正常连接的。

当时觉得是不是testcontainers的网络设置有问题,按照文档修改过一些Docker相关的参数,但是都没有解决问题。那说明跟testcontainers其实没有关系,问题还是出在Docker上了。

3、防火墙排查

再捋一下问题,由于Jenkins每一个task都会新启动一个Docker容器,然后在这个容器中又启动了一个Ryuk服务,然后无法连接。所以当时觉得问题在于Docker in Docker connection refused。怀疑是导致Docker in Docker访问不了宿主机的端口。

思考过后,觉得是否是防火墙的问题,因为连接无法访问。

经过查询,怀疑是不是宿主机的iptables规则不允许来自Docker容器的连接,使用以下命令将允许从Docker访问主机上的任何端口。

1
iptables -A INPUT -i docker0 -j ACCEPT

但是经过尝试没有解决问题。

后面看到了这篇资料,怀疑是否是宿主机的Docker配置,拒绝了访问。于是尝试修复宿主机的Docker配置:

1
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock

但是仍然无法解决问题。

怀疑是不是宿主机的防火墙禁止了某些访问,于是关闭了宿主机的防火墙。

但是依然无法访问。

同时在Github看到了同样问题的Issue,按照提示,使用httpd进行测试:

1
2
docker run -d -p 9090:80 httpd
curl localhost:9090

发现是无法访问httpd服务的。

那说明思路有问题,跟防火墙没有关系。因为所有的权限都打开了,所有的防火墙都关闭了,服务还是不通的。

这时发现测试有点困难,我需要执行mvn命令来启动e2e测试,才会创建一个Ryuk服务,并且在mvn执行完成之后,ryuk会自动销毁,就无法测试联通性了。通常Ryuk服务存在的时间只有一两分钟,测试极其不便。当使用httpd测试也是无法联通的时候,发现可以在Jenkins task pod里直接启动一个httpd服务,这样就不会被自动销毁,只要能在pod里访问成功这个httpd访问,那么我们的问题就解决了。

4、网络设置排查

那问题应该还是出在网络了。

查看Jenkins task pod的网络配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
root@jenkins-pipeline-w34kq:~/agent# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1480
inet 172.17.213.171 netmask 255.255.255.255 broadcast 172.17.213.171
ether c2:76:3d:10:73:1e txqueuelen 0 (Ethernet)
RX packets 29561 bytes 176211133 (168.0 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 21634 bytes 5269854 (5.0 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

感觉没什么问题,Jenkins task pod采用的bridge networks,宿主机会有一个虚拟的网卡, IP段172.17.0.0/24。

登陆宿主机查看网络配置:

1
2
3
4
5
6
7
8
9
10
[root@k8s-worker-01 ~]# ifconfig
......
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:7d:ac:d8:fb txqueuelen 0 (Ethernet)
RX packets 62624 bytes 3005816052 (2.7 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 69613 bytes 280415917 (267.4 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
......

也没看出来什么问题。

现在有点陷入僵局了,不知道哪里的配置有问题导致无法连接。

那能不能换个思路,我在宿主机里面是可以正常访问Ryuk服务的,Jenkins task pod使用的是宿主机的docker.sock文件,无法访问。那能不能在Jenkins task pod里面集成一个完整的Docker服务呢?使用自己的docker.sock文件,那么是肯定是可以连接的。

经过查阅资料,实现起来也比较困难,同时也觉得这个不是一个好的解决方案,问题其实不在这,这样相当于把问题绕过去了,并没有解决核心的问题。

5、host模式启动测试

后续在查询中,无意看到这篇资料。如果我在pod里按host模式启动一下nginx,那么能不能在pod里访问到呢?

1
2
3
4
5
6
7
8
9
10
11
12
root@jenkins-pipeline-q68td:~/agent# docker run -d --name nginx --network host nginx
025906c124c9067f49c0eb1db7745540af88cea34b0094df0df815439e2d836b
root@jenkins-pipeline-q68td:~/agent# wget 172.17.0.1:80
--2022-08-07 10:52:43-- http://172.17.0.1/
Connecting to 172.17.0.1:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 615 [text/html]
Saving to: 'index.html'

index.html 100%[=====================================================================================================>] 615 --.-KB/s in 0s

2022-08-07 10:52:43 (43.7 MB/s) - 'index.html' saved [615/615]

竟然可以访问,那说明通过host模式启动的docker服务,在pod里面是可以访问的。

也就是说如果我通过host模式启动testcontainers,那么应该也是可以访问的,问题就可以解决了。

现在的思路非常清楚了,通过host模式启动testcontainers。

尝试过修改Java代码,在启动的时候指定network为host,但是没有生效。我也想过能不能修改docker启动参数来使用host模式启动,但是没找到方法。经过查找也没找到其他方法来设置testcontainers使用host启动。

又陷入僵局。

6、Jenkins host模式启动

那么换个思路,如果把Jenkins slaver pod的启动模式修改为host模式,那么能访问其他bridge networks模式启动的pod吗?

Jenkins自身是支持这个功能的,只需要在configureClouds页面,Pod Templates模块启用Host Network,并且修改Jenkins URLhost模式的地址即可。

经过测试,没有任何问题。

1
2
3
4
root@pdak8s-worker-01:~/agent# docker run -d -p 9090:80 httpd
8749834785fca955997b5cad94755f7abe12b00eb6d09cd3d33b36e6b53eb075
root@pdak8s-worker-01:~/agent# curl 172.17.0.1:9090
<html><body><h1>It works!</h1></body></html>

那么来重新跑一下E2E测试,看下是否可以正常访问Ryuk服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2022-08-06 10:59:48,874 docker[testcontainers/ryuk:0.3.3] 440 [main] INFO  [] - Container testcontainers/ryuk:0.3.3 is starting: 0859ffdf6c6b6c06e0ea39acf6155aaa98bb837f2926640af8acd8d268d0d34b
2022-08-06 10:59:49,734 docker[testcontainers/ryuk:0.3.3] 520 [main] INFO [] - Container testcontainers/ryuk:0.3.3 started in PT1.497S
......
2022-08-06 10:59:49,747 docker[testcontainers/sshd:1.1.0] 376 [main] INFO [] - Creating container for image: testcontainers/sshd:1.1.0
2022-08-06 10:59:49,777 docker[testcontainers/sshd:1.1.0] 440 [main] INFO [] - Container testcontainers/sshd:1.1.0 is starting: fcbd8d04666b5ec6d39aaba8fa6671815bff9572b97428cd0f3581c03aba838c
2022-08-06 10:59:50,660 docker[testcontainers/sshd:1.1.0] 520 [main] INFO [] - Container testcontainers/sshd:1.1.0 started in PT0.913S
......
2022-08-06 10:59:50,898 docker[selenium/standalone-chrome-debug:3.141.59] 376 [main] INFO [] - Creating container for image: selenium/standalone-chrome-debug:3.141.59
2022-08-06 10:59:52,284 docker[selenium/standalone-chrome-debug:3.141.59] 440 [main] INFO [] - Container selenium/standalone-chrome-debug:3.141.59 is starting: 9ab1d8d6f9996e5a94920214d904c51c0b5767b65f862192a407f7e763bb80c8
2022-08-06 10:59:55,899 docker[selenium/standalone-chrome-debug:3.141.59] 520 [main] INFO [] - Container selenium/standalone-chrome-debug:3.141.59 started in PT5.001S
2022-08-06 10:59:55,902 docker[testcontainers/vnc-recorder:1.2.0] 376 [main] INFO [] - Creating container for image: testcontainers/vnc-recorder:1.2.0
2022-08-06 10:59:55,946 docker[testcontainers/vnc-recorder:1.2.0] 440 [main] INFO [] - Container testcontainers/vnc-recorder:1.2.0 is starting: 14e9dbcc3fafd3356113624117e792a18929fbb494c7d41b1bc62089c3a06f5f
2022-08-06 10:59:56,803 docker[testcontainers/vnc-recorder:1.2.0] 520 [main] INFO [] - Container testcontainers/vnc-recorder:1.2.0 started in PT0.902S
Aug 06, 2022 10:59:58 AM org.openqa.selenium.remote.ProtocolHandshake createSession
......

完全正常,那说明我们的问题终于解决了。

三、总结

现在在回头想,我们最开始的思路其实是有问题的,其实并不是Docker In Docker。

Jenkins的task pod使用的跟宿主机是同一个/var/run/docker.sock文件。也就是Jenkins task pod是宿主机上的一个Docker服务,Jenkins task pod启动的Ryuk其实也是启动在宿主机的Docker上,也就是Jenkins task pod和Ryuk是宿主机上同级别的两个Docker服务。

所以走了很多的弯路,好在最终将问题解决了。我也在Github那个描述相同的问题的Issue下留了言,毕竟不要重复造轮子。

解决了新的问题,其实也引出了新的问题。我们正常部署在Docker上的Java服务,也会连接其他中间件,比如Nginx、Redis,这些中间件也是部署在Docker上另外的服务,他们之间的通信是完全正常的。那为什么Jenkins task pod和Ryuk两个bridge networks启动的容器无法互相访问呢?

未完待续。


评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×