深入理解 Docker:从入门到实践

深入理解 Docker:从入门到实践

在当今的软件开发和运维领域,Docker 已经成为一种不可或缺的技术。它通过容器化的方式,极大地简化了应用程序的开发、部署和运维流程。本文将带你全面了解 Docker,从基本概念、架构原理到实际操作,涵盖安装、常用命令、Dockerfile 构建、网络配置等核心内容。


一、Docker 总览

Docker 是一个开源的容器化平台,允许开发者将应用程序及其依赖项打包到一个轻量级、可移植的容器中。与传统的虚拟机不同,Docker 容器共享宿主机的操作系统内核,因此启动更快、资源占用更少。

为什么使用 Docker?

  • 一致性:开发、测试、生产环境一致,避免"在我机器上能运行"的问题。
  • 轻量级:容器共享内核,比虚拟机更节省资源。
  • 可移植性:一次构建,随处运行。
  • 快速部署:容器启动通常在秒级完成。
  • 微服务架构:支持将应用拆分为多个独立服务,便于管理和扩展。

二、Docker 架构原理

Docker 采用客户端-服务器(C/S)架构,主要由以下几个核心组件构成:

1. Docker Daemon(守护进程)

运行在宿主机上,负责管理 Docker 对象,如镜像、容器、网络和卷。它监听 Docker API 请求并处理容器的创建、运行、停止等操作。

2. Docker Client(客户端)

用户与 Docker 交互的主要方式。当你运行 docker run 命令时,客户端会将请求发送给 Docker Daemon。

3. Docker Images(镜像)

镜像是只读模板,包含运行应用程序所需的所有内容:代码、运行时、库、环境变量和配置文件。镜像通过分层结构实现,每一层代表一个文件系统的变化。

4. Docker Containers(容器)

容器是镜像的运行实例。你可以启动、停止、移动或删除容器。每个容器都是相互隔离的,并拥有自己的文件系统、网络和进程空间。

5. Docker Registry(注册中心)

用于存储和分发 Docker 镜像。最著名的公共注册中心是 Docker Hub。你也可以搭建私有 Registry。

6. Dockerfile

一个文本文件,包含一系列指令,用于自动化构建 Docker 镜像。

当然可以。下面我们深入探讨 Docker 的底层原理 ,重点解析它是如何实现"轻量级"、"快速启动"和"环境一致性"的,特别是其核心机制:共享内核、镜像分层、命名空间(Namespaces)、控制组(Cgroups)以及 UnionFS(联合文件系统)


Docker 的工作原理详解

1. 共享操作系统内核(Shared Kernel)

这是 Docker 与传统虚拟机(VM)最本质的区别。

特性 虚拟机(VM) Docker 容器
架构 Hypervisor + Guest OS + App Host OS + Docker Engine + Container
是否运行完整操作系统 是(每个 VM 都有独立的 OS) 否(只运行应用进程)
内核使用方式 每个 VM 运行自己的内核(通过虚拟化) 所有容器共享宿主机的 Linux 内核
资源开销 高(内存、CPU 占用大) 低(仅应用本身资源)
启动速度 慢(秒到分钟级) 快(毫秒到秒级)

关键点

Docker 容器并不是一个完整的操作系统,它只是在宿主机上运行的一个或多个进程,并通过 Linux 内核提供的隔离机制,让这些进程"以为"自己运行在一个独立环境中。

这意味着:

  • 你不能在 Linux 宿主机上运行 Windows 容器(除非使用 WSL2 等特殊技术)
  • 但可以在 Linux 上运行各种基于 Linux 的发行版容器(如 Ubuntu、CentOS、Alpine),因为它们都使用相同的 Linux 内核

2. 如何实现"复用"?------ 镜像分层(Layered File System)

Docker 镜像是由多个只读层(layers)组成的。每一层代表对文件系统的一次修改(比如安装软件、复制文件等)。这种设计实现了高效复用和节省存储空间

📦 示例:Dockerfile 构建过程中的分层
dockerfile 复制代码
FROM ubuntu:20.04          # 基础层:Ubuntu 镜像
RUN apt-get update         # 第二层:执行更新命令 → 新增一个 layer
RUN apt-get install -y nginx # 第三层:安装 Nginx → 又一个 layer
COPY index.html /var/www/html # 第四层:复制文件
CMD ["nginx", "-g", "daemon off;"]

构建后,镜像结构如下:

复制代码
Layer 4 (可读写层,容器运行时) ← CMD 执行入口
      ↓
Layer 3 ← COPY index.html ...
      ↓
Layer 2 ← RUN apt-get install nginx
      ↓
Layer 1 ← RUN apt-get update
      ↓
Base Layer ← FROM ubuntu:20.04
🔁 复用机制说明:
  • 如果两个不同的镜像都基于 ubuntu:20.04,那么这个基础层只会存储一份。
  • 当你再次构建时,如果某一层没有变化(例如 apt-get update 命令未改变),Docker 会直接复用缓存层,无需重新执行。
  • 多个容器启动自同一镜像时,它们共享这些只读层,只有最上面的"可写层"是各自独立的。

💡 这就是为什么 Docker 镜像体积小、构建快、部署迅速的原因之一。


