Docker存储与网络
引言
在前三篇中,我们探讨了Docker的基本概念、安装配置、镜像管理和容器操作。本篇将聚焦于Docker技术栈中至关重要的两个支柱:存储和网络。理解Docker如何管理容器内的数据持久化和网络通信,是构建稳定、可扩展容器化应用的基础。我们将结合OpenEuler操作系统,通过理论讲解、代码示例、功能剖析和日常实践案例,深入解析Docker存储与网络的机制、配置方法和最佳实践。
第一部分:Docker存储
容器默认采用隔离的文件系统。当容器停止或删除时,其内部由镜像提供的文件层以及运行时产生的数据(除非显式保存)都会丢失。这种设计有利于保证环境的一致性,但对于需要持久化保存的数据(如数据库文件、应用日志、配置文件)或需要在容器间共享的数据,就需要Docker提供的存储机制来解决。
1. Docker存储驱动与联合文件系统
- 联合文件系统(UnionFS):Docker镜像和容器文件系统的基石。它允许将多个目录(称为分支或层)透明地叠加挂载到同一个目录下。底层通常是只读的(如基础镜像层),顶层是可读写的(容器层)。对文件的修改发生在顶层(写时复制,CoW),不影响底层。
- 存储驱动(Storage Driver):Docker使用存储驱动来管理和实现联合文件系统。不同的驱动有不同的实现方式和性能特点。在OpenEuler上,常见的驱动有:
overlay2:目前默认推荐且性能较好的驱动,支持多层镜像。devicemapper:在早期CentOS/RHEL系统中常用,但在OpenEuler上overlay2通常是更好的选择。btrfs:如果OpenEuler的根文件系统是Btrfs,可以使用此驱动(需要btrfs工具)。vfs:简单但性能差,主要用于测试。
- 查看和设置存储驱动:
- 查看当前驱动:
docker info | grep "Storage Driver" - 配置驱动:通常在
/etc/docker/daemon.json中设置(若文件不存在则创建):
修改后重启Docker服务:{ "storage-driver": "overlay2" }sudo systemctl restart docker
- 查看当前驱动:
2. 数据卷(Volumes)
数据卷是Docker推荐的持久化数据的方式。它们由Docker管理,独立于容器的生命周期,存储在宿主机文件系统(通常是/var/lib/docker/volumes/)的一个特定区域。
- 核心优势:
- 持久化:卷中的数据在容器删除后仍然存在。
- 解耦:数据存储与容器逻辑分离。
- 共享:可以在多个容器间安全地共享数据卷。
- 高效:绕过存储驱动(直接读写宿主机文件系统),性能更好。
- 备份/迁移:卷可以方便地备份、恢复或迁移。
- 管理命令:
- 创建卷:
docker volume create my_volume # 创建名为my_volume的卷 - 列出卷:
docker volume ls - 查看卷详情:
docker volume inspect my_volume - 删除卷:
docker volume rm my_volume # 删除指定卷 docker volume prune # 删除所有未被使用的卷
- 创建卷:
- 在容器中使用卷:
-v或--volume标志:# 将卷挂载到容器内的/app/data目录 docker run -d --name my_container -v my_volume:/app/data my_image # 创建匿名卷并挂载 (不推荐,不易管理) docker run -d --name my_container -v /app/data my_image--mount标志(语法更清晰,功能更丰富):docker run -d --name my_container \ --mount type=volume,source=my_volume,target=/app/data my_image- 文件/目录挂载:虽然
-v也能挂载宿主机目录(-v /host/path:/container/path),但严格来说这不属于卷管理,而是绑定挂载(Bind Mounts)。卷是Docker管理的,绑定挂载依赖于宿主机的目录结构。
- 权限问题:容器内进程访问卷目录的权限取决于容器内进程的UID/GID和宿主机目录的权限。如果宿主机目录权限严格(如属主为root),可能导致容器内普通用户进程无法写入。解决方法:
- 调整宿主机目录权限(需谨慎)。
- 在Dockerfile中使用
USER指令运行特定用户,并确保该用户在宿主机目录有权限。 - 在
docker run时使用-u指定UID/GID。 - 使用命名卷时,Docker通常会自动处理权限(但具体行为可能因驱动而异)。
3. 绑定挂载(Bind Mounts)
绑定挂载直接将宿主机的文件或目录挂载到容器中。它非常灵活,但将容器与宿主机文件系统紧密耦合。
- 特点:
- 直接映射:宿主机路径 <-> 容器路径。
- 性能好:直接访问宿主机文件系统。
- 依赖宿主机:路径必须在宿主机存在,容器移植性差。
- 权限敏感:权限问题比卷更突出(直接使用宿主机的权限设置)。
- 使用方式:
# 使用 -v docker run -d --name my_container -v /host/data:/container/data my_image # 使用 --mount docker run -d --name my_container \ --mount type=bind,source=/host/data,target=/container/data my_image - 应用场景:
- 开发时挂载源代码目录到容器,实现代码热更新。
- 挂载宿主机配置文件到容器。
- 挂载特定设备文件(如
/dev/sda1,需额外权限)。 - 需要直接读写宿主机已知路径的场景。
4. 临时文件系统(tmpfs mounts)
tmpfs挂载将内存中的临时文件系统挂载到容器中。数据完全存储在内存中,速度快,但容器停止后数据即丢失。
- 特点:
- 内存存储:读写速度极快。
- 非持久化:容器停止即消失。
- 容量限制:可以设置大小限制(
--tmpfs-size)。
- 使用方式:
# 使用 --tmpfs docker run -d --name my_container --tmpfs /app/tmp my_image # 使用 --mount docker run -d --name my_container \ --mount type=tmpfs,destination=/app/tmp my_image - 应用场景:
- 存放不需要持久化的临时文件或缓存。
- 对IO速度要求极高的临时数据处理。
5. 存储实践案例
- 案例1:MySQL数据库持久化
# 创建专用卷 docker volume create mysql_data # 运行MySQL容器,将数据目录挂载到卷 docker run -d --name mysql_db \ -e MYSQL_ROOT_PASSWORD=mysecret \ -v mysql_data:/var/lib/mysql \ mysql:latest # 即使删除容器,数据仍在mysql_data卷中。重新创建容器挂载同一卷即可恢复数据。 - 案例2:开发环境代码热加载
# 假设项目在宿主机的/home/user/project docker run -d --name dev_env \ -v /home/user/project:/app \ -p 8080:8080 \ my_dev_image # 在宿主机修改代码,容器内应用即时生效(需应用支持热加载)。 - 案例3:多容器共享配置文件
# 创建一个卷存放公共配置 docker volume create app_config # 将配置文件放入卷 (需要先挂载到一个临时容器或使用docker cp) docker run --rm -v app_config:/config alpine cp /path/to/local/config.conf /config/ # 多个容器挂载同一个配置卷 docker run -d --name service1 --mount source=app_config,target=/etc/app_config my_image1 docker run -d --name service2 --mount source=app_config,target=/etc/app_config my_image2
第二部分:Docker网络
容器需要与外部世界(其他容器、宿主机、互联网)进行通信。Docker提供了强大的网络功能来满足不同的连接需求。
1. Docker网络驱动
Docker通过可插拔的网络驱动实现不同的网络模型。
bridge:默认网络驱动。创建一个名为docker0的虚拟网桥(在OpenEuler上可使用ip addr show docker0查看)。每个加入该网络的容器会获得一个虚拟网卡(veth pair),一端在容器内,一端连接到网桥。容器通过网桥进行通信,并可通过NAT访问外部网络。这是最常用的模式,适用于单主机上的容器间通信。host:容器直接使用宿主机的网络命名空间(Namespace),共享宿主机的网络栈(IP地址、端口等)。性能最好(无NAT开销),但牺牲了网络隔离性(端口冲突风险)。none:容器拥有独立的网络命名空间,但不配置任何网络接口(只有loopback)。容器完全隔离,需要用户手动配置网络(通常结合其他工具使用)。overlay:用于跨多个Docker宿主机构建容器网络,是Docker Swarm集群模式的基础。它使用VXLAN等技术实现跨主机容器间的二层通信。macvlan:为容器分配一个独立的MAC地址,使其在网络中看起来像是一个物理设备。容器可以直接连接到物理网络(需要交换机支持),获得一个与宿主机同网段的IP地址。适用于需要容器获得真实IP地址的场景。ipvlan:类似于macvlan,但容器共享宿主机的MAC地址,使用不同的IP地址(L2模式)或路由(L3模式)。在某些网络策略下比macvlan更灵活。
2. Docker网络模型
- 容器网络命名空间:每个容器在创建时(除非使用
--network=host或--network=container:<id>)会获得一个独立的网络命名空间,包含自己的网络接口、路由表、防火墙规则等。 docker0网桥:在bridge模式下,宿主机上的虚拟网桥。它连接着所有属于默认bridge网络的容器。- veth pair:虚拟以太网设备对。一端(
vethX)在宿主机的命名空间连接到docker0网桥,另一端(eth0)在容器的命名空间内。 - NAT (SNAT/DNAT):默认情况下,容器通过
docker0网桥访问外部网络时,源IP(Container IP)会被SNAT(源地址转换)为宿主机的IP。当外部访问映射到容器的端口时(-p 80:8080),会使用DNAT(目的地址转换)将目标IP和端口转换为容器的IP和端口。
3. 网络管理命令
- 列出网络:
docker network ls - 查看网络详情:
docker network inspect bridge # 查看默认bridge网络详情 - 创建网络:
docker network create my_network # 创建一个新的bridge网络 docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth0 my_macvlan_net # 创建macvlan网络 - 删除网络:
docker network rm my_network # 删除网络(需无容器连接) docker network prune # 删除所有未被使用的网络 - 将容器连接到网络:
docker run -d --name container1 --network my_network my_image # 启动时连接 docker network connect my_network existing_container # 连接已运行容器 - 断开容器与网络的连接:
docker network disconnect my_network existing_container
4. 端口发布(Port Publishing)
为了让外部网络(宿主机外部)能够访问容器内部的服务,需要将容器内部的端口映射(发布)到宿主机的端口上。
-p或--publish标志:# 将容器80端口映射到宿主机8080端口 (TCP默认) docker run -d -p 8080:80 nginx # 映射UDP端口 docker run -d -p 8080:80/udp my_udp_service # 映射多个端口 docker run -d -p 8080:80 -p 8443:443 nginx # 让Docker自动分配宿主机端口 (查看用docker ps或docker port) docker run -d -p 80 nginx查看端口映射:
docker port container_name # 查看指定容器的端口映射 docker ps # 查看PORTS列
5. 容器间通信
- 同一网络内:Docker为同一网络内的容器提供了基于容器名称(或使用
--name指定的别名)的DNS解析服务。容器间可以直接通过容器名进行通信,无需知道对方的IP地址。docker network create app_net docker run -d --name web --network app_net nginx docker run -it --name client --network app_net alpine / # ping web # 在client容器内ping web容器,成功 / # wget -O- http://web # 访问web容器的nginx - 不同网络:默认情况下,不同网络的容器无法直接通信(除非通过宿主机路由或额外配置)。需要将容器连接到同一个网络,或者配置网络路由规则。
6. 网络实践案例
- 案例1:Web应用栈 (Nginx + PHP-FPM + MySQL)
# 创建专用网络 docker network create web_app_net # 运行MySQL (使用卷持久化) docker run -d --name mysql --network web_app_net -v mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=pass mysql # 运行PHP-FPM (挂载应用代码) docker run -d --name php-fpm --network web_app_net -v $(pwd)/app:/var/www/html php:fpm # 运行Nginx (挂载代码和配置,发布端口) docker run -d --name nginx --network web_app_net -v $(pwd)/app:/var/www/html -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf -p 80:80 nginx # 在app目录放入PHP代码。Nginx配置中设置fastcgi_pass php-fpm:9000; 容器间通过名称php-fpm通信。 - 案例2:使用
host网络提升性能docker run -d --name high_perf_app --network host my_image # 容器直接使用宿主机网络 # 注意:容器内应用监听端口时,会直接占用宿主机的端口,可能导致冲突。 - 案例3:
macvlan给容器真实IP# 假设宿主机eth0在192.168.1.0/24网段,网关192.168.1.1 docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth0 macvlan_net docker run -d --name container_with_ip --network macvlan_net my_image # 容器将获得一个192.168.1.0/24网段的独立IP,可与同网段其他物理设备直接通信。 - 案例4:Docker Compose定义网络与存储 (简化上述Web栈)
运行:version: '3.8' services: mysql: image: mysql:latest volumes: - mysql_data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: pass networks: - app_net php-fpm: image: php:fpm volumes: - ./app:/var/www/html networks: - app_net nginx: image: nginx:latest volumes: - ./app:/var/www/html - ./nginx.conf:/etc/nginx/nginx.conf ports: - "80:80" networks: - app_net volumes: mysql_data: networks: app_net: driver: bridgedocker-compose up -d
OpenEuler系统注意事项
- 防火墙:OpenEuler默认使用
firewalld。Docker在安装时通常会添加dockerzone并开放必要的规则。如果遇到网络问题(如端口映射后无法访问),检查firewalld状态和规则:sudo systemctl status firewalld sudo firewall-cmd --list-all --zone=docker # 查看docker zone规则 sudo firewall-cmd --add-port=80/tcp --permanent --zone=public # 如果需要额外开放端口到public zone sudo firewall-cmd --reload - SELinux:OpenEuler可能启用SELinux。如果使用绑定挂载出现权限问题(即使文件权限正确),可能是SELinux安全上下文导致。可以尝试:
- 临时禁用SELinux(生产环境不推荐):
setenforce 0 - 添加正确的SELinux标签(使用
chcon或semanage fcontext)。 - 在
docker run时使用--security-opt label=disable(禁用SELinux保护,有安全风险)。 - 使用卷代替绑定挂载(卷通常不受此问题影响)。
- 临时禁用SELinux(生产环境不推荐):
- 网络性能:对于高性能网络需求(如HPC、金融交易),考虑使用
host网络、macvlan/ipvlan或高级网络驱动(如SR-IOV),并优化OpenEuler内核参数(如调整net.core.somaxconn,net.ipv4.tcp_tw_reuse等)。测试工具可使用iperf3。
总结
Docker的存储和网络机制为容器化应用提供了灵活、可靠的数据管理和通信能力。在OpenEuler系统上:
- 存储:优先使用数据卷进行持久化存储管理。理解
overlay2驱动原理。掌握volume、bind mount、tmpfs的适用场景和权限管理。结合docker volume命令和Compose文件进行管理。 - 网络:掌握
bridge、host、none、macvlan等核心网络模式及其工作原理。熟练使用docker network命令创建和管理网络。理解容器间通过容器名称在同一网络内通信的便利性。熟练使用端口发布(-p)。在OpenEuler上注意firewalld和SELinux对网络和存储访问的影响。
通过本篇的学习和实践,您应该能够为容器应用配置合适的持久化存储方案,并构建满足不同需求的容器网络拓扑,为在OpenEuler上部署和管理复杂的容器化服务打下坚实基础。在后续篇章中,我们将探讨Docker Compose、Docker Swarm/Kubernetes集群管理等更高级的主题。