Docker-镜像存储机制-网络
一、Docker镜像制作
1.制作前端应用镜像
配置文件:
[root@localhost front-dockerfile]# vi Dockerfile FROM nginx:1.22.1
COPY /dist /usr/local/web/
COPY nginx.conf /etc/nginx/nginx.conf
CMD ["nginx", "-g", "daemon off;"][root@localhost front-dockerfile]# vi nginx.conf user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {worker_connections 1024;
}
http {include /etc/nginx/mime.types;default_type application/octet-stream;log_format main '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log /var/log/nginx/access.log main;sendfile on;keepalive_timeout 65;server {listen 80;server_name localhost;real_ip_header X-Real-IP;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;location / {root /usr/local/web;index index.html;try_files $uri $uri/ /index.html;}error_page 500 502 503 504 /50x.html;location = /50x.html {root /usr/share/nginx/html;}}
}前端项目打包后会生成 dist 目录,一起打包到镜像里,目录结构如下:
[root@localhost front-dockerfile]# ll
总用量 8
drwxr-xr-x 7 root root 147 2月 21 2025 dist
-rw-r--r-- 1 root root 120 2月 24 2025 Dockerfile
-rw-r--r-- 1 root root 933 2月 21 2025 nginx.conf
制作镜像:
[root@localhost front-dockerfile]# docker build -t front-app:v1 .
[root@localhost front-dockerfile]# docker run -d front-app:v12.构建上下文
当运行 docker build 命令时,当前工作目录被称为构建上下文——build context
docker build默认查找当前目录的 Dockerfile 作为构建输入,也可以通过 -f 指定Dockerfiledocker build -f ./Dockerfile
当 docker build 运行时,首先会构建上下文传输给 docker daemon,把没用的文件包含在构建上下文时,会导致传输时间长,构建需要的资源多,构建出的镜像大等问题。
试着找一下包含较多文件的目录试一下区别
可以通过 .dockerignore 文件从编译上下文排除文件
需要确认构建上下文清晰,建议创建一个单独的目录放置 Dockerfile,并在目录中运行
docker build
注:随着版本的更新换代,目前来看构建上下文已经不怎么会影响到构建速度了,用处不大
3..dockerignore 文件
- 在使用 Docker 构建镜像时,我们通常会将项目的文件和文件夹复制到镜像中
- 然而,并不是所有的文件和文件夹都需要被复制进镜像中
- 有时我们需要排除一些不需要的文件夹或文件,这就是使用 .dockerignore 文件的作用
例:如果我们只想将 app 文件夹复制到镜像中,而不包含 config 和 tests 文件夹,我们可以在 .dockerignore 文件中添加以下内容:
config/
tests/.dockerignore 文件的规则:
#:用于添加注释。以 # 开头的行将被忽略
/path/to/folder:排除指定的文件夹以及其内容
/path/to/file:排除指定的文件
!:用于取反。如果文件夹被排除了,但是又想包含此文件夹内的某个文件,可以在前面加上 !
# 忽略所有 .txt 文件
*.txt# 忽略所有文件夹和子文件夹中的 .log 文件
**/*.log# 排除 .git 文件夹及其内容
.git/# 排除 node_modules 文件夹及其内容
node_modules/# 但是包含 node_modules/myapp 文件夹及其内容
!node_modules/myapp/4.多平台镜像介绍
(1)查看本地镜像支持的架构
[root@localhost ~]# docker inspect nginx:1.22.1 | grep -i arch"Architecture": "amd64",
(2)查看远端仓库中的镜像支持的架构
[root@localhost ~]#docker manifest inspect nginx:1.22.1 | grep architecture"architecture": "amd64","architecture": "arm","architecture": "arm","architecture": "arm64","architecture": "386","architecture": "mips64le","architecture": "ppc64le","architecture": "s390x",
二、镜像存储机制
1.Docker镜像简介
- Docker镜像是一个只读的Docker容器模板,含有启动Docker容器所需的文件系统结构及其内容
- rootfs是Docker容器在启动时内部进程可见的文件系统,即Docker容器的根目录
在Docker架构中,当Docker daemon为Docker容器挂载rootfs时,沿用了Linux内核启动时的方法,即将rootfs设为只读模式
在挂载完毕之后,利用联合挂载(union mount)技术在已有只读rootfs上再挂载一个读写层

2.OverlayFS 存储原理
OverlayFS(解决镜像分层问题) 结构分为三个层: LowerDir、Upperdir、MergedDir:
LowerDir (只读层) 只读的 image layer,其实就是 rootfs, 包含镜像中的所有层。
UpperDir (读写层) upperdir 则是在 lowerdir 之上的一层, 为读写层。容器在启动的时候会创建, 所有对容器的修改, 都是在这层。 比如容器启动写入的日志文件,或者是应用程序写入的临时文件。
MergedDir (合并层) merged 目录是容器的挂载点,在用户视角能够看到的所有文件,都是从这层展示的。
WorkDir (工作目录)workdir 是 OverlayFS 内部用于处理写入操作的 临时目录。

例:查看当前启动容器的挂载点——
# 查看当前启动容器
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
79518939ce04 front-app:v1 "/docker-entrypoint.…" 51 minutes ago Up 51 minutes 80/tcp keen_ellis# 查看容器挂载点merged
[root@localhost ~]# df -h
文件系统 容量 已用 可用 已用% 挂载点
devtmpfs 4.0M 0 4.0M 0% /dev
tmpfs 1.8G 0 1.8G 0% /dev/shm
tmpfs 725M 9.1M 716M 2% /run
/dev/nvme0n1p3 16G 7.3G 8.8G 46% /
/dev/nvme0n1p1 2.0G 278M 1.7G 15% /boot
tmpfs 363M 0 363M 0% /run/user/0
overlay 16G 7.3G 8.8G 46% /var/lib/docker/overlay2/6a2005234d448560462ac6b4c12838a50181e0dbb2074ab4c44c2b6d72f480c4/merged# 查看挂载点下的文件
[root@localhost ~]# ls /var/lib/docker/overlay2/6a2005234d448560462ac6b4c12838a50181e0dbb2074ab4c44c2b6d72f480c4/merged
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
3.OverlayFS 演示
(1)创建演示目录和文件
# 创建需要的目录和文件
[root@localhost ~]# cd /tmp/
[root@localhost tmp]# mkdir upper lower merged work
[root@localhost tmp]# echo "I'm from lower" > lower/in_lower.txt
[root@localhost tmp]# echo "I'm from upper" > upper/in_upper.txt
[root@localhost tmp]# echo "I'm from lower" > lower/in_both.txt
[root@localhost tmp]# echo "I'm from upper" > upper/in_both.txt# 挂载 overlay 文件系统
[root@localhost tmp]# mount -t overlay overlay \-o lowerdir=/tmp/lower,upperdir=/tmp/upper,workdir=/tmp/work \/tmp/merged
(2)查看文件合并后的效果
[root@localhost tmp]# cat /tmp/merged/in_lower.txt
I'm from lower
[root@localhost tmp]# cat /tmp/merged/in_upper.txt
I'm from upper# upper 层会覆盖 lower 层的文件
[root@localhost tmp]# cat /tmp/merged/in_both.txt
I'm from upper
(3)创建一个新文件
[root@localhost tmp]# echo 'new file' > merged/new_file
[root@localhost tmp]# ls -l */new_file
-rw-r--r-- 1 root root 9 11月 10 20:50 merged/new_file
-rw-r--r-- 1 root root 9 11月 10 20:50 upper/new_file
(4)删除一个文件
# 删除 merged 层的文件 in_both.txt
[root@localhost tmp]# rm merged/in_both.txt# 在 merged 层找不到文件 in_both.txt
# 在 upper 层还可以看到 in_both.txt, 但文件类型是 c (character )
[root@localhost tmp]# ls -l merged/in_both.txt lower/in_both.txt upper/in_both.txt
ls: 无法访问 'merged/in_both.txt': 没有那个文件或目录
-rw-r--r-- 1 root root 15 11月 10 20:43 lower/in_both.txt
c--------- 2 root root 0, 0 11月 10 20:51 upper/in_both.txt
三、分析镜像存储结构
1.下载redis6 的镜像
[root@localhost ~]# docker pull redis:62.查看镜像存储结构
[root@localhost ~]# docker inspect redis:6"GraphDriver": {"Data": {"LowerDir": "/var/lib/docker/overlay2/cf1e7eb869d44c4f60b77b007460b016e0d9e034aea3101595b84c24fdb01e76/diff:/var/lib/docker/overlay2/8a76b643063fb5e48a14c1fd78a731523e56ec6c68db4eb5d8a84db543a8757c/diff:/var/lib/docker/overlay2/dc23466c82e922705d9b6bebf2aed326e76785371657aace2cfdd3f6fb438c7b/diff:/var/lib/docker/overlay2/fed6af7dd89ae7ee82dcee6e7f8f8a6fe440b44408c77055da665692f1e1c236/diff:/var/lib/docker/overlay2/5eb42b1b2cb046a868b0d4784e25cda914902ad06fef3d5fb56e3659d25f21d2/diff:/var/lib/docker/overlay2/395e80505a884735be5f5c57d8f0c34959f79d0a27b0aadfb2a8ab185baf2568/diff:/var/lib/docker/overlay2/c0144e4a78f56a21ba47632b6579b8f825665bee26db834ccdab11f82389408c/diff","MergedDir": "/var/lib/docker/overlay2/fa26139f3606288d5b8ff30100ca20175e8bc61225962d6d945ed5044f89f489/merged","UpperDir": "/var/lib/docker/overlay2/fa26139f3606288d5b8ff30100ca20175e8bc61225962d6d945ed5044f89f489/diff","WorkDir": "/var/lib/docker/overlay2/fa26139f3606288d5b8ff30100ca20175e8bc61225962d6d945ed5044f89f489/work"},"Name": "overlay2"},# Docker 镜像在磁盘上解压后的目录:
[root@localhost ~]# ll /var/lib/docker/overlay2/
3.运行中容器的存储结构
# 启动容器
[root@localhost ~]# docker run -d --name redis6 redis:6
199ed36330c133905bb2d7819519058f78a4c963093ce059323a1ff5f7bf42dd
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
199ed36330c1 redis:6 "docker-entrypoint.s…" 11 seconds ago Up 11 seconds 6379/tcp redis6
[root@localhost ~]# docker inspect redis6"GraphDriver": {"Data": {"LowerDir": "/var/lib/docker/overlay2/42414edf9ad16c1ee14e280b82ab21bd79c1115d8156e8218b863801c133ef67-init/diff:/var/lib/docker/overlay2/fa26139f3606288d5b8ff30100ca20175e8bc61225962d6d945ed5044f89f489/diff:/var/lib/docker/overlay2/cf1e7eb869d44c4f60b77b007460b016e0d9e034aea3101595b84c24fdb01e76/diff:/var/lib/docker/overlay2/8a76b643063fb5e48a14c1fd78a731523e56ec6c68db4eb5d8a84db543a8757c/diff:/var/lib/docker/overlay2/dc23466c82e922705d9b6bebf2aed326e76785371657aace2cfdd3f6fb438c7b/diff:/var/lib/docker/overlay2/fed6af7dd89ae7ee82dcee6e7f8f8a6fe440b44408c77055da665692f1e1c236/diff:/var/lib/docker/overlay2/5eb42b1b2cb046a868b0d4784e25cda914902ad06fef3d5fb56e3659d25f21d2/diff:/var/lib/docker/overlay2/395e80505a884735be5f5c57d8f0c34959f79d0a27b0aadfb2a8ab185baf2568/diff:/var/lib/docker/overlay2/c0144e4a78f56a21ba47632b6579b8f825665bee26db834ccdab11f82389408c/diff","MergedDir": "/var/lib/docker/overlay2/42414edf9ad16c1ee14e280b82ab21bd79c1115d8156e8218b863801c133ef67/merged","UpperDir": "/var/lib/docker/overlay2/42414edf9ad16c1ee14e280b82ab21bd79c1115d8156e8218b863801c133ef67/diff","WorkDir": "/var/lib/docker/overlay2/42414edf9ad16c1ee14e280b82ab21bd79c1115d8156e8218b863801c133ef67/work"},"Name": "overlay2"},
4.向UpperDir 层写入文件
# 写入upper.txt文件
[root@localhost ~]# touch /var/lib/docker/overlay2/42414edf9ad16c1ee14e280b82ab21bd79c1115d8156e8218b863801c133ef67/diff/upper.txt

