Dockerfile 中的 EXPOSE 作用到底是什么?

下面进行测试 junit.jar(SpringBoot应用),默认端口 8080,访问curl 127.0.0.1:8080/hello返回“Hello World!”

不指定 EXPOSE

》》》需要docker run -p 1234:8080 ...,进行一次代理,这样访问主机ip:1234就相当于 访问容器ip:8080
》》》如果不进行代理,主机的1234,8080端口都没有被占用,外部是肯定不能访问的。
》》》外部与容器的网络又是不通的,容器只与主机通信,所以外部就不可以访问容器了。自然需要 -p 进行一层代理。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
------------------------ls
junit junit.jar
------------------------cat junit
FROM java:8

COPY junit.jar /app.jar

CMD java -jar /app.jar
------------------------docker build -f junit -t junit .
Sending build context to Docker daemon 40.58MB
Step 1/3 : FROM java:8
---> d23bdf5b1b1b
Step 2/3 : COPY junit.jar /app.jar
---> Using cache
---> 64074851bd6e
Step 3/3 : CMD java -jar /app.jar
---> Using cache
---> 20b16a205802
Successfully built 20b16a205802
Successfully tagged junit:latest
------------------------docker run --name junit01 -d junit
04443f216a5975caa49e64db6e346de54b694c1bee955baddc66ebe7eee5c0b9
------------------------docker inspect junit01 | grep "\"Ports\"" -A 10
"Ports": {},
"SandboxKey": "/var/run/docker/netns/8f90f443846f",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "1df796f0f7a11c06c44434424fd46e879527a108f67074052ce6ace486a3a82e",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
------------------------
------------------------netstat -atlunp | grep 8080
------------------------
------------------------docker exec -it junit01 curl 127.0.0.1:8080/hello
Hello World!
------------------------curl 127.0.0.1:8080/hello
curl: (7) Failed connect to 127.0.0.1:8080; 拒绝连接
------------------------curl 172.17.0.2:8080/hello
Hello World!
------------------------
------------------------
------------------------
------------------------
------------------------docker rm -f junit01 ; docker run --name junit01 -p 8080:8080 -d junit
junit01
23bb56c0b2cb20893d982b9a0c4baaebd5b33ff2b8811c0ae374c833c2be0fba
------------------------docker inspect junit01 | grep "\"Ports\"" -A 10
"Ports": {
"8080/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8080"
}
]
},
"SandboxKey": "/var/run/docker/netns/d5f7109d1470",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
------------------------
------------------------netstat -atlunp | grep 8080
tcp6 0 0 :::8080 :::* LISTEN 59597/docker-proxy
------------------------docker exec -it junit01 curl 127.0.0.1:8080/hello
Hello World!
------------------------curl 127.0.0.1:8080/hello #===> 使用 -p 映射端口,本地可以访问
Hello World!
------------------------docker rm -f junit01 ; docker run --name junit01 -p 1234:8080 -d junit
junit01
7c6aec366e23f8c62fb67d63975617414d902509ebd4612875641f229529d479
------------------------
------------------------
------------------------docker inspect junit01 | grep "\"Ports\"" -A 10
"Ports": {
"8080/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "1234"
}
]
},
"SandboxKey": "/var/run/docker/netns/8fa400790019",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
------------------------netstat -atlunp | grep 8080
------------------------netstat -atlunp | grep 1234
tcp6 0 0 :::1234 :::* LISTEN 59819/docker-proxy
------------------------curl 127.0.0.1:1234/hello
Hello World!

指定 EXPOSE

》》》使用 -P 会把容器的 EXPOSE 端口 和 主机的一个随机端口进行映射,此时外部访问这个主机的随机端口就相当于访问容器的 EXPOSE 端口了。
》》》若不使用 -P 或 -p 映射端口,那么 EXPOSE 就相当于一个 说明,说明容器哪些端口可用。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
------------------------cat junit
FROM java:8

EXPOSE 1234

COPY junit.jar /app.jar

