参考书籍 :
《Docker技术入门与实战》第三版 杨保华 戴王剑 曹亚仑 著
一. 初识Docker与容器
1. 什么是Docker
①Docker开源项目背景
②Linux容器技术
③从Linux容器到Docker
2. 为什么要 使用Docker
①Docker容器虚拟化的好处
②Docker在开发和运维中的优势
③Docker与虚拟机比较
3. Docker与虚拟化
二. 核心概念与安装配置
1. 核心概念
Docker镜像:类似于虚拟机镜像,是一个只读的模板(类)。一个镜像包含一个基本的OS环境,里面仅安装了需要的应用程序。镜像是创建Docker容器(对象)的基础。
Docker容器:Docker容器类似于一个轻量级沙箱,Docker利用容器来运行和隔离应用 。容器是从镜像创建的应用允许实例,各个容器都是隔离、相互不可见的。可以把容器看成一个简易版的Linux环境+允许在其中的应用程序打包而成的盒子。镜像自身是只读的,容器从镜像启动时,会在镜像的最上层创建一个可写层。
Docker仓库:
2. 安装Docker引擎
Docker引擎:提供简单安全弹性的容器集群编排和管理,分为社区版(CE)和企业版(EE)。
DockerHub:官方提供的云托管服务,可以提供公有或私有的镜像仓库。
DockerCloud:官方提供的容器云服务,可以完成容器的部署与管理,可以完整支持容器化项目。
①Ubuntu
设置 Docker 的
apt
存储库
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
安装 Docker 软件包:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
通过运行
hello-world
映像来验证安装是否成功:sudo docker run hello-world
3. 配置Docker服务
①将当前用户加入Docker用户组
为避免每次使用docker命令时都需要切换到特权身份,可以将当前用户加入安装中自动创建的docker用户组:
sudo usermod -aG docker USER_NAME(当前用户名)
。用户更新组信息组,退出重新登录后即可生效。
②Docker配置文件
Docker启动时实际上是调用了dockerd命令,因此用户可以直接通过dockerd命令来启动Docker服务: 且dockerd的命令选项可在
/etc/docker/daemon.json
文件中配置,由dockerd服务启动时读取。OS也对Docker服务进行了封装,对于由systemd来管理的OS,配置文件路径为:
/etc/systemd/system/docker.service.d/docker.conf
。更新后需要通过systemctl命令管理Docker服务 :system$ systemctl daemon-reload
systemctl start docker.service
日志查看:
system$ journalctl -u docker.service
Docker信息查看(重启服务后):docker info
③配置镜像源
修改Docker守护进程配置文件以指定国内镜像源:
sudo vim /etc/docker/daemon.json
在文件中添加以下内容(推荐多镜像源组合):
{
"registry-mirrors": [
"https://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn",
"https://mirror.baidubce.com",
"https://ccr.ccs.tencentyun.com"
]
}
重启Docker服务:
sudo systemctl daemon-reload
sudo systemctl restart docker
验证配置是否生效:
docker info | grep "Registry Mirrors" -A 10
若输出中包含配置的镜像地址(如https://hub-mirror.c.163.com),则说明配置成功。
三. 使用Docker镜像
Docker允许容器前需要本地存在对应镜像,如果镜像不存在,Docker会尝试先从默认镜像仓库下载(Docker Hub公共注册服务器中的仓库),也可以自定义配置,使用自定义的镜像仓库。
1. 获取镜像
docker [image] pull NAME[:TAG]
获取镜像:NAME是镜像仓库名称(用来区分镜像),TAG是镜像的标签(表示版本信息,没有则默认选择latest标签,下载仓库中最新版本的镜像)。通常描述一个镜像需要包括"名称+标签"信息。从上面下载情况可以看出,镜像文件一般由若干层组成,上面的字符串是层的唯一id(实际上完整的id包括256bit,64个十六进制字符串组成)。使用docker pull命令下载中会获取并输出各层信息,当不同镜像包含相同层时,本地仅存储了层的一份内容。
严格讲,镜像仓库名称中还应该添加仓库地址(即注册服务器)作为前缀,默认使用官方Docker Hub服务,该前缀可以忽略。若从非官方的仓库下载,则需要在仓库名称前指定完整的仓库地址。
官方:
docker pull ubuntu
等价于docker pull registry.hub.docker.com/ubuntu
非官方:
docker pull hub.c.163.com/public/ubuntu
子选项命令:
-a,--all-tags=true|false
是否获取仓库中的所有镜像,默认为否。--disable-content-trust
取消镜像的内容校验,默认为真。
2. 查看镜像信息
①使用images命令列出镜像
docker images
docker image ls
列出本地主机上已有镜像的基本信息。
images子命令:
-a, --all=true|false
列出所有(包括临时文件)镜像文件,默认为否。--digests=true|false
列出镜像的数字摘要值,默认为否。-f,--filter=[ ]
过滤列出的镜像,如dangling=true只显示没有被使用的镜像;也可以指定带有特定标注的镜像等。--format="TEMPLATE"
控制输出格式,如.ID代表ID信息,.Repository代表仓库信息。--no-trunc=true|false
对输出结果中太长的部分是否进行截断,默认是。-q,--quiet=true|false
仅输出ID信息,默认为否。
②使用tag命令添加镜像标签
docker tag 旧仓库名:旧标签 新仓库名:新标签
新旧的镜像Id一样,指向同一个文件,只是别名不同。
③使用inspect命令查看详细信息
docker [image] inspect 仓库名:标签
获取该镜像的详细信息。子命令:
-f
获取某项内容:docker inspect hello-world -f {{".RepoTags"}}
获取该镜像的RepoTags信息
④使用history命令查看镜像历史
docker history 仓库名:标签
查看某个镜像,各层的创建信息。
3. 搜寻镜像
docker search [option] keyword
搜索keyword镜像信息。主要命令:
-f,--filter filter
过滤输出内容。--format string
格式化输出内容。--limit int
限制输出结果个数,默认25。--no-trunc
不截断输出结果。
4. 删除和清理镜像
①使用标签|id删除镜像
docker rmi IMAGE [IMAGE...]
docker image rm IMAGE [IMAGE...]
IMAGE可以是标签或id。跟标签:当同一个镜像有多个标签时,该命令只是删除指定的标签,只剩一个标签时,则会删除这个镜像文件的所有文件层。
跟id:会尝试删除所有指向该镜像的标签,然后删除镜像文件本身。
当有该镜像创建的容器存在时,镜像文件默认是无法被删除的。
命令参数:
-f,-force
强制删除镜像,即使有容器依赖它。-no-prune
不要清理未带标签的父镜像。
②清理镜像
docker image prune
清理临时镜像。命令参数:
-a,-all
删除所有无用镜像,不仅是临时镜像。-filter filter
只清理符合给定过滤器的镜像。-f,-force
强制删除镜像,而不进行提示确认。
5. 创建镜像
①基于已有容器创建
docker [container] commit [OPTIONS] [REPOSITORY[:TAG]]
命令选项:
-a,--author=""
作者信息。-c,--change=[ ]
提交的时候执行Dockerfile指令,包括CMD|ENTRYPOINT|ENV|EXPOSE|LABEL|ONBUILD|USER|VOLUME|WORKDIR等。-m,--message=""
提交消息。-p,--pause=true
提交时暂停容器运行。
②基于本地模板导入
docker [image] import [OPTIONS] file|URL| - [REPOSITORY[:TAG]]
③基于Dockerfile创建
Dockerfile创建是一个文本文件,利用给定的指令描述基于某个父镜像创建新镜像的过程。
6. 存出和载入镜像
①存出镜像
docker [image] save
导出镜像到本地文件。docker save -o ubuntu_18.04.tar ubuntu:18.04
导出本地的ubuntu:18.04镜像为文件ubuntu_18.04.tar
命令选项:
-o、-output string
导出镜像到指定的文件中。
②载入镜像
docker [image] load
将导出的tar文件再导入到本地镜像库。docker load -i ubuntu_18.04.tar
docker load < ubuntu_18.04.tar
从文件ubuntu_18.04.tar导入镜像到本地镜像列表。
命令选项:
-i、-input string
从指定文件中读入镜像内容。
7. 上传镜像
docker [image] push NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG]
默认上传到Docker Hub官方仓库(需要登录)。
四. 操作Docker容器
容器是镜像的一个运行实例。
镜像是静态的只读文件,容器带有运行时需要大的可写文件层,同时容器中的应用进程处于运行状态。
虚拟机是模拟运行的一整套操作系统和其上面的应用;容器是独立运行的一个(或一组)应用,以及它们必需的运行环境。
1. 创建容器
①新建容器
docker [container] create NAME[:TAG]|imagesID
创建一个容器,但不启动。
②启动容器
docker [container] start 容器nam|id
③新建并启动容器
docker [container] run 容器Name|id
该命令后台运行的标准操作:检查本地是否存在指定的镜像,不存在则从公有仓库下载;利用镜像创建一个容器,并启动该容器;分配一个文件系统给容器,并在只读的镜像层外挂在一层可读写层;从宿主机配置之的网桥接口中桥接一个虚拟接口到容器中去;从网桥的地址池配置一个IP给容器;执行用户指定的应用程序;执行完毕后容器自动终止。
docker run -it name /bin/bash
-t让docker分配一个伪终端并绑定到容器的标准输入上,-i则让容器的标准输入保持打开;在交互模式下,用户可以通过所创建的终端来输入命令;可以Ctrl+d或输入exit来退出容器。错误码:该命令无法正常执行:125:Docker daemon执行出错(如指定了不支持的参数);126:命令无法执行(如权限出错);127:命令无法找到。
④守护运行
-d参数
⑤查看容器输出
docker [container] logs 容器NAME|id
-details:打印详细信息
-f,-follow:持续保持输出
-since string:输出从某个时间开始的日志
-tail string:输出最近的若干日志
-t,-timestamps:显示时间戳信息
-util string:输出某个时间之前的日志
2. 停止容器
①暂停容器
docker [container] pause container [container...]
暂停一个运行中的容器。docker [container] unpause container [container...]
使暂停的容器运行
②终止容器
docker [container] stop [-t|--time[=10]] [container...]
:该命令会首先向容器发送SIGTERM信号,等待一段超市时间后(默认10s),再发送SIGKILL信号来终止容器。docker container prune
自动清除所有处于停止状态的容器。docker [container] kill
直接发送SIGKILL信号来强行终止容器。docker [container] restart
先终止再运行当docker容器中指定的应用终结时,容器自动终止。
3. 进入容器
使用-d参数时,容器会进入后台,用户无法看到容器中的信息,也无法进行操作。这时可以通过使用attach或exec命令进入容器。
①attach命令
docker [container] attach [--detach-keys[=[ ]]] [--no-stdin] [--sig-proxy[=true]] container
②exec命令
该命令可以在运行中容器内直接执行任意命令。
4. 删除容器
docker [container] rm container [container...]
-f,--force=false:是否强行终止并删除一个运行中的容器。
-l,--link=false:删除容器的链接,但保留容器。
-v,--volumes=false:删除容器挂载的数据卷。
5. 导入和导出容器
需要将容器从一个系统迁移到另一个系统,此时可以使用导入和导出功能。
①导出容器
docker [container] export [-o|--output[=""]] container
导出一个已经创建的容器到一个文件,无论此时容器是否处于运行状态。-o指定导出的文件名,也可以通过重定向实现。docker export -o test.tar container
docker export container > test.tar
②导入容器
docker [container] import container
使导出的文件导入变成镜像。-c,--change=[ ]在导入的同时执行对容器修改的Dockerfile指令。
docker load和docker import:容器快照文件将丢失所有的历史纪录和元数据信息(仅保留容器当时的快照状态),而镜像存储文件将保存完整记录;从容器快照文件导入时可重新指定标签等元数据信息。
6. 查看容器
①查看容器详情
docker container inspect [OPTIONS] container [container...]
以json格式返回包括容器ID、创建时间、路径、状态、镜像、配置各项信息。
②查看容器内进程
docker [container] top [OPTIONS] container [container...]
类似于Linux中的top命令
③查看统计信息
docker [container] stats [OPTIONS] [container...]
显示CPU、内存、存储、网络等使用情况的信息。-a,-all:输出所有容器统计信息,默认仅在运行中。
-format string:格式化输出信息。
-no-stream:不持续输出,默认会自动更新持续实时结果。
-no-trunc:不截断输出信息。
7. 其他容器命令
①复制文件
②查看变更
docker [container] diff container
查看容器内文件系统的变更。
③查看端口映射
docker container port CONTAINER
查看容器端口映射情况。
④更新配置
五. 访问Docker仓库
仓库是集中存放镜像的地方,分为公有仓库和私有仓库。
一个注册服务器上可以有多个仓库,一个仓库下可以有多个镜像。
1. Docker Hub公共镜像市场
Docker Hub是Docker官方提供的最大的公共镜像仓库,地址为:
https://hub.docker.com/
①登录
可以使用
docker login
命令来输入用户名、密码和邮箱来完成注册和登录。注册成功后,本地用户目录下会自动创建.docker/config.json文件,保存用户的认证信息。登录成功的用户可以上传个人制作的镜像到Docker Hub。
②基本操作
用户无须登录就可通过
docker search
命令来查找官方仓库中的镜像,并利用docker [image] pull
命令来将它下载到本地。根据是否为官方提供,可将这些镜像资源分为两类:
一种是类似于centos这样的基础镜像,也称为根镜像。这些镜像是由Docker公司创建、验证、支持、提供,这样的镜像往往使用单个单词作为名字。
另一种类型镜像,比如mg/centos-mg,是由Docker用户mg创建并维护的,带有用户名称为前缀,表明是某用户下的某仓库。可以通过用户名称前缀”user_name/镜像名“来指定某个用户提供的镜像。
③自动创建
自动创建是Docker Hub提供的自动化服务,这一功能可以自动跟随项目代码的变更而重新构建镜像。
例如:用户构建了某应用镜像,如果应用发布新版本,用户需要手动更新镜像。而自动创建允许用户通过Docker Hub指定跟踪一个目标网站上的项目,一旦项目发生新的提交,则执行自动创建。
配置自动创建的步骤:
创建并登录Docker Hub,以及目标网站日GitHub。
在目标网站中允许Docker Hub访问服务。
在Docker Hub中配置一个“自动创建”类型的项目。
选取一个目标网站中的项目(需要含Dockerfile)和分支。
指定Dockerfile位置,并提交创建。
2. 第三方镜像市场
腾讯云、阿里云等。
下载镜像也是docker pull但要在镜像名称前添加注册服务器的具体地址。格式:
注册服务器地址/<namespace>/<repository>:<tag>
3. 搭建本地私有仓库
①安装Docker Registry
基于容器安装运行
命令:
docker run -d -p 5000:5000 --restart=always --name registry registry:2
启动后,服务监听在本地的5000端口,可以通过访问
http://localhost:5000/v2/
测试启动成功。配置文件:默认路径为
/etc/docker/registry/config.yml
,可通过-v
参数映射本地配置文件到容器内:docker run -d -p 5000:5000 --restart=always --name registry -v /home/user/registry-conf/config.yml:/etc/docker/registry/config.yml registry:2
存储路径:默认路径为
/var/lib/registry
,可通过-v
映射本地目录到容器内:docker run -d -p 5000:5000 --restart=always --name registry -v /opt/data/registry:/var/lib/registry registry:2
将镜像数据持久化到本地目录 /opt/data/registry。
本地安装运行
有时需要本地运行仓库服务,可以通过源码方式进行安装。
首先安装Golang环境支持,以Ubuntu为例,可以执行如下命令:
sudo add-apt-repository ppa:ubuntu-lxc/lxd-stable
sudo apt-get update
sudo apt-get install golang
确认Golang环境安装成功,并配置 $GOPATH 环境变量(例如 /go)。
创建 $GOPATH/src/github.com/docker/ 目录,并获取源码:
mkdir -p $GOPATH/src/github.com/docker/
cd $GOPATH/src/github.com/docker/
git clone https://github.com/docker/distribution.git
cd distribution
将模板配置文件复制到 /etc/docker/registry/ 路径下,创建存储目录 /var/lib/registry:
cp cmd/registry/config-dev.yml /etc/docker/registry/config.yml
mkdir -p /var/lib/registry
执行安装操作:
make PREFIX=/go clean binaries
编译成功后启动服务:
registry serve /etc/docker/registry/config.yml
此时使用访问本地的5000端口,看到返回信息为200 OK说明启动成功。
②配置TLS证书
当本地主机运行 Registry 服务后,所有能访问到该主机的 Docker Host 均可将其作为私有仓库使用。操作时需在镜像名称前添加具体的服务器地址。
私有仓库必须启用 TLS 认证,否则操作会报错。可在Docker配置中添加非安全注册表,临时解决:
DOCKER_OPTS="--insecure-registry myrepo.com:5000"
自行生成证书:
通过 OpenSSL 工具可快速生成私人证书文件:
mkdir -p certs
openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/myrepo.key -x509 -days 365 -out certs/myrepo.crt
从代理商申请证书:
若Registry服务需要对外公开部署,必须申请受广泛信任的权威证书;知名证书代理商包括SSLs.com、GoDaddy.com、LetsEncrypt.org、GlobalSign.com等,用户可根据实际需求自行选择合规的证书提供商。
启用证书:
启用 HTTPS 证书的 Docker Registry 完整启动命令:
docker run -d \
--restart=always \
--name registry \
-v `pwd`/certs:/certs \ # 将宿主机证书目录挂载到容器
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \ # 强制监听 443 端口(HTTPS 默认端口)
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/myrepo.crt \ # 证书路径
-e REGISTRY_HTTP_TLS_KEY=/certs/myrepo.key \ # 私钥路径
-p 443:443 \ # 将宿主机的 443 端口映射到容器
registry:2
③管理访问权限
Docker Registry v2的认证模式:
配置Nginx代理:
我们让Registry服务监听在127.0.0.1:5000,这意味着只允许本机才能通过5000端口访问到,其他主机是无法访问到的。为了让其他主机访问到,可以通过Nginx监听在对外地址的15000端口,当外部访问请求到达15000端口时,内部再将请求转发到本地的5000端口。
添加用户认证:
用Compose启动Registry:
④配置Registry
Docker Registry 利用提供了一些样例配置,用户可以直接使用它们进行开发或生产部署。
示例配置:
# ========== Docker Registry 配置文件 (完整版) ==========
# === 基础配置 ===
version: 0.1 # 配置版本标识符
# === 日志配置 ===
log:
level: debug # 日志级别: debug/info/warn/error
formatter: text # 字符串类型,日志输出的格式(text/json/logstash)
fields: # 增加到日志输出消息中的键值对,可以用于过滤日志
service: registry # 服务名称
environment: development # 环境标识 (development/staging/production)
# 告警钩子配置,配置仓库发生异常时,通过邮件发送日志时的参数
hooks:
- type: mail
disabled: true # 邮件通知功能禁用
levels:
- panic # 只在系统崩溃级别错误触发
options:
smtp:
addr: mail.example.com:25 # SMTP服务器地址
username: mailuser # SMTP用户名
password: password # SMTP密码
insecure: true # 禁用SSL/TLS验证
from: sender@example.com # 发件人邮箱
to:
- errors@example.com # 收件人邮箱列表
storage: # 配置存储引擎,默认支持本地文件系统、云存储、分布式存储
# 删除功能设置
delete:
# 是否启用镜像删除功能:true=启用,false=禁用
enabled: true # 开启后需配合权限系统使用
# 缓存机制配置
cache:
# 指定使用Redis存储镜像层描述符(blob metadata)
blobdescriptor: redis # 要求已配置redis服务
# 文件系统设置
filesystem:
# 仓库镜像数据的永久存储路径
rootdirectory: /var/lib/registry # 建议挂载大容量磁盘
# 系统维护配置
maintenance:
# 上传中断处理设置
uploadpurging:
# 是否自动清理中断的上传任务:false=禁用自动清理
enabled: false # 禁用后可避免误删大型文件
# === HTTP与网络配置 (来自图片3) ===
http:
addr: :5000 # 服务监听地址和端口
debug:
addr: localhost:5001 # 调试接口地址
headers:
X-Content-Type-Options: [nosniff] # 安全头设置,防止MIME嗅探攻击
# === Redis缓存配置 (来自图片3) ===
redis:
addr: localhost:6379 # Redis服务器地址
pool:
maxidle: 16 # 最大空闲连接数
maxactive: 64 # 最大活跃连接数
idletimeout: 300s # 空闲连接超时时间(秒)
dialtimeout: 10ms # 连接建立超时时间(毫秒)
readtimeout: 10ms # 读取操作超时时间(毫秒)
writetimeout: 10ms # 写入操作超时时间(毫秒)
# === 通知系统配置 (来自图片4) ===
notifications:
endpoints:
# 第一个通知端点 (禁用状态)
- name: local-5003
url: http://localhost:5003/callback # 回调URL
headers:
Authorization: [Bearer <an example token>] # 认证头(Bearer令牌)
timeout: 1s # 请求超时时间
threshold: 10 # 失败阈值
backoff: 1s # 失败重试间隔
disabled: true # 端点禁用
# 第二个通知端点 (简单配置)
- name: local-8083
url: http://localhost:8083/callback # 回调URL
# === 健康检查配置 (来自图片5) ===
# 全局重试策略
timeout: 1s # 全局操作超时
threshold: 10 # 全局失败阈值
backoff: 1s # 全局重试间隔
disabled: true # 全局禁用标志(谨慎使用)
# 存储驱动健康监控
health:
storagedriver:
enabled: true # 启用存储驱动健康检查
interval: 10s # 检查间隔
threshold: 3 # 连续失败次数阈值
选项:
⑤批量管理镜像(如何利用脚本实现对镜像的批量化处理)
批量上传指定镜像:推送本地的ubuntu:latest和centos:centos7两个镜像到本地仓库:
./push_images.sh ubuntu:latest centos:centos7
#!/bin/sh
# 脚本名称: push_images.sh
# 功能说明: 批量上传本地镜像到指定注册服务器(Registry)
# 默认服务器: 127.0.0.1:5000(本地Registry)
# 用法说明: ./push_images.sh 镜像名称1 [镜像名称2...]
# 作者: yeasy@github
# 创建时间: 2014-09-23
# 参考资源: https://github.com/yeasy/docker_practice
# ============== 重要提示(用户可修改区域) ==============
# 目标注册服务器地址配置(可修改为需要的地址)
registry=127.0.0.1:5000
# 例如修改为私有仓库: registry=myrepo.com:5000
# ===================================================
### 注意:以下为脚本核心逻辑,如无必要请勿修改 ###
# --------------- 颜色输出函数定义 ---------------
# 红色错误提示
echo_r () {
[ $# -ne 1 ] && return 0 # 确保传入1个参数
echo -e "\033[31m$1\033[0m" # \033[31m 红色开始,\033[0m 颜色结束
}
# 绿色成功提示
echo_g () {
[ $# -ne 1 ] && return 0
echo -e "\033[32m$1\033[0m" # \033[32m 绿色
}
# 黄色警告提示
echo_y () {
[ $# -ne 1 ] && return 0
echo -e "\033[33m$1\033[0m" # \033[33m 黄色
}
# 蓝色信息提示
echo_b () {
[ $# -ne 1 ] && return 0
echo -e "\033[34m$1\033[0m" # \033[34m 蓝色
}
# --------------- 使用说明函数 ---------------
usage() {
docker images # 显示所有本地镜像
echo "正确用法: $0 镜像名称1:标签1 [镜像名称2:标签2...]"
}
# --------------- 脚本主体逻辑 ---------------
# 参数检查:如果没有提供镜像名称则显示用法并退出
[ $# -lt 1 ] && usage && exit 1
# 显示目标服务器信息
echo_b "目标注册服务器地址: $registry"
# 循环处理所有输入参数(即镜像列表)
for image in "$@"
do
# 步骤1: 为镜像添加目标服务器标签
echo_b "正在处理镜像: $image"
# 为镜像打上目标仓库标签(格式:registry/镜像名)
docker tag $image $registry/$image
# 步骤2: 推送镜像到目标服务器
docker push $registry/$image
# 步骤3: (可选) 删除本地临时标签
# 注意:此项操作会删除临时创建的标签,不影响原镜像
docker rmi $registry/$image
# 完成提示
echo_g "镜像 $image 推送完成!"
done
# 最终完成提示
echo_g "\n所有镜像已成功上传至 $registry !"
上传本地所有镜像:
#!/bin/sh
# ============== 脚本头部声明与功能说明 ==============
# 脚本名称: push_all.sh
# 核心功能: 自动扫描并上传本地所有Docker镜像到指定Registry服务器
# 依赖脚本: push_images.sh (需提前准备)
# 脚本作者: yeasy@github
# 创建日期: 2014-09-23
# 更新日志:
# [2023] 增加详细注释说明
# 技术参考: https://github.com/yeasy/docker_practice
# ============== 配置区 ==============
# 设置默认注册服务器地址(可根据需要修改)
target_registry="127.0.0.1:5000"
# 有效值示例:
# 私有仓库: myregistry.com:5000
# AWS ECR: xxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com
# ============== 核心函数: 镜像处理逻辑 ==============
# 遍历并推送所有符合条件的本地镜像
for image in $(docker images \
| grep -v "REPOSITORY" \ # 排除表头行
| grep -v "<none>" \ # 排除<none>标签的中间镜像
| awk '{print $1":"$2}') # 提取镜像名:标签格式
do
# 执行镜像推送操作
echo "🔹 正在处理镜像: $image"
# 调用推送脚本 (实际推送操作)
push_images.sh "$image"
# 状态提示
echo "✅ 镜像 $image 推送至 [$target_registry] 完成"
done
# ============== 脚本结束信息 ==============
echo "🎉 所有有效镜像已成功推送到注册服务器: $target_registry"
⑥使用通知系统
六. Docker数据管理
在生产环境中使用docker,需要对数据进行持久化,或者需要在多个容器间进行数据共享,这必然涉及容器的数据管理操作。
容器的管理数据主要有两种方式:
数据卷:容器内数据直接映射到本地主机环境。
数据卷容器:使用特定容器维护数据卷。
1. 数据卷
数据卷:是一个可提供容器使用的特殊目录,它将主机操作系统目录直接映射进容器,类似Linux中的mount行为。
特性:
数据卷可以在容器之间共享和重用,容器之间传递数据更高效与方便。
对数据卷内数据的修改会立马生效,无论是容器内还是本地操作。
对数据卷的更新不会影响镜像,解耦应用和数据。
卷会一直存在,直到没有容器使用,可以安全卸载它。
匿名卷:-v /容器内路径;不会自动删除,需手动清理
docker volume prune (批量清理)
具名卷:-v 卷名:/容器内路径;
docker volume rm 卷名
①创建数据卷
docker volume create -d local test
创建一个本地数据卷test,在/var/lib/docker/volumes路径下。docker volume inspect
查看详细信息。docker volume ls
列出已有数据卷。docker volume prune
清除无用数据卷。docker volume rm
删除数据卷。
②绑定数据卷
除了使用volume子命令来管理数据卷外,还可以在创建容器时将主机本地的任意路径挂载到容器内作为数据卷,这种形式创建的数据卷称为绑定数据卷。
2. 数据卷容器
若需要在多个容器间共享一些持续更新的数据,最简单方式就是使用数据卷容器。数据卷容器也是一个容器,但是其目的是专门提供数据卷给其他容器挂载。
docker run -it -v /dbdata --name dbdata ubuntu
-v /dbdata
在容器内创建名为 /dbdata 的匿名卷;--name dbdata为容器指定名称 dbdata;ubuntu使用官方 Ubuntu 镜像作为容器运行环境。可以使用
--volumes-from
来挂载dbdata中的数据卷:docker run -it --volumes-from dbdata --name db1 ubuntu
docker run -it --volumes-from dbdata --name db2 ubuntu
。此时容器db1和db2都挂载同一个数据卷到相同的/dbdata目录,三个容器任何一方在该目录下的写入,其他容器都可以看到。可以多次使用
--volumes-from
参数来从多个容器挂载多个数据卷,还可以从其他以及挂载了容器卷的容器来挂载数据卷:docker run -d --name db3 --volumes-from db1 training/postgres
使用
--volumes-from
参数所挂载(所有)数据卷的容器自身并不需要保持在运行状态。若删除了挂载的容器(dbdata、db1、db2),数据卷不会自动删除。若要删除一个数据卷,必须在删除最后一个还挂载他的容器时显式使用docker rm -v命令指定同时删除关联的容器。
3. 利用数据卷容器迁移数据
七. 端口映射与容器互联
Docker除了通过网络访问外,还提供两个功能满足服务访问的基本需求:
一是允许映射容器内应用的服务端口到本地宿主主机;
二是互联机制实现多个容器间通过容器名快速访问。
1. 端口映射实现容器访问
①从外部访问容器应用
在启动容器的时候,如果不指定对应参数,容器外部是无法通过网络来访问容器内的网络应用和服务。当容器中运行一些网络应用,要让外部访问这些应用时,可以通过-P或-p参数来指定端口映射。当使用-P(大写的)标记时,Docker会随机映射一个49000~49900的端口到内部容器开放的网络端口:
docker run -d -P training/webapp python app.py
可以使用docker ps看到,本地主机的49155被映射到了容器的5000端口。访问宿主主机的49155端口即可访问容器内web应用提供的界面。-p (小写的)则可以指定要映射的端口,在一个指定端口上只可以绑定一个容器。支持的格式有
IP:HostPort:ContainerPort | IP::ContainerPort | HostPort:ContainerPort
②映射所有接口地址
使用
HostPort:ContainerPort
格式可将本机端口映射到容器内部端口。docker run -d -p 5000:5000 training/webapp python app.py
将本地5000端口映射到容器的5000端口,此操作默认会绑定本地所有网络接口(0.0.0.0)上的所有地址,可多次使用 -p 标记实现多端口绑定。可附加协议类型:-p 5000:5000/tcp 或 -p 3000:80/udp
③映射到指定地址的指定端口
可以使用
IP:HostPort:ContainerPort
格式指定映射使用一个特定地址:即只有这个地址+映射端口才能访问容器。docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
将 localhost(127.0.0.1)的 5000 端口映射到 容器内的 5000 端口,仅允许宿主机本机访问该服务。
④映射到指定地址的任意端口
使用
IP::ContainerPort
绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。docker run -d -p 127.0.0.1::5000 training/webapp python app.py
⑤查看映射端口配置
docker port <容器名或ID> [容器内部端口]
查看容器内部端口与宿主机端口的映射绑定状态。docker port nostalgic_morse 5000
指定5000端口映射状态。容器有自己的内部网络和IP地址,使用
docker [container] inspect + 容器ID
可以获取容器的具体信息。
2. 互联机制实现便捷互访
容器的互联是一种让多个容器中的应用进行快速交互的方式。它会在源和接受容器间创建连接关系,接受容器可以通过容器名快速访问到源容器,而不用指定具体的IP地址,且不用将容器端口和宿主机绑定,以免暴漏端口到外网。
①自定义容器命名(容器名唯一)
连接系统依据容器的名称来执行。 因此,首先需要自定义一个好记的容器命名。虽然当创建容器的时候,系统默认会分配一个名字,但自定义命名容器有两个好处:
自定义的命名,比较好记。
当要连接其他容器时候(即便重启),也可以使用容器名而不用改变。
docker run -d -P --name web training/webapp python app.py
使用 --name 标记自定义容器名。在执行 docker [container] run 的时候如果添加 --rm 标记,则容器在终止后会立刻删除。注意,--rm 和 -d 参数不能同时使用。
②容器互联
使用 --link 参数可以让容器之间安全地进行交互。
--link 参数的格式为 --link name:alias
:name:要链接的目标容器名称(必须是已存在容器);alias:在当前容器中使用的服务别名(用于服务发现);单向通信,除非双向连接。创建数据库容器:
docker run -d --name db training/postgres
建立互联的web容器:
docker run -d -P --name web --link db:db training/webapp python app.py
查看:docker ps可以看到,db的名字列为db,web/db:这表示web容器将被允许访问db容器的信息。
Docker通过两种方式为容器公开连接信息:
环境变量注入:接收容器(web)会获得 db 容器的环境变量
/etc/hosts 更新:在 web 容器中自动添加主机记录
八. 使用Dockerfile创建镜像
Dockerfile是一个文本格式的配置文件,用户可以使用其快速创建自定义的镜像。
1. 基本结构
Dockerfile由一行行命令语句组成,并且支持以#开头的注释行。
一般,Dockerfile主体内容分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
2. 指令说明
Dockerfile中指令的一般格式为INSTRUCTION arguments,包括“配置指令”(配置镜像信息)和“操作指令”(具体执行操作)。
3. 创建镜像
编写完
Dockerfile
后,通过docker [image] build
创建镜像。基本格式:
docker build [OPTIONS] PATH | URL | -
执行流程:
读取指定路径(含子目录)下的
Dockerfile
;将该路径下所有数据作为上下文(Context)发送给Docker服务端;
服务端校验Dockerfile格式后逐条执行指令;
遇到
ADD
、COPY
、RUN
指令时生成新的镜像层;构建成功时返回最终镜像ID。
上下文过大会导致数据传输缓慢;非必需文件勿放入上下文路径。
-f
:指定非上下文路径的Dockerfile
(如docker build -f /path/to/Dockerfile .
);-t
:多次使用可添加多个镜像标签(如-t name:v1 -t name:latest
)。示例:上下文路径:/tmp/docker_builder/;镜像标签:builder/first_image:1.0.0:
docker build -t builder/first_image:1.0.0 /tmp/docker_builder/
①命令选项
②选择父镜像
Docker 父镜像核心说明:生成新镜像必须通过 FROM 指定父镜像,其选择直接影响生成镜像的大小与功能。
③使用.dockerignore文件
④多步骤创建
4. 最佳实践
掌握指令基础:要尽量吃透每个指令的含义和执行效果,多编写一些简单的例子进行测试,弄清楚了再撰写正式的Dockerfile。
学习优秀案例:Docker Hub官方仓库中提供了大量的优秀镜像和对应的Dockerfile,可以通过阅读它们来学习如何撰写高效的Dockerfile。
实践经验总结:
精简镜像用途:尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像;
选用合适的基础镜像:选择过大的父镜像(如Ubuntu系统镜像)会造成最终生成应用镜像的臃肿;推荐选用瘦身过的应用镜像(如
node:slim
),或者较为小巧的系统镜像(如alpine
、busybox
或debian
)。提供注释和维护者信息:Dockerfile也是一种代码,需要考虑方便后续的扩展和他人的使用;
正确使用版本号:使用明确的版本号信息(如
1.0
、2.0
),而非依赖于默认的latest
;通过版本号避免环境不一致导致的问题;减少镜像层数:合并
RUN
、ADD
和COPY
指令;多个RUN
指令可合并为一条RUN
指令;恰当使用多步骤创建(17.05+版本支持):将编译和运行过程分开,保证最终镜像只包含最小化运行环境;避免维护多个Dockerfile(传统方式需分别构造编译镜像和运行镜像)。
使用.dockerignore文件:标记
docker build
时忽略的路径和文件;避免发送不必要数据,加快镜像创建过程;及时删除临时文件和缓存文件:如执行
apt-get
后删除/var/cache/apt
下的安装包缓存;提高生成速度:合理使用cache,减少内容目录下的文件;或用
.dockerignore
指定忽略项;调整合理的指令顺序:在开启cache的情况下,内容不变的指令尽量放在前面以复用;
减少外部源的干扰:若需从外部引入数据,必须指定持久的地址并附带版本信息(确保可复用且无错误)。
九. 核心实现技术
1. 基本架构
Docker 目前采用了标准的 C/S 架构,包括客户端、服务端两大核心组件,同时通过镜像仓库来存储镜像。客户端和服务端既可以运行在一个机器上,也可通过 Socket 或者 RESTful API 来进行通信。
①服务端
Docker服务端一般在宿主主机后台运行,dockerd作为服务端接受来自客户的请求,并通过 containerd 具体处理与容器相关的请求,包括创建、运行、删除容器等。服务端主要包括四个组件:
dockerd:为客户端提供RESTful API,响应来自客户端的请求,采用模块化的架构,通过专门的Engine模块来分发管理各个来自客户端的任务。可以单独升级;
docker-proxy:是dockerd的子进程,当需要进行容器端口映射时,docker-proxy完成网络映射配置;
containerd:是dockerd的子进程,提供gRPC接口响应来自dockerd的请求,对下管理runC镜像和容器环境。可以单独升级;
containerd-shim:是containerd的子进程,为runC容器提供支持,同时作为容器内进程的根进程。
runC是从Docker公司开源的libcontainer项目演化而来的,目前作为一种具体的开放容器标准实现加入Open Containers Initiative (OCI)。runC已经支持了Linux系统中容器相关技术栈,同时正在实现对其他操作系统的兼容。用户也可以通过使用docker-runc命令来直接使用OCI规范的容器。
dockerd默认监听本地的
unix:///var/run/docker.sock
套接字,只允许本地的root用户或docker用户组成员访问。可以通过-H选项来修改监听的方式。例如,让dockerd监听本地的TCP连接1234端口,代码如下:sudo dockerd -H 127.0.0.1:1234
此外,Docker还支持通过TLS认证方式来验证访问。
②客户端
Docker客户端为用户提供一系列可执行命令,使用这些命令可实现与Docker服务端交互。
用户使用的Docker可执行命令即为客户端程序。与Docker服务端保持运行方式不同,客户端发送命令后,等待服务端返回;一旦收到返回后,客户端立刻执行结束并退出。用户执行新的命令,需要再次调用客户端命令。
客户端默认通过本地的 unix:///var/run/docker.sock 套接字向服务端发送命令。如果服务端没有监听在默认的地址,则需要客户端在执行命令的时候显式地指定服务端地址。例如,假定服务端监听在本地的TCP连接1234端口为 tcp://127.0.0.1:1234,只有通过 -H 参数指定了正确的地址信息才能连接到服务端。
③镜像仓库
镜像是使用容器的基础,Docker使用镜像仓库(Registry)在大规模场景下存储和分发Docker镜像。镜像仓库提供了对不同存储后端的支持,存放镜像文件,并且支持RESTful API,接收来自dockerd的命令,包括拉取、上传镜像等。
用户从镜像仓库拉取的镜像文件会存储在本地使用;用户同时也可以上传镜像到仓库,方便其他人获取。
2. 命名空间
命名空间(namespace)是Linux内核的一个强大特性,为容器虚拟化的实现带来极大便利。利用这一特性,每个容器都可以拥有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统环境中一样。命名空间机制保证了容器之间彼此互不影响。
在操作系统中,包括内核、文件系统、网络、进程号(Process ID,PID)、用户号(User ID,UID)、进程间通信(InterProcess Communication,IPC)等资源,所有的资源都是应用进程直接共享的。要想实现虚拟化,除了要实现对内存、CPU、网络IO、硬盘IO、存储空间等的限制外,还要实现文件系统、网络、PID、UID、IPC等的相互隔离。前者相对容易实现一些,后者则需要宿主主机系统的深入支持。
随着Linux系统对于命名空间功能的逐步完善,现在已经可以实现这些需求,让进程在彼此隔离的命名空间中运行。虽然这些进程仍在共用同一个内核和某些运行时环境(runtime,例如一些系统命令和系统库),但是彼此是不可见的,并且认为自己是独占系统的。
Docker容器每次启动时候,通过调用
func setNamespaces(daemon Daemon, s specs.Spec, c *container.Container) error
方法来完成对各个命名空间的配置。
①进程的命名空间
Linux进程基础(前置知识)
Linux进程命名空间(核心机制)
PID映射表:同一进程在不同命名空间有不同PID;宿主空间全局PID:唯一标识;容器空间局部PID:独立计数。
层级关系:子空间对父空间可见(父可管理子进程);父空间对子空间不可见(隔离关键)。
进程创建规则:
fork()
的新进程在父/子命名空间获得不同PID。
Docker如何运用进程命名空间
关键机制深度解析
/proc
/proc 是 Linux 系统中的动态虚拟文件系统(procfs),不占用磁盘空间,而是实时映射内核数据结构与硬件状态。它的核心作用是为用户态程序提供访问内核运行时数据的标准化接口。以下是深度解析:
②IPC命名空间
容器中的进程交互还是采用了 Linux 常见的进程间交互方法(Interprocess Communication, IPC),包括信号量、消息队列和共享内存等方式。PID 命名空间和 IPC 命名空间可以组合起来一起使用,同一个 IPC 命名空间内的进程可以彼此可见,允许进行交互;不同空间的进程则无法交互。
③网络命名空间
有了进程命名空间后,不同命名空间中的进程号可以相互隔离,但是网络端口还是共享本地系统的端口
通过网络命名空间,可以实现网络隔离。一个网络命名空间为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口、IPv4和IPv6协议栈、IP路由表、防火墙规则、sockets等,这样每个容器的网络就能隔离开来。
Docker采用虚拟网络设备(Virtual Network Device,VND)的方式,将不同命名空间的网络设备连接到一起。默认情况下,Docker在宿主机上创建多个虚机网桥(如默认的网桥docker0),容器中的虚拟网卡通过网桥进行连接。使用
docker network ls
命令可以查看到当前系统中的网桥。使用brctl工具(需要安装bridge-utils工具包),还可以看到连接到网桥上的虚拟网口的信息。每个容器默认分配一个网桥上的虚拟网口,并将docker0的IP地址设置为默认的网关,容器发起的网络流量通过宿主机的iptables规则进行转发。
④挂载命名空间
类似于chroot,挂载(Mount,MNT)命名空间可以将一个进程的根文件系统限制到一个特定的目录下。
挂载命名空间允许不同命名空间的进程看到的本地文件位于宿主机中不同路径下,每个命名空间中的进程所看到的文件目录彼此是隔离的。例如,不同命名空间中的进程,都认为自己独占了一个完整的根文件系统(rootfs),但实际上,不同命名空间中的文件彼此隔离,不会造成相互影响,同时也无法影响宿主机文件系统中的其他路径。
⑤UTS命名空间
UTS(UNIX Time-sharing System)命名空间允许每个容器拥有独立的主机名和域名,从而可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。
如果没有手动指定主机名称,Docker容器的主机名就是返回的容器ID的前6字节前缀,否则为指定的用户名。
通过
docker run --hostname my-container
指定。
⑥用户命名空间
每个容器可以有不同的用户和组id,也就是说,可以在容器内使用特定的内部用户执行程序,而非本地系统上存在的用户。
每个容器内部都可以有最高权限的root帐号,但跟宿主主机不在一个命名空间。通过使用隔离的用户命名空间,可以提高安全性,避免容器内的进程获取到额外的权限;同时,使用不同用户也可以进一步在容器内控制权限。
3. 控制组
控制组(CGroups)是Linux内核的一个特性,主要用来对共享资源进行隔离、限制、审计等。只有将分配到容器的资源进行控制,才能避免多个容器同时运行时对宿主机系统的资源竞争。每个控制组是一组对资源的限制,支持层级化结构。
控制组技术可以提供对容器的内存、CPU、磁盘IO等资源进行限制和计费管理。
功能:
资源限制(resource limiting):内存子系统可以为进程组设定一个内存使用上限,一旦进程组使用的内存达到限额再申请内存,就会触发 Out of Memory 警告。
优先级(prioritization):通过优先级让一些组优先得到更多的 CPU 等资源。
资源审计(accounting):使用
cpuacct
子系统记录 某个进程组使用的 CPU 时间,统计资源实际使用情况。隔离(isolation):为组隔离命名空间,使得一个组不会看到另一个组的:进程、网络连接、文件系统。
控制(control):执行 挂起(suspend)、恢复(resume) 和 重启(restart) 等操作。
Docker容器每次启动时候,通过调用func setCapabilities(s specs.Spec, c container.Container) error方法来完成对各个命名空间的配置。安装Docker后,用户可以在/sys/fs/cgroup/memory/docker/目录下看到对Docker组应用的各种限制项,包括全局限制和位于子目录中对于某个容器的单独限制。
用户可以通过修改这些文件值来控制组,从而限制Docker应用资源。例如,通过下面的命令可限制Docker组中的所有进程使用的物理内存总量不超过100MB:
sudo echo 104857600 > /sys/fs/cgroup/memory/docker/memory.limit_in_bytes
进入对应的容器文件夹,可以看到对应容器的限制和目前的使用状态。同时,可以在创建或启动容器时为每个容器指定资源的限制,例如使用-c|--cpu-shares[=0]参数可调整容器使用CPU的权重;使用-m|--memory[=MEMORY]参数可调整容器最多使用内存的大小。
4. 联合文件系统
联合文件系统(UnionFS)是一种轻量级的高性能分层文件系统,它支持将文件系统中的修改信息作为一次提交,并层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,应用看到的是挂载的最终结果。联合文件系统是实现Docker镜像的技术基础。
Docker镜像可以通过分层来进行继承。例如,用户基于基础镜像(用来生成其他镜像的基础,往往没有父镜像)来制作各种不同的应用镜像。这些镜像共享同一个基础镜像层,提高了存储效率。此外,当用户改变了一个Docker镜像(比如升级程序到新的版本),则会创建一个新的层(layer)。因此,用户不用替换整个原镜像或者重新建立,只需要添加新层即可。用户分发镜像的时候,也只需要分发被改动的新层内容(增量部分)。这让Docker的镜像管理变得十分轻量和快速。
①Docker存储原理
Docker通过模块化架构支持多种联合文件系统后端,其中AUFS(Advanced multi layered Unification File System)是Debian/Ubuntu系统的成熟解决方案。
AUFS核心特性:目录权限控制:为每个成员目录设定:只读、读写、写出;分层增量修改:对只读分支进行逻辑修改时创建新层(原始数据不受影响)。
Docker 镜像自身就是由多个文件层组成,每一层有基于内容的唯一的编号(层 ID)。可以通过 docker history 查看一个镜像由哪些层组成。
容器内所有镜像层均为只读状态,容器启动时会在镜像顶部挂载独立可读写层;任何数据修改仅发生于该可读写层,若操作深层文件则触发写时复制(CoW)机制——需将目标文件从镜像层完整复制至可读写层再进行修改,此过程导致显著的IO性能损耗;故高频修改场景(如数据库)必须采用Volume挂载,以绕过容器文件系统的复制开销。
②Docker存储结构
Docker镜像与容器的所有数据存储于宿主机指定目录(如Ubuntu默认为/var/lib/docker),该目录包含builder、containerd、aufs等管理子目录;若使用AUFS存储后端,其aufs目录关键结构为:
layers/:存储镜像层元数据(记录层级依赖关系,如5层镜像的组成逻辑),Docker 1.10+版本后层ID与文件名解耦。
diff/:保存所有镜像层原始内容数据(每层文件变更的实际存储位置)。
mnt/:作为容器最终挂载点,将AUFS多层文件系统合并为单一视图(运行中容器的根文件系统即挂载于此),1.10版本前子目录名与容器ID一致且存放元数据、配置文件及运行日志等。
5. Linux网络虚拟化
Docker 容器的网络能力本质是 Linux 网络命名空间与虚拟网卡协同实现的:网络命名空间为每个容器创建隔离的网络协议栈(含独立 IP、路由表及防火墙规则),而虚拟网卡对(veth pair)的跨命名空间连接——一端在容器内作为 eth0,另一端挂载到宿主机的网桥(如 docker0)——打通了容器与宿主机间的数据传输通道,这种基础能力支撑了桥接模式、主机模式等所有 Docker 网络方案的底层通信。
①基本原理
直观上看,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)与外界相通,并具备数据包收发能力。若涉及不同子网间的通信,则需额外路由机制的支持。
Docker的网络接口默认采用虚拟化实现,其最大优势在于转发效率极高。Linux内核通过在内部直接复制数据包完成虚拟接口间的数据传输——发送接口缓存中的数据包无需经过外部物理网络设备交换,即可被复制到接收接口的缓存中。
对本地系统和容器内系统而言,虚拟接口的功能与物理以太网卡完全一致,但速度显著更快。
Docker容器网络充分利用了Linux虚拟网络技术:在本地主机和容器内各创建一个虚拟接口(veth),并通过互联构成veth pair(虚拟接口对)。
②网络创建过程
Docker 创建一个容器时执行的操作:
创建一对虚拟接口,分别放置在本地主机和新容器的命名空间中;
本地主机端的虚拟接口连接到默认的 docker0 网桥或指定网桥上,并分配以 veth 开头的唯一名称(例如 veth1234);
容器端的虚拟接口放入新容器中,重命名为 eth0(此接口仅在该容器的命名空间内可见);
从网桥可用地址段中分配空闲地址给容器的 eth0(例如 172.17.0.2/16),并配置默认路由网关为 docker0 网卡的内部接口 IP(例如 172.17.42.1/16)。
完成上述步骤后,容器可通过 eth0 虚拟网卡与其他容器通信并访问外部网络。
用户可通过
docker network
命令手动管理网络,具体操作将在后续章节介绍。
使用
docker [container] run
启动容器时,可通过--net
指定网络配置,可选值如下:--net=bridge
(默认值):在docker0
网桥上为容器创建新的网络栈;
--net=none
:将容器放入隔离的网络栈但不进行任何网络配置,需用户手动配置;--net=container:NAME_or_ID
:将新容器加入指定容器的网络栈,共享 IP 地址、端口等网络资源(两容器进程可通过lo
环回接口直接通信);--net=host
:容器直接使用主机网络栈(不隔离命名空间),拥有完整的主机接口访问权限。此模式可能允许容器操作主机网络(如打开低端口、重启主机),配合 --privileged=true 时甚至可直接配置主机网络栈,需谨慎使用。--net=user_defined_network
:用户通过docker network
命令自定义网络,之后将容器连接到指定网络。
手动配置网络:
启动隔离网络容器:当使用
--net=none
参数时,Docker 不提供任何网络配置:docker run -i -t --rm --net=none ubuntu:16.04 /bin/bash
创建网络命名空间:在本地主机查找容器的进程id, 并为其创建网络命名空间(获取容器PID并建立命名空间链接):
确认桥接网段信息:查询 docker0 的子网配置(以实际输出为准):
创建 veth pair 接口:创建一对veth pair接口A和B,绑定A接口到网桥docker0,并启用它:
将B接口放到容器的网络命名空间,命名为eth0,启动并配置一个可用IP(桥接网段)和默认网关:
当容器终止后,Docker会清空容器环境,容器内的网络接口会随其所属的网络命名空间一同被清除;同时,A接口(veth的A端)会自动从docker0网桥卸载并销毁。在手动删除/var/run/netns/目录下的命名空间链接文件前,用户可通过ip netns exec命令在指定网络命名空间中执行配置操作,实时更新容器内的网络参数。
十. 安全防护与配置
1. 命名空间隔离的安全
Docker 容器与 LXC 容器在实现原理和安全特性上高度相似。当通过
docker [container] run
启动容器时,系统会在后台为每个容器创建独立的命名空间(Namespace),这是最基础且直接的隔离层。此机制确保容器内运行的进程不会被主机系统或其他容器中的进程通过常规渠道发现或影响。典型示例如网络栈隔离——每个容器都拥有独享的网络栈,无法直接访问其他容器的网络资源。从网络架构视角看,容器默认与本地主机网络连通。当主机系统配置交换设置后,容器可像主机进程一样与其他容器交互。通过指定公共端口(如
-p 8080:80
)或使用连接系统(--link
),容器能实现相互通信,并可基于策略限制通信范围。所有容器实际上通过主机的 docker0 网桥接口进行通信,其工作原理类似物理设备通过物理交换机互联。Linux 内核自 2008 年 7 月发布的 2.6.15 版本正式集成命名空间机制,经多年演化已应用于大型生产系统。该技术理念可追溯至更早的 OpenVZ 项目——早在 2005 年,OpenVZ 就已实现成熟度更高的命名空间设计,成为容器隔离的核心技术基础。
相较于虚拟机,命名空间提供的隔离并非绝对:容器内应用可直接访问系统内核和部分关键文件,因此用户必须确保容器内应用安全可信(相当于保证主机系统软件安全性)。为应对此挑战,Docker 自 1.3.0 版本起引入镜像签名系统,通过验证镜像完整性和来源可靠性,构建了针对潜在威胁的关键防护层。生产环境中需配合镜像签名验证(
docker trust
)和内核级安全模块(如 Seccomp/AppArmor)实现纵深防御。
2. 控制组资源控制的安全
控制组(cgroups)作为 Linux 容器机制的核心组件,专职资源审计与限制。其技术原型最早于 2006 年提出,并于 2008 年随 Linux 2.6.24 内核正式集成。当执行
docker run
启动容器时,Docker 自动通过内核调用创建独立的控制组策略集合,形成容器资源消耗的精确约束框架。控制组通过双重能力保障系统稳定性:确保所有容器平等共享内存、CPU、磁盘 I/O 等关键资源,消除资源争夺冲突;严格限制单个容器的资源占用峰值,有效阻断因单容器过载引发的主机崩溃或跨容器性能劣化。此机制构成防御 DDoS 攻击的核心屏障——通过压制恶意容器的资源蚕食行为,保护基础设施韧性。
当个别容器因应用缺陷或攻击行为异常时,立即实施资源隔离,确保本地系统持续运行,从技术根本上规避多节点连锁故障风险,维护服务平台的高可用性。
3. 内核能力机制
Linux 能力机制(Capability)是内核的核心安全特性,实现了细粒度权限访问控制(相较于传统 Unix 的 root/非 root 二元模型)。自 Linux 2.2 版本起,该机制将权限拆解为原子级操作能力(如绑定低端口的
net_bind_service
),可精准赋权给进程或文件。这种设计彻底解决了特权膨胀问题——例如 Web 服务进程只需特定能力而非完整 root 权限,显著缩小攻击面。
4. Docker服务端的防护
Docker 服务端需 root 权限运行,使其成为关键安全节点。必须严格确保仅可信用户可访问服务端,原因在于:Docker 允许主机与容器间无限制的文件共享,恶意用户可通过映射主机根目录(如
-v /:/host
)使容器获得主机文件系统任意修改权,本质上绕过了资源隔离机制。此风险普遍存在于虚拟化系统,非Docker独有。当提供容器创建服务(如Web接口)时,必须强化参数安全检查,阻断利用特定参数创建破坏性容器的攻击路径。为加固服务端防护体系,Docker 实施了两大通信层改进:REST API 安全升级(客户端服务端通信接口):自 0.5.2 版本起废弃易受跨站脚本攻击的 TCP 套接字(绑定 127.0.0.1),采用 Unix 套接字机制,依赖文件权限实现访问控制。如需保留 HTTP 访问:应强制限定可信网络或 VPN 环境访问,启用证书保护机制(如 TLS/SSL 认证),配合
dockerd
的 TLS 参数实现双向验证。Linux 命名空间的新近改进与 Docker 自身发展正推动安全范式变革:新命名空间机制允许非 root 用户运行全功能容器,从根本上解决共享文件系统引发的提权风险。Docker 正在实现两大架构级防护:将容器内的 root 用户映射到主机的非 root 用户,阻断权限提升链条;服务端以非 root 权限运行,通过受限子进程代理执行特定特权操作(如网络配置/文件系统管理),实现最小权限原则。
5. 更多安全特性的使用
在 Docker 默认能力机制(Capability)基础上,可通过整合现有安全框架实现深度防护:
①内核级安全加固与外部机制集成
内核强化方案:启用
GRSEC
与PAX
内核补丁,提供编译/运行时检查增强及地址随机化防御能力,有效阻断恶意代码探测攻击。此方案无需 Docker 特殊配置,直接作用于系统层。容器模板扩展:AppArmor 模板提供进程行为管控策略链;RedHat SELinux 模板实现强制访问控制(MAC)。
自定义防护体系:用户可依据安全需求设计更严格的访问控制规则,实现策略级安全加固。
②文件系统防护策略与运行时权限约束
针对容器内文件操作风险,实施双层防护机制:
只读挂载关键目录:将
/proc/sys
、/proc/irq
、/proc/bus
等系统状态目录以只读模式(read-only
)挂载:允许容器获取系统信息;阻断对宿主机配置的篡改。用户权限最小化:强制应用容器以非特权用户身份运行(如专用应用账号);禁用非必要权限(包括 Shell 访问能力);故障场景下限制损害范围。
6. 使用第三方检测工具
之前介绍了大量增强Docker安全性的手段,要逐一去检查会比较繁琐。有一些进行自动化检查的开源工具,比较出名的有Docker Bench和clair,可以降低繁琐检查。
①Docker Bench
Docker Bench 是托管于 GitHub 的开源安全工具,其检查逻辑严格遵循 CIS Docker 1.13.0+ 安全规范。该规范从六大维度定义安全基线:主机配置策略(如内核参数调优)、Docker 引擎防护配置(API访问控制)、配置文件权限管理(敏感文件读写约束)、镜像可信验证机制(签名/漏洞扫描)、容器运行时环境隔离(能力限制/资源配额)、安全审计项合规(日志记录/事件监控)。
②clair
除了Docker Bench外,还有CoreOS团队推出的clair,它基于Go语言实现,支持对容器(支持appc和Docker)的文件层进行静态扫描发现潜在漏洞。项目地址为https://github.com/coreos/clair。读者可以使用Docker或Docker-compose方式快速进行体验。
十一. 高级网络功能
1. 启动与配置参数
①网络启动过程
Docker服务启动时会在主机上自动创建docker0虚拟网桥,这是一个Linux软件交换机。作为网络核心组件,网桥负责挂载其上的接口之间进行数据包转发,实现底层通信调度。
系统会随机选取RFC1918定义的本地私有网段中未被占用的地址分配给docker0接口,典型的如172.17.0.0/16网段(掩码255.255.0.0)。该网段将成为容器网络的地址池,后续所有容器内网口均自动从此网段获取地址。
创建容器时系统会生成一对veth pair互联接口(双向通信通道)。这对特殊接口中:容器内端命名为eth0,主机端以veth前缀命名(如vethAQI2QT)并挂载至docker0网桥。通过该架构实现:主机与容器直接通信,容器间点对点通信,最终构建出覆盖主机和所有容器的虚拟共享网络体系。
②网络相关参数
Docker 网络基础命令参数(服务级配置)
以下参数需在 Docker 服务启动时配置,修改后需重启生效:
-b BRIDGE
或--bridge=BRIDGE
:指定容器挂载的网桥
--bip=CIDR
:定制docker0
的掩码(如172.17.0.0/16
)-H SOCKET...
或--host=SOCKET...
:Docker 服务端命令接收通道--icc=true|false
:控制容器间通信是否允许--ip-forward=true|false
:启用net.ipv4.ip_forward
(网络包转发)--iptables=true|false
:禁止 Docker 自动添加 iptables 规则
--mtu=BYTES
:设置容器网络 MTU(最大传输单元)
运行时可覆盖的默认参数:
以下选项既可在 服务启动时设为默认值,也可在
docker [container] run
启动容器时覆盖:--dns=IP_ADDRESS
:指定 DNS 服务器--dns-opt=""
:配置 DNS 选项(需填入具体参数)
--dns-search=DOMAIN
:设定 DNS 搜索域
容器级专属配置参数:
以下参数 仅支持在
docker [container] run
命令中指定:-h HOSTNAME
或--hostname=HOSTNAME
:配置容器主机名--ip=""
:指定容器内接口 IP 地址(需填入具体 IP)--link=CONTAINER_NAME:ALIAS
:添加指向另一容器的连接--network-alias
:为容器设置网络别名-p SPEC
或--publish=SPEC
:映射容器端口到宿主机-P
或--publish-all=true|false
:自动映射容器所有端口到宿主机--net=bridge|none|container:NAME_or_ID|host|user_defined_network
支持五种网络模式:bridge
(默认):为容器创建独立网络命名空间,自动分配网卡/IP,通过veth
接口对挂载到虚拟网桥(如docker0
)。none
:创建独立网络命名空间,但不配置任何网络(无网卡/IP)。container:NAME_or_ID
:共享指定容器的网络命名空间,网络配置完全复用,其他资源(进程/文件系统)保持隔离。
host
:直接使用宿主机网络命名空间;容器内网络配置(网卡/路由表/iptables)与宿主机一致;其他资源仍与宿主机隔离。user_defined_network
:用户通过network
命令创建自定义网络;同网络内容器自动互通,支持扩展第三方网络插件。
2. 配置容器DNS和主机名
Docker服务启动后会默认启用一个内嵌的DNS服务,来自动解析同一个网络中的容器主机名和地址。如果无法解析,则通过容器内的DNS相关配置进行解析。用户可以通过命令选项自定义容器的主机名和DNS配置。
①相关配置文件
②容器内修改配置文件
容器运行时,可以在运行中的容器里直接编辑 /etc/hosts、/etc/hostname 和 /etc/resolv.conf 文件。
但是这些修改是临时的:仅在当前运行的容器中生效;容器终止或重启后修改内容不会被保存;不会被
docker commit
提交到新镜像。
③通过参数指定
自定义容器的配置,可以在创建或启动容器时利用下面的参数指定(注意一般不推荐与
-net=host
一起使用,会破坏宿主机上的配置信息):
-h HOSTNAME
或--hostname=HOSTNAME
:设定容器的主机名。容器主机名会被写入容器内的/etc/hostname
和/etc/hosts
文件。该主机名仅在容器内可见,不会在docker ps
中显示,不会出现在其他容器的/etc/hosts
中。--link=CONTAINER_NAME:ALIAS
:将目标容器的主机名记录到新建容器的/etc/hosts
文件中。新建容器可通过ALIAS
主机名直接与目标容器通信。--dns=IP_ADDRESS
:指定DNS服务器地址,写入容器的/etc/resolv.conf
。容器将使用该服务器解析所有不在/etc/hosts
中的主机名。--dns-option list
:自定义DNS相关选项(需传入选项列表)。--dns-search=DOMAIN
:设定DNS搜索域(例如.example.com
)。当搜索host
时,DNS会同时尝试host
和host.example.com
。
3. 容器访问控制
容器的访问控制主要通过Linux上的iptables防火墙软件来进行管理和实现。iptables是Linux系统流行的防火墙软件,在大部分发行版中都自带。
①容器访问外部网络
容器默认指定了网关为 docker0 网桥上的 docker0 内部接口。docker0 内部接口同时也是宿主机的一个本地接口。因此,容器默认情况下可以访问到宿主机本地网络。如果容器要想通过宿主机访问到外部网络,则需要宿主机进行辅助转发。
在宿主机 Linux 系统中,检查转发是否打开:
sudo sysctl net.ipv4.ip_forward
如果为 0,说明没有开启转发,则需要手动打开:sudo sysctl -w net.ipv4.ip_forward=1
Docker 服务启动时会默认开启
--ip-forward=true
,自动配置宿主机系统的转发规则。
②容器之间访问
容器互访的条件:
网络拓扑连通性:默认所有容器均接入
docker0
网桥,拓扑天然互通;通信路径:容器 →docker0
网桥 → 目标容器。本地系统防火墙规则(iptables)决定流量放行策略(是否允许)
4. 映射容器端口到宿主主机的实现
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
①容器访问外部实现
②外部访问容器实现
5. 配置容器网桥
Docker服务默认会创建一个名称为docker0的Linux网桥(其上有一个docker0内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。用户使用Docker创建多个自定义网络时可能会出现多个容器网桥。
Docker默认指定了docker0接口的IP地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了MTU(接口允许接收的最大传输单元),通常是1500B,或宿主主机网络路由上支持的默认值。这些值都可以在服务启动的时候进行配置:--bip=CIDR:IP地址加掩码格式,例如192.168.1.5/24;--mtu=BYTES:覆盖默认的Dockermtu配置。
也可以在配置文件中配置DOCKER_OPTS,然后重启服务。Docker网桥是Linux网桥,用户可以使用brctl show来查看网桥和端口连接信息。如果系统中没有自带brctl命令,可以使用
sudo apt-get install bridge-utils
命令来安装(Debian、Ubuntu系列系统)。每次创建一个新容器的时候,Docker从可用的地址段中选择一个空闲的IP地址分配给容器的eth0端口,并且使用本地主机上docker0接口的IP作为容器的默认网关。
Docker不支持在启动容器时候指定IP地址。
6. 自定义网桥
7. 使用OpenvSwitch网桥
Docker 默认使用的是 Linux 自带的网桥实现,可以替换为使用功能更强大的 OpenvSwitch 虚拟交换机实现。
①安装
安装 OpenvSwitch:
sudo aptitude install openvswitch-switch
创建测试网桥:
sudo ovs-vsctl add-br br0
查看网桥信息:
sudo ovs-vsctl show
②配置容器连接到OpenvSwitch网桥
目前OpenvSwitch网桥还不能直接支持挂载容器,需要手动在OpenvSwitch网桥上创建虚拟网口并挂载到容器中。操作方法如下。
创建无网口容器
启动容器时不创建网络(需手动添加),并启用特权模式(授予容器修改网络配置的权限):
docker run --net=none --privileged=true -it debian:stable bash
记录容器ID:
298bbb17c244
在容器内查看网络配置(仅见本地环回接口)
手动为容器添加网络
测试连通
8. 创建一个点到点连接
十二. libnetwork插件化网络功能
从1.7.0版本开始,Docker正式把网络与存储这两部分的功能实现都以插件化形式剥离出来,允许用户通过指令来选择不同的后端实现。剥离出来的独立容器网络项目即为libnetwork项目。Docker希望将来能为不同类型的容器定义统一规范的网络层标准,支持多种操作系统平台,这也是Docker希望构建强大容器生态系统的一些积极的尝试。
1. 容器网络模型
libnetwork中容器网络模型(Container Networking Model, CNM)十分简洁和抽象,可以让其上层使用网络功能的容器最大程度地忽略底层具体实现。
沙盒 (Sandbox):代表一个容器(准确地说,是其网络命名空间)。
接入点 (Endpoint):代表网络上可以挂载容器的接口,核心功能:分配 IP 地址。
网络 (Network):可连通多个接入点的子网。
对于使用CNM的容器管理系统来说,具体底下网络如何实现,不同子网彼此怎么隔离,有没有QoS,都不关心。只要插件能提供网络和接入点,只需把容器给接上或者拔下,剩下的都是插件驱动自己去实现,这样就解耦了容器和网络功能,十分灵活。
CNM的典型生命周期如下所示:首先,驱动注册自己到网络控制器,网络控制器使用驱动类型,来创建网络;然后在创建的网络上创建接口;最后把容器连接到接口上即可。销毁过程则正好相反,先把容器从接入口上卸载,然后删除接入口和网络即可。
libnetwork CNM 驱动类型说明:
Null 驱动:不提供网络服务,容器启动后无网络连接。
Bridge 驱动:Docker 传统默认方案,Linux 网桥 + Iptables,适用单机容器网络。
Overlay 驱动:用vxlan 隧道技术实现跨主机容器网络通信。
Remote 驱动:扩展接口类型,第三方SDN解决方案(如 OpenStack Neutron),其他外部网络实现方案。
2. Docker网络命令
在 libnetwork 支持下,Docker 网络相关操作都作为 network 的子命令出现。
①创建网络
create
命令用于创建一个新的容器网络。Docker 内置了bridge
(默认使用)和overlay
两种驱动,分别支持单主机和多主机场景。Docker 服务在启动后,会默认创建一个bridge
类型的网桥bridge
。不同网络之间默认相互隔离。创建网络命令格式:
docker network create [OPTIONS] NETWORK
参数
-attachable[=false]
支持手动容器挂载功能。-aux-address=map[]
定义辅助 IP 地址。-config-from=""
从指定网络复制配置数据。-config-only[=false]
启用仅可配置模式。-d, -driver="bridge"
网络驱动类型(如 bridge/overlay)。-gateway=[]
设置网关地址-ingress[=false]
创建 Swarm 路由网状网络(用于负载均衡),自动将服务请求转发至合适副本。-internal[=false]
内部模式(禁止外部访问创建的网络)。-ip-range=[]
指定容器 IP 分配范围-ipam-driver="default"
IP 地址管理插件类型。-ipam-opt=map[]
IP 地址管理插件选项-ipv6[=false]
启用 IPv6 地址支持-label value
为网络添加元数据标签-o, -opt=map[]
网络驱动支持的自定义选项-scope=""
指定网络作用域范围-subnet=[]
网络地址段(CIDR 格式,如 172.17.0.0/16)
②接入网络
connect
命令将一个容器连接到一个已存在的网络上。连接到网络上的容器可以与同一网络中其他容器互通,同一个容器可以同时接入多个网络,可在执行docker run
命令时通过--net
参数指定容器启动后自动接入的网络。命令格式:
docker network connect [OPTIONS] NETWORK CONTAINER
参数
--alias=[]
:为容器添加别名(此别名仅在所添加网络上可见)--ip=""
:指定容器 IP 地址(需确保不与已接入容器地址冲突)--ip6=""
:指定容器 IPv6 地址--link value
:添加链接到另一个容器--link-local-ip=[]
:为容器添加链接本地地址
③断开网络
disconnect
命令将一个连接到网络上的容器从网络上断开连接。命令格式:
docker network disconnect [OPTIONS] NETWORK CONTAINER
-f
,--force
:强制把容器从网络上移除
④查看网络信息
inspect
命令用于查看网络具体信息(JSON 格式),包括接入容器、网络配置等关键数据。命令格式:
docker network inspect [OPTIONS] NETWORK [NETWORK...]
参数
-f, -format=""
:给定 Golang 模板字符串对输出格式化,-f '{{.IPAM.Config}}' # 只查看地址配置。-v, -verbose[=false]
:输出调试信息(如网络驱动的内部状态)。
⑤列出网络
ls
命令用于列出所有已创建的 Docker 网络。命令格式:
docker network ls [OPTIONS]
参数
-f,--filter=""
:指定输出过滤器(如driver=bridge
只显示桥接网络)。--format=""
:使用 Golang 模板字符串格式化输出内容--no-trunc[=false]
:禁用截断,显示完整的网络信息-q,--quiet[=false]
:安静模式,仅输出网络 ID(适用于脚本处理)
⑥清理无用网络
prune 命令用于清理已经没有容器使用的网络。
命令格式:
docker network prune [OPTIONS]
参数
--filter=""
:指定选择过滤器-f, --force
:强制清理资源
⑦删除网络
rm
命令用于删除指定的网络。只有当网络上没有容器连接时,才能成功删除。命令格式:
docker network rm NETWORK [NETWORK...]
3. 构建跨主机容器网络
下面演示使用 libnetwork 自带的 Overlay 类型驱动来轻松实现跨主机的网络通信。Overlay 驱动默认采用 VXLAN 协议,在 IP 地址可以互相访问的多个主机之间搭建隧道,让容器可以互相访问。
①配置网络信息管理数据库
物理网络中主机互联需交换机/路由器支持,核心功能包括:物理层连接设备、网络层管理功能。核心管理需求:主机位置定位、地址信息维护、路由表更新。
libnetwork 架构设计实现跨主机容器网络需构建轻量级网络管理平面,实现上面类似的功能:功能拓扑信息维护、实现形式:分布式键值数据库(Consul、Etcd、ZooKeeper等)。
Consul 部署示例
当前主机作为核心数据库节点
# 启动Consul服务容器
docker run -d \
-p "8500:8500" \ # 服务端口映射
-h "consul" \ # 容器主机名指定
progrium/consul \ # 官方镜像
-server -bootstrap # 单节点自举模式
# 返回容器ID
1ad6b71cfdf83e1925d960b7c13f40294b7d84618828792a84069aea2e52770d
②配置Docker主机
启动两台Docker主机n1和n2,分别安装好最新的Docker-engine(1.7.0+)。确保这两台主机之间可以通过IP地址互相访问,另外,都能访问到数据库节点的8500端口。
配置主机的Docker服务启动选项如下:
DOCKER_OPTS="$DOCKER_OPTS--cluster-store=consul://<CONSUL_NODE>:8500--cluster-advertise=eth0:2376"
重新启动Docker服务:
sudo service docker restart