注:UpperDir层只能看到修改的文件,加上LowerDir层才是完整的MergedDir层;若想要改LowerDir层的文件,就会自动复制一个修改后的文件,存储在UpperDir层,覆盖掉原文件

5.Manifest.json文件
注:Manifest.json文件是一个镜像基础信息描述文件
[root@localhost ~]# docker save redis:6 -o redis6.tar
[root@localhost ~]# mkdir redis
[root@localhost ~]# tar -xf redis6.tar -C redis
[root@localhost ~]# cd redis
[root@localhost redis]# ll
总用量 16
drwxr-xr-x 3 root root 20 11月 4 08:27 blobs
-rw-r--r-- 1 root root 352 11月 10 21:19 index.json
-rw-r--r-- 1 root root 2589 1月 1 1970 manifest.json #存放镜像中层的数据
-rw-r--r-- 1 root root 31 1月 1 1970 oci-layout
-rw-r--r-- 1 root root 83 1月 1 1970 repositories
查看manifest.json文件,找一个网站格式化变好看一点


注:Layers标签中的就是镜像每层所独有的层ID,在下载镜像的时候,仓库和本地的层ID就会相互比较,如果有的话就显示已存在,不会再次下载了
四、Docker网络
1.Docker 网络启动过程
- Docker 服务启动时会首先在主机上自动创建一个 docker0 的虚拟网桥,网桥可以理解为一个二层交换机,负责挂载其上的接口之间进行包转发。
- Docker 随机分配一个本地未占用的私有网段中的一个地址给 docker0 接口,默认是 172.17.0.0/16 网段。此后启动的容器会自动分配一个该网段的地址。
- 当创建一个 Docker 容器的时候,同时会创建了一对
veth pair互联接口。当向任一个接口发送包时,另外一个接口自动收到相同的包。 - 互联接口的一端位于容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以veth 开头(例如 vethae12ch)。
- 通过这种方式,主机可以与容器通信,容器之间也可以相互通信。如此一来,Docker 就创建了在主机和所有容器之间一个虚拟共享网络。


注:172.17.0.1作为链接到网桥上的所有容器的网关地址,在宿主机上也能查看到

2.查看网桥
[root@localhost ~]# dnf -y install bridge-utils
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024298a9901e no veth496a9c6vetha0016ca
# 有两个接口,表面该网桥已经连接了两个启动的容器
# 容器内接口 eth0@if11,宿主机上对应 ID 为 11 的接口,这个接口在宿主机上可以看到
[root@2944dfe3ba3f data]# ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.17.0.4/16 brd 172.17.255.255 scope global eth0valid_lft forever preferred_lft forever
3.Docker网络连通性
(1)容器访问外部
注:默认可以通信;容器与虚拟机类似,有自己的ip和dns地址,可以访问外网;容器本身的dns地址继承自宿主机


注:容器中的地址是一个私有地址,想要访问外网需要做地址转换SNAT,在容器访问外网的时候,Docker会自动调用iptables实现SNAT(同时会损耗网络性能)

(2)外部访问容器
- 容器允许外部访问,可以在docker [container] run 时候通过 -p 或 -P 参数来启用,将容器的端口映射到宿主机上
- 不管用哪种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则,将访问外部IP地址的包进行目标地址DNAT,将目标地址修改为容器的 IP 地址
例:以一个开放 80 端口的 Nginx 容器为例,使用 -P 时,会自动映射本地 32768~65535 范围内的随机端口到容器的 80 端口(不符合我们的使用习惯,一般不用)


查看日志【docker logs -f】,此时外部访问就可以正常访问到容器
再次查看防火墙规则

(3)容器访问容器
- 容器默认都连接在 Docker0 网桥上,都可以互相访问,相当连于在一台二层交换机上。
- 但是容器的 IP 地址是动态变化的,如果两个应用之间要互相访问就无法通过 IP 地址互相通信,Docker 提供两种方案来解决
--link传统方式,目前 Docker 官网已不建议使用- user-defined networks 用户定义网络
如容器172.17.0.2访问容器172.17.0.4

注:这里存在一个问题,容器通过ip访问,但容器的ip不是固定的;所以我们通常需要想办法屏蔽掉ip地址,通过域名来解决,容器这里用的是容器名
在启动容器时定义容器名,在hosts文件中添加转换关系(一般情况下):

例:
--link方式:启动两个容器,分别部署wordpress项目 和mysql
下载镜像:
docker pull registry.cn-beijing.aliyuncs.com/xxhf/wordpress:php7.4
docker tag registry.cn-beijing.aliyuncs.com/xxhf/wordpress:php7.4 wordpress:php7.4
docker pull registry.cn-beijing.aliyuncs.com/xxhf/mysql:8.0
docker tag registry.cn-beijing.aliyuncs.com/xxhf/mysql:8.0 mysql:8.0运行 mysql 容器:
[root@docker ~]# docker run -d --name db_wordpress --restart always \
-e MYSQL_ROOT_PASSWORD=wordpress \
-e MYSQL_DATABASE=db_wordpress \
-e MYSQL_USER=wordpress_rw \
-e MYSQL_PASSWORD=123456 \
-p 3306:3306 mysql:8.0
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ded235356e6f mysql:5.7 "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp db_wordpress验证数据库正常运行:
[root@docker ~]# docker run -d --link db_wordpress -it mysql-client -uroot -pwordpress -hdb_wordpress
[root@docker ~]# docker attach 3c5e8d2f9198
MySQL [(none)]>
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| db_wordpress |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.001 sec)
MySQL [(none)]> use db_wordpress
Database changed
MySQL [db_wordpress]> show tables;
Empty set (0.000 sec)
运行 WordPress 容器:
[root@docker ~]# docker run -d --name wordpress --restart always --link db_wordpress \
-e WORDPRESS_DB_HOST=db_wordpress:3306 \
-e WORDPRESS_DB_USER=wordpress_rw \
-e WORDPRESS_DB_PASSWORD=123456 \
-e WORDPRESS_DB_NAME=db_wordpress \
-p 80:80 wordpress:php7.4测试:通过浏览器访问装有wordpress项目的容器的宿主机ip,测试项目能否正常运行

