1. 为什么现在还要聊Docker
如果你最近两年才入行,可能觉得容器化已经是天经地义的事——新项目上来就是Dockerfile,本地跑通了直接推到服务器,中间几乎不用操心环境问题。但往前倒五六年,部署一个Web应用是什么体验?装Python、装数据库、配Nginx、调环境变量、处理各种依赖冲突,一套流程走下来半天没了,换台机器还得重来一遍。Docker解决的正是这个“在我机器上明明能跑”的经典难题。
这篇文章不是官方文档的搬运,而是把我自己从零开始接触Docker、踩坑、到最终把几个项目稳定跑在容器里的完整过程梳理出来。内容会覆盖Docker到底是什么、核心概念怎么理解、安装配置怎么做、以及如何把一个真实的应用打包部署上去。适合两类人看:一类是刚接触容器技术、想快速上手的新手;另一类是用过Docker但一直停留在docker run层面、想系统理解背后逻辑的开发者。我会尽量用生活化的类比来解释概念,同时给出可以直接复制执行的命令和配置,让你看完就能动手。
2. Docker到底是什么,和虚拟机有什么区别
2.1 从“集装箱”这个比喻说起
Docker的Logo是一条鲸鱼驮着一堆集装箱,这个比喻其实非常精准。在没有集装箱之前,货物装卸靠人力,不同形状的货物堆在一起,装船效率极低。集装箱出现后,不管里面装的是衣服还是电子产品,外面尺寸统一,吊装、堆放、运输全部标准化。Docker做的事情类似:它把你的应用和它需要的一切(代码、运行时、系统工具、库、配置)打包成一个标准化的“容器”,这个容器可以在任何安装了Docker的机器上运行,里面的东西完全一致。
这里的关键词是标准化和隔离。标准化保证了可移植性,隔离保证了不同应用之间互不干扰。你可以在同一台机器上跑一个Python 3.8的应用和一个Python 3.12的应用,它们各自带着自己的依赖,互不影响。
2.2 容器和虚拟机的本质区别
很多人第一次接触Docker会问:这不就是虚拟机吗?两者确实都是隔离环境,但实现层次完全不同。
虚拟机是在硬件层面做虚拟化。它通过Hypervisor(比如VMware、KVM)模拟出一整套硬件,然后在上面装一个完整的操作系统。每个虚拟机都有自己的内核、自己的系统进程,资源开销很大。启动一个虚拟机通常要几十秒甚至几分钟,占用的内存也是GB级别。
容器是在操作系统层面做隔离。所有容器共享宿主机的内核,Docker利用Linux的Namespace做资源隔离、Cgroups做资源限制。容器里没有独立的操作系统内核,只有一个精简的用户空间。所以容器启动是秒级甚至毫秒级的,占用内存通常只有几十MB。
| 对比维度 | 虚拟机 | Docker容器 |
|---|---|---|
| 隔离级别 | 硬件级虚拟化 | 操作系统级隔离 |
| 启动速度 | 分钟级 | 秒级/毫秒级 |
| 资源开销 | 高,每台独占内存和CPU | 低,共享内核,按需分配 |
| 镜像大小 | GB级别 | 通常几十到几百MB |
| 隔离程度 | 完全隔离 | 进程级隔离,共享内核 |
| 适用场景 | 需要不同操作系统的场景 | 微服务、快速部署、CI/CD |
理解这个区别很重要,因为它决定了你什么时候该用Docker、什么时候不该用。如果你需要在一个Linux主机上跑Windows应用,那还是得用虚拟机。但如果你只是想把一个Web服务快速部署到多台服务器上,Docker是更轻、更快的选择。
2.3 三个核心概念:镜像、容器、仓库
Docker的世界里有三个必须搞清楚的概念,我用做菜来类比。
镜像(Image)就像一份菜谱。它是一份只读的模板,里面写清楚了这道菜需要什么材料、按什么步骤做。镜像本身不能吃,但你可以根据它做出无数份菜。技术上,镜像是一个分层存储的文件系统,每一层代表一次修改操作。
容器(Container)就是按照菜谱实际做出来的那道菜。它是镜像的运行实例,可以被启动、停止、删除。每个容器都是独立的,你在一个容器里加盐,不会影响另一个容器。容器在镜像之上加了一个可写层,所有运行时的修改都写在这一层。
仓库(Registry)是存放菜谱的图书馆。最常用的是Docker Hub,上面有大量官方和社区维护的镜像。你可以从仓库拉取镜像,也可以把自己的镜像推上去。企业内网通常会搭建私有仓库,比如Harbor或Registry。
这三者的关系是:从仓库拉取镜像,运行镜像产生容器,容器里的修改可以通过commit保存为新镜像,再推回仓库。日常工作中,我们更多是通过Dockerfile来构建镜像,而不是手动commit。
3. 安装Docker:不同系统的实操步骤
3.1 Linux上的安装(以主流发行版为例)
Linux是Docker的原生平台,安装最顺畅。我以Ubuntu和CentOS这两个最常见的发行版为例,其他发行版思路类似。
Ubuntu上的安装:
先卸载可能存在的旧版本,避免冲突:
sudo apt-get remove docker docker-engine docker.io containerd runc然后安装依赖包,添加Docker的官方软件源。这里注意,不同Ubuntu版本代号不同,需要先确认:
sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null更新源并安装:
sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin安装完成后验证:
sudo docker run hello-world如果看到“Hello from Docker!”的输出,说明安装成功。
CentOS上的安装:
CentOS 7和CentOS 8的步骤略有差异,但核心逻辑一样。先安装yum-utils,添加仓库:
sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo然后安装:
sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin启动并设置开机自启:
sudo systemctl start docker sudo systemctl enable docker注意:CentOS 8已经停止维护,官方源可能有问题,建议用CentOS 7或者迁移到其他发行版。如果遇到源不可用,可以换成阿里云或清华的镜像源。
3.2 免sudo使用Docker的配置
默认情况下,只有root用户和docker组的成员才能操作Docker。每次敲命令都要加sudo很烦,解决办法是把当前用户加入docker组:
sudo groupadd docker sudo usermod -aG docker $USER执行完后需要退出终端重新登录,或者执行newgrp docker让组变更生效。验证一下:
docker run hello-world不加sudo也能跑,说明配置成功。
这里有个安全提醒:docker组实际上等同于root权限,因为容器可以挂载宿主机的任意目录。所以在生产服务器上,不要把无关用户加进docker组。
3.3 配置国内镜像加速器
从Docker Hub拉取镜像在国内网络环境下可能很慢,配置镜像加速器是必做的一步。编辑/etc/docker/daemon.json:
{ "registry-mirrors": [ "https://mirror.ccs.tencentyun.com", "https://docker.mirrors.ustc.edu.cn" ] }然后重启Docker服务:
sudo systemctl daemon-reload sudo systemctl restart docker验证加速器是否生效:
docker info在输出里找到Registry Mirrors字段,看看配置的地址是否出现。
实操心得:镜像加速器地址不是一成不变的,有些会失效。如果发现拉取镜像还是很慢,可以多试几个地址,或者用
docker pull时指定具体的镜像仓库地址。另外,企业内网通常有自己的私有仓库,配置方式类似,把地址换成内网仓库即可。
4. Docker核心操作:从拉取镜像到运行容器
4.1 镜像管理的常用命令
安装完Docker后,第一件事通常是拉取一个镜像。以Nginx为例:
docker pull nginx不加标签默认拉取latest,也就是最新版。但生产环境不建议用latest,因为版本会变,可能导致不可预期的行为。最好指定具体版本:
docker pull nginx:1.25.3查看本地已有的镜像:
docker images输出会显示仓库名、标签、镜像ID、创建时间和大小。删除镜像用docker rmi,可以跟镜像ID或名称加标签:
docker rmi nginx:1.25.3如果镜像正在被容器使用,需要先删除容器再删镜像,或者加-f强制删除。
4.2 容器的生命周期管理
运行一个Nginx容器:
docker run -d -p 8080:80 --name my-nginx nginx:1.25.3这条命令拆开看:-d表示后台运行,-p 8080:80把宿主机的8080端口映射到容器的80端口,--name给容器起个名字方便管理,最后是镜像名和标签。
查看运行中的容器:
docker ps加上-a可以看到所有容器,包括已停止的。
停止、启动、重启容器:
docker stop my-nginx docker start my-nginx docker restart my-nginx删除容器:
docker rm my-nginx如果容器还在运行,需要先停止,或者用-f强制删除。
进入正在运行的容器内部:
docker exec -it my-nginx /bin/bash这个命令非常常用,-it表示交互式终端,后面跟容器名和要执行的命令。进去之后可以查看日志、修改配置、调试问题。退出用exit。
注意事项:用
docker exec进入容器后做的修改,在容器删除后会丢失。如果需要持久化,应该通过挂载卷或者构建新镜像的方式。另外,不是所有镜像都有bash,有些精简镜像只有sh,这时候把/bin/bash换成/bin/sh。
4.3 数据卷:让数据活得更久
容器本身是无状态的,删除容器后里面的数据就没了。但数据库、上传文件这些需要持久化,这时候就要用数据卷。
最简单的挂载方式是把宿主机的目录映射到容器里:
docker run -d -p 3306:3306 -v /data/mysql:/var/lib/mysql --name mysql-db -e MYSQL_ROOT_PASSWORD=yourpassword mysql:8.0-v /data/mysql:/var/lib/mysql把宿主机的/data/mysql目录挂载到容器的/var/lib/mysql。这样MySQL的数据文件就存在宿主机上,容器删了数据还在。
另一种方式是使用命名卷,由Docker管理存储位置:
docker volume create mysql-data docker run -d -v mysql-data:/var/lib/mysql --name mysql-db mysql:8.0命名卷的好处是Docker会统一管理,备份和迁移更方便。查看所有卷:
docker volume ls实操心得:挂载宿主机目录时,如果宿主机目录不存在,Docker会自动创建,但权限可能不对。比如MySQL容器需要写权限,如果宿主机目录是root所有,容器里的mysql用户可能写不了。解决办法是提前创建目录并设置合适的权限,或者用命名卷让Docker自己处理。
5. Dockerfile:把应用打包成镜像
5.1 Dockerfile的基本结构
手动commit容器来制作镜像不是好习惯,因为过程不可追溯、不可重复。正确做法是写Dockerfile,用声明式的方式描述镜像怎么构建。
一个典型的Python Web应用Dockerfile:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "app.py"]逐行解释:FROM指定基础镜像,WORKDIR设置工作目录,COPY把文件复制进镜像,RUN在构建时执行命令,EXPOSE声明容器监听的端口(只是声明,实际映射靠-p),CMD指定容器启动时执行的命令。
5.2 构建镜像的实操过程
在Dockerfile所在目录执行:
docker build -t my-app:1.0 .-t指定镜像名和标签,最后的.表示构建上下文是当前目录。构建过程中Docker会按顺序执行每条指令,每条指令生成一层。如果某条指令失败,构建会中断,但之前的层会缓存下来,下次构建时如果前面的指令没变,会直接复用缓存,速度很快。
查看构建好的镜像:
docker images | grep my-app运行这个镜像:
docker run -d -p 8000:8000 --name my-app-container my-app:1.05.3 多阶段构建:让镜像更小
直接构建的镜像往往包含编译工具、测试文件等运行时不需要的东西,导致镜像臃肿。多阶段构建可以解决这个问题。
以Go应用为例:
# 构建阶段 FROM golang:1.21 AS builder WORKDIR /build COPY . . RUN CGO_ENABLED=0 go build -o myapp . # 运行阶段 FROM alpine:3.18 WORKDIR /app COPY --from=builder /build/myapp . EXPOSE 8080 CMD ["./myapp"]第一个阶段用完整的Go镜像编译出二进制文件,第二个阶段只复制编译好的二进制到一个精简的Alpine镜像里。最终镜像可能只有几十MB,而不是几百MB。
注意事项:多阶段构建时,
COPY --from=builder的路径要写对。另外,Alpine用的是musl libc而不是glibc,如果程序依赖glibc,需要静态编译或者换用debian-slim作为运行阶段的基础镜像。
6. Docker Compose:多容器应用编排
6.1 为什么需要Compose
一个真实的Web应用通常不止一个容器:Web服务、数据库、缓存、消息队列,可能还有Nginx做反向代理。如果每个容器都手动docker run,命令又长又容易出错,而且容器之间的网络连接、启动顺序都要手动处理。Docker Compose用一个YAML文件描述所有服务,一条命令全部启动。
6.2 编写docker-compose.yml
一个典型的Web应用加数据库的配置:
version: '3.8' services: web: build: . ports: - "8000:8000" environment: - DATABASE_URL=postgresql://user:pass@db:5432/mydb depends_on: - db volumes: - ./app:/app db: image: postgres:15 environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=mydb volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:这里定义了两个服务:web和db。web用当前目录的Dockerfile构建,db用官方Postgres镜像。depends_on保证db先启动,但注意它只保证启动顺序,不保证db已经准备好接受连接。environment设置环境变量,web服务里用db作为主机名连接数据库,因为Compose会自动创建一个网络,服务之间可以用服务名互相访问。
6.3 Compose的常用操作
启动所有服务:
docker compose up -d注意新版本是docker compose(中间有空格),老版本是docker-compose(中间有横线)。查看服务状态:
docker compose ps查看某个服务的日志:
docker compose logs -f web停止并删除所有容器:
docker compose down如果还想删除数据卷,加-v:
docker compose down -v实操心得:
depends_on不保证服务就绪,如果web启动时db还没准备好,连接会失败。解决办法是在应用里加重试逻辑,或者用健康检查配合condition: service_healthy。健康检查的配置是在服务下加healthcheck字段,指定检测命令和间隔。
7. 常见问题与排查技巧实录
7.1 容器启动就退出
这是新手最常遇到的问题。docker run之后docker ps看不到容器,docker ps -a显示状态是Exited。原因通常是容器的主进程执行完就结束了。容器不是虚拟机,它需要一个前台进程持续运行,如果主进程退出,容器就退出。
排查方法:先看日志。
docker logs <容器ID或名称>日志里通常会显示错误信息。如果是命令执行完就退出,比如docker run ubuntu,因为ubuntu镜像默认命令是bash,没有终端交互就退出了。解决办法是让容器跑一个持续运行的进程,比如docker run -d ubuntu sleep infinity。
7.2 端口被占用
启动容器时报错:
Error response from daemon: driver failed programming external connectivity: Bind for 0.0.0.0:8080 failed: port is already allocated说明宿主机的8080端口已经被占用了。查看谁占用了:
sudo lsof -i :8080 # 或者 sudo netstat -tlnp | grep 8080要么停掉占用端口的进程,要么换一个宿主机端口映射,比如-p 8081:80。
7.3 容器内无法访问外网
有时候容器里ping外网不通,或者apt-get update失败。先检查宿主机的网络转发是否开启:
sysctl net.ipv4.ip_forward如果是0,需要开启:
sudo sysctl -w net.ipv4.ip_forward=1还要检查防火墙规则,有些防火墙会阻止Docker的NAT转发。另外,如果Docker的默认网段和宿主机所在网段冲突,也会导致网络异常,可以通过修改/etc/docker/daemon.json里的bip字段来调整。
7.4 镜像构建缓存导致代码没更新
Dockerfile里COPY . .这一行,如果前面的指令没变,Docker会复用缓存,导致新代码没被复制进去。解决办法是在构建时加--no-cache:
docker build --no-cache -t my-app:1.0 .但这样每次都要重新安装依赖,很慢。更好的做法是优化Dockerfile的层顺序,把不常变的部分放前面,常变的部分放后面。比如先COPY requirements.txt并安装依赖,再COPY源代码。
7.5 常见问题速查表
| 问题现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
| 容器启动即退出 | 主进程执行完毕 | docker logs <容器> | 让主进程前台运行 |
| 端口冲突 | 宿主机端口被占用 | lsof -i :端口 | 换端口或停占用进程 |
| 容器无外网 | 转发未开启或防火墙 | sysctl net.ipv4.ip_forward | 开启转发,检查防火墙 |
| 代码未更新 | 构建缓存 | docker build --no-cache | 优化层顺序或禁用缓存 |
| 数据丢失 | 未挂载卷 | docker inspect <容器> | 配置数据卷挂载 |
| 权限拒绝 | 用户不在docker组 | groups | 加入docker组并重登 |
独家避坑技巧:遇到问题时,
docker inspect是你的好朋友。它能输出容器的完整配置,包括网络、挂载、环境变量、状态等。配合docker logs和docker exec,大部分问题都能定位。另外,养成看容器日志的习惯,很多错误在日志里写得清清楚楚,比瞎猜快得多。
8. 把应用真正部署上去:一个完整的流程
8.1 从本地到服务器的完整链路
假设你在本地开发了一个Web应用,现在要部署到一台云服务器上。完整流程是这样的:
第一步,在本地写好Dockerfile和docker-compose.yml,确保docker compose up能正常跑起来。第二步,把代码推送到代码仓库。第三步,登录服务器,拉取代码。第四步,在服务器上执行docker compose up -d。第五步,配置Nginx反向代理或者直接开放端口。第六步,验证服务是否正常。
如果每次更新都要手动登录服务器拉代码,效率太低。更好的做法是用CI/CD工具自动化这个过程。代码推送到仓库后,自动构建镜像、推送到镜像仓库、然后在服务器上拉取新镜像并重启容器。
8.2 生产环境的注意事项
生产环境和本地开发有几个关键区别。
日志管理:本地开发看终端输出就行,生产环境需要把日志收集起来。Docker默认把日志写到JSON文件里,可以用docker logs查看,但长期存储需要配置日志驱动,把日志发送到集中式日志系统。
资源限制:生产环境必须限制容器能使用的CPU和内存,防止某个容器把整台机器拖垮。在Compose里可以配置:
services: web: deploy: resources: limits: cpus: '1.0' memory: 512M重启策略:服务器重启或者容器意外退出时,需要自动重启。配置restart: always或者restart: unless-stopped。
镜像标签:生产环境绝对不要用latest,每次构建用具体的版本号或者git commit hash作为标签,方便回滚。
安全加固:容器默认以root运行,生产环境应该创建非root用户。在Dockerfile里加:
RUN useradd -m appuser USER appuser8.3 一次真实的部署踩坑记录
我之前部署一个Python应用时遇到一个问题:本地跑得好好的,服务器上启动就报数据库连接失败。排查了半天,发现是Compose里depends_on只保证db容器先启动,但Postgres初始化需要几秒钟,web容器启动太快,连不上就退出了。
解决办法有两个:一是在应用里加连接重试逻辑,二是用健康检查。我选了后者,在db服务下加:
healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d mydb"] interval: 5s timeout: 5s retries: 5然后在web服务里把depends_on改成:
depends_on: db: condition: service_healthy这样web会等db健康检查通过后才启动,问题解决。
另一个坑是时区。容器默认用UTC时间,应用里记录的时间比北京时间少8小时。解决办法是在Dockerfile里设置时区:
ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone或者在Compose里挂载宿主机的时区文件:
volumes: - /etc/localtime:/etc/localtime:ro这些都是文档里不会特意写、但实际部署一定会遇到的问题。踩过一次就记住了。
9. 镜像体积优化与构建速度提升
9.1 选择合适的基础镜像
基础镜像的选择直接决定最终镜像的大小。同样是Python环境,python:3.11完整版可能有1GB,python:3.11-slim只有150MB左右,python:3.11-alpine更小,大概50MB。但Alpine用的是musl libc,某些依赖glibc的包可能装不上或者运行异常。
我的经验是:优先用slim版本,除非有特殊需求。Alpine适合对体积极度敏感的场景,但要做好踩坑的准备。如果应用依赖numpy、pandas这类科学计算库,Alpine上编译会很痛苦,直接用slim更省心。
9.2 减少镜像层数
Dockerfile里每条RUN、COPY、ADD指令都会生成一层。层数越多,镜像越大,构建也越慢。把多个RUN命令用&&连接起来:
RUN apt-get update && \ apt-get install -y --no-install-recommends \ gcc \ libpq-dev \ && rm -rf /var/lib/apt/lists/*注意最后清理apt缓存,否则缓存文件也会被打进镜像里,白白增加几十MB。
9.3 利用构建缓存加速
Docker构建时会逐层检查缓存,如果某一层的内容没变,就直接复用。所以Dockerfile的指令顺序很关键:把不常变的部分放前面,常变的部分放后面。
比如Node.js应用,package.json不常变,源代码经常变。正确的顺序是:
COPY package.json package-lock.json ./ RUN npm ci COPY . .这样只要package.json没改,npm ci这一层就一直用缓存,只有COPY . .之后的层需要重新构建。如果反过来先COPY . .再npm ci,那每次改代码都会导致依赖重新安装,构建时间从几秒变成几分钟。
9.4 使用.dockerignore
和.gitignore类似,.dockerignore文件告诉Docker在构建时忽略哪些文件。如果不配置,COPY . .会把node_modules、.git、日志文件等全部复制进镜像,导致镜像巨大、构建缓慢。
一个典型的.dockerignore:
.git node_modules __pycache__ *.pyc .env *.log .vscode .idea实操心得:
.dockerignore经常被忽略,但它对构建速度和镜像大小的影响非常大。我见过一个项目因为没配.dockerignore,把整个.git目录和node_modules打进了镜像,镜像从200MB膨胀到1.5GB。养成习惯,创建Dockerfile的同时就创建.dockerignore。
10. 容器网络:理解Docker的网络模型
10.1 默认网络模式
Docker安装后会创建三种默认网络:bridge、host、none。
bridge是默认模式。每个容器分配一个独立的IP,通过Docker创建的虚拟网桥和宿主机通信。容器之间可以通过IP互相访问,但需要端口映射才能从外部访问。Compose创建的网络也是bridge类型,但提供了服务名DNS解析,所以容器之间可以用服务名通信。
host模式让容器直接使用宿主机的网络栈,没有隔离。容器里监听的端口就是宿主机的端口,不需要-p映射。性能最好,但失去了网络隔离,端口冲突的风险也高。
none模式给容器一个只有loopback的网络,完全隔离。适合不需要网络的批处理任务。
10.2 自定义网络的好处
默认的bridge网络里,容器之间只能用IP互相访问,IP在容器重启后可能变化,很不方便。自定义网络提供了自动DNS解析,容器可以用名字互相访问:
docker network create my-network docker run -d --name app --network my-network my-app docker run -d --name db --network my-network postgres这样app容器里可以直接用db作为主机名连接数据库,不用关心IP是多少。
10.3 端口映射的几种写法
-p参数的写法有几种,含义不同:
-p 8080:80:把宿主机所有网卡的8080端口映射到容器的80端口-p 127.0.0.1:8080:80:只映射到宿主机的loopback地址,外部无法访问-p 8080:80/udp:映射UDP端口-P:随机映射容器暴露的所有端口到宿主机的高端口
生产环境如果前面有Nginx做反向代理,通常只需要映射到127.0.0.1,让Nginx转发,避免直接暴露容器端口。
11. 数据持久化与备份策略
11.1 三种挂载方式的区别
Docker支持三种挂载方式:绑定挂载(bind mount)、命名卷(volume)、临时文件系统(tmpfs)。
绑定挂载直接把宿主机目录映射到容器,路径必须存在,权限由宿主机控制。适合开发环境,因为改代码后容器里立即生效。
命名卷由Docker管理,存储在/var/lib/docker/volumes/下。适合生产环境的数据持久化,因为Docker会处理权限和生命周期。
tmpfs挂载在内存里,容器停止后数据消失。适合存储敏感信息或者临时缓存。
11.2 数据库容器的备份实操
以MySQL为例,备份数据有两种方式。
第一种是用mysqldump导出:
docker exec mysql-db mysqldump -u root -p'password' mydb > backup.sql第二种是直接备份数据卷。先找到卷的挂载点:
docker volume inspect mysql-data输出里的Mountpoint就是宿主机上的实际路径。停掉容器后,直接打包这个目录:
docker stop mysql-db sudo tar -czf mysql-backup.tar.gz /var/lib/docker/volumes/mysql-data/_data docker start mysql-db恢复时把备份解压回原路径即可。
注意事项:直接备份数据卷时一定要先停容器,否则可能备份到不一致的数据。用mysqldump则不需要停容器,但导出期间最好避免大量写入。生产环境建议用专业的备份工具,或者配置主从复制做实时备份。
12. 从单机到多机:Docker的边界在哪里
Docker本身是单机工具,它管理的是同一台机器上的容器。当应用规模变大,需要多台机器协同工作时,Docker就力不从心了。这时候需要更上层的编排工具,比如Kubernetes或者Docker Swarm。
但这不意味着Docker不重要。恰恰相反,Kubernetes调度的最小单位就是容器,底层用的还是Docker(或者containerd)那套东西。理解了Docker的镜像、容器、网络、存储,再学Kubernetes会顺畅很多。
对于中小规模的应用,单机Docker加Compose完全够用。一台配置不错的服务器,跑十几个容器轻轻松松。等到真的需要多机编排了,再上Kubernetes也不迟。技术选型要看实际需求,不要为了用而用。
我个人在实际操作中的体会是,Docker最大的价值不是技术本身有多高深,而是它改变了部署的思维方式。以前部署是“登录服务器、装环境、传代码、配服务”,现在是“构建镜像、推送、拉取、运行”。整个过程标准化、可重复、可回滚。一旦习惯了这种方式,就再也回不去了。最后分享一个小技巧:如果你在本地开发时经常需要重建环境,可以写一个Makefile或者shell脚本,把常用的docker compose命令封装起来,比如make up、make down、make logs,能省不少敲键盘的时间。