基础概念
Docker 是什么? 容器化平台,将应用及其依赖打包成轻量级、可移植的容器,实现操作系统级虚拟化。
为什么容器被称为”一次构建,到处运行”? 镜像把应用和它的所有依赖(库、运行时、配置)一起打包,容器运行时只依赖宿主机内核,因此只要内核兼容,任何装了 Docker 的机器行为都一致,消除了”环境不一致”问题。
Docker 镜像与容器的关系? 镜像是只读模板(代码+依赖+环境),容器是镜像的运行实例(可读写层)。
Docker 的三大核心是什么? 镜像(Image)、容器(Container)、仓库(Repository)。
Docker 工作原理是什么? Docker 使用客户端-服务器架构。Docker 客户端与 Docker 守护进程通信,后者负责构建、运行和分发 Docker 容器的繁重工作。
Docker 组成部分?
- docker client / CLI,客户端/命令行工具,为用户提供一系列可执行命令,用户用这些命令实现跟 docker daemon 交互;
- docker daemon,守护进程,在宿主主机后台运行,等待接收来自客户端的请求消息;
- docker build,用于构建镜像,使用 BuildKit 作为构建器;
- docker container,容器,一个系统级别的服务,拥有自己的 ip 和系统目录结构。
Docker 容器与虚拟机的本质区别? 虚拟机通过 Hypervisor 虚拟化硬件,每个 VM 运行独立 Guest OS(GB 级、分钟级启动);Docker 共享宿主机内核,进程级隔离(MB 级、秒级启动)。
容器是一种轻量级虚拟机吗? 不是。容器本质就是宿主机上被 namespace 隔离、被 cgroup 限流的普通进程,与宿主共享同一个内核,所以:
- 优势是启动快、开销小、密度高;
- 代价是隔离性弱于虚拟机,内核漏洞会影响所有容器,且无法运行不同内核版本(如 Linux 上跑 Windows 容器)。
Dockerfile / 镜像构建
Dockerfile 中常用指令?
- FROM(基础镜像)
- COPY/ADD(复制文件)
- RUN(构建时执行)
- CMD(默认命令)
- ENTRYPOINT(入口点)
- EXPOSE(声明端口,仅文档作用)
- WORKDIR(工作目录)
- ENV(环境变量)
- ARG(构建参数,构建后可丢弃)
- USER(运行用户)
- VOLUME(声明匿名卷)
- LABEL(元数据)
- HEALTHCHECK(健康检查)
- ONBUILD(被继承时触发)
CMD 和 ENTRYPOINT 的区别?
- CMD 可被
docker run命令覆盖; - ENTRYPOINT 不可覆盖,适合固定执行程序。
- 两者可组合使用:ENTRYPOINT 定可执行文件,CMD 提供默认参数。
ADD 和 COPY 的区别?
- COPY 只复制本地文件;
- ADD 支持自动解压 tar 和远程 URL 下载。
- 官方推荐用 COPY(行为更可预期)。
RUN、CMD、ENTRYPOINT 的区别?
- RUN 在构建时执行并把结果固化进镜像层;
- CMD/ENTRYPOINT 在容器启动时执行,写进镜像元数据,只有最后一条生效。
RUN 命令 shell 形式和 exec 形式的区别?
RUN apt-get update(shell)等价于/bin/sh -c "...",能使用变量和管道,但会多一层 shell 进程;RUN ["apt-get","update"](exec)直接执行,PID 1 就是目标进程,能正确接收信号。- CMD/ENTRYPOINT 推荐用 exec 形式,否则
docker stop的 SIGTERM 会被 sh 吞掉,导致容器等 10 秒后被杀(退出码 137)。
ARG 和 ENV 的区别?
- ARG 只在构建期有效,容器运行时拿不到(可用
--build-arg传入,不应放敏感信息,docker history能看到); - ENV 会写入镜像元数据,构建期和运行期都可见。
如何减小 Docker 镜像体积 / 加速镜像构建?
- .dockerignore 排除非必要文件(
.git、node_modules); - 使用轻量级基础镜像(如 alpine/slim、distroless);
- 编译型语言采用多阶段构建;
- 合并 RUN 命令减少层数;
- 清理 apt/pip/npm 缓存(且必须与安装写在同一层,否则删除只是标记,体积不减);
- 把变化频率低的指令放前面,充分利用层缓存;
- BuildKit 的
--mount=type=cache缓存包管理器目录。
多阶段构建的好处? 最终镜像只保留运行时所需文件,不包含编译工具和中间产物,大幅减小镜像体积,同时降低攻击面(工具链不进生产镜像)。
镜像分层存储的好处? 共享和缓存——多个镜像共享同一基础层只存一份;构建时缓存不变层,加快构建速度。写时容器只在其上叠加一个可写层(Copy-on-Write),多个容器可共享同一个只读镜像层。
镜像构建缓存机制? Docker 逐条指令比对缓存,某条指令的命令文本、父层、以及 COPY/ADD 的文件内容校验和任一变化,该层及其后续所有层缓存全部失效,需要重新构建。所以:
- 先把
package.json复制进去装依赖,再复制源码,改动代码时不会重装依赖; --no-cache强制不用缓存,--cache-from复用 CI 中拉取的镜像缓存。
HEALTHCHECK 的作用?
在容器内定时执行命令检查应用是否健康(docker ps 显示 healthy/unhealthy),仅状态标记,Docker 本身不会自动重启 unhealthy 容器(这正是 K8s 探针要解决的问题)。
网络与存储
如何让容器随 Docker 服务自动重启?
--restart=always 或 --restart=unless-stopped,还有 on-failure[:max-retries]。注意 docker stop 手动停止的容器在 always 下仍会随 daemon 启动。
Docker 数据持久化方式?
Volume(docker volume create)、Bind Mount(挂载宿主机目录)、tmpfs(内存中)。
Volume 和 Bind Mount 的区别?
- Volume:由 Docker 管理,存放在
/var/lib/docker/volumes/,跨平台、可备份/迁移、可用 volume driver 接 NFS;适合生产数据。 - Bind Mount:直接映射宿主机任意路径,路径依赖宿主机结构,权限与 SELinux 容易出问题;适合开发时挂代码、挂配置文件。
- 两者用
docker inspect的 Mounts 字段区分(Type 为 volume 或 bind)。 - 容器内数据不持久化的场景:不挂载时数据留在容器可写层,容器删除即丢失。
容器网络隔离方式? 存在以下网络模式:
- Bridge 模式:默认模式,容器通过虚拟网桥 docker0 通信,拥有独立的网络命名空间。
- Host 模式:容器与宿主机共享网络命名空间,直接使用宿主机的 IP 和端口,性能较高但缺乏隔离。
- None 模式:容器没有网络配置,适用于无需网络的场景。
- Container 模式:多个容器共享一个网络命名空间,适合需要紧密协作的容器。
- Overlay / Macvlan:跨主机通信(Swarm)或让容器直接获得物理网段 IP。
容器之间如何互相通信?
默认 bridge 网络下只能用 IP 互访,且没有 DNS 解析。推荐自定义 bridge 网络:docker network create app-net,加入同一网络的容器可直接用容器名 / --network-alias 作为主机名互访(Docker 内置 DNS 127.0.0.11)。
端口映射的原理?
-p 8080:80 由 docker-proxy 与 iptables 的 DNAT 规则实现,把宿主机 8080 的流量转发到容器 IP 的 80 端口。-P 随机映射所有 EXPOSE 端口,-p 127.0.0.1:8080:80 只监听本机。
容器
如何进入容器?
docker exec -it <容器> /bin/sh # 推荐:新开一个进程进入docker attach <容器> # 进入主进程的标准输入输出(Ctrl+C 会杀掉容器)docker exec -it -u root <容器> bash # 指定用户exec 会在容器内新起一个进程(需镜像里有 shell,distroless 镜像没有);调试特权容器还可用 nsenter -t <PID> -a 直接进入其命名空间(宿主机上无需容器内有工具)。
exec 和 attach 的区别?
- exec 启动一个新的进程,退出不影响主进程;
- attach 接管容器 PID 1 的标准流,退出(Ctrl+C)可能终止容器,且多个 attach 会共享输出。
如何查看容器日志?
docker logs -f --tail 100 <容器> # 实时跟踪最近 100 行(-f 实时跟踪)docker logs --since 10m <容器> # 最近 10 分钟docker logs --timestamps <容器>日志来自容器主进程的 stdout/stderr,由 json-file 日志驱动落盘到 /var/lib/docker/containers/<id>/*-json.log。默认无大小限制,容易把磁盘写满,需配置 --log-opt max-size=10m --log-opt max-file=3(可在 /etc/docker/daemon.json 全局设置)。应用应把日志打到前台而不是文件里,这是容器的最佳实践。
如何查看容器信息与资源占用?
docker ps -a(状态与端口)docker inspect(完整 JSON,含 IP、挂载、退出码)docker stats(实时 CPU/内存/网络)docker top(容器内进程)docker diff(可写层的文件变更)
容器退出码代表什么?
0 正常退出;1 应用错误;126/127 命令不可执行/不存在;137 = 128+9 被 SIGKILL(OOM 或 docker kill);143 = 128+15 被 SIGTERM(docker stop 优雅停止)。
镜像仓库
如何推送镜像到仓库?
docker login → docker tag app:1.0 registry.example.com/ns/app:1.0(必须带仓库地址前缀)→ docker push。拉取时同理,私有仓库需先登录或配置凭证。
镜像的命名规则?
[registry/][namespace/]repository[:tag][@digest],tag 默认 latest。生产禁止依赖 latest,因为它是可变的,不同节点可能拉到不同版本;用不可变 tag(版本号或 commit id)或 digest 保证一致性。
如何减小镜像拉取时间? 使用更小的基础镜像、减少层数、把 registry 部署在内网、开启镜像加速器、使用 P2P 分发(Dragonfly)或按需拉取(懒加载)。
Docker Compose
Docker Compose 是什么?
单机多容器编排工具,用一个 docker-compose.yml 声明多个服务、网络、卷和依赖关系,一条 docker compose up -d 全部拉起。适合本地开发与小型部署;跨主机的生产编排应使用 Kubernetes/Swarm。
Compose 常用命令?
up -d(后台启动)、down(停止并删除)、ps、logs -f、exec、build、restart、config(校验并渲染最终配置)。
Compose 中 depends_on 能保证依赖服务就绪吗?
不能,它只保证启动顺序。要等待服务真正可用需配合 healthcheck + condition: service_healthy,或在应用内做重试。
常用命令
# 镜像docker build -t app:1.0 . # 构建docker images / docker image prune # 列出 / 清理悬空镜像docker tag / push / pull # 打标签与仓库交互
# 容器docker run -d --name web -p 8080:80 -v data:/usr/share/nginx/html nginxdocker ps -a / start / stop / restart / rmdocker cp file.txt web:/tmp/ # 与容器互拷文件docker commit web web:snapshot # 把容器存成镜像(不推荐,应用 Dockerfile)
# 清理docker system df # 查看磁盘占用docker system prune -a --volumes # 清理未使用镜像/网络/卷docker run 常用参数?
-d 后台运行、-it 交互式 TTY、--name 命名、-p 端口映射、-v 挂载、-e 环境变量、--network 网络、--restart 重启策略、--rm 退出即删除、--memory/--cpus 资源限制、--privileged 特权、-w 工作目录、--entrypoint 覆盖入口。
底层原理
Docker 使用的底层技术? Namespaces(隔离:PID、NET、IPC、UTS、MNT、USER)、Cgroups(资源限制:CPU、内存)、UnionFS(分层存储)。
各个 Namespace 隔离了什么?
- PID:进程号,容器内 PID 1 是应用进程;
- NET:网卡、IP、端口、路由表;
- IPC:信号量、共享内存;
- UTS:主机名与域名;
- MNT:挂载点与文件系统视图;
- USER:用户与 UID 映射;
- Cgroup:cgroup 视图(较新)。
Cgroups 的作用?
限制、统计和隔离进程组的资源使用(CPU、内存、IO、PID 数量)。CPU 用 quota/period(--cpus)或 shares(--cpu-shares 相对权重);内存超限触发 OOM Killer,容器退出码 137。
Docker 的分层架构(与 containerd / runc / OCI 的关系)?
dockerd(接收 CLI 请求、构建、镜像管理)→ containerd(容器生命周期管理,K8s 也直接用它)→ runc(真正调用 Linux namespace/cgroup 创建进程)。镜像格式遵循 OCI Image Spec,运行时遵循 OCI Runtime Spec,因此 Docker 构建的镜像可以被 containerd、CRI-O、Podman 通用。Docker 1.24 之后 K8s 抛弃的是 dockershim 适配层,不是容器本身。
UnionFS 是如何工作的? 采用写时复制(CoW):镜像由多个只读层叠加而成,容器启动时在最上面追加一个可写层;读文件时从上层往下找,修改文件时先把文件复制到可写层再改,因此多个容器共享一份只读镜像层,启动快、占用小。Docker 默认存储驱动为 overlay2。
容器的 1 号进程与信号处理?
容器 PID 1 就是应用进程(exec 形式时),它需要负责回收僵尸进程、处理 SIGTERM 优雅退出。若应用不处理信号或使用 shell 形式启动,Ctrl+C/docker stop 会失效或超时后被 SIGKILL(10 秒 grace period 可用 --stop-timeout 调整)。
安全与排错
容器为什么不安全?如何加固?
- 共享内核:内核漏洞可逃逸;应保持内核与 Docker 版本更新。
- 默认 root 运行:用
USER指定非 root、--user覆盖、开启 user namespace。 - 权限:避免
--privileged,按需--cap-drop=ALL --cap-add=NET_BIND_SERVICE,使用--security-opt no-new-privileges、只读根文件系统--read-only。 - 敏感信息:不要把密钥写进镜像层(
docker history可见),用环境变量、secret 文件或外部密钥管理。 - 其他:AppArmor/SELinux 配置、限制资源防 DoS、镜像扫描(Trivy)与最小化基础镜像。
容器启动失败怎么排查?
docker ps -a看状态与退出码,docker logs看应用日志;docker inspect看 State.Error、OOMKilled、挂载与端口;docker run -it --entrypoint /bin/sh image进镜像手工验证命令与文件是否存在;- 端口冲突(
netstat -tulnp)、磁盘满(docker system df)、权限问题(挂载目录 SELinux 需:z)。
常见问题:磁盘被容器占满怎么办?
先 docker system df -v 定位,再清理:docker container prune、docker image prune -a、docker volume prune、docker builder prune;同时配置日志 max-size、给容器日志与数据盘单独分区。
为什么容器里不能用 systemd / systemctl?
容器只运行一个前台进程,没有 init 系统;应把每个服务拆成独立容器,或用 supervisor 管理多进程(不推荐),确需时用 --privileged + systemd 镜像(脏且不安全)。
Docker 与 Kubernetes 的关系? Docker 是单机容器引擎,K8s 是集群编排系统;K8s 早期通过 dockershim 调用 Docker,1.24 起改为直接用 CRI 对接 containerd/CRI-O,镜像标准(OCI)保持通用。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
最后更新于 ,距今已过 27 天
部分内容可能已过时