Docker命令大全:从入门到入土(不是),从懵懂到精通!一篇搞定所有骚操作 🐳

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。想退出?输入 exitCtrl+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 + startdocker 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 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 和构建上下文(PATHURL 指定目录下的文件)构建一个新的镜像。定制化你的城堡!
    • 常用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客户端会将 PATHURL 指定的整个目录(及其子目录)打包发送给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.
  • 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.

🌐 4. 网络管理 (城堡间的道路)

  • docker network ls - 查看所有道路网(网络)

    • 列出Docker引擎管理的所有网络。bridge, host, none 是默认网络。
  • docker network create [OPTIONS] NETWORK - 修建新的专用道路

    • 创建一个新的Docker网络。可以指定驱动(bridge默认, overlay用于Swarm集群)、子网、网关等。docker network create my_app_net.
  • 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的魔法袍,看看它底层的秘密武器:

  1. Linux Namespaces (命名空间) - 隔离的根基:

    • PID Namespace: 每个容器有自己的进程树 (PID 1是容器内进程),看不到主机和其他容器的进程。
    • Network Namespace: 每个容器有自己的网络栈 (IP地址、端口、路由表、防火墙规则),虚拟网卡。
    • Mount Namespace: 每个容器看到自己独立的文件系统挂载点视图。/ 是它自己的根,看不到主机的 /
    • UTS Namespace: 每个容器有自己的主机名和域名 (hostname/domainname)。
    • IPC Namespace: 隔离进程间通信 (如信号量、消息队列、共享内存),容器间IPC需特殊配置。
    • User Namespace (可选): 映射容器内的UID/GID到主机上不同的(通常非特权)UID/GID,提升安全性。
  2. Control Groups (cgroups) - 资源的缰绳:

    • 限制、记录和隔离进程组(如容器)使用的物理资源:
      • CPU (份额、核绑定)
      • 内存 (限制、软硬限制、Swap)
      • 磁盘 I/O (带宽、IOPS)
      • 网络带宽
    • 防止某个容器耗尽系统资源。
  3. Union File Systems (联合文件系统) - 镜像分层的奥秘:

    • OverlayFS (主流): 将多个目录(层)联合挂载到单一视图。
    • 镜像层: 只读层。基础镜像(如Ubuntu)是一层,RUN apt-get update 添加新层,COPY 添加新层。每一层只存储与下层的差异。
    • 容器层: 最顶层的可写层。所有对容器文件系统的修改(创建、修改、删除文件)都发生在这里。当删除下层文件时,OverlayFS 会在可写层创建一个"白化"(whiteout)文件来隐藏它。
    • 好处: 共享基础层节省存储空间;分层利于复用和快速分发(只传变化的层);Copy-on-Write (写时复制) 高效。
  4. Container Runtime Interface (CRI) - 运行时的抽象:

    • Docker 使用 containerd 作为其核心容器运行时。containerd 管理容器的生命周期(创建、启动、停止、删除)、镜像管理、存储、网络等低层操作。
    • runccontainerd 默认使用的符合 OCI (Open Container Initiative) 规范的轻量级容器运行时工具,它直接调用操作系统底层API (namespaces, cgroups) 来运行容器。

启动一个容器的简化流程:

  1. docker run 命令发送给 Docker 守护进程 (dockerd)。
  2. dockerd 解析命令,检查本地是否有镜像,没有则拉取(docker pull)。
  3. dockerd 通过 containerd API 请求创建容器。
  4. containerd 准备容器运行时环境(设置 namespaces, cgroups 参数,准备 rootfs - 联合挂载镜像层 + 初始化可写层)。
  5. containerd 调用 runc 来实际创建和启动容器进程。
  6. runc 使用操作系统调用 (clone, unshare 等) 创建具有指定 namespaces 的进程,应用 cgroups 限制,设置 rootfs,然后执行容器定义的 ENTRYPOINT/CMD
  7. 容器进程启动并运行。

五、 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里)。

六、 避坑指南:血泪教训换来的经验 😭

  1. TAG 永远不要只依赖 latest

    • 坑: docker pull nginx 默认拉 nginx:latest。今天拉的和明天拉的可能是不同版本!导致生产环境行为不一致。
    • 避坑: 生产环境务必指定具体版本号! docker pull nginx:1.23.4-alpine。在Dockerfile的 FROMdocker run 时都明确版本。
  2. 容器内应用日志不输出到 STDOUT/STDERR

    • 坑: docker logs 只能捕获写到容器 STDOUTSTDERR 的日志。如果应用把日志直接写到文件(如 /app/logs/app.log),docker logs 看不到。
    • 避坑:
      • 最佳实践: 配置应用将日志输出到 STDOUT/STDERR。Spring Boot默认就做到了。
      • 如果必须写文件,考虑挂载卷,并在主机上用 tail -f 看,或使用日志驱动 (docker run --log-driver) 发送到外部系统 (Fluentd, ELK, Splunk)。
  3. 容器内进程不是 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
  4. 文件权限问题 (尤其是挂载卷)

    • 坑: 容器内应用以某个用户(如 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: 放宽主机目录权限(不推荐,安全性差)。
  5. 时区不对!

    • 坑: 容器默认使用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 ...
  6. docker build 上下文过大导致构建缓慢

    • 坑: 构建时发送整个目录(包含 node_modules, .git, 大文件)给Docker守护进程,非常慢且浪费。

    • 避坑: 务必创建 .dockerignore 文件! 排除不必要的文件和目录。语法类似 .gitignore

      bash 复制代码
      # .dockerignore 示例
      .git
      .idea
      target/
      node_modules/
      *.log
      *.bak
      Dockerfile*
      docker-compose*
  7. 容器"磁盘空间"不足?

    • 坑: docker exec my_container df -h 显示 / 快满了。这通常是容器的可写层满了。
    • 原因: 容器内应用写大量日志/临时文件到容器层(而不是挂载的卷);镜像层太多太大;Docker存储驱动配置问题。
    • 避坑:
      • 将需要大量写的目录(日志、临时文件、数据文件)挂载卷 (-v--mount)。
      • 定期清理日志文件(如果挂载卷了,在主机上清理)。
      • 使用 docker system prune 清理空间(注意风险)。
      • 检查并可能调整Docker存储驱动和存储位置 (/var/lib/docker)。
  8. apt-get update 缓存撑大镜像

    • 坑: Dockerfile中 RUN apt-get update && apt-get install -y package 后,apt-get update 的缓存文件留在了镜像层,增加大小。

    • 避坑: 在同一个 RUN 指令中组合 update, install, clean

      dockerfile 复制代码
      RUN apt-get update \
          && apt-get install -y --no-install-recommends package1 package2 \
          && rm -rf /var/lib/apt/lists/* # 清理缓存

七、 最佳实践:打造高效、安全的Docker舰船 🏆

  1. 使用 .dockerignore 如上所述,避免发送无用文件。

  2. 多阶段构建: 如Java案例所示,大幅减小最终镜像体积。适用于编译型语言。

  3. 使用特定版本的基础镜像: FROM openjdk:17-jdk-alpine 而非 FROM openjdk。Alpine等小型基础镜像是首选。

  4. 最小化层数 & 合并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/*
  5. 以非root用户运行容器:

    • 默认容器内进程以root运行,存在安全隐患。

    • 实践: Dockerfile中创建用户并切换:

      dockerfile 复制代码
      RUN addgroup -S appgroup && adduser -S appuser -G appgroup
      USER appuser # 后续指令以此用户身份运行
      WORKDIR /home/appuser # 确保用户有权限
      COPY --chown=appuser:appgroup ... # 复制文件时修改属主
      ENTRYPOINT ["exec", "java", "-jar", "app.jar"]
  6. 正确处理信号 & 优雅关闭:

    • 使用 exec 形式让应用成为PID 1(前文已强调)。
    • 确保应用能捕获 SIGTERM (Docker stop默认发送) 和 SIGINT (Ctrl+C) 进行清理。
    • Spring Boot应用默认支持优雅关闭 (server.shutdown=graceful + 处理 ContextClosedEvent)。
  7. 利用构建缓存:

    • 变化频率低 的指令(如安装基础工具)放在Dockerfile前面
    • 变化频率高 的指令(如复制应用代码)放在后面
    • 这样,当代码改变时,前面的层可以利用缓存,加速构建。
  8. 扫描镜像安全漏洞:

    • 使用 docker scan (集成Snyk) 或Trivy、Clair等工具扫描镜像中的已知漏洞。
    • 定期更新基础镜像。
  9. 限制容器资源: 使用 -m/--memory, --cpus 等选项防止容器耗尽主机资源。

  10. 使用健康检查: HEALTHCHECK 指令或 docker run --health-cmd 让Docker监控容器内应用的健康状态,自动重启不健康的容器。

  11. 优先使用命名卷: 相比主机绑定挂载,命名卷由Docker管理,更容易备份、迁移,且避免了主机路径依赖。

八、 面试考点及解析:征服面试官的Docker秘籍 💼

面试官常问的Docker问题及答案要点:

  1. Q: Docker和虚拟机有什么区别?

    • A: (回顾第五章表格) 核心区别:虚拟化级别(OS vs 硬件)、隔离性(弱 vs 强)、启动速度(秒级 vs 分钟级)、性能损耗(小 vs 大)、资源占用(小 vs 大)、打包内容(应用+依赖 vs 整个OS+应用+依赖)。容器更轻量高效,VM隔离性更强。
  2. Q: Docker镜像分层是什么?有什么好处?

    • A: Docker镜像由多个只读层叠加而成,最上面是容器的可写层。好处:共享基础层节省存储和带宽分层构建利于复用和增量更新 (只传变动的层);写时复制(CoW)提高效率(修改文件时先复制到可写层再改)。
  3. Q: 什么是Union File System?举一个例子。

    • A: UnionFS是一种将多个目录(分支)透明地叠加挂载成一个单一目录视图的文件系统服务。Docker利用它实现镜像分层。例如OverlayFS:下层是只读的镜像层(lowerdir),上层是容器的可写层(upperdir),最终用户看到的是合并层(merged)。修改文件时,文件从lowerdir复制到upperdir再修改(CoW)。删除文件时,在upperdir创建whiteout文件隐藏lowerdir中的文件。
  4. Q: Docker容器如何实现资源隔离和限制?

    • A: 主要依赖Linux内核的两种技术:
      • Namespaces (命名空间): 提供进程、网络、文件系统挂载点、主机名、进程间通信、用户等的隔离视图。让容器拥有独立的"世界"。
      • Control Groups (cgroups): 限制、记录和隔离进程组(容器)使用的物理资源(CPU、内存、磁盘I/O、网络带宽)。防止一个容器耗尽系统资源。
  5. Q: docker rundocker exec 有什么区别?

    • A:
      • docker run: 创建并启动一个新的容器实例。基于一个镜像启动一个全新的进程(通常是容器的入口进程)。
      • docker exec: 在已经运行的一个容器内部执行一个额外的命令。用于调试、管理、查看容器内部状态。不创建新容器。
  6. Q: 如何让容器内的应用优雅关闭?

    • A: 关键两点:
      1. 确保应用进程是容器内的PID 1: 在Dockerfile中使用 ENTRYPOINT ["exec", ...]CMD ["exec", ...] 形式(exec 替换shell进程)。
      2. 应用能捕获和处理 SIGTERM 信号: 实现信号处理器,在收到 SIGTERM (Docker stop默认发送) 时,完成当前请求、释放资源、安全退出。Spring Boot等框架通常内置支持。
  7. Q: Dockerfile中 COPYADD 有什么区别?

    • A:
      • COPY: 基本功能是复制本地文件/目录到镜像中。 推荐首选,语义清晰。
      • ADD: COPY 基础上增加了一些功能:
        • 可以解压本地 tar 文件到镜像目标目录 (ADD app.tar.gz /app/)。
        • 可以从URL下载文件并复制到镜像(不推荐,因为下载的内容无法在构建缓存中失效,且构建可能因网络问题失败)。
      • 最佳实践: 除非需要自动解压tar包,否则优先使用 COPY 。需要下载文件时,在RUN指令里用curlwget下载并清理更可控。
  8. Q: 什么是Docker数据卷(Volume)?为什么要用它?

    • A: Docker数据卷是独立于容器生命周期的持久化数据存储机制 。特点:
      • 存储在主机文件系统上(由Docker管理或在docker run -v指定)。
      • 可以被多个容器挂载和共享。
      • 容器停止或删除后,卷数据依然保留。
    • 为什么用:
      • 持久化数据: 避免容器删除时重要数据(如数据库文件、配置文件、日志)丢失。
      • 数据共享: 在容器间共享数据(如Web容器和日志处理容器共享日志目录)。
      • 性能: 绕过容器文件系统(特别是写时复制)可能带来更好IO性能(对数据库重要)。
      • 备份/迁移: 直接备份主机上的卷目录更方便。
  9. 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 (卷) 等子目录(高危操作,需非常谨慎!)。
      • 核心: 定期清理停止的容器和无用的镜像、卷。对重要数据卷做好备份。
  10. Q: 简述Docker的网络模式 (bridge, host, none)。

    • A:
      • bridge (默认): 为每个容器创建独立的网络命名空间,并通过虚拟网卡连接到名为 docker0 的Linux网桥。容器分配私有IP,通过NAT访问外网。容器间通过IP或容器名(需在同一自定义网络)通信。最常用,提供隔离性。
      • host 容器直接使用宿主机的网络命名空间 。容器没有自己的IP,直接使用主机IP和端口。性能最好(无NAT开销),但牺牲了网络隔离性,端口冲突风险高。
      • none 容器有自己的网络命名空间,但不配置任何网络接口 。只有loopback (lo)。用于需要极高度网络隔离或完全自定义网络的场景。容器无法联网。

九、 总结:扬帆起航,驶向容器化的星辰大海! ✨

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是这片新大陆的基石。

行动起来吧!

  1. 在你的下一个项目中尝试使用Docker。
  2. 重构一个旧应用的部署方式为容器化。
  3. 深入学习Docker Compose管理多容器应用。
  4. 探索Kubernetes,驾驭容器化应用的编排与管理。

愿Docker这位强大的"集装箱船长",助你在应用交付和运维的海洋中乘风破浪,高效远航!🐳🚀

最后一句灵魂拷问:你的应用,上船(Docker)了吗? 😉


(Optional) 互动环节: 你在使用Docker过程中踩过最大的坑是什么?或者有什么独门秘籍?欢迎在评论区分享!👇

相关推荐
贺贺丿2 分钟前
Docker2-容器应用工具及docker命令
linux·运维·docker·容器·自动化·云计算
亿刀1 小时前
【学习VPN之路】路由表
android·docker
超龄超能程序猿2 小时前
图片查重从设计到实现(2)Milvus安装准备etcd介绍、应用场景及Docker安装配置
docker·etcd·milvus
江湖有缘3 小时前
【Docker项目实战】在Docker环境下部署go-file文件分享工具
docker·容器·golang
小和尚同志13 小时前
26.4k Star 的开源自托管仪表盘,关注你想关注的一切
docker·容器·开源
Franciz小测测14 小时前
proxmox 解决docker容器MongoDB创建报错MongoDB 5.0+ requires a CPU with AVX support
运维·docker·容器
鹿先森AI探索之路16 小时前
Windows11 本地安装docker Desktop 部署dify 拉取镜像报错
运维·docker·容器
都叫我大帅哥17 小时前
Docker Compose:让多容器应用一键起飞 🚀
docker
<花开花落>19 小时前
解决 WSL 中无法访问 registry-1.docker.io/v2/,无法用 docker 拉取 image
docker
黑心的奥利奥21 小时前
Docker配置Gitlab-runner实现自动化容器化部署前端项目
docker·自动化·gitlab