user-defined networks:(优先使用)
创建一个用户定义网络:
[root@docker ~]# docker network create wp-net
e4e3d1ee70620fec8e4a5a22f9b0db737faaf5f6e8d67a2958f98f0fc86af975
[root@docker ~]#
[root@docker ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
0cc88dcd390d bridge bridge local
385ab96da9ee host host local
43634758fc70 none null local
e4e3d1ee7062 wp-net bridge local注:创建网络的同时也会创建一个网桥
重新运行 MySQL 和 WordPress 容器:
docker run -d --name db_wordpress --restart always \
--network wp-net \
-e MYSQL_ROOT_PASSWORD=wordpress \
-e MYSQL_DATABASE=db_wordpress \
-e MYSQL_USER=wordpress_rw \
-e MYSQL_PASSWORD=123456 \
-p 3306:3306 mysql:8.0
docker run -d --name wordpress --restart always --network wp-net \
-e WORDPRESS_DB_HOST=db_wordpress:3306 \
-e WORDPRESS_DB_USER=root \
-e WORDPRESS_DB_PASSWORD=wordpress \
-e WORDPRESS_DB_NAME=db_wordpress \
-p 80:80 wordpress:php7.4自定义网桥可以自动实现容器间的DNS解析,没有修改 /etc/hosts 文件:
[root@docker ~]# docker exec wordpress cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.3 24e49d0f2225注:此时两个容器之间同样可以互通
五、Docker网络模式
注:常见的有三种网络驱动模式:host、bridge、none

1.host 模式
- 如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace
- 而是和宿主机共用一个Network Namespace,容器与宿主机不做网络隔离
- 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口
容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的
# 运行一个使用 host 网络模式的容器
[root@localhost ~]# docker run -d --net=host nginx:1.22.1
778a3ab83f1db6fbebaba17123188b171df95175f1ffd4b9518168b50791c026[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
778a3ab83f1d nginx:1.22.1 "/docker-entrypoint.…" 2 seconds ago Up 2 seconds sad_taussig# 容器详情中没有 IP 地址
[root@localhost ~]# docker inspect 778a3ab83f1d | grep -i ipaddr"SecondaryIPAddresses": null,"IPAddress": "","IPAddress": "",# 宿主机可以看到 80 端口的监听
[root@localhost ~]# netstat -nltp | grep nginx
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1504/nginx: master
tcp6 0 0 :::80 :::* LISTEN 1504/nginx: master# 宿主机可以访问 nginx 服务
[root@localhost ~]# curl 127.0.0.1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
.................................2.bridge 模式
- Docker 服务默认会创建一个
docker0网桥(其上有一个docker0内部接口)。 - Docker 默认指定了
docker0接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信 --network bridge:设置容器工作在bridge模式下,即将容器接口添加至 docker0 网桥
# docker0 网桥
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02420c888553 no # docker0 接口
[root@localhost ~]# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255ether 02:42:0c:88:85:53 txqueuelen 0 (Ethernet)RX packets 0 bytes 0 (0.0 B)RX errors 0 dropped 0 overruns 0 frame 0TX packets 0 bytes 0 (0.0 B)TX errors 0 dropped 2 overruns 0 carrier 0 collisions 0
配置 docker0 网络配置:编辑 /etc/docker/daemon.json
{"registry-mirrors": ["https://docker.1ms.run","https://docker.mybacc.com","https://docker.m.daocloud.io","https://bkr5wo56.mirror.aliyuncs.com","https://chdp0mbf.mirror.aliyuncs.com"],"insecure-registries": ["reg.xxhf.cc","172.17.0.1:5000","192.168.5.100:5000"],"bip": "192.168.1.1/24","fixed-cidr": "192.168.1.0/24"
}[root@localhost ~]# systemctl restart docker观察Docker 0 ip地址
[root@localhost ~]# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500inet 192.168.1.1 netmask 255.255.255.0 broadcast 192.168.1.255ether 02:42:0c:88:85:53 txqueuelen 0 (Ethernet)RX packets 0 bytes 0 (0.0 B)RX errors 0 dropped 0 overruns 0 frame 0TX packets 0 bytes 0 (0.0 B)TX errors 0 dropped 2 overruns 0 carrier 0 collisions 0
启动容器查看容器ip地址
[root@localhost ~]# docker run -d nginx:1.22.1
7dc5110927b48ff9b5d12be008f6bfc68c08e75a1d59408bf0da3cf9c8cdfcd7# 默认使用bridge模式
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02420c888553 no veth81e66ce# ip地址和网段变为192.168.1.2
[root@localhost ~]# docker inspect 7dc5110927b4 | grep -i ipaddr"SecondaryIPAddresses": null,"IPAddress": "192.168.1.2","IPAddress": "192.168.1.2",
3.none 模式
- 此模式下容器不参与网络通信,运行于此类容器中的进程仅能访问本地环回接口,仅适用于进程无须网络通信的场景中,例如备份,进程诊断及各种离线任务等
--network=none:设置模式容器工作在none模式下
# 使用 none 网络模式,容器内只有 loopback 接口
[root@localhost ~]# docker run -it --rm --network=none busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever六、Docker命名空间
- Docker 是借助 Linux 内核技术 Namespace 来实现隔离的, Linux Namespaces 机制提供一种资源隔离方案
- PID,IPC,Network 等系统资源不再是全局性的, 而是属于某个特定的 Namespace
- 每个 namespace 下的资源对于其他 namespace 下的资源都是透明, 不可见的。 因此在操作系统层面上看, 就会出现多个相同 pid 的进程
系统中可以同时存在多个进程号为 0,1,2 的进程, 由于属于不同的 namespace, 所以它们之间并不冲突,在用户层面上只能看到属于用户自己 namespace 下的资源
1.内核支持的 Namespace 类型

2.Namespace 常用操作
注:容器本身就是一个进程
(1)查看当前系统下的 namespace
[root@localhost ~]# lsnsNS TYPE NPROCS PID USER COMMAND
4026531834 time 157 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531835 cgroup 154 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531836 pid 154 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531837 user 157 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531838 uts 151 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531839 ipc 154 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531840 net 154 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531841 mnt 144 1 root /usr/lib/systemd/systemd --switched-root --system --deserialize 31
4026531862 mnt 1 32 root kdevtmpfs
4026532496 mnt 1 512 root /usr/lib/systemd/systemd-udevd
4026532497 uts 1 512 root /usr/lib/systemd/systemd-udevd
4026532657 mnt 1 556 root /sbin/auditd
4026532658 mnt 2 582 dbus /usr/bin/dbus-broker-launch --scope system --audit
4026532659 mnt 1 599 chrony /usr/sbin/chronyd -F 2
4026532660 mnt 1 584 root /usr/sbin/NetworkManager --no-daemon
4026532661 mnt 1 587 root /usr/sbin/irqbalance --foreground
4026532662 mnt 1 588 root /usr/lib/systemd/systemd-logind
4026532664 uts 1 588 root /usr/lib/systemd/systemd-logind
4026532665 uts 1 599 chrony /usr/sbin/chronyd -F 2
4026532695 mnt 3 2073 root nginx: master process nginx -g daemon off;
4026532696 uts 3 2073 root nginx: master process nginx -g daemon off;
4026532697 ipc 3 2073 root nginx: master process nginx -g daemon off;
4026532698 pid 3 2073 root nginx: master process nginx -g daemon off;
4026532699 cgroup 3 2073 root nginx: master process nginx -g daemon off;
4026532700 net 3 2073 root nginx: master process nginx -g daemon off;
4026532784 mnt 1 660 root /usr/sbin/rsyslogd -n
(2)查看某个进程的 namespace

注:目录中的1是进程id;proc目录是内存的映射,再查看当前父进程下的子进程,发现namespace号都是相同的,说明这两个进程处于相同命名空间,没有做隔离

(3)进入某个 namespace 执行命令
# 进入系统主进程下执行 ip addr 命令,查看到的结果跟在系统 bash下 执行 ip addr 结果一样
[root@localhost ~]# nsenter -t 1 -n ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000link/ether 00:0c:29:0b:45:32 brd ff:ff:ff:ff:ff:ffaltname enp3s0inet 192.168.5.100/24 brd 192.168.5.255 scope global noprefixroute ens160valid_lft forever preferred_lft foreverinet6 fe80::20c:29ff:fe0b:4532/64 scope link noprefixroute valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:0c:88:85:53 brd ff:ff:ff:ff:ff:ffinet 192.168.1.1/24 brd 192.168.1.255 scope global docker0valid_lft forever preferred_lft foreverinet6 fe80::42:cff:fe88:8553/64 scope link valid_lft forever preferred_lft forever
5: veth81e66ce@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether be:78:fe:3e:e0:57 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet6 fe80::bc78:feff:fe3e:e057/64 scope link valid_lft forever preferred_lft forever
(4)运行一个容器,在容器的 namespace 下执行命令
# 运行一个容器
[root@localhost ~]# docker run -d nginx:1.22.1
abb42feaac9f1112c0ea26a0ec9dfc381afd4cfda4688e02ba312b3d0091540c
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
abb42feaac9f nginx:1.22.1 "/docker-entrypoint.…" 7 seconds ago Up 6 seconds 80/tcp crazy_mcclintock# 查看容器的进程号
[root@localhost ~]# docker inspect abb42feaac9f | grep -i pid"Pid": 2301,"PidMode": "","PidsLimit": null,# 在容器的 namespace 中执行 命令
[root@localhost ~]# nsenter -t 2301 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:c0:a8:01:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 192.168.1.3/24 brd 192.168.1.255 scope global eth0valid_lft forever preferred_lft forever# 查询容器的 ip 地址,可以看到跟在容器的命名空间中执行产生的结果是一样的
[root@localhost ~]# docker inspect abb42feaac9f | grep -i ipaddr"SecondaryIPAddresses": null,"IPAddress": "192.168.1.3","IPAddress": "192.168.1.3",
(5)每个容器都在单独的 Namespace 里
[root@localhost ~]# docker run --name c1 -d nginx:1.22.1
2be8b46708e2e0e59724e44c4a24615b9be953a041b68431177ee73b0b40135d
[root@localhost ~]# docker run --name c2 -d nginx:1.22.1
4130b06156b79b50c5881fcb487424d2b99a77e7f8f5211324aefc2369a027cd
[root@localhost ~]# docker inspect c1 | grep -i pid"Pid": 2438,"PidMode": "","PidsLimit": null,
[root@localhost ~]# docker inspect c2 | grep -i pid"Pid": 2544,"PidMode": "","PidsLimit": null,
[root@localhost ~]# ll /proc/2438/ns
总用量 0
lrwxrwxrwx 1 root root 0 11月 11 19:46 cgroup -> 'cgroup:[4026532915]' #与宿主机隔离
lrwxrwxrwx 1 root root 0 11月 11 19:46 ipc -> 'ipc:[4026532913]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 mnt -> 'mnt:[4026532911]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 net -> 'net:[4026532916]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 pid -> 'pid:[4026532914]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 pid_for_children -> 'pid:[4026532914]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 time -> 'time:[4026531834]' #与宿主机不隔离
lrwxrwxrwx 1 root root 0 11月 11 19:46 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 user -> 'user:[4026531837]' #与宿主机不隔离
lrwxrwxrwx 1 root root 0 11月 11 19:46 uts -> 'uts:[4026532912]'
[root@localhost ~]# ll /proc/2544/ns
总用量 0
lrwxrwxrwx 1 root root 0 11月 11 19:46 cgroup -> 'cgroup:[4026532977]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 ipc -> 'ipc:[4026532975]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 mnt -> 'mnt:[4026532973]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 net -> 'net:[4026532978]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 pid -> 'pid:[4026532976]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 pid_for_children -> 'pid:[4026532976]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 11月 11 19:46 uts -> 'uts:[4026532974]'
注:Docker 默认没有启用 user namespace;另外也可以发现,容器与宿主机的time和user都说不隔离的,和宿主机的namespace相同,但容器本身和宿主机是隔离的
(6)运行一个容器,和宿主机的网络不做隔离
[root@localhost ~]# docker run --network=host --name=nginx-c1 -d nginx:1.22.1
d3a28508aed120a89e44e6be7bf00c5c4e63a1c03df5f57bc185cd257d45df53
[root@localhost ~]# docker inspect nginx-c1 | grep -i pid"Pid": 2673,"PidMode": "","PidsLimit": null,
[root@localhost ~]# ll /proc/2673/ns
总用量 0
lrwxrwxrwx 1 root root 0 11月 11 20:03 cgroup -> 'cgroup:[4026533037]'
lrwxrwxrwx 1 root root 0 11月 11 20:03 ipc -> 'ipc:[4026533035]'
lrwxrwxrwx 1 root root 0 11月 11 20:02 mnt -> 'mnt:[4026533033]'
lrwxrwxrwx 1 root root 0 11月 11 20:03 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 11月 11 20:03 pid -> 'pid:[4026533036]'
lrwxrwxrwx 1 root root 0 11月 11 20:03 pid_for_children -> 'pid:[4026533036]'
lrwxrwxrwx 1 root root 0 11月 11 20:03 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 11月 11 20:03 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 11月 11 20:03 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 11月 11 20:03 uts -> 'uts:[4026533034]'
同宿主机的namespace做比对,此时容器和宿主机的网络不做隔离:

3.网络 Namespace
(1)使用 ip netns 命令
# 创建一个名为 nstest 的 network namespace
[root@localhost ~]# ip netns add nstest# 列出系统已存在的 network namespace
[root@localhost ~]# ip netns list
nstest
[root@localhost ~]# ip netns list
nstest
[root@localhost ~]# ls -al /var/run/netns/
总用量 0
drwxr-xr-x 2 root root 60 11月 11 20:09 .
drwxr-xr-x 29 root root 820 11月 11 20:09 ..
-r--r--r-- 1 root root 0 11月 11 20:09 nstest# 在 network namespace 中执行命令
[root@localhost ~]# ip netns exec nstest ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00# 删除 network namespace: nstest
[root@localhost ~]# ip netns delete nstest
[root@localhost ~]# ip netns exec nstest ip addr
Cannot open network namespace "nstest": No such file or directory
(2)启动none 模式容器
[root@localhost ~]# docker run --network=none -it busybox[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9de3d276e7f2 busybox "sh" 29 seconds ago Up 28 seconds elegant_montalcini[root@localhost ~]# docker exec 9de3d276e7f2 ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
(3)查看网络配置
# 方法一:获取容器进程ID
[root@localhost ~]# docker inspect 9de3d276e7f2 | grep -i pid"Pid": 2770,"PidMode": "","PidsLimit": null,# 方法二:获取容器进程ID
[root@localhost ~]# docker inspect -f '{{.State.Pid}}' 9de3d276e7f2
2770[root@localhost ~]# nsenter -t 2770 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
(4)创建一个自定义网络 net0
[root@localhost ~]# docker network create -d bridge --subnet 172.100.0.0/16 net0
99d0bed960d85201d48f81f03f086b2890f69fab097a88717616b7e9dddf336b# 以上命令创建了一个名为 net0 的网桥,网络 ID 是 99d0bed960d8520...。该网络使用的是 bridge 模
式,网段是 172.100.0.0/16, 该网络的第一个IP地址 172.100.0.1 分配给了网桥,网桥的名字可以通过下面
命令查到[root@localhost ~]# ip a
12: br-99d0bed960d8: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default link/ether 02:42:b3:ac:74:c9 brd ff:ff:ff:ff:ff:ffinet 172.100.0.1/16 brd 172.100.255.255 scope global br-99d0bed960d8valid_lft forever preferred_lft forever# 如果想查看网桥的详细信息,可以使用 inspect 命令
[root@localhost ~]# docker network inspect net0
[{"Name": "net0","Id": "99d0bed960d85201d48f81f03f086b2890f69fab097a88717616b7e9dddf336b","Created": "2025-11-11T20:33:27.746090575+08:00","Scope": "local","Driver": "bridge","EnableIPv6": false,"IPAM": {"Driver": "default","Options": {},"Config": [{"Subnet": "172.100.0.0/16"}]},"Internal": false,"Attachable": false,"Ingress": false,"ConfigFrom": {"Network": ""},"ConfigOnly": false,"Containers": {},"Options": {},"Labels": {}}
]
(5)为容器绑定 veth pair 网卡
[root@localhost ~]# mkdir -p /var/run/netns
[root@localhost ~]# PID=$(docker inspect -f '{{.State.Pid}}' 9de3d276e7f2)
IPADDR=172.100.0.2
NETMASK=255.255.0.0
GATEWAY=172.100.0.1
[root@localhost ~]# ln -s /proc/$PID/ns/net /var/run/netns/$PID
[root@localhost ~]# ip netns ls
2770 # 创建一对veth pair,vetha 和 vethb
[root@localhost ~]# ip link add vetha type veth peer name vethb# 将 vetha 绑定到网桥
[root@localhost ~]# brctl addif br-99d0bed960d8 vetha# 启用 vetha
[root@localhost ~]# ip link set vetha up# vethb 放入指定的网络命名空间
[root@localhost ~]# ip link set vethb netns 2770# 在指定的命名空间中将 vethb 改名为 eth0
[root@localhost ~]# nsenter -t 2770 -n ip link set dev vethb name eth0# 启用 eth0
[root@localhost ~]# nsenter -t 2770 -n ip link set eth0 up# 为 eth0 添加 IP 地址
[root@localhost ~]# nsenter -t 2770 -n ip addr add 172.100.0.2/16 dev eth0# 添加默认网关
[root@localhost ~]# nsenter -t 2770 -n ip route add default via 172.100.0.1(6)验证通信
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope host valid_lft forever preferred_lft forever
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue qlen 1000link/ether 72:6e:83:12:6f:28 brd ff:ff:ff:ff:ff:ffinet 172.100.0.2/16 scope global eth0valid_lft forever preferred_lft foreverinet6 fe80::706e:83ff:fe12:6f28/64 scope link valid_lft forever preferred_lft forever
/ # ping www.baidu.com
PING www.baidu.com (110.242.70.57): 56 data bytes
64 bytes from 110.242.70.57: seq=0 ttl=127 time=2.211 ms
64 bytes from 110.242.70.57: seq=1 ttl=127 time=2.187 ms
64 bytes from 110.242.70.57: seq=2 ttl=127 time=1.655 ms
64 bytes from 110.242.70.57: seq=3 ttl=127 time=2.814 ms
^C
--- www.baidu.com ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 1.655/2.216/2.814 ms
补充:
# 容器内
/ # ps -ef
PID USER TIME COMMAND1 root 0:00 sh16 root 0:00 ps -ef#宿主机
[root@localhost ~]# ps -ef | grep 2770
root 2770 2749 0 20:14 pts/0 00:00:00 sh
root 3052 2796 0 20:52 pts/1 00:00:00 grep --color=auto 2770
注:2770只是宿主机中的一个普通进程,但在容器中就是1号进程,一旦宿主机中杀死2770进程,容器就会立刻关闭
