Docker命令大全:从入门到入土(不是),从懵懂到精通!一篇搞定所有骚操作 🐳
副标题: 包含原理剖析、避坑血泪史、Java实战、面试直通车,让你的容器之旅笑中带泪,泪中带光!
引言: 还记得当年部署应用的噩梦吗?环境配置玄学、依赖冲突、"在我机器上好好的"... 直到Docker这位"集装箱船长"出现,它说:"把应用和它的整个世界打包,扔到任何地方都能跑!" 🥳 今天,我们就来深扒这位船长的航海日志------Docker命令,让你从Docker小白变身掌舵老司机!准备好你的终端,系好安全带,我们出发!💨
一、 Docker简介:集装箱革命,打包你的整个世界
想象一下,你是一个乐高大师。你想把你的精美乐高城堡(你的Java应用)送给远方的朋友(生产服务器)。直接寄?零件散落一地,朋友还缺几块特殊积木(特定库),悲剧!💔
Docker的解决方案是:给你一个标准化的乐高收纳箱(容器)! 这个箱子不仅装着你的城堡(应用代码),还装着搭建说明书(运行时环境、系统工具、库、配置)。朋友收到箱子,按一下按钮(docker run
),城堡瞬间完美呈现!✨
- 镜像(Image): 这个"乐高收纳箱"的蓝图 或只读模板 。它定义了箱子里装什么、怎么装。比如
ubuntu:20.04
,openjdk:11-jdk
,mysql:8.0
。镜像是分层的,像千层蛋糕,每一层是一个修改,共享层节省空间。 - 容器(Container): 根据镜像运行起来的实例。就是那个活生生的、正在运行的"乐高城堡"。你可以启动、停止、删除多个城堡(容器),它们都基于同一个蓝图(镜像)。容器是轻量级、隔离的沙箱环境。
- 仓库(Registry): 存放镜像的超级大货架 。最著名的是Docker Hub (
docker.io
),就像乐高的官方仓库。你也可以自建私有仓库(如Harbor)。
核心价值:
- 一致性: 开发、测试、生产环境完全一致,"在我机器上能跑"成为历史!
- 隔离性: 应用互不干扰,资源可控。
- 便携性: 一次构建,到处运行(只要有Docker引擎)。
- 高效性: 轻量、快速启动、资源利用率高。
- 可重复性: 通过Dockerfile定义构建过程,完全可追溯。
二、 Docker命令详解:船长の航海手册 🧭
Docker命令结构通常为:docker [OPTIONS] COMMAND [ARG...]
。我们按功能分类讲解,附带骚气比喻。
🛠 1. 生命周期管理 (容器的生老病死)
-
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
- 启航!创建并启动一个新容器- 核心功能: 根据指定镜像创建一个新容器并运行它。这是最常用的命令。
- 常用OPTIONS:
-d, --detach
: 后台运行容器(Detached mode)。你的城堡在后台默默搭建。-it
: 组合拳!-i
(保持STDIN打开) +-t
(分配伪终端)。用于交互式容器 ,比如进入一个Bash shell。docker run -it ubuntu:20.04 /bin/bash
- 直接进入Ubuntu容器的bash。想退出?输入exit
或Ctrl+D
。--name
: 给容器起个响亮(或沙雕)的名字,方便后续操作。docker run -d --name my_awesome_container nginx
。-p [HOST_PORT]:[CONTAINER_PORT]
: 端口映射 。把主机(HOST)的端口映射到容器的端口。城堡(容器)的80号门(端口)对应到码头(主机)的8080号门。docker run -d -p 8080:80 nginx
访问http://localhost:8080
就看到Nginx欢迎页。-v [HOST_DIR]:[CONTAINER_DIR]:[OPTIONS]
: 卷挂载 。把主机上的一个目录/文件挂载到容器里。持久化和共享数据的关键! 避免城堡(容器)被拆(删除)后,里面的宝藏(数据)也消失。常用选项:ro
(只读),:rw
(读写,默认)。docker run -d -v /path/on/host:/path/in/container:ro nginx
(主机目录 -> 容器目录,只读)docker run -d -v my_volume_name:/path/in/container mysql
(使用命名卷my_volume_name
)
--rm
: 容器停止后自动删除它。适合跑一次性任务,比如临时编译。-e, --env KEY=VALUE
: 设置环境变量 。给城堡里传递秘密纸条(配置信息)。docker run -d -e "MYSQL_ROOT_PASSWORD=mysecretpassword" mysql:8.0
。--network
: 指定容器使用的网络(默认是bridge
)。让城堡之间可以互相拜访。--restart
: 重启策略。no
(默认,不重启),on-failure
(失败时重启),always
(总是重启),unless-stopped
(除非手动停止,否则总是重启)。保证你的城堡在服务器重启后也能自动站起来!
- 例子:
docker run -d --name my_web -p 8080:80 -v /home/user/html:/usr/share/nginx/html nginx:latest
- 后台运行Nginx,主机8080映射容器80,挂载主机HTML目录到容器网页目录。
-
docker start [OPTIONS] CONTAINER [CONTAINER...]
- 唤醒沉睡的巨人- 启动一个或多个已停止 的容器。
docker start my_awesome_container
。
- 启动一个或多个已停止 的容器。
-
docker stop [OPTIONS] CONTAINER [CONTAINER...]
- 温柔地停下(发送SIGTERM)- 停止一个或多个运行中 的容器。允许容器进行清理工作。
docker stop my_awesome_container
。等不及?可以配合-t
(超时时间,默认10秒) 或者直接用docker kill
。
- 停止一个或多个运行中 的容器。允许容器进行清理工作。
-
docker restart [OPTIONS] CONTAINER [CONTAINER...]
- 重启大法好- 重启一个或多个容器。相当于
stop
+start
。docker restart my_awesome_container
。
- 重启一个或多个容器。相当于
-
docker kill [OPTIONS] CONTAINER [CONTAINER...]
- 强制关机(发送SIGKILL)- 强制立即停止一个或多个运行中的容器。不给清理的机会,简单粗暴。
docker kill my_unresponsive_container
。
- 强制立即停止一个或多个运行中的容器。不给清理的机会,简单粗暴。
-
docker rm [OPTIONS] CONTAINER [CONTAINER...]
- 打扫房间,移除容器- 删除一个或多个已停止 的容器(除非用
-f
强制删除运行中的)。 - 常用OPTIONS:
-f, --force
: 强制删除运行中的容器(先SIGKILL)。-v, --volumes
: 同时删除容器挂载的匿名卷(不会删除命名卷或主机绑定挂载)。避免留下数据垃圾。
- 例子:
docker rm old_container
(删除停止的),docker rm -fv stubborn_container
(强制删除运行中的并清理匿名卷)。 - 清理大法:
docker container prune
- 一键删除所有已停止的容器!爽快!docker rm $(docker ps -aq)
- 经典组合拳,删除所有容器(包括运行中的,慎用!)。
- 删除一个或多个已停止 的容器(除非用
-
docker pause/unpause CONTAINER [CONTAINER...]
- 时间暂停/恢复术pause
: 暂停容器内所有进程。冻结城堡时间。unpause
: 恢复被暂停的容器。解冻。
-
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
- 进入城堡内部视察- 核心功能: 在正在运行的容器内部执行命令。超级有用!用于调试、查看日志、执行管理任务。
- 常用OPTIONS: 同
run
的-it
(进入交互式shell),-d
(后台执行命令),-e
(设置环境变量)。 - 例子:
docker exec -it my_mysql_container bash
- 进入MySQL容器的Bash shell。docker exec my_web_container ls /app
- 在Web容器中执行ls /app
命令并返回结果。docker exec -d my_app_container touch /tmp/upgrade.lock
- 在后台于App容器中创建一个文件。
-
docker logs [OPTIONS] CONTAINER
- 偷看城堡日记(日志)- 获取容器的日志输出。调试必备!
- 常用OPTIONS:
-f, --follow
: 实时跟踪日志输出(类似tail -f
)。盯着城堡的实时动态。--tail N
: 显示最后N行日志(默认所有)。docker logs --tail 100 my_container
。-t, --timestamps
: 显示时间戳。知道城堡什么时候发生了什么事。
- 例子:
docker logs -f -t my_app
- 实时带时间戳查看应用日志。
📦 2. 镜像管理 (蓝图的获取与制作)
-
docker images [OPTIONS] [REPOSITORY[:TAG]]
- 查看我的蓝图仓库- 列出本地存储的Docker镜像。
- 常用OPTIONS:
-a, --all
(显示所有镜像,包括中间层),-q, --quiet
(只显示镜像ID)。 - 例子:
docker images
,docker images ubuntu
,docker images --format "{{.ID}}: {{.Repository}}"
(自定义格式输出)。
-
docker pull [OPTIONS] NAME[:TAG|@DIGEST]
- 从远方仓库搬蓝图- 从仓库(默认Docker Hub)下载镜像到本地。
TAG
不指定默认为latest
(但强烈建议生产环境指定具体版本!)。 - 例子:
docker pull ubuntu:20.04
,docker pull myprivate.registry.com:5000/myapp:1.2.3
.
- 从仓库(默认Docker Hub)下载镜像到本地。
-
docker push [OPTIONS] NAME[:TAG]
- 把我的杰作蓝图分享出去- 将本地的镜像推送到仓库。需要先
docker login
登录有权限的仓库。 - 例子:
docker push myusername/myapp:latest
(推送到Docker Hub),docker push myprivate.registry.com:5000/myapp:1.2.3
.
- 将本地的镜像推送到仓库。需要先
-
docker rmi [OPTIONS] IMAGE [IMAGE...]
- 清理过时的蓝图- 删除本地的一个或多个镜像。如果镜像有容器(即使停止)依赖它,需要先删除容器或使用
-f
强制删除(可能导致依赖的容器出问题)。 - 清理大法:
docker image prune
- 删除所有未被容器使用的悬空镜像(<none>
)。docker image prune -a
- 危险! 删除所有未被任何容器使用的镜像(慎用!可能删掉基础镜像)。
- 删除本地的一个或多个镜像。如果镜像有容器(即使停止)依赖它,需要先删除容器或使用
-
docker build [OPTIONS] PATH | URL | -
- 自己动手画蓝图!(Dockerfile构建)- 核心功能: 根据
Dockerfile
和构建上下文(PATH
或URL
指定目录下的文件)构建一个新的镜像。定制化你的城堡! - 常用OPTIONS:
-t, --tag NAME[:TAG]
: 为构建的镜像指定仓库名和标签。非常重要!docker build -t myapp:1.0 .
(最后的.
代表当前目录是构建上下文)。-f, --file PATH
: 指定使用的Dockerfile路径(默认是上下文路径下的Dockerfile
)。--build-arg NAME[=VALUE]
: 设置构建时的变量(在Dockerfile中用ARG
定义)。--no-cache
: 构建时不使用缓存。确保每次都是全新构建。--target STAGE
: 多阶段构建时,指定构建到哪个阶段。
- 构建上下文: Docker客户端会将
PATH
或URL
指定的整个目录(及其子目录)打包发送给Docker守护进程。务必注意.dockerignore
文件 来排除不需要的文件(如node_modules
,.git
),避免发送巨大上下文拖慢构建!
- 核心功能: 根据
🔍 3. 信息查看与监控 (瞭望塔)
-
docker ps [OPTIONS]
- 看看码头上有哪些城堡在运行/休息- 列出容器。
- 常用OPTIONS:
-a, --all
: 显示所有容器(默认只显示运行中的)。-q, --quiet
: 只显示容器ID。常用于管道操作docker stop $(docker ps -aq)
。--filter, -f
: 根据条件过滤。status=running/exited
,name=myapp
,ancestor=ubuntu
(基于某个镜像) 等。docker ps -f "status=exited"
。--format
: 自定义输出格式。docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}"
。-s, --size
: 显示容器占用的总磁盘空间(包括可写层和日志)。
- 例子:
docker ps -a
,docker ps -f name=web
.
-
docker inspect [OPTIONS] NAME|ID [NAME|ID...]
- X光透视城堡/蓝图- 获取容器或镜像的详细底层信息(JSON格式)。配置、网络设置、卷挂载、状态、日志路径...无所不包!调试神器。
- 常用OPTIONS:
--format
或-f
使用Go模板提取特定信息。docker inspect --format='{{.NetworkSettings.IPAddress}}' my_container
(获取容器IP),docker inspect --format='{{.Config.Image}}' my_container
(获取容器使用的镜像ID)。
-
docker stats [OPTIONS] [CONTAINER...]
- 城堡资源消耗仪表盘- 实时显示一个或多个容器的资源使用统计(CPU、内存、网络IO、块IO)。性能监控必备。
Ctrl+C
退出。docker stats
(所有运行中容器),docker stats my_container1 my_container2
.
- 实时显示一个或多个容器的资源使用统计(CPU、内存、网络IO、块IO)。性能监控必备。
-
docker top CONTAINER [ps OPTIONS]
- 看看城堡里的人在干嘛(进程)- 显示容器内运行的进程信息。相当于在容器内运行
ps
命令。docker top my_container
,docker top my_container aux
.
- 显示容器内运行的进程信息。相当于在容器内运行
-
docker diff CONTAINER
- 城堡和最初蓝图有啥不同?- 检查容器文件系统相对于其镜像的更改(A-Add, D-Delete, C-Change)。查看可写层的变化。
docker diff my_container
.
- 检查容器文件系统相对于其镜像的更改(A-Add, D-Delete, C-Change)。查看可写层的变化。
🌐 4. 网络管理 (城堡间的道路)
-
docker network ls
- 查看所有道路网(网络)- 列出Docker引擎管理的所有网络。
bridge
,host
,none
是默认网络。
- 列出Docker引擎管理的所有网络。
-
docker network create [OPTIONS] NETWORK
- 修建新的专用道路- 创建一个新的Docker网络。可以指定驱动(
bridge
默认,overlay
用于Swarm集群)、子网、网关等。docker network create my_app_net
.
- 创建一个新的Docker网络。可以指定驱动(
-
docker network inspect [OPTIONS] NETWORK [NETWORK...]
- 查看道路详情- 显示一个或多个网络的详细信息。
docker network inspect bridge
.
- 显示一个或多个网络的详细信息。
-
docker network connect [OPTIONS] NETWORK CONTAINER
- 把城堡接入道路网- 将一个运行中的容器连接到一个网络。
docker network connect my_app_net my_web_container
.
- 将一个运行中的容器连接到一个网络。
-
docker network disconnect [OPTIONS] NETWORK CONTAINER
- 让城堡离开道路网- 断开容器与一个网络的连接。
docker network disconnect bridge my_container
(慎用,可能断网)。
- 断开容器与一个网络的连接。
-
docker network prune
- 清理废弃道路- 删除所有未被容器使用的自定义网络。
💾 5. 数据管理 (城堡的金库 - 卷)
-
docker volume ls
- 查看所有金库(卷)- 列出Docker管理的所有卷(命名卷)。
-
docker volume create [OPTIONS] [VOLUME]
- 新建一个金库- 创建一个新的命名卷。可以指定驱动(
local
默认)、标签等。docker volume create my_db_data
.
- 创建一个新的命名卷。可以指定驱动(
-
docker volume inspect [OPTIONS] VOLUME [VOLUME...]
- 查看金库详情- 显示一个或多个卷的详细信息。
docker volume inspect my_db_data
(查看卷在主机上的实际挂载点)。
- 显示一个或多个卷的详细信息。
-
docker volume rm [OPTIONS] VOLUME [VOLUME...]
- 拆除金库- 删除一个或多个卷。确保没有容器在使用它!
docker volume rm old_volume
.
- 删除一个或多个卷。确保没有容器在使用它!
-
docker volume prune
- 清理废弃金库- 删除所有未被容器使用的卷。危险!会删除数据!
🔧 6. 其他实用命令
-
docker login [SERVER]
/docker logout [SERVER]
- 登录/登出远方仓库- 认证以便拉取私有镜像或推送镜像。
docker login
,docker login myprivate.registry.com
.
- 认证以便拉取私有镜像或推送镜像。
-
docker version
- 查看船长(Docker)的版本号- 显示Docker客户端和服务端的版本信息。
-
docker info
- 查看码头(Docker系统)的整体信息- 显示Docker系统的详细信息(容器数、镜像数、存储驱动、操作系统、内核版本、CPU、内存等)。诊断全局问题。
-
docker system df
- 查看码头仓库和道路占了多少地儿(磁盘使用)- 显示Docker磁盘使用情况概览(镜像、容器、卷、构建缓存)。
-
docker system prune [OPTIONS]
- 大规模清理码头(慎用!)- 一键删除所有停止的容器、所有未被使用的网络、所有悬空镜像、所有构建缓存。
-a
选项还会删除所有未被容器使用的镜像(非常危险!)。--volumes
会删除所有未被容器使用的卷(极其危险!会丢数据! )。使用前务必三思!确认!备份!
- 一键删除所有停止的容器、所有未被使用的网络、所有悬空镜像、所有构建缓存。
三、 Java实战案例:打包Spring Boot应用 🍃
理论讲得够多了,是时候动手了!我们来打包一个最简单的Spring Boot应用。
1. 准备Spring Boot应用
创建一个最简单的Spring Boot项目 (例如使用 start.spring.io
),只有一个REST端点:
java
// src/main/java/com/example/dockerdemo/DemoApplication.java
package com.example.dockerdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@RestController
class HelloController {
@GetMapping("/")
public String hello() {
return "Hello from Dockerized Spring Boot! 🐳";
}
}
}
确保应用能在本地运行 (mvn spring-boot:run
或运行 DemoApplication
main方法) 并访问 http://localhost:8080
看到消息。
2. 编写Dockerfile (蓝图)
在项目根目录 (与 pom.xml
同级) 创建文件 Dockerfile
:
dockerfile
# 使用官方OpenJDK 17基础镜像 (Alpine Linux版本,更小巧)
FROM openjdk:17-jdk-alpine
# 维护者信息 (可选)
LABEL maintainer="your.email@example.com"
# 在容器内设置工作目录,后续命令都在此目录执行
WORKDIR /app
# 将Maven构建的JAR文件复制到容器内的当前目录 (WORKDIR/app)
# 注意:这里假设JAR文件名为 'docker-demo-0.0.1-SNAPSHOT.jar'
# 更通用的做法是使用maven插件自动生成带版本或固定名字的JAR,或者在构建命令中指定
COPY target/docker-demo-0.0.1-SNAPSHOT.jar app.jar
# 暴露应用端口 (Spring Boot默认8080)
EXPOSE 8080
# 设置时区 (Alpine镜像需要安装tzdata)
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata
# 启动命令:使用 exec java ... 形式,使Java进程成为PID 1,能接收信号如SIGTERM
ENTRYPOINT ["exec", "java", "-jar", "app.jar"]
关键点解释:
FROM
: 基于轻量级openjdk:17-jdk-alpine
镜像。WORKDIR
: 设置工作目录/app
。COPY
: 将主机上构建好的JAR文件 (target/docker-demo-0.0.1-SNAPSHOT.jar
) 复制到容器内的/app
目录并重命名为app.jar
。注意:这里依赖本地构建。更好的CI/CD流程中,构建JAR和构建Docker镜像通常在一个阶段完成。EXPOSE
: 声明容器监听的端口是8080。RUN
: 安装时区数据,设置容器时区为上海,然后删除安装包以精简镜像。这是Alpine Linux的写法,Ubuntu等基础镜像写法不同。ENTRYPOINT
: 使用exec
形式启动Java应用,确保Java进程是容器内的主进程(PID 1),能正确接收停止信号(SIGTERM
),实现优雅关闭。["java", "-jar", "app.jar"]
也是常见写法,但缺少exec
可能导致信号传递问题。
3. 构建Docker镜像
在项目根目录执行:
bash
# 1. 首先确保应用已构建,生成JAR文件
mvn clean package
# 2. 构建Docker镜像 (-t 指定镜像标签,最后的 . 代表构建上下文是当前目录)
docker build -t my-spring-boot-app:1.0 .
构建过程会按Dockerfile步骤执行。成功后,用 docker images
查看 my-spring-boot-app:1.0
。
4. 运行Docker容器
bash
# 后台运行 (-d),命名为 myapp,主机8080端口映射容器8080端口
docker run -d --name myapp -p 8080:8080 my-spring-boot-app:1.0
5. 访问应用
打开浏览器访问 http://localhost:8080
,你应该看到 "Hello from Dockerized Spring Boot! 🐳"。
6. 查看日志 & 停止
bash
# 查看实时日志
docker logs -f myapp
# 停止容器
docker stop myapp
# 停止后可以删除
docker rm myapp
7. (可选) 使用多阶段构建优化镜像
上面的镜像包含了JDK,对于仅运行JAR的应用,我们可以使用更小的JRE基础镜像。修改 Dockerfile
:
dockerfile
# 第一阶段:构建 (使用JDK)
FROM openjdk:17-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN ./mvnw clean package -DskipTests # 假设使用Maven Wrapper
# 第二阶段:运行 (使用JRE)
FROM openjdk:17-jre-alpine
WORKDIR /app
# 从builder阶段复制构建好的JAR文件
COPY --from=builder /app/target/docker-demo-0.0.1-SNAPSHOT.jar app.jar
# 设置时区 (同上)
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata
EXPOSE 8080
ENTRYPOINT ["exec", "java", "-jar", "app.jar"]
重新构建 (docker build -t my-spring-boot-app:1.0-optimized .
),你会发现最终镜像大小显著减小!
四、 原理剖析:Docker如何实现魔法? 🧙
理解了命令,让我们掀开Docker的魔法袍,看看它底层的秘密武器:
-
Linux Namespaces (命名空间) - 隔离的根基:
- PID Namespace: 每个容器有自己的进程树 (PID 1是容器内进程),看不到主机和其他容器的进程。
- Network Namespace: 每个容器有自己的网络栈 (IP地址、端口、路由表、防火墙规则),虚拟网卡。
- Mount Namespace: 每个容器看到自己独立的文件系统挂载点视图。
/
是它自己的根,看不到主机的/
。 - UTS Namespace: 每个容器有自己的主机名和域名 (
hostname
/domainname
)。 - IPC Namespace: 隔离进程间通信 (如信号量、消息队列、共享内存),容器间IPC需特殊配置。
- User Namespace (可选): 映射容器内的UID/GID到主机上不同的(通常非特权)UID/GID,提升安全性。
-
Control Groups (cgroups) - 资源的缰绳:
- 限制、记录和隔离进程组(如容器)使用的物理资源:
- CPU (份额、核绑定)
- 内存 (限制、软硬限制、Swap)
- 磁盘 I/O (带宽、IOPS)
- 网络带宽
- 防止某个容器耗尽系统资源。
- 限制、记录和隔离进程组(如容器)使用的物理资源:
-
Union File Systems (联合文件系统) - 镜像分层的奥秘:
- OverlayFS (主流): 将多个目录(层)联合挂载到单一视图。
- 镜像层: 只读层。基础镜像(如Ubuntu)是一层,
RUN apt-get update
添加新层,COPY
添加新层。每一层只存储与下层的差异。 - 容器层: 最顶层的可写层。所有对容器文件系统的修改(创建、修改、删除文件)都发生在这里。当删除下层文件时,OverlayFS 会在可写层创建一个"白化"(whiteout)文件来隐藏它。
- 好处: 共享基础层节省存储空间;分层利于复用和快速分发(只传变化的层);Copy-on-Write (写时复制) 高效。
-
Container Runtime Interface (CRI) - 运行时的抽象:
- Docker 使用
containerd
作为其核心容器运行时。containerd
管理容器的生命周期(创建、启动、停止、删除)、镜像管理、存储、网络等低层操作。 runc
是containerd
默认使用的符合 OCI (Open Container Initiative) 规范的轻量级容器运行时工具,它直接调用操作系统底层API (namespaces, cgroups) 来运行容器。
- Docker 使用
启动一个容器的简化流程:
docker run
命令发送给 Docker 守护进程 (dockerd
)。dockerd
解析命令,检查本地是否有镜像,没有则拉取(docker pull
)。dockerd
通过containerd
API 请求创建容器。containerd
准备容器运行时环境(设置 namespaces, cgroups 参数,准备 rootfs - 联合挂载镜像层 + 初始化可写层)。containerd
调用runc
来实际创建和启动容器进程。runc
使用操作系统调用 (clone
,unshare
等) 创建具有指定 namespaces 的进程,应用 cgroups 限制,设置 rootfs,然后执行容器定义的ENTRYPOINT
/CMD
。- 容器进程启动并运行。
五、 Docker vs. 传统虚拟机:轻量化的胜利 ⚖️
特性 | Docker (容器) | 传统虚拟机 (VM) |
---|---|---|
虚拟化级别 | 操作系统级 | 硬件级 (Hypervisor) |
隔离性 | 进程级隔离 (Namespaces/cgroups) | 强隔离 (完整操作系统沙箱) |
启动速度 | 秒级 (毫秒级) | 分钟级 |
性能损耗 | 极小 (接近原生) | 较大 (CPU/内存/IO穿透开销) |
资源占用 | 极小 (共享内核,MB级) | 较大 (独占OS,GB级) |
系统要求 | 需与宿主机内核兼容 | 不依赖宿主机OS (只要Hypervisor) |
镜像大小 | 小 (MB ~ 百MB) | 大 (GB级) |
打包内容 | 应用 + 依赖库 + 环境 | 整个OS + 应用 + 依赖库 + 环境 |
部署密度 | 高 | 低 |
安全性 | 较弱 (共享内核攻击面大) | 较强 (硬件隔离) |
典型代表 | Docker, containerd, Podman | VMware, VirtualBox, KVM |
总结: Docker容器在轻量、快速、高效 方面完胜,特别适合微服务、CI/CD、弹性伸缩场景。VM在强隔离、运行异构OS、遗留应用兼容性上仍有优势。两者常结合使用(容器运行在VM里)。
六、 避坑指南:血泪教训换来的经验 😭
-
TAG
永远不要只依赖latest
!- 坑:
docker pull nginx
默认拉nginx:latest
。今天拉的和明天拉的可能是不同版本!导致生产环境行为不一致。 - 避坑: 生产环境务必指定具体版本号!
docker pull nginx:1.23.4-alpine
。在Dockerfile的FROM
和docker run
时都明确版本。
- 坑:
-
容器内应用日志不输出到
STDOUT/STDERR
?- 坑:
docker logs
只能捕获写到容器STDOUT
和STDERR
的日志。如果应用把日志直接写到文件(如/app/logs/app.log
),docker logs
看不到。 - 避坑:
- 最佳实践: 配置应用将日志输出到
STDOUT/STDERR
。Spring Boot默认就做到了。 - 如果必须写文件,考虑挂载卷,并在主机上用
tail -f
看,或使用日志驱动 (docker run --log-driver
) 发送到外部系统 (Fluentd, ELK, Splunk)。
- 最佳实践: 配置应用将日志输出到
- 坑:
-
容器内进程不是 PID 1?优雅关闭失效!
- 坑: 如果容器启动命令是
sh -c 'java -jar app.jar'
,那么PID 1是sh
。当docker stop
发送SIGTERM
时,sh
收到但不会传递给java
进程,导致Java应用无法优雅关闭(处理完当前请求)。 - 避坑: 在Dockerfile中使用
ENTRYPOINT ["exec", "java", ...]
或CMD ["exec", "java", ...]
。exec
形式会替换当前进程,让java
成为PID 1。确保应用能处理SIGTERM
。
- 坑: 如果容器启动命令是
-
文件权限问题 (尤其是挂载卷)
- 坑: 容器内应用以某个用户(如
appuser
,UID=1000)运行。将主机目录(主机用户UID=1001)挂载到容器内,容器内应用可能没有权限读写该目录。 - 避坑:
- 方法1 (推荐): 在Dockerfile中创建用户并指定
USER
,且确保该用户的UID与主机上运行Docker的用户UID一致(或主机目录权限对Docker用户开放)。 - 方法2: 在
docker run
时使用-u
指定运行容器的UID。docker run -u $(id -u):$(id -g) ...
。 - 方法3: 放宽主机目录权限(不推荐,安全性差)。
- 方法1 (推荐): 在Dockerfile中创建用户并指定
- 坑: 容器内应用以某个用户(如
-
时区不对!
- 坑: 容器默认使用UTC时区,日志和应用时间显示不对。
- 避坑:
- 方法1 (通用): 在Dockerfile中安装时区包并设置(如前面Java案例所示)。
- 方法2 (Linux): 启动时挂载主机时区文件
docker run -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro ...
。 - 方法3 (Docker >= 20.10): 使用
--tz
选项 (实验性)docker run --tz=Asia/Shanghai ...
。
-
docker build
上下文过大导致构建缓慢-
坑: 构建时发送整个目录(包含
node_modules
,.git
, 大文件)给Docker守护进程,非常慢且浪费。 -
避坑: 务必创建
.dockerignore
文件! 排除不必要的文件和目录。语法类似.gitignore
。bash# .dockerignore 示例 .git .idea target/ node_modules/ *.log *.bak Dockerfile* docker-compose*
-
-
容器"磁盘空间"不足?
- 坑:
docker exec my_container df -h
显示/
快满了。这通常是容器的可写层满了。 - 原因: 容器内应用写大量日志/临时文件到容器层(而不是挂载的卷);镜像层太多太大;Docker存储驱动配置问题。
- 避坑:
- 将需要大量写的目录(日志、临时文件、数据文件)挂载卷 (
-v
或--mount
)。 - 定期清理日志文件(如果挂载卷了,在主机上清理)。
- 使用
docker system prune
清理空间(注意风险)。 - 检查并可能调整Docker存储驱动和存储位置 (
/var/lib/docker
)。
- 将需要大量写的目录(日志、临时文件、数据文件)挂载卷 (
- 坑:
-
apt-get update
缓存撑大镜像-
坑: Dockerfile中
RUN apt-get update && apt-get install -y package
后,apt-get update
的缓存文件留在了镜像层,增加大小。 -
避坑: 在同一个
RUN
指令中组合update
,install
,clean
:dockerfileRUN apt-get update \ && apt-get install -y --no-install-recommends package1 package2 \ && rm -rf /var/lib/apt/lists/* # 清理缓存
-
七、 最佳实践:打造高效、安全的Docker舰船 🏆
-
使用
.dockerignore
: 如上所述,避免发送无用文件。 -
多阶段构建: 如Java案例所示,大幅减小最终镜像体积。适用于编译型语言。
-
使用特定版本的基础镜像:
FROM openjdk:17-jdk-alpine
而非FROM openjdk
。Alpine等小型基础镜像是首选。 -
最小化层数 & 合并RUN指令:
-
减少镜像层数(虽然层可共享,但过多层管理开销大)。
-
合并相关的
RUN
命令,避免创建不必要的中间层和缓存:dockerfile# 不好: RUN apt-get update RUN apt-get install -y git RUN rm -rf /var/lib/apt/lists/* # 好: RUN apt-get update \ && apt-get install -y --no-install-recommends git \ && rm -rf /var/lib/apt/lists/*
-
-
以非root用户运行容器:
-
默认容器内进程以root运行,存在安全隐患。
-
实践: Dockerfile中创建用户并切换:
dockerfileRUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser # 后续指令以此用户身份运行 WORKDIR /home/appuser # 确保用户有权限 COPY --chown=appuser:appgroup ... # 复制文件时修改属主 ENTRYPOINT ["exec", "java", "-jar", "app.jar"]
-
-
正确处理信号 & 优雅关闭:
- 使用
exec
形式让应用成为PID 1(前文已强调)。 - 确保应用能捕获
SIGTERM
(Docker stop默认发送) 和SIGINT
(Ctrl+C) 进行清理。 - Spring Boot应用默认支持优雅关闭 (
server.shutdown=graceful
+ 处理ContextClosedEvent
)。
- 使用
-
利用构建缓存:
- 将变化频率低 的指令(如安装基础工具)放在Dockerfile前面。
- 将变化频率高 的指令(如复制应用代码)放在后面。
- 这样,当代码改变时,前面的层可以利用缓存,加速构建。
-
扫描镜像安全漏洞:
- 使用
docker scan
(集成Snyk) 或Trivy、Clair等工具扫描镜像中的已知漏洞。 - 定期更新基础镜像。
- 使用
-
限制容器资源: 使用
-m/--memory
,--cpus
等选项防止容器耗尽主机资源。 -
使用健康检查:
HEALTHCHECK
指令或docker run --health-cmd
让Docker监控容器内应用的健康状态,自动重启不健康的容器。 -
优先使用命名卷: 相比主机绑定挂载,命名卷由Docker管理,更容易备份、迁移,且避免了主机路径依赖。
八、 面试考点及解析:征服面试官的Docker秘籍 💼
面试官常问的Docker问题及答案要点:
-
Q: Docker和虚拟机有什么区别?
- A: (回顾第五章表格) 核心区别:虚拟化级别(OS vs 硬件)、隔离性(弱 vs 强)、启动速度(秒级 vs 分钟级)、性能损耗(小 vs 大)、资源占用(小 vs 大)、打包内容(应用+依赖 vs 整个OS+应用+依赖)。容器更轻量高效,VM隔离性更强。
-
Q: Docker镜像分层是什么?有什么好处?
- A: Docker镜像由多个只读层叠加而成,最上面是容器的可写层。好处:共享基础层节省存储和带宽 ;分层构建利于复用和增量更新 (只传变动的层);写时复制(CoW)提高效率(修改文件时先复制到可写层再改)。
-
Q: 什么是Union File System?举一个例子。
- A: UnionFS是一种将多个目录(分支)透明地叠加挂载成一个单一目录视图的文件系统服务。Docker利用它实现镜像分层。例如OverlayFS:下层是只读的镜像层(
lowerdir
),上层是容器的可写层(upperdir
),最终用户看到的是合并层(merged
)。修改文件时,文件从lowerdir
复制到upperdir
再修改(CoW)。删除文件时,在upperdir
创建whiteout
文件隐藏lowerdir
中的文件。
- A: UnionFS是一种将多个目录(分支)透明地叠加挂载成一个单一目录视图的文件系统服务。Docker利用它实现镜像分层。例如OverlayFS:下层是只读的镜像层(
-
Q: Docker容器如何实现资源隔离和限制?
- A: 主要依赖Linux内核的两种技术:
- Namespaces (命名空间): 提供进程、网络、文件系统挂载点、主机名、进程间通信、用户等的隔离视图。让容器拥有独立的"世界"。
- Control Groups (cgroups): 限制、记录和隔离进程组(容器)使用的物理资源(CPU、内存、磁盘I/O、网络带宽)。防止一个容器耗尽系统资源。
- A: 主要依赖Linux内核的两种技术:
-
Q:
docker run
和docker exec
有什么区别?- A:
docker run
: 创建并启动一个新的容器实例。基于一个镜像启动一个全新的进程(通常是容器的入口进程)。docker exec
: 在已经运行的一个容器内部执行一个额外的命令。用于调试、管理、查看容器内部状态。不创建新容器。
- A:
-
Q: 如何让容器内的应用优雅关闭?
- A: 关键两点:
- 确保应用进程是容器内的PID 1: 在Dockerfile中使用
ENTRYPOINT ["exec", ...]
或CMD ["exec", ...]
形式(exec
替换shell进程)。 - 应用能捕获和处理
SIGTERM
信号: 实现信号处理器,在收到SIGTERM
(Docker stop默认发送) 时,完成当前请求、释放资源、安全退出。Spring Boot等框架通常内置支持。
- 确保应用进程是容器内的PID 1: 在Dockerfile中使用
- A: 关键两点:
-
Q: Dockerfile中
COPY
和ADD
有什么区别?- A:
COPY
: 基本功能是复制本地文件/目录到镜像中。 推荐首选,语义清晰。ADD
: 在COPY
基础上增加了一些功能:- 可以解压本地
tar
文件到镜像目标目录 (ADD app.tar.gz /app/
)。 - 可以从URL下载文件并复制到镜像(不推荐,因为下载的内容无法在构建缓存中失效,且构建可能因网络问题失败)。
- 可以解压本地
- 最佳实践: 除非需要自动解压tar包,否则优先使用
COPY
。需要下载文件时,在RUN
指令里用curl
或wget
下载并清理更可控。
- A:
-
Q: 什么是Docker数据卷(Volume)?为什么要用它?
- A: Docker数据卷是独立于容器生命周期的持久化数据存储机制 。特点:
- 存储在主机文件系统上(由Docker管理或在
docker run -v
指定)。 - 可以被多个容器挂载和共享。
- 容器停止或删除后,卷数据依然保留。
- 存储在主机文件系统上(由Docker管理或在
- 为什么用:
- 持久化数据: 避免容器删除时重要数据(如数据库文件、配置文件、日志)丢失。
- 数据共享: 在容器间共享数据(如Web容器和日志处理容器共享日志目录)。
- 性能: 绕过容器文件系统(特别是写时复制)可能带来更好IO性能(对数据库重要)。
- 备份/迁移: 直接备份主机上的卷目录更方便。
- A: Docker数据卷是独立于容器生命周期的持久化数据存储机制 。特点:
-
Q: 如何清理Docker占用的磁盘空间?
- A:
docker container prune
: 删除所有停止的容器。docker image prune
: 删除所有未被容器使用的悬空镜像 (<none>:<none>
)。docker image prune -a
: 删除所有未被任何容器使用的镜像(慎用!)。docker volume prune
: 删除所有未被容器使用的卷(危险!会丢数据!)。docker system prune
: 一键删除停止的容器、悬空镜像、未使用的网络、构建缓存。加-a
和--volumes
会更彻底(极其危险!)。- 手动清理
/var/lib/docker
(Docker默认存储目录) 下的overlay2
(镜像层)、containers
(容器)、volumes
(卷) 等子目录(高危操作,需非常谨慎!)。 - 核心: 定期清理停止的容器和无用的镜像、卷。对重要数据卷做好备份。
- A:
-
Q: 简述Docker的网络模式 (
bridge
,host
,none
)。- A:
bridge
(默认): 为每个容器创建独立的网络命名空间,并通过虚拟网卡连接到名为docker0
的Linux网桥。容器分配私有IP,通过NAT访问外网。容器间通过IP或容器名(需在同一自定义网络)通信。最常用,提供隔离性。host
: 容器直接使用宿主机的网络命名空间 。容器没有自己的IP,直接使用主机IP和端口。性能最好(无NAT开销),但牺牲了网络隔离性,端口冲突风险高。none
: 容器有自己的网络命名空间,但不配置任何网络接口 。只有loopback (lo
)。用于需要极高度网络隔离或完全自定义网络的场景。容器无法联网。
- A:
九、 总结:扬帆起航,驶向容器化的星辰大海! ✨
Docker不仅仅是一组命令,它代表了一种革命性的应用交付和运行方式。通过这篇长文,我们系统地探索了:
- 核心概念: 镜像(Image)是蓝图,容器(Container)是运行实例,仓库(Registry)是货架。
- 丰富命令: 从生命周期管理 (
run
,start
,stop
,rm
) 到镜像操作 (build
,pull
,push
),从信息查看 (ps
,inspect
,logs
) 到网络(network
)和卷(volume
)管理,武装你的终端。 - Java实战: 手把手打包Spring Boot应用,编写Dockerfile,体验多阶段构建优化。
- 底层原理: Namespaces提供隔离,cgroups限制资源,UnionFS实现分层和高效存储。
dockerd
->containerd
->runc
的协作流程。 - 对比分析: Docker容器在轻量、快速、高效上碾压传统VM,VM在强隔离和OS兼容性上占优。
- 避坑指南: 警惕
latest
标签、处理好日志和信号、注意文件权限和时区、善用.dockerignore
、小心磁盘空间。 - 最佳实践: 小镜像、非root用户、多阶段构建、资源限制、健康检查、命名卷、安全扫描。
- 面试考点: 从容应对关于Docker核心原理、命令、实践的常见问题。
Docker的世界博大精深,本文只是一个起点。随着你对Kubernetes (K8s)、容器编排、Service Mesh (如Istio)、云原生等领域的深入,你会发现Docker是这片新大陆的基石。
行动起来吧!
- 在你的下一个项目中尝试使用Docker。
- 重构一个旧应用的部署方式为容器化。
- 深入学习Docker Compose管理多容器应用。
- 探索Kubernetes,驾驭容器化应用的编排与管理。
愿Docker这位强大的"集装箱船长",助你在应用交付和运维的海洋中乘风破浪,高效远航!🐳🚀
最后一句灵魂拷问:你的应用,上船(Docker)了吗? 😉
(Optional) 互动环节: 你在使用Docker过程中踩过最大的坑是什么?或者有什么独门秘籍?欢迎在评论区分享!👇