3. 隔离机制:Linux Namespaces(命名空间)

为了让容器看起来像是一个独立的系统,Docker 使用了 Linux 内核的 Namespaces 技术,为每个容器提供独立的视图。

常见的 Namespace 类型:

Namespace 功能 容器中表现
PID (Process ID) 进程隔离 容器内只能看到自己的进程,ps aux 不会显示宿主机其他进程
NET (Network) 网络隔离 拥有独立的网络栈(IP、端口、路由表)
MNT (Mount) 文件系统挂载点隔离 容器有自己的挂载目录结构
UTS (Unix Timesharing System) 主机名和域名隔离 容器可以有自己的 hostname
IPC (Inter-Process Communication) 进程间通信隔离 信号量、消息队列等独立
USER 用户 ID 隔离 容器内用户与宿主机用户映射不同(支持安全增强)

✅ 举例:即使你在容器里以 root 身份运行程序,由于 USER namespace 的存在,它在宿主机上可能只是一个普通用户权限,从而提升安全性。


4. 资源限制:Control Groups(Cgroups)

虽然 Namespaces 提供了"隔离",但如果没有资源控制,某个容器可能会耗尽宿主机的所有 CPU 或内存。

Cgroups(Control Groups) 是 Linux 内核功能,用于:

  • 限制资源使用(CPU、内存、磁盘 I/O、网络带宽)
  • 统计资源消耗
  • 优先级控制
  • 进程冻结/恢复
🧪 实际命令示例:
bash 复制代码
# 限制容器最多使用 50% 的 CPU 和 512MB 内存
docker run -d \
  --cpus=0.5 \
  --memory=512m \
  --name limited-app \
  nginx

你可以通过以下命令查看资源限制效果:

bash 复制代码
docker stats limited-app

输出将显示该容器的实际 CPU、内存、网络使用情况。


5. 文件系统:UnionFS(联合文件系统)

UnionFS 是一种"分层"文件系统,允许将多个文件系统"叠加"成一个统一的视图。Docker 利用这一点实现镜像的分层和容器的可写层。

工作原理:
  • 镜像的每一层都是只读层
  • 当容器启动时,Docker 在最顶层添加一个可写层(Writable Layer)
  • 所有对文件的修改(新建、删除、修改)都发生在这一层
  • 下层的原始数据保持不变

⚠️ 注意:"写时复制"(Copy-on-Write, CoW)策略:

  • 当你尝试修改一个位于底层的文件时,Docker 会先将该文件复制到可写层,然后进行修改。
  • 原始层仍然保持不变,保证了镜像的不可变性和多容器共享。

6. 容器是如何运行起来的?流程总结

  1. 用户执行:docker run -d nginx
  2. Docker Client 发送请求给 Docker Daemon
  3. Daemon 检查本地是否有 nginx 镜像,没有则从 Registry 拉取
  4. Daemon 创建一个新的容器对象
  5. 为容器分配:
    • 一个独立的 Namespace 集合(PID、NET、MNT 等)
    • Cgroup 来限制资源
    • 联合文件系统挂载点(只读层 + 可写层)
  6. 启动容器内的主进程(如 nginx
  7. 返回容器 ID,运行完成

整个过程通常在 1 秒以内完成,远快于启动一个虚拟机。


总结:Docker 轻量高效的四大支柱

技术 作用
共享内核 避免重复运行操作系统,极大降低资源开销
镜像分层 + UnionFS 实现镜像复用、缓存加速、节省磁盘空间
Namespaces 提供进程、网络、文件系统等维度的隔离,营造"独立环境"假象
Cgroups 控制资源使用,防止"流氓容器"拖垮宿主机

正是这四项 Linux 内核技术的结合,使得 Docker 成为现代云原生基础设施的核心组件。


补充:容器 ≠ 虚拟机

维度 容器 虚拟机
隔离级别 进程级隔离 硬件级隔离(更强)
性能损耗 极低(接近原生) 较高(需模拟硬件)
启动速度 快(<1s) 慢(>10s)
存储占用 小(MB 级) 大(GB 级)
安全性 依赖内核,风险略高 更强(完全隔离)
使用场景 微服务、CI/CD、开发测试 多租户、异构系统、强隔离需求

✅ 推荐选择:

  • 开发、测试、微服务部署 → Docker 容器
  • 需要运行不同操作系统或极高安全要求 → 虚拟机

三、Docker 安装指南

1. CentOS 安装 Docker

bash 复制代码
# 卸载旧版本
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

# 安装依赖
sudo yum install -y yum-utils
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

# 安装 Docker Engine
sudo yum install docker-ce docker-ce-cli containerd.io

# 启动并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker

# 验证安装
docker --version

2. Debian/Ubuntu 安装 Docker

bash 复制代码
# 更新包索引
sudo apt-get update

# 安装依赖
sudo apt-get install ca-certificates curl gnupg

# 添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# 设置仓库
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 启动服务
sudo systemctl start docker
sudo systemctl enable docker

3. Windows 安装 Docker

Windows 用户推荐使用 Docker Desktop for Windows

  1. 下载 Docker Desktop
  2. 安装并启动
  3. 确保已启用 WSL 2 (Windows Subsystem for Linux)Hyper-V
  4. 安装完成后,Docker 会自动运行

注意:Windows 10/11 Pro 或 Enterprise 版本支持 Hyper-V,家庭版需使用 WSL 2 后端。


四、Docker 常用命令详解

基础命令

命令 说明
docker version 查看 Docker 版本
docker info 显示 Docker 系统信息
docker --help 查看帮助

镜像相关命令

命令 说明
docker images 列出本地镜像
docker pull ubuntu:20.04 从仓库拉取镜像
docker build -t myapp:v1 . 构建镜像
docker rmi image_id 删除镜像
docker rmi $(docker images -q) 删除所有镜像
docker tag old_image new_name 重命名镜像

容器相关命令

命令 说明
docker run -d -p 8080:80 nginx 后台运行容器,映射端口
docker run -it ubuntu /bin/bash 交互式运行容器
docker ps 查看运行中的容器
docker ps -a 查看所有容器(包括已停止)
docker stop container_id 停止容器
docker start container_id 启动已停止的容器
docker restart container_id 重启容器
docker rm container_id 删除容器
docker rm -f container_id 强制删除运行中的容器
docker exec -it container_id /bin/bash 进入运行中的容器
docker logs container_id 查看容器日志
docker inspect container_id 查看容器详细信息
docker stats 实时查看容器资源使用情况

命令区别示例

  • docker run vs docker start

    • run 是创建并启动新容器
    • start 是启动已存在的容器
  • docker stop vs docker kill

    • stop 发送 SIGTERM,允许优雅关闭
    • kill 发送 SIGKILL,立即终止
  • docker rm vs docker rmi

    • rm 删除容器
    • rmi 删除镜像

五、Dockerfile 构建与最佳实践

Dockerfile 示例

dockerfile 复制代码
# 使用基础镜像
FROM ubuntu:20.04

# 维护者信息
LABEL maintainer="dev@example.com"

# 更新包并安装软件
RUN apt-get update && apt-get install -y \
    nginx \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 暴露端口
EXPOSE 80

# 复制文件到容器
COPY index.html /var/www/html/

# 设置工作目录
WORKDIR /var/www/html

# 容器启动时执行的命令
CMD ["nginx", "-g", "daemon off;"]

Dockerfile 指令详解

指令 说明
FROM 指定基础镜像
RUN 执行命令(构建时)
CMD 容器启动命令(可被覆盖)
ENTRYPOINT 入口点(不易被覆盖)
COPY 复制文件到镜像
ADD 类似 COPY,支持 URL 和自动解压
ENV 设置环境变量
ARG 构建参数
WORKDIR 设置工作目录
EXPOSE 声明端口
VOLUME 创建挂载点
USER 切换用户
ONBUILD 触发器指令

当然可以!下面将详细讲解 Dockerfile 中常用的指令 ,包括每个命令的作用、语法、使用场景、注意事项,以及相关指令之间的区别。最后会给出一个完整的、生产级别的构建示例


一、Dockerfile 常用指令详解

最佳实践建议 :所有 Dockerfile 指令推荐使用 exec 形式(数组语法) 而非 shell 形式,以避免信号传递问题和隐式 shell 启动。


1. FROM ------ 指定基础镜像

dockerfile 复制代码
FROM ubuntu:20.04
# 或使用多阶段构建
FROM golang:1.21 AS builder
  • 作用:定义镜像的起点,所有后续操作基于此镜像。
  • 必须是第一个非注释指令
  • 支持指定别名(用于多阶段构建):AS <name>
  • 推荐使用带标签的官方镜像 (如 alpine, debian, ubuntu),避免使用 latest 标签(不利于可重现性)。

⚠️ 注意:选择轻量基础镜像(如 alpine)可显著减小最终镜像体积。


2. RUN ------ 构建时执行命令

dockerfile 复制代码
RUN apt-get update && apt-get install -y nginx
RUN ["apt-get", "clean", "/var/lib/apt/lists/*"]
  • 作用 :在镜像构建过程中执行命令,并将结果保存为新的一层
  • 支持两种语法:
    • shell 形式:RUN yum install -y httpd
    • exec 形式:RUN ["yum", "install", "-y", "httpd"](推荐)
  • 每个 RUN 指令会创建一个新层,因此建议将多个命令合并以减少层数。

优化技巧

dockerfile 复制代码
RUN apt-get update && \
    apt-get install -y \
        nginx \
        curl \
        vim && \
    rm -rf /var/lib/apt/lists/*

3. CMD ------ 容器启动时的默认命令

dockerfile 复制代码
CMD ["nginx", "-g", "daemon off;"]
CMD ["--port=8080"]  # 配合 ENTRYPOINT 使用
  • 作用 :指定容器运行时的默认命令或参数
  • 一个 Dockerfile 中只能有一个有效 CMD(最后一个生效)。
  • 可被 docker run 命令行参数完全覆盖

📌 示例:

bash 复制代码
docker run myapp echo "hi"  # 完全覆盖 CMD

4. ENTRYPOINT ------ 定义容器的可执行入口

dockerfile 复制代码
ENTRYPOINT ["java", "-jar", "/app.jar"]
  • 作用:让容器像一个"可执行程序"一样运行。
  • 容器启动时,ENTRYPOINT固定主命令CMDdocker run 参数作为其参数传入
  • 必须使用 --entrypoint 才能覆盖。

CMD 的核心区别

组合 docker run 行为
ENTRYPOINT ["echo"] CMD ["hello"] docker run img → 输出 hello docker run img world → 输出 world
CMD ["echo", "hello"] docker run img → 输出 hello docker run img world → 输出 world(覆盖整个 CMD)

🔁 总结:ENTRYPOINT + CMD = 主程序 + 默认参数


5. COPY vs ADD ------ 复制文件到镜像

COPY(推荐)
dockerfile 复制代码
COPY ./src /app/src
COPY requirements.txt /app/
  • 作用:从构建上下文复制文件或目录到镜像中。
  • 只支持本地路径
  • 简单、安全、透明,推荐用于大多数场景
ADD(慎用)
dockerfile 复制代码
ADD https://example.com/config.json /app/
ADD archive.tar.gz /app/  # 自动解压
  • 功能比 COPY 多:
    • 支持 URL 下载
    • 自动解压 tar.gz 等压缩包
  • 不推荐滥用,因为行为不够透明(自动解压可能引发意外)。

最佳实践

  • 本地文件 → 用 COPY
  • 远程文件或需解压 → 显式用 RUN curl/wget && tar,更可控

6. WORKDIR ------ 设置工作目录

dockerfile 复制代码
WORKDIR /app
  • 作用 :为后续的 RUN, CMD, ENTRYPOINT, COPY, ADD 等指令设置当前工作目录
  • 如果目录不存在,会自动创建。
  • 推荐使用绝对路径。
  • 可多次使用,路径会叠加。

✅ 示例:

dockerfile 复制代码
WORKDIR /app
WORKDIR src  # 实际路径为 /app/src

7. ENV ------ 设置环境变量

dockerfile 复制代码
ENV NODE_ENV=production
ENV PATH=/app/bin:$PATH
  • 作用:定义环境变量,在构建和运行时都可用。
  • 可在后续指令中使用 ${VAR_NAME} 引用。
  • 常用于设置语言、路径、配置参数等。

⚠️ 注意:ENV 设置的变量在容器运行时仍存在,可用于应用配置。


8. ARG ------ 构建参数

dockerfile 复制代码
ARG BUILD_VERSION
ARG NODE_ENV=development

RUN echo "Building version: ${BUILD_VERSION}"
  • 作用 :定义仅在构建阶段使用的变量。
  • 不会出现在最终镜像的环境变量中(比 ENV 更安全)。
  • 可通过 docker build --build-arg VAR=value 传入。

ENV 的区别

特性 ARG ENV
是否存在于运行时容器 ❌ 否 ✅ 是
是否可用于 RUN 指令 ✅ 是 ✅ 是
是否可被 --build-arg 覆盖 ✅ 是 ❌ 否
安全性(敏感信息) 更高 较低(建议不用)

🔐 敏感信息(如密钥)应使用 Docker Secrets 或外部配置管理,不要写在 ARGENV


9. EXPOSE ------ 声明端口

dockerfile 复制代码
EXPOSE 80/tcp
EXPOSE 443
  • 作用:声明容器在运行时会监听的端口。
  • 仅是文档说明,不会自动映射端口。
  • 实际端口映射需在 docker run -p 时指定。

📌 示例:

bash 复制代码
docker run -p 8080:80 myapp  # 将宿主机 8080 映射到容器 80

10. VOLUME ------ 定义挂载点

dockerfile 复制代码
VOLUME ["/data", "/config"]
  • 作用:创建一个挂载点,用于持久化数据或与宿主机共享目录。
  • 在容器运行时,Docker 会自动创建匿名卷或绑定挂载。
  • 常用于数据库、配置文件等需要持久化的场景。

⚠️ 注意:VOLUME 指令在构建阶段不会复制数据 到卷中。如果需要初始化数据,应在 RUN 中操作目标路径。


11. USER ------ 切换用户

dockerfile 复制代码
USER www-data
USER 1000:1000
  • 作用:指定后续指令以哪个用户身份运行。
  • 提高安全性,避免以 root 运行应用。
  • 推荐在 CMDENTRYPOINT 前切换到非 root 用户。

最佳实践

dockerfile 复制代码
# 创建用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

12. ONBUILD ------ 触发器指令(已过时,不推荐)

dockerfile 复制代码
ONBUILD COPY . /app/src
ONBUILD RUN npm install
  • 作用 :当当前镜像被其他镜像 FROM 时,自动执行这些指令。
  • 已被现代实践取代(如使用多阶段构建或脚本化构建流程)。
  • 不推荐使用

13. STOPSIGNAL ------ 设置停止信号

dockerfile 复制代码
STOPSIGNAL SIGTERM
  • 作用 :指定 docker stop 时发送给容器主进程的信号。
  • 默认是 SIGTERM,允许优雅关闭。
  • 对 Java、Node.js 等应用很重要。

14. HEALTHCHECK ------ 健康检查

dockerfile 复制代码
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost/ || exit 1
  • 作用:定义如何检查容器是否健康。
  • Docker 会定期执行该命令,失败则标记容器为 unhealthy
  • 对编排系统(如 Kubernetes、Swarm)非常重要。

15. LABEL ------ 添加元数据

dockerfile 复制代码
LABEL maintainer="dev@example.com"
LABEL version="1.0.0"
LABEL description="A web application"
  • 作用:为镜像添加键值对形式的元信息。
  • 可通过 docker inspect 查看。

二、指令关联与区别总结

指令对 区别
COPY vs ADD COPY 安全简单;ADD 支持 URL 和自动解压,行为不透明
CMD vs ENTRYPOINT CMD 可被覆盖;ENTRYPOINT 固定入口,参数可变
ENV vs ARG ENV 运行时存在;ARG 仅构建时使用
EXPOSE vs -p EXPOSE 是声明;-p 是实际映射
VOLUME vs -v VOLUME 是声明;-v 是运行时挂载

三、完整构建示例:Node.js Web 应用(生产级)

dockerfile 复制代码
# ================================
# 多阶段构建:第一阶段 - 构建
# ================================
FROM node:18-alpine AS builder

# 设置工作目录
WORKDIR /app

# 复制 package 文件并安装依赖(利用缓存)
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# 复制源码
COPY src ./src

# 构建应用(如果需要)
# RUN npm run build

# ================================
# 第二阶段 - 运行时
# ================================
FROM node:18-alpine

# 设置环境变量
ENV NODE_ENV=production \
    PORT=3000

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001 && \
    mkdir -p /home/nodejs/app && \
    chown -R nodejs:nodejs /home/nodejs/app

# 切换到非 root 用户
USER nodejs

# 设置工作目录
WORKDIR /home/nodejs/app

# 从构建阶段复制依赖和源码
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/src ./src

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD node -e 'require("http").get("http://localhost:3000")' || exit 1

# 入口点(可执行程序)
ENTRYPOINT ["node", "src/index.js"]

# 默认参数(可被覆盖)
CMD ["--port=3000"]

四、构建与运行

bash 复制代码
# 构建镜像
docker build -t my-node-app:1.0 .

# 运行容器
docker run -d \
  -p 3000:3000 \
  --name myapp \
  my-node-app:1.0 \
  --port=3000

# 查看日志
docker logs myapp

# 进入容器(调试)
docker exec -it myapp sh

五、总结

指令 用途 是否推荐
FROM 基础镜像 ✅ 必须
RUN 构建命令
COPY 复制文件 ✅ 推荐
ADD 复制+解压+下载 ⚠️ 慎用
CMD 默认命令
ENTRYPOINT 入口程序
ENV 环境变量
ARG 构建参数
EXPOSE 声明端口
VOLUME 挂载点
USER 切换用户 ✅ 安全必备
HEALTHCHECK 健康检查 ✅ 生产推荐
LABEL 元数据

六、最佳实践总结

  1. 使用多阶段构建减少镜像体积
  2. 优先使用 COPY 而非 ADD
  3. 使用 exec 形式避免信号问题
  4. 非 root 用户运行应用
  5. 合理使用 .dockerignore 避免不必要的文件进入构建上下文
  6. 添加 HEALTHCHECK 提升可观测性
  7. 避免在镜像中存储敏感信息

构建命令

bash 复制代码
# 构建镜像
docker build -t mywebapp:latest .

# 指定 Dockerfile 路径
docker build -f ./path/Dockerfile.prod -t myapp:prod .

# 使用构建参数
docker build --build-arg VERSION=1.2.3 -t myapp:v1.2.3 .

六、Docker 网络详解

Docker 提供了多种网络模式,用于管理容器间的通信。

1. 默认网络驱动

网络模式 说明
bridge 默认模式,容器通过虚拟网桥通信
host 容器共享宿主机网络命名空间
none 容器无网络
overlay 用于 Swarm 集群
macvlan 为容器分配 MAC 地址,使其像物理机一样出现在网络中

2. 网络命令

命令 说明
docker network ls 列出网络
docker network create mynet 创建网络
docker network inspect mynet 查看网络详情
docker network connect mynet container1 将容器连接到网络
docker network disconnect mynet container1 断开连接
docker network rm mynet 删除网络

3. 自定义 Bridge 网络示例

bash 复制代码
# 创建自定义网络
docker network create --driver bridge mynet

# 启动两个容器并连接到同一网络
docker run -d --name web --network mynet nginx
docker run -d --name db --network mynet mysql:5.7

# 容器间可通过名称通信(如 web 容器可访问 db)

4. 端口映射

bash 复制代码
# 将容器 80 端口映射到宿主机 8080
docker run -d -p 8080:80 nginx

# 指定 IP 和端口
docker run -d -p 127.0.0.1:8080:80 nginx

# 随机端口映射
docker run -d -P nginx

七、一些例子

当然可以!下面是 Java 应用使用 Docker 环境变量(-e)的完整实战例子,包括:

  • Java 代码如何读取环境变量
  • Maven 构建
  • Dockerfile 打包
  • docker run -e 传参
  • 连接数据库的实际场景

🎯 目标

我们写一个简单的 Java 程序,它从环境变量中读取数据库配置,并打印出来:

bash 复制代码
DB Host: mysql-db
DB Port: 3306

然后打包成 Docker 镜像并运行。


一、Java 项目结构

复制代码
java-env-demo/
├── src/
│   └── main/java/
│       └── com/example/App.java
├── pom.xml
└── Dockerfile

二、1. 编写 Java 代码:src/main/java/com/example/App.java

java 复制代码
package com.example;

public class App {
    public static void main(String[] args) {
        // 读取环境变量
        String dbHost = System.getenv("DB_HOST");          // 如:mysql-db
        String dbPort = System.getenv("DB_PORT");          // 如:3306
        String dbName = System.getenv("DB_NAME");          // 如:myapp
        String dbUser = System.getenv("DB_USER");          // 如:root
        String dbPassword = System.getenv("DB_PASSWORD");  // 如:123456

        // 如果没设置,默认值
        dbHost = dbHost != null ? dbHost : "localhost";
        dbPort = dbPort != null ? dbPort : "3306";
        dbName = dbName != null ? dbName : "test";
        dbUser = dbUser != null ? dbUser : "root";

        // 打印配置
        System.out.println("==================================");
        System.out.println("✅ Java 应用启动成功!");
        System.out.println("📊 数据库配置:");
        System.out.println("   Host     : " + dbHost);
        System.out.println("   Port     : " + dbPort);
        System.out.println("   Database : " + dbName);
        System.out.println("   User     : " + dbUser);
        // 密码不打印(安全)
        System.out.println("==================================");

        // 模拟应用运行(防止容器退出)
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

二、2. Maven 配置:pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>java-env-demo</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <exec.mainClass>com.example.App</exec.mainClass>
    </properties>

    <build>
        <finalName>app</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.example.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

二、3. 构建 jar 包

bash 复制代码
mvn clean package

生成:target/app.jar


三、编写 Dockerfile

dockerfile 复制代码
# 使用官方 OpenJDK 镜像
FROM openjdk:11-jre-slim

# 创建应用目录
WORKDIR /app

# 将打包好的 jar 文件复制到容器
COPY target/app.jar app.jar

# 声明运行时端口(可选)
EXPOSE 8080

# 启动命令
CMD ["java", "-jar", "app.jar"]

四、构建 Docker 镜像

bash 复制代码
docker build -t java-env-app:1.0 .

五、运行容器并传入环境变量

步骤 1:创建自定义网络(用于容器通信)

bash 复制代码
docker network create java-app-network

步骤 2:启动 MySQL 容器(模拟数据库)

bash 复制代码
docker run -d \
  --name mysql-db \
  --network java-app-network \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -e MYSQL_DATABASE=myapp \
  mysql:8.0

步骤 3:启动 Java 应用容器,通过 -e 传配置

bash 复制代码
docker run -d \
  --name java-app \
  --network java-app-network \
  -e DB_HOST=mysql-db \
  -e DB_PORT=3306 \
  -e DB_NAME=myapp \
  -e DB_USER=root \
  -e DB_PASSWORD=123456 \
  java-env-app:1.0

六、查看日志,验证环境变量生效

bash 复制代码
docker logs java-app

输出:

复制代码
==================================
✅ Java 应用启动成功!
📊 数据库配置:
   Host     : mysql-db
   Port     : 3306
   Database : myapp
   User     : root
==================================

✅ 成功!Java 程序正确读取了 -e 设置的环境变量。


七、进阶:在 Spring Boot 中使用

如果你用的是 Spring Boot ,环境变量可以直接在 application.yml 中使用:

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:demo}
    username: ${DB_USER:root}
    password: ${DB_PASSWORD}

或者用 @Value 注解:

java 复制代码
@Value("${DB_HOST:localhost}")
private String dbHost;

运行时传参方式完全一样:

bash 复制代码
docker run -e DB_HOST=mysql-db -e DB_PORT=3306 spring-boot-app

八、最佳实践建议(Java 场景)

建议 说明
✅ 使用 System.getenv("KEY") 读取 简单直接
✅ 变量名全大写 + 下划线 DB_HOST, REDIS_URL
✅ 提供默认值 System.getenv("DB_HOST", "localhost")
✅ 敏感信息不要打印日志 避免 System.out.println(password)
✅ 生产用 Secrets Docker Swarm Secrets / Kubernetes Secrets
✅ 使用 .env 文件管理 开发时更方便

九、总结

你已经学会了:

  1. ✅ Java 如何通过 System.getenv() 读取 -e 设置的环境变量
  2. ✅ 如何构建 Java 应用并打包成 Docker 镜像
  3. ✅ 如何用 -e DB_HOST=mysql-db 传数据库配置
  4. ✅ Java 容器如何通过自定义网络连接 MySQL 容器
  5. ✅ Spring Boot 中的高级用法

📌 一句话记住

"Java 应用通过 System.getenv() 拿到 -e 传来的'口令',就能知道数据库在哪、怎么连!"

这是现代 Java 微服务部署的标准做法,掌握它,你就能轻松部署 Spring Boot、Dubbo、Vert.x 等任何 Java 应用!

好的!这是一个非常全面且深入的问题,涉及 Docker 网络的核心原理。我们来一步步 从零开始,详细讲解

创建自定义网络 → 容器 IP 分配 → 端口映射 → 多容器映射同一宿主端口 → 网络基础(IP/子网)


🎯 目标

我们通过一个完整例子,回答以下问题:

  1. ✅ 如何创建自定义网络?
  2. ✅ 容器的 IP 是怎么分配的?
  3. ✅ 端口映射 -p 是怎么工作的?
  4. ✅ 多个容器可以映射到宿主机的 8888 端口吗?会冲突吗?
  5. ✅ IP 地址分类和子网是什么?Docker 用的是哪一类?

一、创建自定义网络(Bridge 模式)

bash 复制代码
# 创建一个名为 myapp-net 的自定义桥接网络
docker network create myapp-net

查看网络详情

bash 复制代码
docker network inspect myapp-net

输出(简化):

json 复制代码
[
    {
        "Name": "myapp-net",
        "Driver": "bridge",
        "Subnet": "172.18.0.0/16",
        "Gateway": "172.18.0.1"
    }
]

关键信息

  • 子网(Subnet) : 172.18.0.0/16 → 可用 IP 范围:172.18.0.2 ~ 172.18.255.254
  • 网关(Gateway) : 172.18.0.1 → 宿主机在该网络的"代表"
  • 驱动(Driver) : bridge → Linux 虚拟网桥

二、启动容器并加入网络

bash 复制代码
# 启动第一个容器(web1)
docker run -d \
  --name web1 \
  --network myapp-net \
  -p 8888:80 \
  nginx

# 启动第二个容器(web2)
docker run -d \
  --name web2 \
  --network myapp-net \
  -p 8889:80 \
  nginx

三、容器的 IP 是怎么来的?(IP 分配原理)

1. 查看容器 IP

bash 复制代码
docker inspect web1 | grep IPv4Address
# 输出: "IPv4Address": "172.18.0.2/16"

docker inspect web2 | grep IPv4Address
# 输出: "IPv4Address": "172.18.0.3/16"

2. IP 分配过程

步骤 说明
① Docker Daemon 检查 myapp-net 的子网 172.18.0.0/16
② 选择可用 IP 找一个未被使用的 IP(如 172.18.0.2
③ 创建 veth pair 一对虚拟网卡: - 一端在容器内(eth0) - 一端在宿主机(如 vethxxxxx
④ 连接到网桥 将宿主机端的 veth 连接到虚拟网桥 br-abc123
⑤ 配置 IP 在容器内设置 eth0 的 IP 为 172.18.0.2
⑥ 启动容器 容器启动,网络就绪

📌 容器 IP 来源 :由 Docker 在自定义网络的子网范围内自动分配 ,通常是按顺序分配(0.2, 0.3, ...)。


四、端口映射 -p 原理:容器 80 → 宿主 8888

命令:

bash 复制代码
-p 8888:80

意思是:

"把宿主机的 8888 端口映射到容器的 80 端口"

原理:基于 iptables 的 NAT(网络地址转换)

bash 复制代码
# 宿主机上实际添加的 iptables 规则
-A DOCKER -p tcp -d 0/0 --dport 8888 -j DNAT --to-destination 172.18.0.2:80

数据流向(用户访问 http://localhost:8888):

复制代码
用户请求
    ↓
[宿主机:8888] ← iptables DNAT 转换
    ↓
[容器 web1:172.18.0.2:80]
    ↓
Nginx 返回响应
    ↓
[容器 → 宿主机] ← iptables SNAT 转换(自动)
    ↓
返回给用户

关键点

  • 容器内部监听的是 80 端口
  • 外部通过宿主机 8888 端口访问
  • Docker 自动维护 iptables 规则实现转发

五、多个容器映射到宿主机 8888 端口?会冲突吗?

❌ 情况 1:两个容器都映射 8888:80

bash 复制代码
# 第一个可以
docker run -d -p 8888:80 --name web1 --network myapp-net nginx

# 第二个会失败!
docker run -d -p 8888:80 --name web2 --network myapp-net nginx

错误:

复制代码
Error: port is already allocated

会冲突!同一个宿主端口只能被一个容器占用。


✅ 情况 2:不同宿主端口,相同容器端口(推荐)

bash 复制代码
# web1 映射 8888 → 80
docker run -d -p 8888:80 --name web1 nginx

# web2 映射 8889 → 80
docker run -d -p 8889:80 --name web2 nginx

✅ 成功!访问:

  • http://localhost:8888 → web1
  • http://localhost:8889 → web2

✅ 情况 3:同一个宿主端口,但不同 IP(多网卡场景)

如果宿主机有多个 IP(如 192.168.1.10, 192.168.1.11):

bash 复制代码
# 绑定到不同 IP 的 8888 端口
docker run -d -p 192.168.1.10:8888:80 --name web1 nginx
docker run -d -p 192.168.1.11:8888:80 --name web2 nginx

✅ 成功!因为监听的是不同 IP 的同一端口。


✅ 情况 4:使用反向代理统一入口(生产推荐)

用 Nginx 或 Traefik 做反向代理,统一监听 8888,然后根据路径或域名转发:

nginx 复制代码
# nginx.conf
server {
    listen 8888;
    location /app1/ {
        proxy_pass http://web1:80/;
    }
    location /app2/ {
        proxy_pass http://web2:80/;
    }
}

✅ 实现:单端口 → 多服务


六、IP 地址分类与子网基础(Docker 网络基础)

1. IPv4 地址分类(A/B/C 类)

类别 范围 子网掩码 用途
A 类 1.0.0.0 ~ 126.255.255.255 255.0.0.0 (/8) 大型网络(政府、ISP)
B 类 128.0.0.0 ~ 191.255.255.255 255.255.0.0 (/16) 中型网络(企业)
C 类 192.0.0.0 ~ 223.255.255.255 255.255.255.0 (/24) 小型网络(家庭)
D 类 224.0.0.0 ~ 239.255.255.255 - 组播
E 类 240.0.0.0 ~ 255.255.255.255 - 保留

2. 私有 IP 地址(RFC 1918)

这些 IP 不会在互联网上路由,专用于内部网络(Docker 用的就是这些):

范围 子网 用途
10.0.0.0/8 10.0.0.0 ~ 10.255.255.255 大型私有网络
172.16.0.0/12 172.16.0.0 ~ 172.31.255.255 中型私有网络 ✅ Docker 默认用这个
192.168.0.0/16 192.168.0.0 ~ 192.168.255.255 家庭/小型网络

📌 Docker 默认桥接网络用 172.17.0.0/16

自定义网络从 172.18.0.0/16 开始递增(172.19, 172.20...)


3. 子网(Subnet)与 CIDR 表示法

  • 172.18.0.0/16 表示:
    • 网络地址:172.18.0.0
    • 子网掩码:255.255.0.0
    • 可用 IP 数:2^(32-16) - 2 = 65534
    • 可用范围:172.18.0.1 ~ 172.18.255.254

/16 表示前 16 位是网络号,后 16 位是主机号。


4. 为什么 Docker 用 172.x 而不用 192.168

  • 192.168 常用于家庭路由器(如 192.168.1.1
  • 如果 Docker 也用 192.168,可能和宿主机网络冲突
  • 172.16.0.0/12 范围大,足够 Docker 动态分配

七、总结:完整原理图

复制代码
宿主机
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   iptables 规则                                              │
│   -A DOCKER -p tcp --dport 8888 -j DNAT --to 172.18.0.2:80   │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  Linux Bridge: br-abc123 (172.18.0.1)               │   │
│   │                                                     │   │
│   │   ┌─────────────┐       ┌─────────────┐             │   │
│   │   │   veth-A    │       │   veth-B    │             │   │
│   │   └─────────────┘       └─────────────┘             │   │
│   │         │                     │                     │   │
│   └─────────┼─────────────────────┼─────────────────────┘   │
│             │                     │                         │
│         ┌───▼───┐             ┌───▼───┐                     │
│         │ web1  │             │ web2  │                     │
│         │172.18.0.2│           │172.18.0.3│                   │
│         └───────┘             └───────┘                     │
│                                                             │
│   宿主端口: 8888              8889                          │
│   映射到: web1:80           web2:80                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

八、关键结论

问题 回答
容器 IP 怎么来? Docker 在自定义网络的子网内自动分配(如 172.18.0.2
端口映射原理? iptables DNAT 实现宿主端口 → 容器 IP:端口 转发
多个容器映射 8888? ❌ 不行!宿主端口唯一,会冲突
如何解决? 用不同宿主端口,或反向代理统一入口
Docker 用什么 IP? 私有 IP 172.16.0.0/12 范围,避免冲突
子网 /16 是什么? 表示前 16 位是网络号,可用 6.5 万个 IP

📌 一句话记住

"Docker 为每个自定义网络创建一个'虚拟局域网',容器 IP 自动分配,端口映射靠 iptables 转发,宿主端口不能重复占用。"

掌握这些原理,你就能游刃有余地设计和调试任何 Docker 网络架构!

相关推荐
Lin_Aries_04219 小时前
容器化简单的 Java 应用程序
java·linux·运维·开发语言·docker·容器·rpc
人逝花落空.11 小时前
docker容器的三大核心技术UnionFS(下)
运维·docker·容器
Insist75311 小时前
基于OpenEuler部署kafka消息队列
分布式·docker·kafka
程序猿费益洲12 小时前
Docker 网络详解:(一)Linux 网络虚拟化技术
linux·网络·docker·容器·云计算
神都少年12 小时前
Linux Ubuntu Docker 常用命令
linux·ubuntu·docker
再难也得平12 小时前
Docker基础与项目部署
运维·docker·容器
云宏信息13 小时前
赛迪顾问《2025中国虚拟化市场研究报告》解读丨虚拟化市场迈向“多元算力架构”,国产化与AI驱动成关键变量
网络·人工智能·ai·容器·性能优化·架构·云计算
沧澜sincerely13 小时前
Redis 键空间 & 五大类型
java·redis·docker
key_Go15 小时前
06.容器存储
运维·服务器·网络·docker