一、容器化基础:Docker核心概念、镜像与容器、Dockerfile编写规范、Docker Compose入门
说实话,我第一次接触Docker的时候,心里是有点抵触的。那时候我还在用虚拟机部署项目,每次环境不一致都搞得焦头烂额。后来真正上手Docker,才发现——这玩意儿,说白了就是给应用打包了一个「随身携带的运行环境」。
咱们做《因子工厂》这个项目,容器化是第一步。你想想看,如果每个开发者的电脑环境都不一样,测试和生产环境又差一截,那部署起来得多痛苦?Docker就是来解决这个问题的。
1.1 Docker核心概念:镜像、容器、仓库
这三个概念,我建议你一开始就记牢。它们就像「菜谱、菜、菜市场」的关系。
- 镜像(Image):一个只读的模板,包含了运行应用所需的一切——代码、运行时、系统工具、库、配置。说白了就是「菜谱」。
- 容器(Container):镜像的运行实例。你可以启动、停止、删除它。就像按照菜谱做出来的「一道菜」。
- 仓库(Registry):存放镜像的地方。Docker Hub是最大的公共仓库,就像「菜市场」。
我个人习惯:把镜像想象成「类」,容器就是「对象」。一个镜像可以启动多个容器,每个容器都是独立的运行环境。
我在项目中遇到过一个问题:有个同事把镜像和容器搞混了,以为删了镜像容器就没了。其实不是——容器是基于镜像创建的,删了镜像,已经运行的容器还能继续跑,只是不能再启动新容器了。
1.2 镜像与容器的基本操作
咱们直接上手。先拉一个Nginx镜像试试:
# 拉取镜像
docker pull nginx:latest
# 查看本地镜像
docker images
# 运行容器
docker run -d --name my-nginx -p 8080:80 nginx
# 查看运行中的容器
docker ps
# 进入容器内部
docker exec -it my-nginx /bin/bash
# 停止容器
docker stop my-nginx
# 删除容器
docker rm my-nginx
# 删除镜像
docker rmi nginx
嗯,这里要注意:docker run 如果本地没有镜像,会自动从仓库拉取。但生产环境我建议你提前拉好,避免部署时网络波动。
避坑指南:我曾经在CI/CD流水线里直接用 docker run 拉取镜像,结果某次网络超时导致部署失败。后来我改成先 docker pull,再 docker run,稳多了。
1.3 Dockerfile编写规范
Dockerfile就是构建镜像的「配方」。写得好,镜像小、构建快、安全;写得不好,镜像几百兆、构建慢、漏洞多。
我总结了几条核心规范:
- 选择合适的基础镜像:别上来就
ubuntu:latest,试试alpine或slim版本。我见过一个项目,基础镜像从1.2GB降到150MB,构建时间缩短了70%。 - 减少层数:每个
RUN、COPY、ADD都会创建一层。能用&&连接的,就别拆开。 - 利用构建缓存:把不常变动的指令放在前面,比如
apt-get install,把经常变动的代码放在后面。 - 使用多阶段构建:编译环境和运行环境分开,最终镜像只保留运行所需。
来看一个《因子工厂》后端服务的Dockerfile示例:
# 第一阶段:编译
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o factor-factory
# 第二阶段:运行
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app/factor-factory /usr/local/bin/
EXPOSE 8080
CMD ["factor-factory"]
你看,最终镜像只有十几MB,而且不包含Go编译器,安全多了。
警告:千万不要在Dockerfile里写 RUN apt-get upgrade 或 RUN pip install --upgrade 这种指令。它们会破坏构建的可重复性——今天构建的镜像和明天构建的可能不一样。
1.4 Docker Compose入门
单容器好办,但《因子工厂》有前端、后端、数据库、缓存等多个服务。一个个 docker run 太累了。Docker Compose就是用来定义和运行多容器应用的。
说白了,你写一个 docker-compose.yml 文件,把服务、网络、卷都定义好,然后一条命令全部启动。
来看一个简化版的《因子工厂》Compose文件:
version: '3.8'
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: factor_factory
POSTGRES_USER: factory
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
backend:
build: ./backend
depends_on:
- db
- redis
environment:
DB_HOST: db
REDIS_HOST: redis
ports:
- "8080:8080"
frontend:
build: ./frontend
depends_on:
- backend
ports:
- "3000:3000"
volumes:
pgdata:
启动命令就一行:
docker-compose up -d
停止也简单:
docker-compose down
我的经验:开发环境用 docker-compose up(不加-d),可以看到所有服务的日志。生产环境用 docker-compose up -d 后台运行。另外,depends_on 只控制启动顺序,不保证服务就绪——比如数据库可能还在初始化。我一般会在应用层加重试机制。
知识体系总览
下面这张图,是我梳理的本章知识结构。你可以把它当作学习地图:
这张图把本章的核心内容串起来了。从左到右,从概念到实践,再到多容器编排。你学完这一章,应该能回答这几个问题:
- 镜像和容器有什么区别?
- 怎么写一个高效的Dockerfile?
- 怎么用Docker Compose一键启动所有服务?
嗯,这些就是容器化的基本功。我在《因子工厂》项目里,就是靠这套东西把部署时间从半小时压缩到了两分钟。你掌握了这些,后面的章节会越学越顺。