docker
第1章 容器生态系统
容器生态系统
- 容器核心技术
- 容器平台技术
- 容器支持技术
容器核心技术
容器核心技术是指能够让 container 在 host os上运行起来的那些技术。
- 容器规范
- 容器 runtime
- 容器管理工具
- 容器定义工具
- registries
- 容器 OS
容器规范
目前 OCI 发布了两个规范:runtime spec 和 image format spec。
有了这两个规范,不同组织和厂商开发的容器能够在不同的 runtime 上运行。这样就保证了容器的可移植性和互操作性。
容器 runtime
runtime 是容器真正运行的地方。runtime 需要跟操作系统 kernel 紧密协作,为容器提供运行环境。
容器管理工具
容器管理工具对内与 runtime 交互,对外为用户提供 interface。
容器定义工具
容器定义工具允许用户定义容器的内容和属性,这样容器就能够被保存,共享和重建。
Registry
容器是通过 image 创建的,需要有一个仓库来统一存放 image,这个仓库就叫做 Registry。
容器 OS
容器 OS 是专门运行容器的操作系统。与常规 OS 相比,容器 OS 通常体积更小,启动更快。因为是为容器定制的 OS,通常它们运行容器的效率会更高。
容器平台技术
容器核心技术使得容器能够在单个 host 上运行。而容器平台技术能够让容器作为集群在分布式环境中运行。
- 容器编排引擎
- 容器管理平台
- 基于容器的PaaS
容器编排引擎
基于容器的应用一般会采用微服务架构。在这种架构下,应用被划分为不同的组件,并以服务的形式运行在各自的容器中,通过 API 对外提供服务。为了保证应用的高可用,每个组件都可能会运行多个相同的容器。这些容器会组成集群,集群中的容器会根据业务需要被动态地创建、迁移和销毁。
编排,通常包括容器管理、调度、集群定义和服务发现等。通过容器编排引擎,容器被有机的组合成微服务应用,实现业务需求。
容器管理平台
容器管理平台是架构在容器编排引擎之上的一个更为通用的平台。通常容器管理平台能够支持多种编排引擎,抽象了编排引擎的底层实现细节,为用户提供更方便的功能。
基于容器的 PaaS
基于容器的 PaaS 为微服务应用开发人员和公司提供了开发、部署和管理应用的平台。
容器支持技术
- 容器网络
- 服务发现
- 监控
- 数据管理
- 日志管理
- 安全性
容器网络
docker network 是 Docker 原生的网络解决方案。
服务发现
动态变化是微服务应用的一大特点。当负载增加时,集群会自动创建新的容器;负载减小,多余的容器会被销毁。
服务发现会保存容器集群中所有微服务最新的信息。
监控
docker ps/top/stats 是 Docker 原生的命令行监控工具。
数据管理
容器经常会在不同的 host 之间迁移,如何保证持久化数据也能够动态迁移,是 Flocker 这类数据管理工具提供的能力。
日志管理
日志为问题排查和事件管理提供了重要依据。
docker logs 是 Docker 原生的日志工具。而 logspout 对日志提供了路由功能,它可以收集不同容器的日志并转发给其他工具进行后处理。
安全性
OpenSCAP 能够对容器镜像进行扫描,发现潜在的漏洞。
安装docker
bash
# 安装所需软件
[root@localhost ~]# yum install -y yum-utils device-mapper-persistent-data lvm2 vim
# 添加新的yum仓库
[root@localhost ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@localhost ~]# yum makecache
Docker CE Stable - x86_64 294 kB/s | 66 kB 00:00
CentOS Stream 8 - BaseOS 32 kB/s | 3.9 kB 00:00
CentOS Stream 8 - AppStream 44 kB/s | 4.4 kB 00:00
Metadata cache created.
[root@localhost ~]# hostnamectl set-hostname docker
# allinone部署
[root@docker ~]# yum install -y docker-ce
# 验证安装
[root@docker ~]# docker --version
Docker version 26.1.3, build b72abbb
[root@docker ~]# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor >
Active: active (running) since Wed 2025-12-03 19:06:52 CST; 51s ago
...
配置镜像加速器(华为云)
bash
[root@docker ~]# vi /etc/docker/daemon.json
{
"registry-mirrors": [ "https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com" ]
}
#重启容器引擎
[root@docker ~]# systemctl restart docker
#确认配置结果
[root@docker ~]# docker info
...
Registry Mirrors:
`https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com/ <---看这
...
运行第一个容器
环境就绪,马上运行第一个容器,执行命令:
bash
[root@docker ~]# docker run hello-world
- 本地没有hello-wrold镜像,
- 从 Docker Hub 下载hello-world镜像。
- 启动hello-world容器。
清空实验环境:
bash
# 删除所有容器
[root@docker ~]# docker rm -f $(docker ps -aq)
# 删除镜像hello-world
[root@docker ~]# docker rmi -f hello-world
docker环境没有问题,关机拍摄快照
第2章 容器架构
docker介绍
什么是容器?
容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。
容器与虚拟机
容器由两部分组成:
- 应用程序本身
- 依赖:比如应用程序需要的库或其他软件
下图展示了二者的区别
| 容器 | 虚拟机 | |
|---|---|---|
| 启动速度 | 秒甚至毫秒启动 | 数秒至数十秒 |
| 系统内核 | 共享内核 | 不共享内核 |
| 实现技术 | 利用Linux内核技术Namespace/Cgroup等实现。 | 依赖虚拟化技术实现,由Hypervisor层实现对资源的隔离 |
| 隔离效果 | 进程级别的隔离 | 系统资源级别的隔离 |
| 资源消耗(性能) | 容器中的应用只是宿主机上的一个普通进程 | 使用虚拟化技术,就会有额外的资源消耗和占用 |
| 资源调用 (敏捷性) | 应用进程直接由宿主机OS管理 | 应用进程需经过Hypervisor的拦截和处理,才能调用系统资源 |
| 运行数量 | 一台服务器上能启动1000+容器 | 一台服务器上一般不超过100台虚拟机 |
| 应用 | DevOps、微服务等 | 用于硬件资源划分 |
| 镜像 | 分层镜像 | 非分层镜像 |
为什么需要容器?
容器使软件具备了超强的可移植能力
Docker介绍
Docker提供了各种容器管理工具 (如分发、版本、移植等),让用户无需关注底层的操作,可以更简单明了地管理和使用容器;其次,Docker引入分层文件系统构建和高效的镜像机制降低了迁移难度,极大地提升了用户体验。用户操作Docker容器就像操作应用自身一样简单。
简单地讲,可以将Docker容器理解为一种轻量级的沙盒。**每个容器内运行着一个应用,不同的容器相互隔离,容器之间也可以通过网络互相通信。**容器的创建和停止都十分快速,几乎跟创建和终止原生应用一致;另外,容器自身对系统资源的额外需求也十分有限,远远低于传统虚拟机。很多时候,甚至直接把容器当作应用本身也没有任何问题。
Docker核心概念
Docker的大部分操作都围绕着它的三大核心概念------镜像、容器和仓库而展开
Docker镜像
Docker镜像类似于虚拟机镜像,可以将它理解为一个只读的模板。
Docker容器
Docker容器类似于一个轻量级的沙箱,Docker利用容器来运行和隔离应用。容器是从镜像创建的应用运行实例。
Docker仓库
Docker仓库类似于代码仓库,它是Docker集中存放镜像文件的场所。仓库注册服务器存放着很多类镜像,每类镜像包括多个镜像文件,通过不同的标签(tag)来进行区分。
根据所存储的镜像公开分享与否,Docker仓库可以分为:
- 公开仓库
- 私有仓库
Docker架构详解
完整的Docker由以下几部分构成:
-
**守护进程:**Docker守护进程侦听Docker API请求并管理Docker对象,,如图像、容器、网络和卷。守护进程还可以与其他守护进程通信来管理Docker服务。
REST API: 主要与Docker Daemon进行交互,比如Docker Cli或者直接调用REST API;
-
客户端: 它是与Docker交互的主要方式通过命令行接口(CLI)客户端(docker命令),客户机将命令通过REST API发送给并执行其命令;
-
Register Repository 镜像仓库: Docker注册表存储Docker镜像,可以采用Docker Hub(公共注册仓库),或者采用企业内部自建的Harbor私有仓库;
-
Image 镜像: 映像是一个只读模板,带有创建Docker容器的指令。映像通常基于另一个映像,还需要进行一些额外的定制,你可以通过Docker Hub公共镜像仓库进行拉取对应的系统或者应用镜像;
-
Container 容器: 容器是映像的可运行实例。您可以使用Docker API或CLI创建、启动、停止、移动或删除容器。
-
Services : Docker引擎支持集群模式服务允许您跨多个Docker守护进程()扩展管理容器,服务允许您定义所需的状态,例如在任何给定时间必须可用的服务副本的数量。默认情况下,服务在所有工作节点之间进行负载平衡。
Docker 内部具体实现:
- 用户是使用Docker Client与Docker Daemon建立通信,并发送请求给后者。
- Docker Daemon作为Docker架构中的主体部分,首先提供Docker Server的功能使其可以接受Docker Client的请求。
- Docker Engine执行Docker内部的一系列工作,每一项工作都是以一个Job的形式的存在。
- Job的运行过程中,当需要容器镜像时,则从DockerRegistry中下载镜像,并通过镜像管理驱动Graph driver将下载镜像以Graph的形式存储。
- 当需要为Docker创建网络环境时,通过网络管理驱动Networkdriver创建并配置Docker容器网络环境。
- 当需要限制Docker容器运行资源或执行用户指令等操作时,则通过Exec driver来完成。
- Libcontainer是一项独立的容器管理包,Networkdriver以及Execdriver都是通过Libcontainer来实现具体对容器进行的操作。
Docker 客户端
最常用的 Docker 客户端是 docker 命令。通过 docker 可以方便地在 Host 上构建和运行容器。
shell
[root@docker ~]# docker
Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Common Commands:
run Create and run a new container from an image
exec Execute a command in a running container
ps List containers
build Build an image from a Dockerfile
pull Download an image from a registry
push Upload an image to a registry
images List images
login Log in to a registry
logout Log out from a registry
search Search Docker Hub for images
version Show the Docker version information
info Display system-wide information
...
Docker 服务器
Docker daemon 是服务器组件,以 Linux 后台服务的方式运行。
Docker 镜像
Docker 镜像是只读模板,通过它可以创建 Docker 容器。
镜像有多种生成方法:
- 从无到有开始创建镜像
- 下载并使用别人创建好的现成的镜像
- 在现有镜像上创建新的镜像
Docker 容器
Docker 容器就是 Docker 镜像的运行实例。
用户可以通过 CLI(docker)或是 API 启动、停止、移动或删除容器。
Registry
Registry 是存放 Docker 镜像的仓库,Registry 分私有和公有两种。
Docker Hub(https://hub.docker.com/) 是默认的 Registry,由 Docker 公司维护,上面有数以万计的镜像。
出于对速度或安全的考虑,也可以创建自己的私有 Registry。
docker pull 命令可以从 Registry 下载镜像。
docker run 命令则是先下载镜像(如果本地没有),然后再启动容器。
Docker组件如何协作
Docker 各个组件是如何协作的
- Docker 客户端执行
docker run命令。 - Docker daemon 发现本地没有镜像。
- daemon 从 Docker Hub 下载镜像。
- 下载完成,镜像被保存到本地。
- Docker daemon 启动容器。
shell
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 1b44b5a3e06a 3 months ago 10.1kB
[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b2a971708ef0 hello-world "/hello" 2 weeks ago Exited (0) 2 weeks ago strange_wiles
第3章 镜像
最小的镜像
镜像的内部结构
hello-world 是 Docker 官方提供的一个镜像,通常用来验证 Docker 是否安装成功。
shell
[root@docker ~]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
Digest: sha256:d4aaab6242e0cace87e2ec17a2ed3d779d18fbfd03042ea58f2995626396a274
Status: Image is up to date for hello-world:latest
docker.io/library/hello-world:latest
用 docker images 命令查看镜像的信息。
shell
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 1b44b5a3e06a 4 months ago 10.1kB
通过 docker run 运行。
shell
[root@docker ~]# docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
...
base镜像
rootfs
内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。
用户空间的文件系统是 rootfs,包含 /dev, /proc, /bin 等目录。
对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
base 镜像提供的是最小安装的 Linux 发行版。
支持运行多种 Linux OS
这里需要说明的是:
-
base 镜像只是在用户空间与发行版一致,kernel 版本与发型版是不同的。
bash#Host OS kernel 为 4.18.0 [root@docker ~]# uname -r 4.18.0-553.6.1.el8.x86_64启动一个ubuntu,ubuntu内核与host os(centos stream 8)不一致
bash[root@docker ~]# docker run -it ubuntu Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu 20043066d3d5: Pull complete Digest: sha256:c35e29c9450151419d9448b0fd75374fec4fff364a27f176fb458d472dfc9e54 Status: Downloaded newer image for ubuntu:latest root@d1232370012f:/# uname -r 4.18.0-553.6.1.el8.x86_64 -
容器只能使用 Host 的 kernel,并且不能修改。
镜像的分层结构
Docker 支持通过扩展现有镜像,创建新的镜像。
新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
最大的一个好处就是 - 共享资源。
可写的容器层
当容器启动时,一个新的可写层被加载到镜像的顶部。
这一层通常被称作"容器层","容器层"之下的都叫"镜像层"。
所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。
只有容器层是可写的,容器层下面的所有镜像层都是只读的。
对容器增删改差操作如下:
| 操作 | 具体执行 |
|---|---|
| 创建文件 | 新文件只能被添加在容器层中。 |
| 删除文件 | 依据容器分层结构由上往下依次查找。找到后,在容器层中记录该删除操作。 具体实现是,UnionFS会在容器层创建一个"whiteout"文件,将被删除的文件"遮挡"起来。 |
| 修改文件 | 依据容器分层结构由上往下依次查找。找到后,将镜像层中的数据复制到容器层进行修改,修改后的数据保存在容器层中。(copy-on-write) |
| 读取文件 | 依据容器分层结构由上往下依次查找。 |
容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
镜像可以被多个容器共享。
构建镜像
Docker 容器文件系统
- Docker 镜像代表了容器的文件系统里的内容,是容器的基础,镜像一般是通过 Dockerfile 生成的;
- Docker 的镜像是分层的,所有的镜像(除了基础镜像)都是在之前镜像的基础上加上自己这层的内容生成的;
- Docker 中每一层镜像的元数据都是存在 json 文件中的,除了静态的文件系统之外,还会包含动态的数据;
- Docker 镜像生产容器后会在此基础之上加入挂载点到安装Docker宿主机文件系统之中,并提供一个读写层(Read-Write Layer),所以容器进程的所有操作都在读写层进行;
Docker 提供了两种构建镜像的方法:
- docker commit 命令
- Dockerfile 构建文件
docker commit
docker commit 创建镜像过程包含三个步骤:
- 运行容器
- 修改容器
- 将容器保存为新的镜像
在 ubuntu base 镜像中安装 vim并保存为新镜像。
-
第一步, 运行容器
shell[root@docker ~]# docker run -it ubuntu root@b73222a7b64f:/#-it参数的作用是以交互模式进入容器,并打开终端。 -
安装 vim
确认 vim 没有安装。
shellroot@b73222a7b64f:/# vim bash: vim: command not found安装 vim。
shellroot@b73222a7b64f:/# apt-get update Get:1 http://archive.ubuntu.com/ubuntu noble InRelease [256 kB] Get:2 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB] Get:3 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB] ... root@b73222a7b64f:/# apt-get install -y vim root@b73222a7b64f:/# vim test1 root@b73222a7b64f:/# ls bin dev home lib64 mnt proc run srv test1 usr boot etc lib media opt root sbin sys tmp var -
保存为新镜像
打开一个新窗口中查看当前运行的容器。
bash
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b73222a7b64f ubuntu "/bin/bash" 42 minutes ago Up 42 minutes awesome_buck
上面查看结果的解释如下:
b73222a7b64f 是新创建容器的ID
awesome_buck 是 Docker 为的容器随机分配的名字。
执行 docker commit 命令将容器保存为镜像。
shell
[root@docker ~]# docker commit awesome_buck ubuntu-with-vim
sha256:06a6d7e1ef003ca09ba4bacfcbf5cade1d63c72b76f4f746e1042303ae549743
新镜像命名为 ubuntu-with-vim。
查看新镜像的属性。
bash
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim latest 06a6d7e1ef00 14 seconds ago 207MB
ubuntu latest c3a134f2ace4 2 months ago 78.1MB
hello-world latest 1b44b5a3e06a 4 months ago 10.1kB
Docker 并不建议用户通过这种方式构建镜像。原因如下:
- 这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。
- 无法对镜像进行审计,存在安全隐患。
Dockerfile构建镜像
Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。
Dockerfile内容基础知识:
- 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
- #表示注释
- 每条指令都会创建一个新的镜像层并对镜像进行提交
第一个 Dockerfile
bash
[root@docker ~]# pwd
/root
[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile .
[+] Building 146.7s (6/6) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 94B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:late 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/2] FROM docker.io/library/ubuntu:latest 0.0s
=> [2/2] RUN apt-get update && apt-get install -y vim 146.4s
=> exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:41948cc291fc416c721f6ef7592c483e29 0.0s
=> => naming to docker.io/library/ubuntu-with-vim-dockerfile 0.0s
① 当前目录为 /root。
② Dockerfile 准备就绪。
③ 运行 docker build 命令,-t 将新镜像命名为 ubuntu-with-vim-dockerfile,命令末尾的 . 指明 build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,也可以通过 -f 参数指定 Dockerfile 的位置。
④ 从这步开始就是镜像真正的构建过程。 首先 Docker 将 build context 中的所有文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。
Dockerfile 中的 ADD、COPY 等命令可以将 build context 中的文件添加到镜像。此例中,build context 为当前目录 /root,该目录下的所有文件和子目录都会被发送给 Docker daemon。
⑤ Step 1:执行 FROM,将 ubuntu 作为 base 镜像。
⑥ Step 2:执行 RUN,安装 vim
⑦ 镜像构建成功。
⑧ 镜像重命名为ubuntu-with-vim-dockerfile
通过 docker images 查看镜像信息。
shell
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim-dockerfile latest 41948cc291fc 22 seconds ago 207MB
ubuntu-with-vim latest 06a6d7e1ef00 4 minutes ago 207MB
ubuntu latest c3a134f2ace4 2 months ago 78.1MB
hello-world latest 1b44b5a3e06a 4 months ago 10.1kB
在上面的构建过程中,要特别注意指令 RUN 的执行过程。Docker 会在启动的临时容器中执行操作,并通过 commit 保存为新的镜像。
查看镜像分层结构
docker history 会显示镜像的构建历史,也就是 Dockerfile 的执行过程。
bash
[root@docker ~]# docker history ubuntu
IMAGE CREATED CREATED BY SIZE COMMENT
c3a134f2ace4 2 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 2 months ago /bin/sh -c #(nop) ADD file:ddf1aa62235de6657... 78.1MB
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.opencontainers.... 0B
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.opencontainers.... 0B
<missing> 2 months ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B
<missing> 2 months ago /bin/sh -c #(nop) ARG RELEASE 0B
镜像的缓存特性
Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。
在前面的 Dockerfile 中添加一点新内容,往镜像中复制一个文件:
bash
[root@docker ~]# pwd
/root
[root@docker ~]# ls
Dockerfile
[root@docker ~]# touch testfile
[root@docker ~]# ls
Dockerfile anaconda-ks.cfg testfile
[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
COPY testfile /
[root@docker ~]# docker build -t ubuntu-with-vim-dockerfile-2 .
[+] Building 0.1s (8/8) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 110B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:late 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/3] FROM docker.io/library/ubuntu:latest 0.0s
=> CACHED [2/3] RUN apt-get update && apt-get install -y vim 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 29B 0.0s
=> [3/3] COPY testfile / 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:43f3f711294f5e6a60881451602cd90f31 0.0s
=> => naming to docker.io/library/ubuntu-with-vim-dockerfile- 0.0s
① 确保 testfile 已存在。可以通过touch创建
② 重点在这里:之前已经运行过相同的 RUN 指令,这次直接使用缓存中的镜像层
③ 执行 COPY 指令。
其过程是启动临时容器,复制 testfile,提交新的镜像层,删除临时容器。
在 ubuntu-with-vi-dockerfile 镜像上直接添加一层就得到了新的镜像 ubuntu-with-vim-dockerfile-2。
Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。
调试Dockerfile
Dockerfile 构建镜像的过程:
- 从 base 镜像运行一个容器。
- 执行一条指令,对容器做修改。
- 执行类似 docker commit 的操作,生成一个新的镜像层。
- Docker 再基于刚刚提交的镜像运行一个新容器。
- 重复 2-4 步,直到 Dockerfile 中的所有指令执行完毕。
从这个过程可以看出,如果 Dockerfile 由于某种原因执行到某个指令失败了,也将能够得到前一个指令成功执行构建出的镜像,这对调试 Dockerfile 非常有帮助。可以运行最新的这个镜像定位指令失败的原因。
bash
[root@docker ~]# vim Dockerfile
FROM busybox
RUN touch tmpfile
RUN /bin/bash -c "echo continue to build..."
COPY testfile /
执行 docker build:
bash
[root@docker ~]# ls
Dockerfile anaconda-ks.cfg testfile
[root@docker ~]# docker build -t image-debug .
[+] Building 7.6s (7/8) docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 129B 0.0s
=> [internal] load metadata for docker.io/library/busybox:lat 5.1s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/4] FROM docker.io/library/busybox:latest@sha256:d80cd69 2.0s
=> => resolve docker.io/library/busybox:latest@sha256:d80cd69 0.0s
=> => sha256:d80cd694d3e9467884fcb94b8ca1e204 9.54kB / 9.54kB 0.0s
=> => sha256:870e815c3a50dd0f6b40efddb319c72c32c3 610B / 610B 0.0s
=> => sha256:08ef35a1c3f050afbbd64194ffd1b8d58786 459B / 459B 0.0s
=> => sha256:e59838ecfec5e79eb4371e9995ef86c8 2.21MB / 2.21MB 1.9s
=> => extracting sha256:e59838ecfec5e79eb4371e9995ef86c8000fe 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 27B 0.0s
=> [2/4] RUN touch tmpfile 0.2s
=> ERROR [3/4] RUN /bin/bash -c "echo continue to build..." 0.3s
------
> [3/4] RUN /bin/bash -c "echo continue to build...":
0.233 /bin/sh: /bin/bash: not found
------
Dockerfile:3
--------------------
1 | FROM busybox
2 | RUN touch tmpfile
3 | >>> RUN /bin/bash -c "echo continue to build..."
4 | COPY testfile /
5 |
--------------------
ERROR: failed to solve: process "/bin/sh -c /bin/bash -c \"echo continue to build...\"" did not complete successfully: exit code: 127
Dockerfile 在执行第三步 RUN 指令时失败。
手工执行 RUN 指令很容易定位失败的原因是 busybox 镜像中没有 bash,busybox中用的是sh。
bash
# 找出错误原因,修改错误
[root@docker ~]# cat Dockerfile
FROM busybox
RUN touch tmpfile
#将错误的/bin/bash修改为正确的/bin/sh
RUN /bin/sh -c "echo continue to builld..."
COPY testfile /
[root@docker ~]# docker build -t image-debug .
[+] Building 0.9s (9/9) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 127B 0.0s
=> [internal] load metadata for docker.io/library/busybox:lat 0.6s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/4] FROM docker.io/library/busybox:latest@sha256:d80cd69 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 27B 0.0s
=> CACHED [2/4] RUN touch tmpfile 0.0s
=> [3/4] RUN /bin/sh -c "echo continue to build..." 0.3s
=> [4/4] COPY testfile / 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:81b5ecdaf653ebca31cd72efe969f415a6 0.0s
=> => naming to docker.io/library/image-debug 0.0s
Dockerfile常用指令
FROM
指定 base 镜像。第一条必须是FROM
MAINTAINER
设置镜像的作者,可以是任意字符串。
COPY
将文件从 build context 复制到镜像。
COPY 支持两种形式:
- COPY src dest
- COPY ["src", "dest"]
注意:src 只能指定 build context 中的文件或目录。
ADD
与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。
ENV
设置环境变量,环境变量可被后面的指令使用。
EXPOSE
指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。
VOLUME
将文件或目录声明为 volume。
WORKDIR
为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。
RUN
在容器中运行指定的命令。
CMD
容器启动时运行指定的命令。
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。
ENTRYPOINT
设置容器启动时运行的命令。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。
RUN vs CMD vs ENTRYPOINT
RUN、CMD 和 ENTRYPOINT 这三个 Dockerfile 指令看上去很类似,很容易混淆。
- RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
- CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被
docker run后面跟的命令行参数替换。 - ENTRYPOINT 配置容器启动时运行的命令。
Shell 和 Exec 格式
可用两种方式指定 RUN、CMD 和 ENTRYPOINT 要运行的命令:Shell 格式和 Exec 格式。
dockerfile
RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"
当指令执行时,shell 格式底层会调用 /bin/sh -c 。
dockerfile
[root@docker ~]# vim Dockerfile
FROM busybox
ENV name wan
ENTRYPOINT echo "Hello, $name"
用上面的Dockerfile创建镜像dockerfile1用于测试
bash
[root@docker ~]# docker build -t dockerfile1 .
[+] Building 2.6s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 95B 0.0s
=> [internal] load metadata for docker.io/library/busybox:lat 2.5s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/busybox:latest@sha256: 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:5576aca4272d4704e9804b480e80fdbe99 0.0s
=> => naming to docker.io/library/dockerfile1 0.0s
执行 docker run dockerfile1:
bash
[root@docker ~]# docker run dockerfile1
Hello,wan
注意环境变量 name 已经被值 wan 替换。
下面来看 Exec 格式。
Exec
当指令执行时,会直接调用 ,不会被 shell 解析。
例如下面的 Dockerfile :
dockerfile
[root@docker ~]# vim Dockerfile
FROM busybox
ENV name wan
ENTRYPOINT ["/bin/echo", "Hello, $name"]
用上面的Dockerfile创建镜像dockerfile2用于测试
bash
[root@docker ~]# docker build -t dockerfile2 .
[+] Building 0.4s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 106B 0.0s
=> [internal] load metadata for docker.io/library/busybox:lat 0.4s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/busybox:latest@sha256: 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:7be5681f9308f09f41c7af059dbc20ab4b 0.0s
=> => naming to docker.io/library/dockerfile2 0.0s
执行 docker run dockerfile2:
bash
[root@docker ~]# docker run dockerfile2
hello,$name
注意环境变量"name"没有被替换。
如果希望使用环境变量,照如下修改
dockerfile
[root@docker ~]# vim Dockerfile
FROM busybox
ENV name wan
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
用上面的Dockerfile创建镜像dockerfile3用于测试
bash
[root@docker ~]# docker build -t dockerfile3 .
[+] Building 1.3s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 115B 0.0s
=> [internal] load metadata for docker.io/library/busybox:lat 1.2s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/busybox:latest@sha256: 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:496065921f68bbc5d91cd36411c5d17f29 0.0s
=> => naming to docker.io/library/dockerfile3 0.0s
执行 docker run dockerfile3:
bash
[root@docker ~]# docker run dockerfile3
Hello, wan
CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可以。
RUN
RUN 指令通常用于安装应用和软件包。
RUN 在当前镜像的顶部执行命令,并通过创建新的镜像层。Dockerfile 中常常包含多个 RUN 指令。
RUN 有两种格式:
- Shell 格式:RUN
- Exec 格式:RUN ["executable", "param1", "param2"]
下面的Dockerfile是使用 RUN 安装多个软件包
dockerfile
[root@docker ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
用上面的Dockerfile创建镜像dockerfile4用于测试
bash
[root@docker ~]# docker build -t dockerfile4 .
[+] Building 122.0s (6/6) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 135B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:late 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/2] FROM docker.io/library/ubuntu:latest 0.0s
=> [2/2] RUN apt-get update && apt-get install -y bzr cvs g 121.4s
=> exporting to image 0.6s
=> => exporting layers 0.6s
=> => writing image sha256:6426607321c49d9b4448fd3bb6a8d900e6 0.0s
=> => naming to docker.io/library/dockerfile4 0.0s
执行 docker run -it dockerfile4:
bash
[root@docker ~]# docker run -it dockerfile4
root@a00d389854c6:/# apt list install brz cvs git mercurial subversion
Listing... Done
brz/noble,now 3.3.5-6build2 amd64 [installed,automatic]
cvs/noble,now 2:1.12.13+real-30build1 amd64 [installed]
git/noble-updates,noble-security,now 1:2.43.0-1ubuntu7.3 amd64 [installed]
mercurial/noble-updates,now 6.7.2-1ubuntu2.2 amd64 [installed]
subversion/noble,now 1.14.3-1build4 amd64 [installed]
CMD
CMD 指令允许用户指定容器的默认执行的命令。
此命令会在容器启动且 docker run 没有指定其他命令时运行。
- 如果 docker run 指定了其他命令,CMD 指定的默认命令将被忽略。
- 如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 有效。
CMD 有三种格式:
- Exec 格式:CMD ["executable","param1","param2"] 这是 CMD 的推荐格式。
- CMD ["param1","param2"] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 Exec 格式。
- Shell 格式:CMD command param1 param2
Exec 和 Shell 格式前面已经介绍过了。
第二种格式 CMD ["param1","param2"] 要与 Exec 格式 的 ENTRYPOINT 指令配合使用,其用途是为 ENTRYPOINT 设置默认的参数。
下面看看 CMD 是如何工作的。Dockerfile 如下:
dockerfile
[root@docker ~]# vim Dockerfile
FROM busybox
CMD echo "Hello,world"
用上面的Dockerfile创建镜像dockerfile5用于测试
bash
[root@docker ~]# docker build -t dockerfile5 .
[+] Building 1.4s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 73B 0.0s
=> [internal] load metadata for docker.io/library/busybox:lat 1.4s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/busybox:latest@sha256: 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:d153164533cdba1b9abd1a715b77d7669e 0.0s
=> => naming to docker.io/library/dockerfile5 0.0s
运行容器docker run -it dockerfile5将输出:
bash
[root@docker ~]# docker run -it dockerfile5
Hello,world
但当后面加上一个命令,比如docker run -it dockerfile5 /bin/sh,CMD 会被忽略掉,命令 sh 将被执行:
bash
[root@docker ~]# docker run -it dockerfile5 /bin/sh
/ #
ENTRYPOINT
ENTRYPOINT 指令可让容器以应用程序或者服务的形式运行。
ENTRYPOINT 有两种格式:
- Exec 格式:ENTRYPOINT ["executable", "param1", "param2"] 这是 ENTRYPOINT 的推荐格式。
- Shell 格式:ENTRYPOINT command param1 param2
在为 ENTRYPOINT 选择格式时必须小心,因为这两种格式的效果差别很大。
Exec 格式
ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。
ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。
比如下面的 Dockerfile :
dockerfile
[root@docker ~]# vim Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
用上面的Dockerfile创建镜像dockerfile6用于测试
bash
[root@docker ~]# docker build -t dockerfile6 .
[+] Building 0.4s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 98B 0.0s
=> [internal] load metadata for docker.io/library/busybox:lat 0.4s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/busybox:latest@sha256: 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:24e440bbbfc720fd05894aa2d6cc95a0db 0.0s
=> => naming to docker.io/library/dockerfile6 0.0s
当容器通过 docker run -it dockerfile6 启动时,输出为:
bash
[root@docker ~]# docker run -it dockerfile6
Hello world
而如果通过 docker run -it dockerfile6 wan 启动,则输出为:
bash
[root@docker ~]# docker run -it dockerfile6 wan
Hello wan
Shell 格式
ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。
比如下面的 Dockerfile :
dockerfile
[root@docker ~]# vim Dockerfile
FROM busybox
ENTRYPOINT echo "Hello,"
CMD ["world"]
用上面的Dockerfile创建镜像dockerfile7用于测试
bash
[root@docker ~]# docker build -t dockerfile7 .
[+] Building 1.3s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 89B 0.0s
=> [internal] load metadata for docker.io/library/busybox:lat 1.2s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/1] FROM docker.io/library/busybox:latest@sha256: 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:882e09dcfa420f93156832cc1b3ab099d9 0.0s
=> => naming to docker.io/library/dockerfile7 0.0s
当容器通过 docker run -it dockerfile7 启动时,输出为:
bash
[root@docker ~]# docker run -it dockerfile7
Hello,
而如果通过 docker run -it dockerfile7 wan 启动,则输出为:
bash
[root@docker ~]# docker run -it dockerfile7 wan
Hello,
Shell 格式
最佳实践
- 使用 RUN 指令安装应用和软件包,构建镜像。
- CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。
- 如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。
镜像命名的最佳实践
- 用相同的 Dockerfile 在其他 host 构建镜像。
- 将镜像上传到公共 Registry(比如 Docker Hub),Host 直接下载使用。
- 搭建私有的 Registry 供本地 Host 使用。
为镜像命名
当执行 docker build命令时已经为镜像取了个名字
docker build -t ubuntu-with-vim
这里的 ubuntu-with-vim 就是镜像的名字。通过 dock images 可以查看镜像的信息。
这里注意到 ubuntu-with-vim 对应的是 REPOSITORY ,而且还有一个叫 latest 的 TAG。
一个特定镜像的名字由两部分组成:repository 和 tag。
latest tag
Docker 会使用默认值 latest。
Docker Hub 上很多 repository 将 latest 作为最新稳定版本的别名。
tag 使用最佳实践
借鉴软件版本命名方式能够让用户很好地使用镜像。
一个高效的版本命名方案可以让用户清楚地知道当前使用的是哪个镜像,同时还可以保持足够的灵活性。
每个 repository 可以有多个 tag,而多个 tag 可能对应的是同一个镜像。
企业级私有仓库Harbor
如果需要部署一个私有镜像仓库来使用,最简单的就是 registry ,一行命令就可以运行在 Docker 中,但功能也比较弱,如果想要私有镜像仓库功能更丰富些,可以使用 Harbor。
Harbor是构建企业级私有docker镜像的仓库的开源解决方案,它是Docker Registry的更高级封装,除了提供友好的Web UI界面,角色和用户权限管理,用户操作审计等功能外,它还整合了K8s的插件(Add-ons)仓库。
harbor下载:https://github.com/goharbor/harbor/releases
安装
bash
[root@docker ~]# wget https://github.com/goharbor/harbor/releases/download/v2.9.1/harbor-offline-installer-v2.9.1.tgz
如果无法通过 wget 进行下载,可以直接到 Github 网站:https://github.com/goharbor/harbor/releases/ 进行下载,然后拷贝到服务器中
bash
[root@docker ~]# ls
anaconda-ks.cfg harbor-offline-installer-v2.9.1.tgz
执行下面命令进行解压
bash
[root@docker ~]# tar -xvf harbor-offline-installer-v2.9.1.tgz
harbor/harbor.v2.9.1.tar.gz
harbor/prepare
harbor/LICENSE
harbor/install.sh
harbor/common.sh
harbor/harbor.yml.tmpl
执行下面命令新建目录,并将程序文件复制到目录中:
bash
[root@docker ~]# mkdir /opt/harbor
[root@docker ~]# mv harbor/* /opt/harbor/
导入Harbor镜像
bash
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# docker load -i harbor.v2.9.1.tar.gz
...
Loaded image: goharbor/prepare:v2.9.1
d0dcb5740755: Loading layer 115.1MB/115.1MB
a68394b34761: Loading layer 6.46MB/6.46MB
e47863752870: Loading layer 245.8kB/245.8kB
eb0d64571e29: Loading layer 1.233MB/1.233MB
Loaded image: goharbor/harbor-portal:v2.9.1
修改 Harbor 配置文件
bash
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# cp -ar harbor.yml.tmpl harbor.yml
[root@docker harbor]# vim harbor.yml
**hostname:**如果只是内网访问,设置为内网 IP,如果需要外网访问,就必须设置为外网域名或 IP
https: #注释掉
harbor_admin_password: harbor登录密码
编辑完配置文件,接下来在 harbor 目录下安装 Harbor。先进行预处理更新配置文件
bash
[root@docker harbor]# ./prepare
prepare base dir is set to /opt/harbor
WARNING:root:WARNING: HTTP protocol is insecure. Harbor will deprecate http protocol in the future. Please make sure to upgrade to https
Generated configuration file: /config/portal/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
Generated and saved secret to file: /data/secret/keys/secretkey
Successfully called func: create_root_cert
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir
执行下面命令进行安装
bash
[root@docker harbor]# ./install.sh
...
✔ ----Harbor has been installed and started successfully.----
稍等一会,如果所有容器的状态都是 healthy ,说明正常
登录WEB界面:http://192.168.108.30

使用
Harbor 功能有项目、用户管理、项目定额。
- 项目:可以针对不同的项目单独创建,每个项目都有自己的镜像地址
- 用户管理:可以维护用户,不同的项目可以设置不同的维护人员
- 项目定额:设置项目对应的镜像仓库最大空间容量
1、在用户管理中创建名称为 images_admin 的用户:


在项目中创建名称为cloud的项目,并添加 images_admin 为项目管理员






将内网服务器 IP 和端口配置到 daemon.json 文件中,执行下面命令进行配置
bash
[root@docker ~]# vim /etc/docker/daemon.json
[root@docker ~]# cat /etc/docker/daemon.json
{
"insecure-registries": ["192.168.108.30"],
"registry-mirrors": [
"https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com" ]
}
[root@docker ~]# systemctl restart docker
#重新执行安装命令
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# ./install.sh
登录服务器
bash
[root@docker harbor]# docker login 192.168.108.30
Username: images_admin
Password: 'Xiaomi123'
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
登录后家目录下会有一个.docker文件夹
bash
[root@docker ~]# cd ~/.docker/
[root@docker .docker]# pwd
/root/.docker
[root@docker .docker]# cat config.json
{
"auths": {
"192.168.108.30": {
"auth": "aW1hZ2VzX2FkbWluOlhpYW9taTEyMw=="
}
}
}
上传镜像

bash
[root@docker .docker]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
1733a4cd5954: Pull complete
5b219a92f92a: Pull complete
ee3a09d2248a: Pull complete
7382b41547b8: Pull complete
9ee60c6c0558: Pull complete
114e699da838: Pull complete
5b5fa0b64d74: Pull complete
Digest: sha256:fb01117203ff38c2f9af91db1a7409459182a37c87cced5cb442d1d8fcc66d19
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
[root@docker .docker]# docker tag nginx:latest 192.168.108.30/cloud/nginx:latest
[root@docker .docker]# docker push 192.168.108.30/cloud/nginx:latest
The push refers to repository [192.168.108.30/cloud/nginx]
8921786c2de3: Pushed
08ba9962589f: Pushed
6e32bc56a725: Pushed
6898c33749d5: Pushed
69f56ce8c461: Pushed
20cf308e6957: Pushed
77a2b55fbe8b: Pushed
latest: digest: sha256:460a7081b2a0e17940688563b294c03f326cd96dc43d768691f58abb50f7746f size: 1778
下载镜像
bash
[root@docker .docker]# docker pull 192.168.108.30/cloud/nginx
Using default tag: latest
latest: Pulling from cloud/nginx
a2318d6c47ec: Pull complete
095d327c79ae: Pull complete
bbfaa25db775: Pull complete
7bb6fb0cfb2b: Pull complete
0723edc10c17: Pull complete
24b3fdc4d1e3: Pull complete
3122471704d5: Pull complete
Digest: sha256:596c783ac62b9a43c60edb876fe807376cd5022a4e25e89b9a9ae06c374299d4
Status: Downloaded newer image for 192.168.108.30/cloud/nginx:latest
192.168.108.30/cloud/nginx:latest
卸载harbor
清理容器
bash
[root@docker ~]# cd /opt/harbor/
[root@docker harbor]# docker compose down
WARN[0000] /opt/harbor/docker-compose.yml: `version` is obsolete
[+] Running 10/10
✔ Container nginx Removed 0.1s
✔ Container registryctl Removed 0.1s
✔ Container harbor-jobservice Remov... 0.1s
✔ Container harbor-portal Removed 0.1s
✔ Container harbor-core Removed 0.1s
✔ Container registry Removed 0.1s
✔ Container harbor-db Removed 0.1s
✔ Container redis Removed 0.1s
✔ Container harbor-log Removed 10.1s
✔ Network harbor_harbor Removed 0.1s
清理镜像
bash
[root@docker harbor]# docker images |grep harbor|awk '{print $1":"$2}' | xargs docker rmi
清理harbor使用的目录/data,由prepare脚本定义
bash
[root@docker harbor]# rm -rf /data
删除软件包
bash
[root@docker harbor]# cd
[root@docker ~]# ls
anaconda-ks.cfg harbor harbor-offline-installer-v2.9.1.tgz
[root@docker ~]# rm -f harbor-offline-installer-v2.9.1.tgz
[root@docker ~]# rm -rf /opt/harbor/
Docker镜像小结
镜像的常用操作子命令:
images 显示镜像列表
history 显示镜像构建历史
commit 从容器创建新镜像
build 从 Dockerfile 构建镜像
tag 给镜像打 tag
pull 从 registry 下载镜像
push 将 镜像 上传到 registry
rmi 删除 Docker host 中的镜像
search 搜索 Docker Hub 中的镜像
除了 rmi 和 search,其他命令都已经用过了。
rmi
rmi 只能删除 host 上的镜像,不会删除 registry 的镜像。
如果一个镜像对应了多个 tag,只有当最后一个 tag 被删除时,镜像才被真正删除。
search
search 让无需打开浏览器,在命令行中就可以搜索 Docker Hub 中的镜像。
保存本地镜像为文件-save
docker默认使用overlay2存储驱动存储镜像。
镜像存储在本地/var/lib/docker/overlay2,通过文件系统层面拷贝image,操作复杂。可以使用save命令,将本地镜像保存为单个文件,并分享给他人使用。
将本地镜像文件导入本地-load
bash
[root@docker ~]# docker load --help
Usage: docker load [OPTIONS]
Load an image from a tar archive or STDIN
Aliases:
docker image load, docker load
Options:
-i, --input string Read from tar archive file, instead of STDIN
-q, --quiet Suppress the load output
说明:如果本地镜像名与导入的镜像重名,则本地的镜像会被覆盖。
bash[root@docker ~]# docker rm -f $(docker ps -aq) #删除所有容器 [root@docker ~]# docker rmi -f $(docker images -aq) #删除所有镜像
第4章 容器
如何运行容器?
运行容器
docker run=docker create + docker start
docker run 是启动容器的方法。可用三种方式指定容器启动时执行的命令:
- CMD 指令。
- ENTRYPOINT 指令。
- 在
docker run命令行中指定。
让容器长期运行
因为容器的生命周期依赖于启动时执行的命令,只要该命令不结束,容器也就不会退出。
bash
[root@docker ~]# docker run ubuntu /bin/bash -c "while true ; do sleep 1 ; echo hahaha; done"
while 语句让 bash 不会退出。可以打开另一个终端查看容器的状态:
可见容器仍处于运行状态。不过这种方法有个缺点:它占用了一个终端。
可以加上参数 -d 以后台方式启动容器。
bash
[root@docker ~]# docker run -d ubuntu /bin/bash -c "while true ; do sleep 1 ; echo hahaha; done"
f7a21bfeb831b7b0cc3b594b365e30528abebe06e4741e14df2b3de0343dc8b9
两种进入容器的方法
有两种方法进入容器:attach 和 exec。
docker attach
通过 docker attach 可以 attach 到容器启动命令的终端,例如:
bash
[root@docker ~]# docker run -d ubuntu /bin/bash -c "while true ; do sleep 1 ; echo I_am_in_container ; done"
61575d27e0d7979d03baae4baee5db0c310288732b457646d49f1b1782a0d7be
[root@docker ~]# docker attach 61575
I_am_in_container
I_am_in_container
I_am_in_container
I_am_in_container
这次通过 "长ID" attach 到了容器的启动命令终端,之后看到的是echo 每隔一秒打印的信息。
注:可通过 Ctrl+p 然后 Ctrl+q 组合键退出 attach 终端。
docker exec
通过 docker exec 进入相同的容器:
bash
[root@docker ~]# docker exec -it 61575d27e0d7 bash
root@61575d27e0d7:/#
说明如下:
① -it 以交互模式打开 pseudo-TTY,执行 bash,其结果就是打开了一个 bash 终端。
② 进入到容器中,容器的 hostname 就是其 "短ID"。
③ 可以像在普通 Linux 中一样执行命令。ps -elf 显示了容器启动进程while 以及当前的 bash 进程。
④ 执行 exit 退出容器,回到 docker host。
docker exec -it <container> bash|sh 是执行 exec 最常用的方式。
attach VS exec
attach 与 exec 主要区别如下:
- attach 直接进入容器 启动命令 的终端,不会启动新的进程。
- exec 则是在容器中打开新的终端,并且可以启动新的进程。
- 如果想直接在终端中查看启动命令的输出,用 attach;其他情况使用 exec。
查看启动命令的输出,可以使用 docker logs 命令:
bash
[root@docker ~]# docker logs -f 61575d27e0d7
I_am_in_container
I_am_in_container
I_am_in_container
I_am_in_container
-f 的作用与 tail -f 类似,能够持续打印输出。
容器运行小结
容器运行相关的知识点:
- 当 CMD 或 Entrypoint 或 docker run 命令行指定的命令运行结束时,容器停止。
- 通过
-d参数在后台启动容器。 - 通过
exec -it可进入容器并执行命令。
指定容器的三种方法:
- 短ID。
- 长ID。
- 容器名称。 可通过
--name为容器命名。重命名容器可执行docker rename。
容器按用途可分为两类:
- 服务类的容器。
- 工具类的容器。
容器常用操作
stop/start/restart 容器
通过 docker stop 可以停止运行的容器。
bash
[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
06b7b795c440 httpd "httpd-foreground" 9 seconds ago Up 9 seconds 80/tcp distracted_carson
e2b0f04d7eaf httpd "httpd-foreground" 44 seconds ago Exited (0) 16 seconds ago stoic_bardeen
[root@docker ~]# docker stop distracted_carson
distracted_carson
[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
06b7b795c440 httpd "httpd-foreground" 43 seconds ago Exited (0) 6 seconds ago distracted_carson
e2b0f04d7eaf httpd "httpd-foreground" About a minute ago Exited (0) 50 seconds ago stoic_bardeen
容器在 docker host 中是一个进程,docker stop 命令本质上是向该进程发送一个 SIGTERM 信号。快速停止容器,可使用 docker kill 命令。
对于处于停止状态的容器,可以通过 docker start 重新启动。
docker start 会保留容器的第一次启动时的所有参数。
docker restart 可以重启容器。
pause/unpause 容器
让容器暂停工作一段时间可以执行 docker pause。
处于暂停状态的容器不会占用 CPU 资源,直到通过 docker unpause 恢复运行。
删除容器
使用 docker 一段时间后,host 上可能会有大量已经退出了的容器。
这些容器依然会占用 host 的文件系统资源,如果确认不会再重启此类容器,可以通过 docker rm 删除。
bash
[root@docker ~]# docker rm 06b7b795c440 e2b0f04d7eaf
06b7b795c440
e2b0f04d7eaf
docker rm 一次可以指定多个容器,如果希望批量删除所有已经退出的容器,可以执行如下命令:
docker rm -v $(docker ps -aq -f status=exited)
bash
# !!!慎用,删除所有状态容器
[root@docker ~]# docker rm -f $(docker ps -aq)
限制容器对内存的使用
cgroup简介
docker 通过 cgroup 来控制容器使用的资源配额,包括 CPU、内存、磁盘三大方面,。
cgroup 是 Control Groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源(如 cpu、memory、磁盘IO等等) 的机制,被 LXC、docker 等很多项目用于实现进程资源控制。cgroup 将任意进程进行分组化管理的 Linux 内核功能。cgroup 本身是提供将进程进行分组化管理的功能和接口的基础结构,I/O 或内存的分配控制等具体的资源管理功能是通过这个功能来实现的。这些具体的资源管理功能称为 cgroup 子系统,有以下几大子系统实现:
blkio:设置限制每个块设备的输入输出控制。
cpu:使用调度程序为 cgroup 任务提供 cpu 的访问。
cpuacct:产生 cgroup 任务的 cpu 资源报告。
cpuset:如果是多核心的 cpu,这个子系统会为 cgroup 任务分配单独的 cpu 和内存。
devices:允许或拒绝 cgroup 任务对设备的访问。
freezer:暂停和恢复 cgroup 任务。
memory:设置每个 cgroup 的内存限制以及产生内存资源报告。
net_cls:标记每个网络包以供 cgroup 方便使用。
ns:命名空间子系统。
perf_event:增加了对每 group 的监测跟踪的能力,可以监测属于某个特定的 group 的所有线程以及运行在特定CPU上的线程。
stress
是模拟压力测试的工具
在机器上模拟cpu、内存等使用率
来检测不同状态下的运行情况
可以使用 stress 工具来测试 CPU 和内存。
Dockerfile
dockerfile
[root@docker dockerfile]# vim Dockerfile
FROM ubuntu
MAINTAINER wan "2769311430@qq.com"
RUN apt-get -y update && apt-get -y install stress
# 以服务或进程的形式运行
ENTRYPOINT ["/usr/bin/stress"]
使用Dockerfile构建镜像ubuntu-with-stress
bash
[root@docker ~]# docker build -t ubuntu-with-stress .
[+] Building 78.0s (6/6) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 168B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:latest 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/2] FROM docker.io/library/ubuntu:latest 0.0s
=> [2/2] RUN apt-get -y update && apt-get -y install stress 77.8s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:766cb60a12dfcf6cb1022a8e09bd0d9ffa677fc7e 0.0s
=> => naming to docker.io/library/ubuntu-with-stress 0.0s
内存限额
与操作系统类似,容器可使用的内存包括两部分:物理内存和 swap。 Docker 通过下面两组参数来控制容器内存的使用量。
-m或--memory:设置内存的使用限额,例如 100M, 2G。--memory-swap:设置 内存+swap 的使用限额。
当执行如下命令:
docker run -m 200M --memory-swap=300M ubuntu
允许该容器最多使用 200M 的内存和 100M 的 swap。
正常情况下,--memory-swap 的值包含容器可用内存和可用swap。
如果--memory-swap 设置为0 或者不设置,则容器可以使用的swap大小为-m值的两倍。
如果 --memory-swap 的值和-m 值相同,则容器不能使用swap
如果 --memory-swap值为-1。它表示容器程序使用的内存受限,而可以使用的swap空间不受限制(宿主机有多少swap空间该容器就可以使用多少)
使用ubuntu-with-stress镜像来学习如何为容器分配内存。该镜像可用于对容器执行压力测试。执行如下命令:
bash
[root@docker ~]# docker run -it -m 200M --memory-swap=300M ubuntu-with-stress --vm 1 --vm-bytes 280M -v
--vm 1:启动 1 个内存工作线程。
--vm-bytes 280M:每个线程分配 280M 内存。
运行结果如下:
bash
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 293601280 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: dbug: [7] freed 293601280 bytes
stress: dbug: [7] allocating 293601280 bytes ...
...
因为 280M 在可分配的范围(300M)内,所以工作线程能够正常工作,其过程是:
- 分配 280M 内存。
- 释放 280M 内存。
- 再分配 280M 内存。
- 再释放 280M 内存。
- 一直循环...
如果让工作线程分配的内存超过 300M,结果如下:
bash
[root@docker ~]# docker run -it -m 200M --memory-swap=300M ubuntu-with-stress --vm 1 --vm-bytes 310M -v
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 325058560 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: FAIL: [1] (425) <-- worker 7 got signal 9
stress: WARN: [1] (427) now reaping child worker processes
stress: FAIL: [1] (431) kill error: No such process
stress: FAIL: [1] (461) failed run completed in 1s
分配的内存超过限额,stress 线程报错,容器退出。
如果在启动容器时只指定 -m 而不指定 --memory-swap,那么 --memory-swap 默认为 -m 的两倍,比如:
docker run -it -m 200M ubuntu-with-stress
容器最多使用 200M 物理内存和 200M swap。
限制容器对CPU的使用
默认设置下,所有容器可以平等地使用 host CPU 资源并且没有限制。
Docker 可以通过 -c 或 --cpu-shares 设置容器使用 CPU 的权重。如果不指定,默认值为 1024。
--cpu-shares的值不能保证可以获得1个 vcpu 或者多少 GHz 的 CPU 资源,仅仅只是一个弹性的加权值。
默认情况下,每个 docker 容器的 cpu 份额都是1024。单独一个容器的份额是没有意义的,只有在同时运行多个容器时,容器的 CPU 加权的效果才能体现出来。
通过 cpu share 可以设置容器使用 CPU 的优先级。
bash
[root@docker ~]# docker run --name "container_A" -it -c 1024 ubuntu-with-stress --cpu 4 -v
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 12000us
stress: dbug: [1] --> hogcpu worker 4 [7] forked
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [8] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [9] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [10] forked
#再开一个窗口
#查询容器长id
[root@docker ~]# docker ps -a --no-trunc
#使用如下命令,创建容器,则最终生成的 cgroup 的 CPU 份额配置可以下面的文件中找到
[root@docker ~]# cat /sys/fs/cgroup/cpu/docker/2b289877380a89b3a0fdee4959f11dcbb03148bee3930bdf103d67968e0fa4a5/cpu.shares
1024
--cpu 用来设置工作线程的数量。因为当前 host 有 4颗 CPU,所以要4个工作线程才能将 CPU 压满。如果 host 有多颗 CPU,则需要相应增加 --cpu 的数量。
实现容器的底层技术
cgroup 和 namespace 是最重要的两种技术。cgroup 实现资源限额, namespace 实现资源隔离。
cgroup
cgroup 全称 Control Group。Linux 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。相信你已经猜到了:前面看到的--cpu-shares、-m、--device-write-bps 实际上就是在配置 cgroup。
namespace
在每个容器中,都可以看到文件系统,网卡等资源,这些资源看上去是容器自己的。拿网卡来说,每个容器都会认为自己有一块独立的网卡,即使 host 上只有一块物理网卡。这种方式非常好,它使得容器更像一个独立的计算机。
Linux 实现这种方式的技术是 namespace。namespace 管理着 host 中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。换句话说,namespace 实现了容器间资源的隔离。
容器的常用操作命令:
create 创建容器
run 运行容器
pause 暂停容器
unpause 取消暂停继续运行容器
stop 发送 SIGTERM 停止容器
kill 发送 SIGKILL 快速停止容器
start 启动容器
restart 重启容器
attach attach 到容器启动进程的终端
exec 在容器中启动新进程,通常使用 "-it" 参数
logs 显示容器启动进程的控制台输出,用 "-f" 持续打印
rm 从磁盘中删除容器
第5章 网络
none和host网络的适用场景
Docker 网络从覆盖范围可分为单个 host 上的容器网络和跨多个 host 的网络。
Docker 安装时会自动在 host 上创建三个网络,可用 docker network ls 命令查看:
bash
[root@docker ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
a4499c26f0d1 bridge bridge local
f0a2d06e9abf host host local
e4309972e9a5 none null local
none 网络
none网络的driver类型是null,IPAM字段为空。挂在none网络上的容器只有lo,无法与外界通信。
none 网络就是什么都没有的网络。挂在这个网络下的容器除了 lo,没有其他任何网卡。容器创建时,可以通过 --network=none 指定使用 none 网络。
bash
[root@docker ~]# docker run -it --network=none busybox
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
e59838ecfec5: Already exists
Digest: sha256:d80cd694d3e9467884fcb94b8ca1e20437d8a501096cdf367a5a1918a34fc2fd
Status: Downloaded newer image for busybox:latest
/ # ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
/ # hostname
304f97c7fd48
一些对安全性要求高并且不需要联网的应用可以使用 none 网络。
host 网络
挂在host网络上的容器共享宿主机的network namespace。容器的网络配置与host网络配置完全一样。
连接到 host 网络的容器共享 Docker host 的网络栈,容器的网络配置与 host 完全一样。可以通过 --network=host 指定使用 host 网络。
bash
[root@docker ~]# docker run -it --network=host busybox
/ # ip l
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
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq qlen 1000
link/ether 00:0c:29:0d:d7:1e brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
link/ether 02:42:3a:29:7a:89 brd ff:ff:ff:ff:ff:ff
#容器主机与宿主机相同
/ # hostname
docker
直接使用 Docker host 的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择 host 网络。
Docker host 的另一个用途是让容器可以直接配置 host 网路。
brige网络
Docker 安装时会创建一个 命名为 docker0 的 linux bridge,实际上它是 Linux 的一个 bridge (网桥)。如果不指定--network,创建的容器默认都会挂到 docker0 上。
- 一端在容器内即 eth0;
- 另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头
bash
#配置yum源
[root@docker ~]# cd /etc/yum.repos.d/
[root@docker yum.repos.d]# vim cloud.repo
[centos-openstack-victoria]
name=CentOS 8 - OpenStack victoria
baseurl=https://mirrors.aliyun.com/centos-vault/8-stream/cloud/x86_64/openstack-victoria/
enabled=1
gpgcheck=0
[root@docker yum.repos.d]# yum install -y bridge-utils
bash
[root@docker ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02423a297a89 no
当前 docker0 上没有任何其他网络设备,创建一个容器
bash
[root@docker ~]# docker run -itd --name busybox1 busybox
f39988f47c2b044e337e0f5158c704dcff49597fdbaeafe192fe3ef60966359c
[root@docker ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02423a297a89 no veth331bb78
容器的网络配置
bash
[root@docker ~]# docker exec -it busybox1 sh
/ # 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
119: eth0@if120: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
bridge 网络的配置信息:
bash
[root@docker ~]# docker network inspect bridge
[
{
"Name": "bridge",
"Id": "a4499c26f0d1112e7bd3c1e1f5f86ed1a848caa7661eff57ba5c0d6ed706b177",
"Created": "2025-12-26T16:07:24.189378275+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"f39988f47c2b044e337e0f5158c704dcff49597fdbaeafe192fe3ef60966359c": {
"Name": "busybox1",
"EndpointID": "e4ffe1214e3f2ebf6232bc4f9cdca8e891281f61e3419b7706d7b5cd79f5a791",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
原来 bridge 网络配置的 subnet 就是 172.17.0.0/16,并且网关是 172.17.0.1。这个网关是 docker0。
bash
[root@docker ~]# ip a | grep docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
120: veth331bb78@if119: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
容器创建时,docker 会自动从 172.17.0.0/16 中分配一个 IP,这里 16 位的掩码保证有足够多的 IP 可以供容器使用。
除了 none, host, bridge 这三个自动创建的网络,用户也可以根据业务需要创建 user-defined 网络。
自定义容器网络
除了 none, host, bridge 这三个自动创建的网络,用户也可以根据业务需要创建 user-defined 网络。
Docker 提供三种 user-defined 网络驱动:bridge, overlay 和 macvlan。overlay 和 macvlan 用于创建跨主机的网络。
可通过 bridge 驱动创建类似前面默认的 bridge 网络
bash
[root@docker ~]# docker network create --driver bridge my_net
90c3076936231dca2d43a6d799ebcc04cd071e632bd978095be5cbe18aff894c
查看一下当前 host 的网络结构变化:
bash
[root@docker ~]# brctl show
bridge name bridge id STP enabled interfaces
br-90c307693623 8000.02429c0961c5 no
docker0 8000.02423a297a89 no veth331bb78
查看一下 my_net 的配置信息:
bash
[root@docker ~]# docker network inspect my_net
[
{
"Name": "my_net",
"Id": "90c3076936231dca2d43a6d799ebcc04cd071e632bd978095be5cbe18aff894c",
"Created": "2025-12-26T17:22:59.722063446+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.19.0.0/16",
"Gateway": "172.19.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
这里 172.18.0.0/16 是 Docker 自动分配的 IP 网段。
创建自定义网络,只需在创建网段时指定 --subnet 和 --gateway 参数:
bash
[root@docker ~]# docker network create --driver bridge --subnet 172.22.16.0/24 --gateway 172.22.16.1 my_net2
ba9531c097dacc68ccbac9cd453843de4969033ea62b4ce699ebb9f64b9c4644
[root@docker ~]# brctl show
bridge name bridge id STP enabled interfaces
br-90c307693623 8000.02429c0961c5 no
br-ba9531c097da 8000.0242239abec4 no
docker0 8000.02423a297a89 no veth331bb78
[root@docker ~]# docker network inspect my_net2
[
{
"Name": "my_net2",
"Id": "ba9531c097dacc68ccbac9cd453843de4969033ea62b4ce699ebb9f64b9c4644",
"Created": "2025-12-26T17:24:46.352608221+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
#指定的网段和网关
"Subnet": "172.22.16.0/24",
"Gateway": "172.22.16.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
容器通信的三种方式
容器之间可通过 IP,Docker DNS Server 或 joined 容器三种方式通信。
IP 通信
两个容器要能通信,必须要有属于同一个网络的网卡。
满足这个条件后,容器就可以通过 IP 交互了。具体做法是在容器创建时通过 --network 指定相应的网络,或者通过 docker network connect 将现有容器加入到指定网络。
Docker DNS Server
从 Docker 1.10 版本开始,docker daemon 实现了一个内嵌的 DNS server,使容器可以直接通过"容器名"通信。在启动时用 --name 为容器命名。
joined 容器
joined 容器是另一种实现容器间通信的方式。
joined 容器非常特别,它可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined 容器之间可以通过 127.0.0.1 直接通信。
joined 容器非常适合以下场景:
- 不同容器中的程序希望通过 loopback 高效快速地通信,比如 web server 与 app server。
- 希望监控其他容器的网络流量,比如运行在独立容器中的网络监控程序。
第6章 存储
Docker的两类存储资源
Docker 为容器提供了两种存放数据的资源:
- 由 storage driver 管理的镜像层和容器层。
- Data Volume。
storage driver
容器由最上面一个可写的容器层,以及若干只读的镜像层组成,容器的数据就存放在这些层中。这样的分层结构最大的特性是 Copy-on-Write:
- 新数据会直接存放在最上面的容器层。
- 修改现有数据会先从镜像层将数据复制到容器层,修改后的数据直接保存在容器层中,镜像层保持不变。
- 如果多个层中有命名相同的文件,用户只能看到最上面那层中的文件。
分层结构使镜像和容器的创建、共享以及分发变得非常高效,这些都归于 Docker storage driver。
优先使用 Linux 发行版默认的 storage driver。
Docker 安装时会根据当前系统的配置选择默认的 driver。默认 driver 具有最好的稳定性,因为默认 driver 在发行版上经过了严格的测试。
运行docker info查看CentOS的默认 driver:
bash
[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: 0
Running: 0
Paused: 0
Stopped: 0
Images: 17
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: 2
Total Memory: 3.798GiB
Name: docker
ID: 1759e792-a603-401f-b7cd-43e0cec7a2b9
Docker Root Dir: /var/lib/docker
Debug Mode: false
Experimental: false
Insecure Registries:
192.168.108.30
127.0.0.0/8
Registry Mirrors:
https://054b8ac70e8010d90f2ac00ef29e6580.mirror.swr.myhuaweicloud.com/
Live Restore Enabled: false
CentOS Stream 8 用的overlay2,底层文件系统是xfs,各层数据存放在 /var/lib/docker。
对于某些容器,直接将数据放在由 storage driver 维护的层中是很好的选择。
对于有持久化数据的需求,容器启动时需要加载已有的数据,容器销毁时希望保留产生的新数据,也就是说,这类容器是有状态的。
Data Volume之bind mount
storage driver 和 data volume 是容器存放数据的两种方式。
Data Volume 本质上是 Docker Host 文件系统中的目录或文件,能够直接被 mount 到容器的文件系统中。Data Volume 有以下特点:
- Data Volume 是目录或文件,而非没有格式化的磁盘(块设备)。
- 容器可以读写 volume 中的数据。
- volume 数据可以被永久的保存,即使使用它的容器已经销毁。
docker 提供了两种类型的 volume:bind mount 和 docker managed volume。
bind mount
bind mount 是将 host 上已存在的目录或文件 mount 到容器。
bash
[root@docker ~]# pwd
/root
[root@docker ~]# mkdir htdocs
[root@docker ~]# cd htdocs/
[root@docker htdocs]# touch index.html
[root@docker htdocs]# vim index.html
<html><body><h1>This is a file in host file system !</h1></body></html>
[root@docker htdocs]# cd
通过 -v 将其 mount 到 httpd 容器:
bash
[root@docker ~]# docker run -d -p 80:80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
837fefc64f07b9689c4e492b3399ab43120506fffaaccb887298e27bcbe70bce
/usr/local/apache2/htdocs 就是 apache server 存放静态文件的地方
bash
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>This is a file in host file system !</h1></body></html>
curl 显示当前主页确实是 $HOME/htdocs/index.html 中的内容
bash
[root@docker ~]# echo "updated index page!" > ~/htdocs/index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
updated index page!
host 中的修改生效了
将容器销毁 bind mount 的影响:
bash
[root@docker ~]# docker stop 837fefc
837fefc
[root@docker ~]# docker rm 837fefc
837fefc
[root@docker ~]# cat ~/htdocs/index.html
updated index page!
[root@docker ~]#
Data Volume之docker managed volume
docker managed volume 与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明 mount point 就行了。
bash
#-v指定目录
[root@docker ~]# docker run -d -p 80:80 -v /usr/local/apache2/htdocs httpd
7ddc9879c1784664d04f3c1c0f39551965367eb6899e880779d4ee68957fc4bd
bash
#查看volume
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 2d8320702acaeb83d7136acaa5614d1f6a112ed57304d1d615dc133bbd10982a
#查看volume详细信息
[root@docker ~]# docker volume inspect 2d8320702acaeb83d7136acaa5614d1f6a112ed57304d1d615dc133bbd10982a
[
{
"CreatedAt": "2025-12-26T16:08:00+08:00",
"Driver": "local",
"Labels": {
"com.docker.volume.anonymous": ""
},
"Mountpoint": "/var/lib/docker/volumes/2d8320702acaeb83d7136acaa5614d1f6a112ed57304d1d615dc133bbd10982a/_data",
"Name": "2d8320702acaeb83d7136acaa5614d1f6a112ed57304d1d615dc133bbd10982a",
"Options": null,
"Scope": "local"
}
]
bind mount 和 docker managed volume区别
- 相同点:两者都是 host 文件系统中的某个路径。
- 不同点:
| bind mount | docker managed volume | |
|---|---|---|
| volume 位置 | 可任意指定 | /var/lib/docker/volumes/... |
| 对已有mount point 影响 | 隐藏并替换为 volume | 原有数据复制到 volume |
| 是否支持单个文件 | 支持 | 不支持,只能是目录 |
| 权限控制 | 可设置为只读,默认为读写权限 | 无控制,均为读写权限 |
| 移植性 | 移植性弱,与 host path 绑定 | 移植性强,无需指定 host 目录 |
如何共享数据
容器与 host 共享数据
有两种类型的 data volume,它们均可实现在容器与 host 之间共享数据,但方式有所区别。
对于 bind mount 是非常明确的:直接将要共享的目录 mount 到容器。
docker managed volume 就要麻烦点。由于 volume 位于 host 中的目录,是在容器启动时才生成,所以需要将共享数据拷贝到 volume 中。
bash
[root@docker ~]# docker run -d -p 80:80 -v /usr/local/apache2/htdocs httpd
889b9d5309bd12499f8e4ee78a491d9ba7acef5660be00f30cda6569c9974c6f
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>It works!</h1></body></html>
[root@docker ~]#
[root@docker ~]# docker cp ~/htdocs/index.html 889b9d5309bd:/usr/local/apache2/htdocs #将host os的/root/htdocs/index.html拷贝到容器中的/usr/local/apache2/htdocs目录下
Successfully copied 2.05kB to 889b9d5309bd:/usr/local/apache2/htdocs
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
updated index page!
[root@docker ~]#
docker cp 可以在容器和 host 之间拷贝数据,可以直接通过 Linux 的 cp 命令复制到 /var/lib/docker/volumes/xxx。
第7章 容器监控
Docker自带的监控子命令
当Docker部署规模逐步变大后,可视化监控容器环境的性能和健康状态将会变得越来越重要。
ps
docker ps 方便查看当前运行的容器。
top
查看容器的进程
bash
[root@docker ~]# docker top --help
Usage: docker top CONTAINER [ps OPTIONS]
Display the running processes of a container
Aliases:
docker container top, docker top
stats
列出容器资源使用率
bash
[root@docker ~]# docker stats --help
Usage: docker stats [OPTIONS] [CONTAINER...]
Display a live stream of container(s) resource usage statistics
Aliases:
docker container stats, docker stats
Options:
-a, --all Show all containers (default shows just running)
--format string Format output using a custom template:
'table': Print output in table format with column headers (default)
'table TEMPLATE': Print output in table format using the given Go template
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates
--no-stream Disable streaming stats and only pull the first result
--no-trunc Do not truncate output
默认会显示一个实时变化的列表,展示每个容器的 CPU 使用率,内存使用量和可用量,网络和磁盘的 IO 数据。
注意:容器启动时如果没有特别指定内存 limit,stats 命令会显示 host 的内存总量,但这并不意味着每个 container 都能使用到这么多的内存。
第8章 容器日志
高效的监控和日志管理对保持生产系统持续稳定地运行以及排查问题至关重要。
Docker logs
对于一个运行的容器,Docker 会将日志发送到 容器标准输出设备(STDOUT)和标准错误设备(STDERR),STDOUT 和 STDERR 实际上就是容器的控制台终端。
举个例子,用下面的命令运行 httpd 容器:
bash
[root@docker ~]# docker run -p 80:80 httpd
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.3. 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.3. Set the 'ServerName' directive globally to suppress this message
[Tue Oct 08 14:28:10.312876 2024] [mpm_event:notice] [pid 1:tid 1] AH00489: Apache/2.4.62 (Unix) configured -- resuming normal operations
[Tue Oct 08 14:28:10.316058 2024] [core:notice] [pid 1:tid 1] AH00094: Command line: 'httpd -D FOREGROUND'
在启动日志的时候没有用 -d 参数,httpd 容器以前台方式启动,日志会直接打印在当前的终端窗口。
如果加上 -d 参数以后台方式运行容器,就看不到输出的日志了。
这种情况下如果要查看容器的日志,有两种方法:
- attach 到该容器。
- 用
docker logs命令查看日志。
attach 的方法在实际使用中不太方便,因为:
- 只能看到 attach 之后的日志,以前的日志不可见。
- 退出 attach 状态比较麻烦(Ctrl+p 然后 Ctrl+q 组合键),一不小心很容器将容器杀掉(比如按下 Ctrl+C)。
查看容器日志推荐的方法是用 docker logs 命令。
bash
[root@docker ~]# docker logs 7ddc
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. 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.2. Set the 'ServerName' directive globally to suppress this message
[Fri Dec 26 12:36:30.426697 2025] [mpm_event:notice] [pid 1:tid 1] AH00489: Apache/2.4.66 (Unix) configured -- resuming normal operations
[Fri Dec 26 12:36:30.426753 2025] [core:notice] [pid 1:tid 1] AH00094: Command line: 'httpd -D FOREGROUND'
docker logs 能够打印出自容器启动以来完整的日志,并且 -f 参数可以继续打印出新产生的日志。
第9章 Docker-compose
Docker Compose 可以轻松、高效地管理容器,它是一个用于定义和运行多容器的管理工具。
通过一个单独的 docker-compose.yml 模板文件(YAML 格式)定义一组相关联资源集。
Compose 中有两个重要的概念:
- 服务 (
service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。 - 项目 (
project):由一组关联的应用容器组成的一个完整业务单元,在docker-compose.yml文件中定义。
Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
命令说明
bash
[root@docker ~]# docker compose -h
Flag shorthand -h has been deprecated, please use --help
Usage: docker compose [OPTIONS] COMMAND
Define and run multi-container applications with Docker
Options:
--all-resources Include all resources, even those not used by services
--ansi string Control when to print ANSI control characters ("never"|"always"|"auto") (default "auto")
--compatibility Run compose in backward compatibility mode
--dry-run Execute command in dry run mode
--env-file stringArray Specify an alternate environment file
-f, --file stringArray Compose configuration files
--parallel int Control max parallelism, -1 for unlimited (default -1)
--profile stringArray Specify a profile to enable
--progress string Set type of progress output (auto, tty, plain, quiet) (default "auto")
--project-directory string Specify an alternate working directory
(default: the path of the, first specified, Compose file)
-p, --project-name string Project name
Commands:
attach Attach local standard input, output, and error streams to a service's running container
build Build or rebuild services
config Parse, resolve and render compose file in canonical format
cp Copy files/folders between a service container and the local filesystem
create Creates containers for a service
down Stop and remove containers, networks
events Receive real time events from containers
exec Execute a command in a running container
images List images used by the created containers
kill Force stop service containers
logs View output from containers
ls List running compose projects
pause Pause services
port Print the public port for a port binding
ps List containers
pull Pull service images
push Push service images
restart Restart service containers
rm Removes stopped service containers
run Run a one-off command on a service
scale Scale services
start Start services
stats Display a live stream of container(s) resource usage statistics
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show the Docker Compose version information
wait Block until the first service container stops
watch Watch build context for service and rebuild/refresh containers when files are updated
Run 'docker compose COMMAND --help' for more information on a command.
综合实验
构建WordPress
通过WordPress和mysql镜像构建WordPress应用。
下载镜像:
bash
[root@docker ~]# docker pull mysql
[root@docker ~]# docker pull wordpress
创建mysql容器,并创建wordpress数据库:
bash
[root@docker ~]# docker run -d -p 3306:3306 \
-v /mysql:/var/lib/mysql:z \
--name mysql \
-e MYSQL_ROOT_PASSWORD=huawei \
-e MYSQL_DATABASE=wordpress \
mysql
d884f9117e1af9ffb256c8ba1332b5448dd14b0994ef6e94b1a2afca8ab64c89
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d884f9117e1a mysql "docker-entrypoint.s..." 7 minutes ago Up 7 minutes 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql
创建WordPress容器:
bash
[root@docker ~]# docker inspect mysql|grep IPAddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2",
[root@docker ~]# docker run -d -p 80:80 -v /www:/var/www/html:z \
--name wordpress \
-e WORDPRESS_DB_HOST=172.17.0.2 \
-e WORDPRESS_DB_USER=root \
-e WORDPRESS_DB_PASSWORD=huawei \
-e WORDPRESS_DB_NAME=wordpress \
wordpress
7ce58021fb2b7247554e7e88f2f7a4e2a9563165c7f31c16734d2ffc4dab2d05
验证 :浏览器打开http://192.168.108.30,配置WordPress:




