DevOps 入门系列:Docker 容器化 —— 一次构建,到处运行


一、为什么需要 Docker?—— 一个真实的故事

1.1 “在我的机器上是好的”

本地开发的 Python 应用运行正常,部署到生产服务器后直接报错,典型问题如下:

  • 本地 Python 3.9,服务器 Python 3.6,版本不兼容

  • 项目依赖库要求 glibc 2.31,服务器仅为 glibc 2.28

  • 本地配置环境变量 DEBUG=True,生产环境未配置

这就是软件行业长期存在的环境不一致问题,也是开发与运维之间最常见的矛盾点。

1.2 传统解决方案的缺点

方案一:逐台服务器手动配置环境
  • 部署效率低,人工操作易遗漏配置项

  • 多服务器之间配置容易出现偏差(配置漂移)

  • 无法实现业务快速扩容

方案二:虚拟机(VM)
  • 每个虚拟机搭载完整操作系统,镜像体积大(数 GB~ 数十 GB)

  • 启动速度慢,单次启动耗时数分钟

  • 资源开销极高,每台虚拟机都独立运行完整操作系统内核

1.3 Docker 的解决方案

Docker 基于容器技术实现优化:容器与宿主机共享操作系统内核,仅对进程、文件系统做隔离,轻量化优势显著。

核心概念通俗解释:

  • 镜像:将应用、依赖库、配置文件、运行环境打包为只读模板

  • 容器:镜像启动后的运行实例,秒级启动、资源占用极低

一句话总结:Docker 可将应用及全套运行环境统一打包,任意安装了 Docker 的机器,都能以完全一致的方式运行应用


二、Docker 的核心三要素

2.1 镜像(Image)

定义:只读模板,包含应用运行所需的文件系统、依赖库、环境变量、启动命令等全部内容。

类比:镜像等同于系统 ISO 安装包 / 编程语言中的类定义

核心特点:

  • 静态文件,存储在磁盘中,本身不会运行

  • 采用分层存储结构

  • 可上传至镜像仓库,实现共享与分发

2.2 容器(Container)

定义:镜像的运行实例,由镜像创建并启动,运行在内存中。

类比:镜像是「类」,容器就是通过 new 实例化出来的「对象」;一个镜像可以启动多个相互隔离的容器

核心特点:

  • 动态实体,状态分为「运行中」「已停止」

  • 支持随时创建、启动、停止、删除

  • 容器内进程对宿主机、其他容器相互隔离

2.3 仓库(Registry)

定义:专门用于存储、分发镜像的服务平台。

常用仓库分类
  1. Docker Hub:Docker 官方公共镜像仓库,可免费拉取 Nginx、MySQL、Python 等官方镜像(国内访问及其困难,截止2026年6月国内大部分镜像站都已无法稳定访问,最好自己去搜集一份可用的镜像网站列表)

  2. 私有仓库:企业内部搭建,用于存放业务私有镜像,代表产品:Harbor、阿里云容器镜像服务

镜像标准地址格式
[仓库地址]/命名空间/镜像名:标签

示例解析:docker.io/library/nginx:1.25

  • docker.io:Docker Hub 官方地址(日常使用可省略)

  • library:官方镜像默认命名空间

  • nginx:镜像名称

  • 1.25:版本标签;latest 代表最新版本(生产环境不建议使用)


三、安装 Docker(Ubuntu / WSL2 环境推荐)

以下为 Ubuntu 系统完整安装命令,全程使用终端执行:

# 1. 更新软件源索引
sudo apt update

# 2. 安装前置依赖
sudo apt install -y ca-certificates curl

# 3. 创建密钥目录,并添加 Docker 官方 GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# 4. 添加 Docker 官方软件源(国内连不上换镜像站)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 5. 再次更新源,安装 Docker 核心组件
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io

# 6. 将当前用户加入 docker 用户组,免去每次执行命令加 sudo
sudo usermod -aG docker $USER

生效与验证

  1. 重新登录终端,或执行 newgrp docker 即时生效

  2. 执行测试命令,出现欢迎页即代表安装成功

docker run hello-world

四、Docker 基础命令

4.1 镜像管理

1. 拉取镜像 docker pull

从远程仓库下载镜像到本地

# 拉取 Nginx 镜像(为了方便这里使用alpine,alpine 基于极简 Linux,体积约 5MB)
docker pull nginx:alpine
2. 查看本地镜像 docker images
docker images

输出示例:

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        alpine    abc123def456   2 weeks ago   5.2MB
3. 删除镜像 docker rmi
# 根据【镜像名:标签】删除镜像
docker rmi nginx:alpine

注意:若镜像被容器关联(即使容器已停止),需先删除容器,或添加 -f 强制删除。

4. 镜像导出 / 导入(离线传输场景)
# 导出镜像为 tar 包(离线迁移使用)
docker save -o mynginx.tar nginx:alpine

# 从 tar 包导入镜像
docker load -i mynginx.tar
5. 查看镜像构建历史
# 显示镜像各层的创建时间、大小及构建指令
docker history

4.2 容器生命周期管理

1. 运行容器 docker run

核心命令,基于镜像创建并启动容器,参数是学习重点:

docker run -d --name mynginx -p 8080:80 nginx:alpine

参数详解:

  • -d:后台守护进程模式运行,终端不会被日志占用;不加则前台运行,Ctrl+C 会停止容器

  • --name mynginx:自定义容器名称,便于后续管理(不指定则 Docker 随机命名)

  • -p 8080:80:端口映射,宿主机端口:容器内部端口。访问 宿主机IP:8080 即可转发至容器 80 端口

  • 末尾 nginx:alpine:指定使用的镜像

执行后浏览器访问 http://localhost:8080,可看到 Nginx 欢迎页面。

2. 查看容器 docker ps
# 仅查看【正在运行】的容器
docker ps

# 查看【所有容器】(包含已停止的容器)
docker ps -a

输出字段:容器 ID、容器名、所用镜像、运行状态、端口映射等。

3. 停止 / 启动 / 重启容器
# 优雅停止容器(发送终止信号,正常退出进程)
docker stop mynginx

# 启动已停止的容器
docker start mynginx

# 重启容器
docker restart mynginx
4. 删除容器 docker rm
# 仅删除【已停止】的容器
docker rm mynginx

# 强制删除【运行中】的容器
docker rm -f mynginx
5. 进入容器内部 docker exec

用于调试、查看容器内文件、执行命令,不会中断容器运行

# 交互式进入容器终端
docker exec -it mynginx sh

参数说明:

  • -it:分配交互式终端(-i 交互模式 + -t 伪终端)

  • sh:执行的终端命令(Alpine 镜像无 bash,统一使用 sh)

操作:进入后可执行 lspscat 等命令,输入 exit 退出终端。

6. 查看容器日志 docker logs
# 查看全部历史日志
docker logs mynginx

# 实时滚动查看日志(Ctrl+C 退出)
docker logs -f mynginx

# 仅查看最后 50 行日志
docker logs --tail 50 mynginx

4.3 常用组合示例

示例 1:临时测试容器(退出自动删除)

适合一次性调试,容器退出后自动清理,无残留

docker run --rm -it alpine sh

执行后会先下载镜像并启动容器,-it直接进入容器内部的命令行,我们执行完测试任务后输入exit容器即可退出并自动消失,无需手动删除容器。

示例 2:运行 MySQL 容器

通过 -e 注入环境变量,初始化数据库账号密码

docker run -d --name mydb \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -p 3306:3306 \
  mysql:8.0

五、Docker 网络:容器之间互相通信

5.1 默认 bridge 网络的问题

未指定网络时,容器默认加入 Docker 内置 bridge 网络:

  • 同网络内容器可以互通,但无法通过容器名访问,只能使用 IP 地址

  • 容器重启后 IP 会发生变化,服务连接地址需要频繁修改,实用性差

5.2 自定义 bridge 网络(生产 / 开发推荐)

自定义网络自带内置 DNS 解析,支持直接使用容器名互通

步骤 1:创建自定义网络
docker network create my-net
步骤 2:将多个容器加入同一网络
# 创建并启动 Nginx 容器,命名为app1,加入 my-net 网络
docker run -d --name app1 --network my-net nginx:alpine

# 创建并启动 Redis 容器,命名为app2,加入 my-net 网络
docker run -d --name app2 --network my-net redis:alpine
步骤 3:验证容器互通

app1 容器内,直接通过容器名访问 app2

docker exec app1 ping app2

可以正常 ping 通,原理:自定义网络内置 DNS(127.0.0.11),自动将容器名解析为对应 IP。

5.3 端口映射 vs 容器互通

  • 端口映射(-p):面向外部(宿主机 / 外网) 访问容器服务

  • 容器互通:同一自定义网络内的容器,直接用容器名访问,无需端口映射

实战场景:应用容器连接数据库容器,两者同属一个自定义网络,连接字符串可直接写 mysql://mysql:3306

5.4 其他网络模式

  1. host 模式:--network host,容器直接复用宿主机网络栈,无网络隔离,性能最优,但易出现端口冲突

  2. none 模式:--network none,容器禁用所有网络接口,完全网络隔离

日常开发、测试优先使用 自定义 bridge 网络


六、数据持久化:容器重启后数据留存

6.1 问题场景

容器默认文件系统是临时存储:容器删除后,内部所有写入的数据(数据库文件、上传文件、日志等)都会永久丢失。
典型案例:MySQL 容器删除重建,原有数据库数据全部清空。

6.2 解决方案:数据卷(Volume)

数据卷是 Docker 独立管理的存储空间,生命周期与容器解绑,容器删除后卷数据依然保留。分为两种常用挂载方式:

方式一:命名卷(官方推荐,可移植性强)

适用于数据库等核心数据存储

# 1. 创建命名卷
docker volume create mysql-data

# 2. 运行容器并挂载卷
docker run -d --name mydb \
  -v mysql-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:8.0

参数说明:-v 卷名:容器内目录,将 mysql-data 卷挂载到 MySQL 数据目录 /var/lib/mysql

复用卷:删除旧容器后,新容器挂载同一个卷,数据无缝恢复:

# 删除旧容器
docker rm -f mydb
# 新容器复用原有数据卷
docker run -d --name newdb -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
方式二:绑定挂载(Bind Mount)

直接将宿主机本地目录挂载到容器内,适合开发调试(修改本地代码,容器实时生效)

# 宿主机 /home/devops/data 目录 挂载到 容器 /app/data 目录
docker run -d -v /home/devops/data:/app/data myapp

6.3 卷相关命令

# 查看所有数据卷
docker volume ls

# 查看卷详细信息(元数据,即存储路径、挂载情况等信息)
docker volume inspect 卷名

# 删除无用数据卷(删除后数据永久丢失)
docker volume rm 卷名

七、动手练习:部署完整 Web 应用栈

注:本节基于AI生成,不保证能运行,仅展示基本流程用

实战目标:基于 Docker 搭建 Nginx + PHP + MySQL 多容器应用栈,结合「自定义网络」+「数据卷」综合使用。

步骤 1:创建专属自定义网络

docker network create app-net

步骤 2:启动 MySQL 容器(挂载数据卷)

# 创建数据库数据卷
docker volume create db-data

# 启动 MySQL 容器,加入自定义网络、初始化账号与数据库
docker run -d --name mysql \
  --network app-net \
  -v db-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -e MYSQL_DATABASE=mydb \
  mysql:8.0

步骤 3:启动 PHP 应用容器(绑定本地代码目录)

假设本地代码存放在当前目录 src/ 文件夹中:

docker run -d --name php-app \
  --network app-net \
  -v "$PWD/src":/var/www/html \
  php:8.2-apache

步骤 4:启动 Nginx 反向代理容器

对外暴露端口,提供外网访问入口:

docker run -d --name nginx \
  --network app-net \
  -p 8080:80 \
  nginx:alpine

验证互通

进入 PHP 容器,通过容器名访问 MySQL,验证网络连通性:

docker exec php-app ping mysql

拓展:可进入 Nginx 容器修改配置文件,实现反向代理转发至 php-app 容器。


八、Docker 常用命令速查表

操作场景执行命令
拉取远程镜像docker pull 镜像名:标签
查看本地所有镜像docker images
删除本地镜像docker rmi 镜像ID/镜像名:标签
后台运行容器(端口映射)docker run -d --name 容器名 -p 宿主机端口:容器端口 镜像名
查看运行中的容器docker ps
查看所有容器(含停止状态)docker ps -a
停止容器docker stop 容器名/容器ID
启动已停止容器docker start 容器名/容器ID
删除容器docker rm -f 容器名/容器ID
交互式进入容器docker exec -it 容器名 sh
实时查看容器日志docker logs -f 容器名
创建自定义网络docker network create 网络名
查看所有网络docker network ls
创建数据卷docker volume create 卷名
运行容器并挂载数据卷docker run -v 卷名:容器内目录 镜像名

更多推荐