Docker
网络
容器通信的三种方式
容器之间可通过 IP,Docker DNS Server 或 joined 容器三种方式通信。
IP 通信
IP 通信就是设备靠IP 地址(如 192.168.108.30)定位,通过网络传数据:
- 同局域网(如家里两台电脑):直接发;
- 跨网(如连外地服务器):经路由器转发;
- 特点:不保证数据不丢 / 不乱序,发前不用先连。
现在主要用 IPv4,正过渡到地址更多的 IPv6。
Docker DNS Server
Docker DNS Server 是 Docker 内置的 DNS 服务,用于容器之间的域名解析,让容器可以通过容器名或服务名互相访问,无需记住 IP 地址。
示例:
启动两个容器 bbox1 和 bbox2
[root@docker ~]# docker run -it --network my_net2 --name bbox1 busybox
/ # [root@docker ~]#
[root@docker ~]# docker run -it --network my_net2 --name bbox2 busybox
/ # [root@docker ~]#
bbox2 就可以直接 ping 到 bbox1
/ # [root@docker ~]# docker exec -it bbox2 sh
/ # ping -c 3 bbox1
PING bbox1 (172.22.16.4): 56 data bytes
64 bytes from 172.22.16.4: seq=0 ttl=64 time=0.144 ms
64 bytes from 172.22.16.4: seq=1 ttl=64 time=0.101 ms
64 bytes from 172.22.16.4: seq=2 ttl=64 time=0.101 ms
--- bbox1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.101/0.115/0.144 ms
/ #
Docker 的 DNS 服务(容器名解析)仅在用户自定义网络(user-defined network)中生效,默认的 bridge
网络不支持这一功能。
验证这一结论:
创建 bbox3 和 bbox4,均连接到 bridge 网络。
[root@docker ~]# docker run -it --name bbox3 busybox
/ # [root@docker ~]# docker run -it --name bbox4 busybox
/ # [root@docker ~]#
bbox4 无法 ping 到 bbox3
/ # [root@docker ~]# docker exec -it bbox4 sh
/ # ping -c 3 bbox3
ping: bad address 'bbox3'
/ #
Docker DNS Server使用场景:
- 同一
docker network
中的容器,可直接用容器名
通信 - Compose 中定义的服务,可通过
服务名
互相访问(如db
服务可被web
服务通过db
域名访问)
joined 容器
joined
容器(通常称为 “joined container” 或 “容器联合”)是 Docker 中一种特殊的容器运行模式,其核心特点是共享另一个容器的网络命名空间(Network Namespace),实现网络栈的完全共享。
核心特性:
- 网络完全共享:joined 容器与被关联的容器共享 IP 地址、端口、网络接口等,相当于 “共用同一个网络身份”。
- 进程独立:虽然网络共享,但容器的文件系统、进程空间是独立的。
- 通信无网络开销:容器间通信可通过
localhost
直接进行,无需端口映射或网络转发。
示例:
先创建一个 httpd 容器,名字为 web1
[root@docker ~]# docker run -d -it --name web1 httpd
Unable to find image 'httpd:latest' locally
latest: Pulling from library/httpd
ce1261c6d567: Pull complete
aeb6d226161f: Pull complete
4f4fb700ef54: Pull complete
56926e6ce68f: Pull complete
4938babf7b43: Pull complete
307fcc49c641: Pull complete
Digest: sha256:c4edd43414d68d7abd223ef20bef385c95bbbbeaea53b01c6ffef01d62bf6309
Status: Downloaded newer image for httpd:latest
dce76e79f444042c1c9ca0e6c4cdd14e0bda94dd23ed28d18ad8f5ea05161a97
查看一下 web1 的网络:
[root@docker ~]# docker exec -it web1 bash
root@dce76e79f444:/usr/local/apache2# hostname -I
172.17.0.5
或者是:
root@dce76e79f444:/usr/local/apache2# apt-get update
root@dce76e79f444:/usr/local/apache2# apt-get install iproute2 -y
root@dce76e79f444:/usr/local/apache2# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
22: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
root@dce76e79f444:/usr/local/apache2#
然后创建 busybox 容器并通过 --network=container:web1 指定 jointed 容器为 web1:
[root@docker ~]# docker run -it --network container:web1 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
22: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:05 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.5/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ #
busybox 和 web1 的网卡 mac 地址与 IP 完全一样,它们共享了相同的网络栈。busybox 可以直接 用 127.0.0.1 访问 web1 的 http 服务。
/ # wget 127.0.0.1
Connecting to 127.0.0.1 (127.0.0.1:80)
saving to 'index.html'
index.html 100% |******************************************| 45 0:00:00 ETA
'index.html' saved
/ # cat index.html
<html><body><h1>It works!</h1></body></html>
/ #
joined 容器 使用场景:
- 拆分单一容器的功能(如将应用和日志收集器分为两个容器,但共享网络)。
- 为现有容器添加辅助服务(如监控代理),无需修改原容器配置。
容器如何访问外部世界
当前环境下不需要配置docker host和容器也可以访问外网
注意:这里外网指的是容器网络以外的网络环境,并非特指 internet。
[root@docker ~]# ping -c 3 www.baidu.com
PING www.a.shifen.com (180.101.51.73) 56(84) bytes of data.
64 bytes from 180.101.51.73 (180.101.51.73): icmp_seq=1 ttl=128 time=33.8 ms
64 bytes from 180.101.51.73 (180.101.51.73): icmp_seq=2 ttl=128 time=111 ms
64 bytes from 180.101.51.73 (180.101.51.73): icmp_seq=3 ttl=128 time=50.6 ms
--- www.a.shifen.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 33.763/65.204/111.242/33.272 ms
[root@docker ~]# docker run -it --name test1 busybox
/ # ping -c 3 www.baidu.com
PING www.baidu.com (180.101.49.44): 56 data bytes
64 bytes from 180.101.49.44: seq=0 ttl=127 time=60.990 ms
64 bytes from 180.101.49.44: seq=1 ttl=127 time=111.941 ms
64 bytes from 180.101.49.44: seq=2 ttl=127 time=110.252 ms
--- www.baidu.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 60.990/94.394/111.941 ms
/ # [root@docker ~]#
docker host 上的 iptables 规则:
[root@docker ~]# iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P POSTROUTING ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.22.16.0/24 ! -o br-a87bc5c0a709 -j MASQUERADE
-A POSTROUTING -s 172.18.0.0/16 ! -o br-e16b4272602c -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A DOCKER -i br-a87bc5c0a709 -j RETURN
-A DOCKER -i br-e16b4272602c -j RETURN
-A DOCKER -i docker0 -j RETURN
# -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
# 含义:
# 如果网桥 docker0 收到来自 172.17.0.0/16 网段的外出包,把它交给 MASQUERADE 处理。而 MASQUERADE 的处理方式是将包的源地址替换成 host 的地址发送出去,即做了一次网络地址转换(NAT)。
通过 tcpdump 查看地址是如何转换的
先查看 docker host 的路由表:
[root@docker ~]# ip r
default via 192.168.108.2 dev ens160 proto static metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.18.0.0/16 dev br-e16b4272602c proto kernel scope link src 172.18.0.1 linkdown
172.22.16.0/24 dev br-a87bc5c0a709 proto kernel scope link src 172.22.16.1
192.168.108.0/24 dev ens160 proto kernel scope link src 192.168.108.30 metric 100
# 默认路由通过 ens160 发出去,所以我们要同时监控 ens160 和 docker0 上的 icmp(ping)数据包。并且使用tcpdump观察现象。
安装抓包软件
[root@docker ~]# yum install -y tcpdump
开两个窗口
[root@docker ~]# tcpdump -i docker0 -n icmp
[root@docker ~]# tcpdump -i ens160 -n icmp
再开一个窗口, busybox ping www.baidu.com:
[root@docker ~]# docker run -it busybox
/ # ping -c 3 www.baidu.com
PING www.baidu.com (180.101.49.44): 56 data bytes
64 bytes from 180.101.49.44: seq=0 ttl=127 time=68.294 ms
64 bytes from 180.101.49.44: seq=1 ttl=127 time=64.250 ms
64 bytes from 180.101.49.44: seq=2 ttl=127 time=71.800 ms
--- www.baidu.com ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 64.250/68.114/71.800 ms
/ #
这就是 iptable NAT 规则处理的结果,从而保证数据包能够到达外网。
Docker 容器(如 busybox)访问外部网络的核心流程基于默认 bridge 模式的 NAT 机制,可简化为 3 步:
- 容器发请求:容器(如 172.17.0.2)将请求发送到 Docker 虚拟网桥 docker0(172.17.0.1,作为容器网关)。
- 主机做转换:Docker 主机通过 NAT 技术,将请求源 IP 从容器内网 IP 替换为主机外网 IP(如 192.168.108.130),并记录映射关系。
- 响应回传:外部服务(如百度)的响应先到主机,主机再根据 NAT 记录,将响应转发回原容器。
外部世界如何访问容器
通过 端口映射
docker 可将容器对外提供服务的端口映射到 host 的某个端口,外网通过该端口访问容器。容器启动时 通过 -p 参数映射端口:
[root@docker ~]# docker run -d -p 80 httpd
[root@docker ~]# docker ps
[root@docker ~]# docker port gracious_sanderson
80/tcp -> 0.0.0.0:32768
80/tcp -> [::]:32768
容器启动后,可通过 docker ps 或者 docker port 查看到 host 映射的端口。在上面的例子中,httpd 容器的 80 端口被映射到 host 32768 上,这样就可以通过 :<32768> 访问容器的 web 服务 了。
[root@docker ~]# curl 192.168.108.30:32768
<html><body><h1>It works!</h1></body></html>
去浏览器测试
除了映射动态端口,也可在 -p 中指定映射到 host 某个特定端口,例如可将 80 端口映射到 host 的 8080 端口:
[root@docker ~]# docker run -d -p 8080:80 httpd
58d98478b5f0104e71f4ee647a810310bf3c8b8d76f3e7bd22f6319e02c8885d
[root@docker ~]# curl 192.168.108.30:8080
<html><body><h1>It works!</h1></body></html>
[root@docker ~]# ps -elf | grep docker-proxy
每一个映射的端口,host 都会启动一个 docker-proxy 进程来处理访问容器的流量:
以 0.0.0.0:8080->80/tcp 为例分析整个过程:
1、外部请求到达 Docker 主机
- 外部设备(如用户电脑)发起请求,目标地址为 Docker 主机的 IP + 8080 端口(例如
192.168.1.100:8080
)。 - 请求通过物理网络到达 Docker 主机的网卡(如
ens160
)。
2、Docker 端口代理(docker-proxy)转发
- Docker 启动容器时,会在主机上创建
docker-proxy
进程,负责监听0.0.0.0:8080
(即主机所有网卡的 8080 端口)。 docker-proxy
接收到外部请求后,根据端口映射规则(8080->80
),将请求转发到 Docker 虚拟网桥docker0
(主机内网网关,通常为172.17.0.1
)。
3、网桥路由到目标容器
docker0
网桥根据容器的内网 IP(如172.17.0.2
),将请求路由到对应容器。- 容器内部的应用监听 80 端口(如 Nginx),接收并处理请求。
4、响应回传
- 容器处理完请求后,生成响应,按原路返回:先发送到
docker0
网桥,再由docker-proxy
转发到主机的 8080 端口。 - 最终响应通过主机网卡发送回外部设备。
实战:安装tomcat
docker hub上面查找tomcat镜像
[root@docker ~]# docker search tomcat
从docker hub上拉取tomcat镜像到本地
[root@docker ~]# docker pull tomcat
docker images查看是否有拉取到的tomcat
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat latest a79522cb3b39 3 days ago 468MB
httpd latest 65005131d37e 4 weeks ago 117MB
busybox latest 0ed463b26dae 11 months ago 4.43MB
使用tomcat镜像创建容器实例(也叫运行镜像)
[root@docker ~]# docker run -itd -p 8080:8080 tomcat
7c6439c04c8cdc8f0ede4b4bb2b777ac010f307836e0dd822bf79a51e7149abc
docker: Error response from daemon: driver failed programming external connectivity on endpoint nostalgic_franklin (2df72cd69455e4f3e5345ea4c2efa6a1c81d72a04e0109b59c14b2ca898d2dd3): Bind for 0.0.0.0:8080 failed: port is already allocated.
# 此时发现端口被占用了
# 解决:
[root@docker ~]# docker rm -f $(docker ps -aq)
[root@docker ~]# docker run -itd -p 8080:8080 tomcat
f818ae9f8b782a3e827fe7d611e516257d7259b6807ad9810c6765fcd06aa2ab
浏览器访问tomcat首页,发现默认页面被修改了
把webapps.dist目录换成webapps
[root@docker ~]# docker ps
# 进入tomcat容器内部,并启动一个交互式的命令行终端(bash)
[root@docker ~]# docker exec -it f8 bash
# webapps文件夹为空
root@f818ae9f8b78:/usr/local/tomcat# ls webapps
root@f818ae9f8b78:/usr/local/tomcat#
# 文件在webapps.dist
root@f818ae9f8b78:/usr/local/tomcat# ls webapps.dist/
docs examples host-manager manager ROOT
# 用webapps.dist替换webapps
root@f818ae9f8b78:/usr/local/tomcat# rm -r webapps
root@f818ae9f8b78:/usr/local/tomcat# mv webapps.dist webapps
再次访问tomcat
存储
Docker的两类存储资源
Docker 为容器提供了两种存放数据的资源:
由 storage driver 管理的镜像层和容器层。
Data Volume。
storage driver
Docker 镜像与容器的分层存储结构
- 镜像层(Image layers,只读):由多个只读层(如
d3a1f33e8a5a
、c22013c84729
等)堆叠而成,每个层包含镜像的一部分文件系统内容,这些层不可修改(图中带锁标识)。 - 容器层(Container layer,可读写):在镜像层之上有一个 thin(精简的)可读写层(Thin R/W layer),容器运行时产生的新文件、对已有文件的修改,都只会记录在这一层,不会影响底层的只读镜像层。这样多个容器可共享同一镜像的只读层,节省空间且便于镜像复用。
优先使用 Linux 发行版默认的 storage driver。
Docker 安装时会根据当前系统的配置选择默认的 driver。默认 driver 具有最好的稳定性,因为默认 driver 在发行版上经过了严格的测试。
查看CentOS的默认 driver:
[root@docker ~]# docker info
Client: Docker Engine - Community
Version: 26.1.3
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.14.0
Path: /usr/libexec/docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v2.27.0
Path: /usr/libexec/docker/cli-plugins/docker-compose
Server:
Containers: 4
Running: 3
Paused: 0
Stopped: 1
Images: 7
Server Version: 26.1.3
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 1
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 8b3b7ca2e5ce38e8f31a34f35b2b68ceb8470d89
runc version: v1.1.12-0-g51d5e94
init version: de40ad0
Security Options:
seccomp
Profile: builtin
Kernel Version: 4.18.0-553.6.1.el8.x86_64
Operating System: CentOS Stream 8
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 3.549GiB
Name: docker
ID: 1a80ba28-0749-4316-950f-ba74c81de259
Docker Root Dir: /var/lib/docker
Debug Mode: false
Experimental: false
Insecure Registries:
127.0.0.0/8
Registry Mirrors:
https://faf9c955231a47648fa3dad688d6db6c.mirror.swr.myhuaweicloud.com/
Live Restore Enabled: false
CentOS Stream 8 用的overlay2,底层文件系统是xfs,各层数据存放在 /var/lib/docker。
对于容器存储数据,需分情况:
无状态应用(如 busybox,仅执行临时命令,无需持久化数据),可将数据放在 storage driver 维护的层中,容器删除时数据也删除,下次启动新容器即可;
有状态应用(需持久化数据,希望容器启动加载已有数据、销毁时保留新数据),这种存储方式就不合适。
Data Volume之bind mount
在 Docker 中,Data Volume(数据卷)是一种用于持久化存储容器数据的机制,独立于容器的生命周期,可解决容器数据持久化和共享等问题。
主要特点
- 独立于容器生命周期:即便容器被删除,数据卷中的数据依然保留 。例如,数据库容器被删除后,若使用了数据卷,数据库中的数据依然存在,下次创建新容器时可继续使用这些数据。
- 可共享和重用:多个容器可以同时挂载同一个数据卷,实现数据共享。比如,一个 Web 应用容器和日志分析容器,可以挂载同一个数据卷,日志分析容器能直接读取 Web 应用容器写入的数据卷中的日志文件。
- 读写性能高:数据卷绕过了联合文件系统,直接在宿主机上进行读写操作,相比在容器层中存储数据,读写性能更好。
创建和使用方式
- 创建数据卷:可以使用
docker volume create [数据卷名]
命令来创建数据卷。例如docker volume create my - volume
,会创建一个名为my-volume
的数据卷。 - 在容器中挂载数据卷:在使用docker run命令启动容器时,通过-v或–mount参数将数据卷挂载到容器内。
- 使用
-v
参数:格式为docker run -v [数据卷名]:[容器内挂载路径] [镜像名]
。比如docker run -v my-volume:/app/data nginx
,表示将名为my-volume
的数据卷挂载到nginx
容器内的/app/data
路径。 - 使用
--mount
参数:格式更详细,如docker run --mount type=volume,src=my-volume,dst=/app/data nginx
,type
指定类型为数据卷,src
指定数据卷名,dst
指定容器内挂载路径 。
- 使用
数据卷的优势场景
- 数据库应用:像 MySQL、PostgreSQL 等数据库容器,使用数据卷可确保数据库数据的持久化,避免因容器的创建、删除影响数据。
- 日志管理:Web 应用容器可以将日志写入挂载的数据卷,方便日志分析工具读取和分析,同时也不会因为容器的更新或删除丢失历史日志 。
- 代码和配置共享:在开发环境中,可将开发代码目录挂载到容器内,这样在宿主机修改代码后,容器内的应用能实时感知变化;还可以将配置文件挂载到容器内,实现灵活的配置管理。
bind mount
bind mount 是将 host 上已存在的目录或文件 mount 到容器。
例如 docker host 上有目录 $HOME/htdocs:
[root@docker ~]# mkdir htdocs
[root@docker ~]# cd htdocs/
[root@docker htdocs]# touch index.html
[root@docker htdocs]# vim index.html
[root@docker htdocs]# cd ..
[root@docker ~]# cat htdocs/index.html
<html><body><h1>This is a file in host file system !</h1></body></html>
通过 -v 将其 mount 到 httpd 容器:
[root@docker ~]# docker run -d -p 80:80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
6a7c5e18fe62720955c720f441eb57fb09cf0ab24093ce24e8a8ed543d21c413
# -v 的格式为 <host path>:<container path>
# /usr/local/apache2/htdocs 就是 apache server 存放静态文件的地方
# 当把主机的 $HOME/htdocs 文件夹和它绑在一起后:
# 容器里原来的 /usr/local/apache2/htdocs 里的东西会被 “藏起来”,暂时看不见了
# 取而代之的是主机 $HOME/htdocs 文件夹里的内容
# 在主机上改这个文件夹里的文件,容器里的 Apache 服务器会直接用新内容(不用重新配置容器)
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>This is a file in host file system !</h1></body></html>
[root@docker ~]# echo "updated index page!" > ~/htdocs/index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
将容器销毁,看看对 bind mount 有什么影响
[root@docker ~]# docker ps
[root@docker ~]# docker stop 6a7c5e18fe62
6a7c5e18fe62
[root@docker ~]# docker rm 6a7c5e18fe62
6a7c5e18fe62
[root@docker ~]# cat ~/htdocs/index.html
updated index page!
# 可见,即使容器没有了,bind mount 也还在。
另外,bind mount 时还可以指定数据的读写权限,默认是可读可写,可指定为只读:
[root@docker ~]# docker run -d -p 80:80 -v ~/htdocs:/usr/local/apache2/htdocs:ro httpd
2b45f726d158baba90d3be053ed2ce39ffad26ec06146cf71e83ba85574d0644
[root@docker ~]#
[root@docker ~]# docker ps
[root@docker ~]# docker exec -it 2b45 bash
root@2b45f726d158:/usr/local/apache2#
root@2b45f726d158:/usr/local/apache2# echo "do some changes" > htdocs/index.html
bash: htdocs/index.html: Read-only file system
ro 设置了只读权限,在容器中是无法对 bind mount 数据进行修改的。只有 host 有权修改数据,提高 了安全性。
除了 bind mount 目录,还可以单独指定一个文件:
[root@docker ~]# docker rm -f $(docker ps -aq)
4a206e27d218
2b45f726d158
f818ae9f8b78
[root@docker ~]# docker run -d -p 80:80 -v ~/htdocs/index.html:/usr/local/apache2/htdocs/new_index.html httpd
fbe505a6ee7d1c8cd723e653abe38004916a7f08edcad58a0545b2cfb9356507
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>It works!</h1></body></html>
[root@docker ~]# curl 127.0.0.1:80/new_index.html
updated index page!
bind mount 用于向容器添加单个文件(如给 apache 加 html 文件,且不覆盖容器整个目录,还能保留容器原有数据),但宿主端源文件必须存在,否则会被当作新目录挂载。它应用场景丰富,像挂载源代码目录实现代码实时更新、挂载 mysql 数据方便备份迁移。不过,bind mount 需指定宿主特定路径,限制了容器可移植性,容器迁移到其他宿主时,若对应数据或路径不存在,操作会失败。
Data Volume之docker managed volume
ocker managed volume 与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明 mount point 就行了。还是以 httpd 容器为例:
[root@docker ~]# docker rm -f $(docker ps -aq)
[root@docker ~]# docker run -d -p 80:80 -v /usr/local/apache2/htdocs httpd
1cea33d01a63749bfbfe64eb343997644290b8c9b3d8c67c66da1bbe7e44095e
# 通过-v告诉docker需要一个data volume并将其mount到 /usr/local/apache2/htdocs。
data volume
[root@docker ~]# docker inspect 1cea
......
"Mounts": [
{
"Type": "volume",
"Name": "458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8",
"Source": "/var/lib/docker/volumes/458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8/_data",
"Destination": "/usr/local/apache2/htdocs",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
......
# docker inspect 的输出很多,我们感兴趣的是 Mounts 这部分,这里会显示容器当前使用的所有 data volume,包括 bind mount 和 docker managed volume。
# Source 就是该 volume 在 host 上的目录。
# 每当容器申请 mount docker manged volume 时,docker 都会在 /var/lib/docker/volumes 下生成一个目录
#/var/lib/docker/volumes/458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8/_data,这个目录就是 mount 源。
[root@docker ~]# ls -l /var/lib/docker/volumes/458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8/_data
total 4
-rw-r--r-- 1 501 ftp 45 Jun 12 2007 index.html
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>It works!</h1></body></html>
# volume 的内容跟容器原有 /usr/local/apache2/htdocs 完全一样
# 因为如果 mount point 指向的是已有目录,原有数据会被复制到 volume 中。
# 但是此时的 /usr/local/apache2/htdocs 已经不再是由 storage driver 管理的层数据了,它已经是一个 data volume。
可以像 bind mount 一样对数据进行操作
示例:
更新数据
[root@docker ~]# echo "update volume from host !" > /var/lib/docker/volumes/458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8/_data/index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
update volume from host !
docker managed volume 的创建过程:
容器启动时,仅需告知 Docker“需挂载卷到容器内
/abc
目录”,无需指定宿主路径;Docker 自动在宿主的
/var/lib/docker/volumes
下生成随机命名的目录,作为卷的实际存储源;若容器内
/abc
目录原本已有数据,Docker 会先将这些数据复制到刚生成的宿主卷目录中;最后将宿主的这个卷目录,挂载到容器内的
/abc
目录,完成卷的创建与关联。
除了通过 docker inspect 查看 volume,也可以用 docker volume 命令查看:
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8
local f84217e5c24a0bc6d5a60405e7314e0e3985724b53b48678335f680760087831
[root@docker ~]# docker volume inspect 458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8
[
{
"CreatedAt": "2025-09-09T13:37:05+08:00",
"Driver": "local",
"Labels": {
"com.docker.volume.anonymous": ""
},
"Mountpoint": "/var/lib/docker/volumes/458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8/_data",
"Name": "458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8",
"Options": null,
"Scope": "local"
}
]
# 目前, docker volume 只能查看 docker managed volume,还看不到 bind mount;同时也无法知道 volume 对应的容器,这些信息还得靠 docker inspect 。
两种 data volume 的对比
1、相同点:两者本质都是宿主文件系统中的某个路径,核心作用都是实现容器数据的持久化或共享,避免数据随容器生命周期消亡。
2、不同点:
对比维度 | Bind Mount(绑定挂载) | Docker Managed Volume(Docker 管理卷) |
---|---|---|
Volume 位置 | 可自主指定宿主任意路径(如 /home/user/data ) |
固定在宿主 /var/lib/docker/volumes/ 下(自动生成随机目录) |
对已有 mount point 影响 | 直接隐藏容器内原有目录,用宿主路径内容替换 | 会先将容器内原有目录的数据,复制到宿主的卷目录中 |
是否支持单个文件 | 支持(可挂载单个文件到容器) | 不支持,仅能挂载目录 |
权限控制 | 可设置只读(如 -v /host/path:/container/path:ro ),默认读写 |
无额外控制,默认均为读写权限 |
移植性 | 弱,与宿主特定路径强绑定(迁移容器时需确保目标宿主有对应路径 / 数据) | 强,无需手动指定宿主路径(Docker 自动管理),迁移容器时卷可随容器关联 |
如何共享数据
容器与 host 共享数据
两种类型的 data volume,它们均可实现在容器与 host 之间共享数据,但方式有所区别。
对于 bind mount 是非常明确的:直接将要共享的目录 mount 到容器。
docker managed volume 就要麻烦点。由于 volume 位于 host 中的目录,是在容器启动时才生成,所 以需要将共享数据拷贝到 volume 中。
示例
[root@docker ~]# docker run -d -p 80:80 -v /usr/local/apache2/htdocs httpd
c39d0650ec1b2cc2810a83fdd1295319685a50436a0e2c5e0f044f5cfba792bd
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>It works!</h1></body></html>
[root@docker ~]# docker cp ~/htdocs/index.html c39d0650ec1b2:/usr/local/apache2/htdocs
Successfully copied 2.05kB to c39d0650ec1b2:/usr/local/apache2/htdocs
[root@docker ~]# curl 127.0.0.1:80
updated index page!
docker cp 可以在容器和 host 之间拷贝数据,当然我们也可以直接通过 Linux 的 cp 命令复制到 /var/lib/docker/volumes/xxx。
容器之间共享数据
第一种方法是将共享数据放在 bind mount 中,然后将其 mount 到多个容器。
创建由三个 httpd 容器组成的 web server 集群,它们使用相同的 html 文 件
示例:
# 将 $HOME/htdocs mount 到三个 httpd 容器。
[root@docker ~]# docker run --name web1 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
b248cf04ecfbe3f670285d75e4af3a1cc36430282e4cd089ef3764f10b81933c
[root@docker ~]# docker run --name web2 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
210229c54b4d511cc18eb2a7336173d9db1762a84d59798172db467589d81e55
[root@docker ~]# docker run --name web3 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
4da13957b783ad571ee44161d9924c3626c52a034608ea55a4d804e584a2ad6a
# 查看当前主页内容。
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4da13957b783 httpd "httpd-foreground" 35 seconds ago Up 34 seconds 0.0.0.0:32771->80/tcp, :::32771->80/tcp web3
210229c54b4d httpd "httpd-foreground" 40 seconds ago Up 40 seconds 0.0.0.0:32770->80/tcp, :::32770->80/tcp web2
b248cf04ecfb httpd "httpd-foreground" About a minute ago Up About a minute 0.0.0.0:32769->80/tcp, :::32769->80/tcp web1
c39d0650ec1b httpd "httpd-foreground" 7 minutes ago Up 7 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp nostalgic_cori
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:32769
updated index page!
[root@docker ~]# curl 127.0.0.1:32770
updated index page!
[root@docker ~]# curl 127.0.0.1:32771
updated index page!
[root@docker ~]#
# 修改 volume 中的主页文件,再次查看并确认所有容器都使用了新的主页。
[root@docker ~]# echo "This is a new index page for web cluster" > ~/htdocs/index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:32769
This is a new index page for web cluster
[root@docker ~]# curl 127.0.0.1:32770
This is a new index page for web cluster
[root@docker ~]# curl 127.0.0.1:32771
This is a new index page for web cluster
[root@docker ~]#
用volume container共享数据
volume container 是专门为其他容器提供 volume 的容器。它提供的卷可以是 bind mount,也可以是 docker managed volume。下面我们创建一个 volume container:
[root@docker ~]# docker create --name vc_data -v ~/htdocs/:/usr/local/apache2/htdocs -v /other/userful/tools busybox
caca3f9e514f9eab224b1a256b12cc21cba3b346156e8a13ce0525424be12ca5
# 通过 docker inspect 可以查看到这两个 volume
[root@docker ~]# docker inspect vc_data
......
"Mounts": [
{
"Type": "bind",
"Source": "/root/htdocs",
"Destination": "/usr/local/apache2/htdocs",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "volume",
"Name": "943203e65358f3875cf8cda7e5206e9c8abcca3d2d9d94e092c9249933611b57",
"Source": "/var/lib/docker/volumes/943203e65358f3875cf8cda7e5206e9c8abcca3d2d9d94e092c9249933611b57/_data",
"Destination": "/other/userful/tools",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
......
# 把之前的123删掉重新run
[root@docker ~]# docker rm -f web1
web1
[root@docker ~]# docker rm -f web2
web2
[root@docker ~]# docker rm -f web3
web3
# 其他容器可以通过 --volumes-from 使用 vc_data 这个 volume container:
[root@docker ~]# docker run --name web1 -d -p 80 --volumes-from vc_data httpd
ec0a7e713634cd82414ce6db4d622bcaa61762609ef8ee09272a2f115e31a576
[root@docker ~]# docker run --name web2 -d -p 80 --volumes-from vc_data httpd
93c6cc1f32711c2d9656cde1659208be7043b2f102096967f2f4ac4b582d334a
[root@docker ~]# docker run --name web3 -d -p 80 --volumes-from vc_data httpd
f6d58d47753733e5d494d694a7c1531764470035cf91ff66dd2be3b3d1adb57d
三个 httpd 容器都使用了 vc_data,看看它们现在都有哪些 volume,以 web1 为例:
[root@docker ~]# docker inspect web1
......
"Mounts": [
{
"Type": "volume",
"Name": "943203e65358f3875cf8cda7e5206e9c8abcca3d2d9d94e092c9249933611b57",
"Source": "/var/lib/docker/volumes/943203e65358f3875cf8cda7e5206e9c8abcca3d2d9d94e092c9249933611b57/_data",
"Destination": "/other/userful/tools",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "bind",
"Source": "/root/htdocs",
"Destination": "/usr/local/apache2/htdocs",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
......
web1 容器使用的就是 vc_data 的 volume,而且连 mount point 都是一样的。验证一下数据共享的效 果:
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f6d58d477537 httpd "httpd-foreground" 6 minutes ago Up 6 minutes 0.0.0.0:32774->80/tcp, :::32774->80/tcp web3
93c6cc1f3271 httpd "httpd-foreground" 6 minutes ago Up 6 minutes 0.0.0.0:32773->80/tcp, :::32773->80/tcp web2
ec0a7e713634 httpd "httpd-foreground" 7 minutes ago Up 7 minutes 0.0.0.0:32772->80/tcp, :::32772->80/tcp web1
c39d0650ec1b httpd "httpd-foreground" 28 minutes ago Up 28 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp nostalgic_cori
[root@docker ~]# echo "1234567890" > ~/htdocs/index.html
[root@docker ~]# curl 127.0.0.1:32772
1234567890
[root@docker ~]# curl 127.0.0.1:32773
1234567890
[root@docker ~]# curl 127.0.0.1:32774
1234567890
# 三个容器已经成功共享了 volume container 中的 volume
总结
实现容器与主机的解耦
用 bind mount 时,每个容器都要显式指定 -v <主机路径>:<容器路径>
,这会让容器 “依赖” 主机的具体路径(比如 /home/user/data
)。如果换一台主机,路径可能不一样,容器就跑不起来。
而 volume container 把所有挂载路径都 “封装” 在自己内部(比如在数据卷容器里定义 -v /data
),其他容器只需通过 --volumes-from 数据卷容器名
关联,完全不用关心主机上的实际存储路径(由 Docker 管理)。
这样一来,容器配置里看不到任何主机路径,实现了 “容器和主机的解耦”—— 容器可以在不同主机间迁移,只要带上数据卷容器的配置,数据就能正常使用。
挂载路径统一,规范但有局限
使用同一个 volume container 的所有应用容器,都会继承相同的挂载点(比如都挂载 /data
目录)。
- 好处:所有容器的目录结构一致(比如都从
/data/config
读配置,向/data/logs
写日志),方便团队统一规范(不用每个人记不同的路径),尤其适合多容器协作的场景(比如前后端容器共享静态资源目录)。 - 局限:如果某个容器需要特殊的挂载路径(比如想把数据存在
/app/data
而不是默认的/data
),就很难灵活调整,必须跟着数据卷容器的定义走,不够灵活。
简单说,volume container 就像一个 “数据配置模板”:统一管理路径,减少容器对主机的依赖,但代价是牺牲了部分灵活性。这也是后来命名卷(Named Volumes)逐渐替代它的原因 —— 命名卷既保留了 “解耦主机路径” 的优点,又能让每个容器灵活指定挂载点,更符合大多数场景的需求。
data-packed volume container
要实现 “数据完全打包到容器中,同时供其他容器共享”,核心是创建 Data-Packed Volume Container(数据打包型卷容器),原理是把数据嵌入镜像,再通过 Docker 管理卷共享。
示例:
[root@docker ~]# vim Dockerfile
[root@docker ~]# cat Dockerfile
FROM busybox:latest
ADD htdocs /usr/local/apache2/htdocs
VOLUME /usr/local/apache2/htdocs
# ADD 将静态文件添加到容器目录 /usr/local/apache2/htdocs。
# VOLUME 的作用与 -v 等效,用来创建 docker managed volume,mount point 为 /usr/local/apache2/htdocs,因为这个目录就是 ADD 添加的目录,所以会将已有数据拷贝到 volume 中。
[root@docker ~]# echo "666666666666666" > htdocs/index.html
[root@docker ~]# docker build -t datapacked .
[+] Building 0.1s (7/7) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 128B 0.0s
=> [internal] load metadata for docker.io/library/busybox:latest 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 90B 0.0s
=> [1/2] FROM docker.io/library/busybox:latest 0.0s
=> [2/2] ADD htdocs /usr/local/apache2/htdocs 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:74c2b6a5c3dbe6fce453235e1a86bdff34c6dd46d55b045fbdf24dc5f2dccc95 0.0s
=> => naming to docker.io/library/datapacked 0.0s
用新镜像创建 data-packed volume container:
[root@docker ~]# docker create --name vc_data datapacked
93e2a017de4ab15e4e7b1c5a98eb728bcab688fcf799e19268323b0608d0a102
因为在 Dockerfile 中已经使用了 VOLUME 指令,这里就不需要指定 volume 的 mount point 了。启动 httpd 容器并使用 data-packed volume container:
[root@docker ~]# docker run -d -p 80:80 --volumes-from vc_data httpd
83b5e79df0156d850f3fabce494e27089e6a418633cd41f856daedac243b7644
[root@docker ~]# curl 127.0.0.1:80
666666666666666
Data-Packed Volume Container(数据打包型卷容器)的核心价值可总结为两点:
- 数据自包含,不依赖宿主:静态数据(如配置文件、Web 静态文件)直接打包进镜像,容器启动时通过 Docker 管理卷自动加载数据,无需宿主提前准备数据,彻底摆脱对宿主路径的依赖;
- 移植性极强:迁移时只需拷贝镜像,在新环境启动卷容器和业务容器,数据就能正常共享,尤其适配 “仅用静态数据” 的场景,无需额外处理数据同步问题。
volume生命周期管理
备份
因为 volume 实际上是 host 文件系统中的目录和文件,所以 volume 的备份实际上是对文件系统的备 份。
[root@docker ~]# docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:2
恢复
volume 的恢复也很简单,如果数据损坏了,直接用之前备份的数据拷贝到 /myregistry 就可以了。
迁移
如果我们想使用更新版本的 Registry,这就涉及到数据迁移,方法是:
docker stop 当前 Registry 容器。
启动新版本容器并 mount 原有 volume。
docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:latest
销毁
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8
local 943203e65358f3875cf8cda7e5206e9c8abcca3d2d9d94e092c9249933611b57
local cf88d7bad5be212d960e2dd1dad1c9638e02c9965ea48179ce23b60ba46dbd13
local ee467975716cdf8c6904126c375ea01259c0bf194c62dec6d96b1c2def72e371
local f84217e5c24a0bc6d5a60405e7314e0e3985724b53b48678335f680760087831
[root@docker ~]# docker run --name bbox -v /test/data busybox
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8
local 898d2a7ac0f8881f0d80878740d7eec692b26d169915dd721e4855877da0472b
local 943203e65358f3875cf8cda7e5206e9c8abcca3d2d9d94e092c9249933611b57
local cf88d7bad5be212d960e2dd1dad1c9638e02c9965ea48179ce23b60ba46dbd13
local ee467975716cdf8c6904126c375ea01259c0bf194c62dec6d96b1c2def72e371
local f84217e5c24a0bc6d5a60405e7314e0e3985724b53b48678335f680760087831
容器 bbox 使用的 docker managed volume 可以通过 docker volume ls 查看到。
删除 bbox:
[root@docker ~]# docker rm bbox
bbox
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8
local 898d2a7ac0f8881f0d80878740d7eec692b26d169915dd721e4855877da0472b
local 943203e65358f3875cf8cda7e5206e9c8abcca3d2d9d94e092c9249933611b57
local cf88d7bad5be212d960e2dd1dad1c9638e02c9965ea48179ce23b60ba46dbd13
local ee467975716cdf8c6904126c375ea01259c0bf194c62dec6d96b1c2def72e371
local f84217e5c24a0bc6d5a60405e7314e0e3985724b53b48678335f680760087831
因为没有使用 -v ,volume 遗留了下来。对于这样的孤儿 volume,可以用 docker volume rm 删除:
[root@docker ~]# docker volume rm 458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8
458cc02fa1c3748b302ab8f40ffc449ddb030a54a832ed4858b1c6cb8d3e90c8
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 898d2a7ac0f8881f0d80878740d7eec692b26d169915dd721e4855877da0472b
local 943203e65358f3875cf8cda7e5206e9c8abcca3d2d9d94e092c9249933611b57
local cf88d7bad5be212d960e2dd1dad1c9638e02c9965ea48179ce23b60ba46dbd13
local ee467975716cdf8c6904126c375ea01259c0bf194c62dec6d96b1c2def72e371
local f84217e5c24a0bc6d5a60405e7314e0e3985724b53b48678335f680760087831
# 如果想批量删除孤儿 volume,可以执行:docker volume rm $(docker volume ls -q)
实战:安装mysql
# docker hub上查找mysql镜像
[root@docker ~]# docker search mysql
# 从华为云加速器拉取mysql镜像到本地标签为5.7
[root@docker ~]# docker pull mysql:5.7
使用mysql
方法1(基础)
[root@docker ~]# docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
44a03436dbb25ae4ac3cb3715fa2d88f0ab2891d8847fb8eace9dc751f035066
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
44a03436dbb2 mysql:5.7 "docker-entrypoint.s…" 10 seconds ago Up 9 seconds 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp happy_cray
[root@docker ~]# docker exec -it 44a03436dbb2 bash
bash-4.2# mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.44 MySQL Community Server (GPL)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
建库建表插入数据
mysql> CREATE DATABASE db01;
Query OK, 1 row affected (0.00 sec)
mysql> USE db01;
Database changed
mysql> CREATE TABLE tablea(id int,name varchar(20));
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO tablea VALUES(1,'yuxb');
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM tablea;
+------+------+
| id | name |
+------+------+
| 1 | yuxb |
+------+------+
1 row in set (0.01 sec)
mysql>
打开navicat测试链接
插入中文试试
因为docker上默认字符集不支持中文
mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.01 sec)
删除容器后,里面的mysql数据就没有了
[root@docker ~]# docker rm -f 44a03436dbb2
44a03436dbb2
再用同样的方式创建一个mysql,数据还是没有
[root@docker ~]# docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
eb4799cbbc51cb71e80774629d064f09eee11c1525b9c17a3a4e78885eee251b
方法2(进阶)
# 新建mysql实例
[root@docker ~]# docker run -d -p 3306:3306 --privileged=true -v /yuxb/mysql/log:/var/log/mysql -v /yuxb/mysql/data:/var/lib/mysql -v /yuxb/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7
b55cc2e8fdedda2c32c9776805c83d43f0867ff54ae27bd051616ef46733446a
# --privileged=true:给容器赋予更高权限(避免某些文件操作因权限不足失败,比如 MySQL 写入数据时)
# 三个 -v 参数(数据持久化):
## /-v /yuxb/mysql/log:/var/log/mysql:把主机的 /yuxb/mysql/log 文件夹,和容器里 MySQL 的日志目录绑定(日志会存在主机上,容器删了日志也在)
## -v /yuxb/mysql/data:/var/lib/mysql:绑定 MySQL 的数据目录(核心!数据库表、数据会存在主机上,容器删了数据不丢)
## -v /yuxb/mysql/conf:/etc/mysql/conf.d:绑定配置文件目录(可以在主机上直接改 MySQL 配置,不用进入容器)
# 具体说:
## 第一个 -v:容器里 MySQL 产生的日志(比如谁连接过、有没有报错),会自动存在你电脑的 /yuxb/mysql/log 文件夹里。就算容器删了,日志还在,方便你查问题。
## 第二个 -v:最重要的!你在 MySQL 里建的表、存的数据,本来是存在容器内部的。加了这个后,会存到你电脑的 /yuxb/mysql/data 里。就算容器不小心删了,只要这个文件夹还在,数据就丢不了,下次再建个新容器还能用这些数据。
## 第三个 -v:MySQL 的配置文件(比如改密码规则、调性能参数),本来要进容器里改。现在你直接在自己电脑的 /yuxb/mysql/conf 里改就行,容器里会自动用你改好的配置,不用费劲进容器操作了。
# 简单说,这三个 -v 就是把容器里 “重要的东西” 挪到自己能直接摸到的地方,既安全(不怕容器没了)又方便(直接在电脑上操作)。
# 查看
[root@docker ~]# cd /yuxb/
[root@docker yuxb]# ls
mysql
[root@docker yuxb]# cd mysql/
[root@docker mysql]# ls
conf data log
# 新建my.cnf,通过容器卷同步给mysql容器实例
[root@docker mysql]# cd /yuxb/mysql/conf/
[root@docker conf]# ls
[root@docker conf]# vim my.cnf
# #实现mysql支持中文
[root@docker conf]# cat my.cnf
[client]
default_character_set=utf8
[mysqld]
collation_server = utf8_general_ci
character_set_server = utf8
# 重新启动mysql容器实例再重新进入并查看字符编码
[root@docker conf]# docker restart mysql
mysql
[root@docker conf]# docker exec -it mysql bash
bash-4.2#
bash-4.2# mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.44 MySQL Community Server (GPL)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.01 sec)
创建库表
mysql> CREATE DATABASE db01;
Query OK, 1 row affected (0.00 sec)
mysql> USE db01;
Database changed
mysql> CREATE TABLE tablea(id int,name varchar(20));
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO tablea VALUES(1,'yuxb');
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM tablea;
+------+------+
| id | name |
+------+------+
| 1 | yuxb |
+------+------+
1 row in set (0.00 sec)
mysql>
去软件里查看库
插入中文测试
发现并没有报错
mysql> SELECT * FROM tablea;
+------+--------+
| id | name |
+------+--------+
| 1 | yuxb |
| 2 | 哈哈 |
+------+--------+
2 rows in set (0.00 sec)
删除测试
[root@docker conf]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b55cc2e8fded mysql:5.7 "docker-entrypoint.s…" 7 minutes ago Up 5 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql
[root@docker conf]# docker rm -f b55
b55
重新创建容器,发现数据还在
[root@docker conf]# docker run -d -p 3306:3306 --privileged=true -v /yuxb/mysql/log:/var/log/mysql -v /yuxb/mysql/data:/var/lib/mysql -v /yuxb/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7
543935ff4fd92004d79003639e611193b26081361c9a9b5fac89673d9f61350a
容器监控
Docker自带的监控子命令
ps
主机上正在运行的程序
[root@docker conf]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
543935ff4fd9 mysql:5.7 "docker-entrypoint.s…" 24 minutes ago Up 24 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql
top
查看容器的进程
[root@docker conf]# docker top mysql
UID PID PPID C STIME TTY TIME CMD
systemd+ 11807 11788 0 15:50 ? 00:00:01 mysqld
# 命令后面还可以跟上 Linux 操作系统 ps 命令的参数显示特定的信息,比如 -au 。
[root@docker conf]# docker top mysql -au
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
systemd+ 11807 0.0 6.1 1360648 228012 ? Ssl 15:50 0:01 mysqld
stats
列出容器资源使用率
[root@docker conf]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
543935ff4fd9 mysql 0.00% 206.8MiB / 3.549GiB 5.69% 6.13kB / 11.1kB 0B / 30.4MB 30
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
543935ff4fd9 mysql 0.04% 206.8MiB / 3.549GiB 5.69% 6.13kB / 11.1kB 0B / 30.4MB 30
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
543935ff4fd9 mysql 0.04% 206.8MiB / 3.549GiB 5.69% 6.13kB / 11.1kB 0B / 30.4MB 30
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
543935ff4fd9 mysql 0.04% 206.8MiB / 3.549GiB 5.69% 6.13kB / 11.1kB 0B / 30.4MB 30
# 默认会显示一个实时变化的列表,展示每个容器的 CPU 使用率,内存使用量和可用量,网络和磁盘的 IO 数据。
cAdvisor
cAdvisor 是 google 开发的容器监控工具。
[root@docker ~]# docker run --volume=/:/rootfs:ro --volume=/var/run:/var/run:rw --volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:ro --publish=8080:8080 --detach=true --name=cadvisor google/cadvisor:latest
打开浏览器输入192.168.108.30:8080访问cAdvisor
总结:
- 缺点:操作界面略显简陋,而且需要在不同页面之间跳转,并且只能监控一个 host。
- 优点:可以将监控到的数据导出给第三方工具,由这些工具进一步加工处理。
结论:我们把 cAdvisor 定位为一个监控数据收集器,并导出数据给第三方工具,而非展示数据。
容器日志
Docker logs
对于一个运行的容器,Docker 会将日志发送到 容器标准输出设备(STDOUT)和标准错误设备 (STDERR),STDOUT 和 STDERR 实际上就是容器的控制台终端。
示例:
# 运行httpd容器
[root@docker conf]# docker run -p 80:80 httpd
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.4. Set the 'ServerName' directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.4. Set the 'ServerName' directive globally to suppress this message
[Tue Sep 09 08:38:50.472141 2025] [mpm_event:notice] [pid 1:tid 1] AH00489: Apache/2.4.65 (Unix) configured -- resuming normal operations
[Tue Sep 09 08:38:50.472442 2025] [core:notice] [pid 1:tid 1] AH00094: Command line: 'httpd -D FOREGROUND'
^C[Tue Sep 09 08:39:02.107747 2025] [mpm_event:notice] [pid 1:tid 1] AH00491: caught SIGTERM, shutting down
# 没有用 -d 参数,httpd 容器以前台方式启动,日志会直接打印在当前的终端窗口。
# 加上 -d 参数以后台方式运行容器,就看不到输出的日志了。
[root@docker conf]# docker run -d -p 80:80 httpd
9349e12a220a57013fbecaf34645f3407d2c407958edc917a884747fcc881fc8
查看日志内容
方法一:
attach 到该容器。
# 先attach然后新开一个窗口,curl localhost就可以输出以下内容
# attach 的终端就会打印出新的日志。
[root@docker conf]# docker attach 9349
172.17.0.1 - - [09/Sep/2025:08:42:20 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [09/Sep/2025:08:42:21 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [09/Sep/2025:08:42:22 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [09/Sep/2025:08:42:22 +0000] "GET / HTTP/1.1" 200 45
......
[root@docker ~]# curl localhost
<html><body><h1>It works!</h1></body></html>
[root@docker ~]# curl localhost
<html><body><h1>It works!</h1></body></html>
[root@docker ~]# curl localhost
<html><body><h1>It works!</h1></body></html>
[root@docker ~]# curl localhost
<html><body><h1>It works!</h1></body></html>
attach 的方法在实际使用中不太方便,因为:
- 只能看到 attach 之后的日志,以前的日志不可见。
- 退出 attach 状态比较麻烦(Ctrl+p 然后 Ctrl+q 组合键),一不小心很容器将容器杀掉(比如按下 Ctrl+C)。
方法二:
用 docker logs 命令查看日志。
[root@docker conf]# docker logs 9349
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.4. Set the 'ServerName' directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.4. Set the 'ServerName' directive globally to suppress this message
[Tue Sep 09 08:39:52.501312 2025] [mpm_event:notice] [pid 1:tid 1] AH00489: Apache/2.4.65 (Unix) configured -- resuming normal operations
[Tue Sep 09 08:39:52.501443 2025] [core:notice] [pid 1:tid 1] AH00094: Command line: 'httpd -D FOREGROUND'
172.17.0.1 - - [09/Sep/2025:08:42:20 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [09/Sep/2025:08:42:21 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [09/Sep/2025:08:42:22 +0000] "GET / HTTP/1.1" 200 45
172.17.0.1 - - [09/Sep/2025:08:42:22 +0000] "GET / HTTP/1.1" 200 45
[Tue Sep 09 08:45:34.041905 2025] [mpm_event:notice] [pid 1:tid 1] AH00491: caught SIGTERM, shutting down
# docker logs 能够打印出自容器启动以来完整的日志,并且 -f 参数可以继续打印出新产生的日志,效果上与 Linux 命令 tail -f 一样。