CMD java -jar /app.jar
------------------------docker build -f junit -t junit .
Sending build context to Docker daemon 40.58MB
Step 1/4 : FROM java:8
---> d23bdf5b1b1b
Step 2/4 : EXPOSE 1234
---> Using cache
---> acb330da8a12
Step 3/4 : COPY junit.jar /app.jar
---> Using cache
---> 1ea866f79c76
Step 4/4 : CMD java -jar /app.jar
---> Using cache
---> c2ffe1a0204c
Successfully built c2ffe1a0204c
Successfully tagged junit:latest
------------------------docker rm -f junit01 ; docker run --name junit01 -p 1234:8080 -d junit
junit01
dbbab8f28cd4b312e292a920b89dd614f1df69fb2734bf8f31c62f6cedcc1c62
------------------------
------------------------docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dbbab8f28cd4 junit "/bin/sh -c 'java -j…" 5 seconds ago Up 4 seconds 1234/tcp, 0.0.0.0:1234->8080/tcp junit01
5a5c972c7eb3 wurstmeister/kafka "start-kafka.sh" 3 days ago Exited (143) 16 hours ago kafka01
39090de45eca wurstmeister/zookeeper "/bin/sh -c '/usr/sb…" 3 days ago Exited (137) 16 hours ago zookeeper01
65cde980ee00 mysql "docker-entrypoint.s…" 3 days ago Exited (0) 47 minutes ago mysql01
1044d0e07bf8 redis "docker-entrypoint.s…" 3 days ago Exited (0) 16 hours ago redis01
------------------------
------------------------
------------------------docker inspect junit01 | grep "\"Ports\"" -A 10
"Ports": {
"1234/tcp": null,
"8080/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "1234"
}
]
},
"SandboxKey": "/var/run/docker/netns/c212f2722a8e",
"SecondaryIPAddresses": null,
------------------------curl 127.0.0.1:1234/hello
Hello World!
------------------------
------------------------
------------------------docker rm -f junit01 ; docker run --name junit01 -P -d junit
junit01
b228ef822045e34cd9ded7cab7693ec9414a88c6c3f48d5164de331acf6410f4
------------------------docker ps -a | grep junit
b228ef822045 junit "/bin/sh -c 'java -j…" 16 seconds ago Up 14 seconds 0.0.0.0:32768->1234/tcp junit01
------------------------docker inspect junit01 | grep "\"Ports\"" -A 10
"Ports": {
"1234/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "32768"
}
]
},
"SandboxKey": "/var/run/docker/netns/7ddd2b29746f",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,

总结

1、EXPOSE 就相当于 VOLUME 容器卷一样,只是一个声明而已,但 VOLUME 在 docker run 不知道 -v 时,也会使用默认的匿名挂载,但 EXPOSE 声明的端口 则需要docker run 加上 -p(小写) Linux端口:容器端口 来进行映射,或 使用 -P(大写)让 EXPOSE 声明的端口都映射到主机的某一个随机端口上
2、EXPOSE 只是一个声明而已,真正使用是 docker run -p 主机端口:容器端口 或 -P 来指定的。
(一开始我傻乎乎的猜测,只有 EXPOSE 声明的端口,docker run 时才可以使用 -p 指定映射,或 使用 -P 随机映射,事实证明并不是这样)

官方文档

EXPOSE

1
EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令通知Docker容器在运行时监听指定的网络端口。您可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认值为TCP。

EXPOSE指令实际上并未发布端口。它充当构建映像的人员和运行容器的人员之间的一种文档类型,有关打算发布哪些端口的信息。要在运行容器时实际发布端口,请使用-p标记ondocker run 发布和映射一个或多个端口,或者使用-P标记发布所有公开的端口并将它们映射到高阶端口。

默认情况下,EXPOSE假定为TCP。您还可以指定UDP:

1
EXPOSE 80/udp

要同时在TCP和UDP上公开,请包括以下两行:

1
2
EXPOSE 80/tcp
EXPOSE 80/udp

在这种情况下,如果-P与配合使用docker run,则该端口仅对TCP公开一次,对于UDP公开一次。请记住,-P该端口在主机上使用临时的随机主机端口,因此该端口对于TCP和UDP将是不同的。

无论EXPOSE设置如何,都可以在运行时使用该-p标志覆盖它们。例如

1
docker run -p 80:80/tcp -p 80:80/udp ...