第一章:容器化基础——Docker核心概念、镜像与容器、Dockerfile编写规范、Docker Compose入门
大家好,我是你们的老朋友。今天咱们正式开始聊云原生应用容器化改造。说实话,我接触Docker也有七八年了,从最初在测试环境里瞎折腾,到后来在生产环境大规模落地,踩过的坑真不少。这一章,咱们就把容器化的地基打牢。
1.1 Docker核心概念:镜像、容器、仓库
先说说最核心的三个概念。你想想看,Docker到底解决了什么问题?说白了,就是「环境一致性」这个老大难。以前我们经常遇到「在我机器上跑得好好的」这种尴尬,Docker就是来终结这个问题的。
- 镜像(Image):一个只读的模板,包含了运行应用所需的一切——代码、运行时、系统工具、库、配置。你可以把它理解成一个「快照」或者「压缩包」。
- 容器(Container):镜像的运行实例。镜像启动后就变成了容器,容器是可写的,你可以往里面写日志、改文件。但注意,容器一旦被删除,这些改动就没了。
- 仓库(Registry):存放镜像的地方。最常用的是Docker Hub,企业里一般用Harbor或者阿里云镜像仓库。
重要理解:镜像和容器的关系,就像类和对象的关系。镜像是静态的,容器是动态的。我见过不少新手把镜像和容器混为一谈,嗯,这里要特别注意。
1.2 镜像与容器的基本操作
光说不练假把式。咱们直接上手操作。我个人习惯把常用命令记在便签上,因为有些参数确实容易忘。
镜像操作
# 拉取镜像
docker pull nginx:1.25-alpine
# 查看本地镜像
docker images
# 删除镜像
docker rmi nginx:1.25-alpine
# 构建镜像(后面会细讲)
docker build -t my-app:v1 .
容器操作
# 创建并启动容器
docker run -d --name my-nginx -p 8080:80 nginx:1.25-alpine
# 查看运行中的容器
docker ps
# 查看所有容器(包括已停止的)
docker ps -a
# 进入容器内部
docker exec -it my-nginx /bin/sh
# 停止容器
docker stop my-nginx
# 启动已停止的容器
docker start my-nginx
# 删除容器
docker rm my-nginx
小技巧:我建议你给容器命名(--name参数),不然Docker会随机生成一个名字,比如「focused_curie」这种,时间长了根本分不清哪个是哪个。
1.3 Dockerfile编写规范
Dockerfile是构建镜像的「配方」。写得好不好,直接影响镜像大小、构建速度和安全性。我在项目中遇到过不少因为Dockerfile写得随意导致线上问题的案例。
一个标准的Dockerfile示例
# 1. 选择基础镜像
FROM node:18-alpine AS builder
# 2. 设置工作目录
WORKDIR /app
# 3. 先复制依赖文件,利用缓存
COPY package*.json ./
# 4. 安装依赖
RUN npm ci --only=production
# 5. 复制源码
COPY . .
# 6. 构建应用
RUN npm run build
# 7. 多阶段构建:用更小的镜像运行
FROM nginx:1.25-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
编写规范要点
- 选择合适的基础镜像:能用alpine就别用ubuntu。alpine只有5MB,ubuntu有200MB。我曾经把一个Java应用的镜像从1.2GB压缩到180MB,就是靠换基础镜像。
- 减少层数:每个RUN、COPY、ADD都会创建一层。可以用&&把多个命令连起来。
- 利用构建缓存:把不常变动的指令放在前面(比如安装依赖),常变动的放在后面(比如复制源码)。
- 使用.dockerignore:排除不需要的文件,比如node_modules、.git、日志文件。
- 多阶段构建:构建阶段用大镜像,运行阶段用小镜像。这是生产环境的标配。
避坑指南:我曾经犯过一个错误——在Dockerfile里直接写RUN npm install而不是npm ci。npm install会更新package-lock.json,导致每次构建都不同。npm ci则严格按照lock文件安装,保证一致性。
1.4 Docker Compose入门
单机单容器还好说,一旦涉及多个服务(比如前端+后端+数据库+Redis),手动启动就太痛苦了。Docker Compose就是用来解决这个问题的——用一个YAML文件定义和运行多容器应用。
一个典型的docker-compose.yml
version: '3.8'
services:
# 后端服务
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- DB_HOST=db
- DB_PORT=3306
depends_on:
- db
networks:
- app-net
# 前端服务
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
networks:
- app-net
# 数据库
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: myapp
volumes:
- db-data:/var/lib/mysql
networks:
- app-net
volumes:
db-data:
networks:
app-net:
常用命令
# 启动所有服务
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f
# 停止并删除所有服务
docker-compose down
# 重新构建并启动
docker-compose up -d --build
关键点:Compose文件里的服务名就是容器间的DNS名称。比如上面backend服务里配置了DB_HOST=db,它就能直接通过db这个主机名访问到MySQL容器。你想想看,这比配IP地址方便多了吧?
1.5 实战经验总结
最后,分享几个我在实际项目中总结的经验:
- 镜像标签不要用latest:生产环境一定要指定具体版本号,比如
nginx:1.25.3-alpine。latest会随着时间变化,哪天拉下来一个不兼容的版本就麻烦了。 - 容器数据要持久化:数据库、日志、上传文件这些数据,一定要用volume挂载出来。不然容器一删,数据全没了。我见过有人把数据库跑在容器里没挂载volume,结果升级时把数据搞丢了……
- 资源限制要加上:
docker run --memory=512m --cpus=0.5。不加限制的话,一个容器可能把宿主机所有资源吃光。 - 日志要管理:容器默认会把日志写到宿主机,时间长了会撑爆磁盘。建议配置日志轮转:
--log-opt max-size=10m --log-opt max-file=3。
好了,第一章的内容就到这里。这一章我们打下了容器化的基础——理解了镜像和容器的关系,学会了Dockerfile的编写规范,也知道了怎么用Compose编排多容器应用。下一章,咱们会深入聊聊容器网络和存储,这些都是生产环境绕不开的话题。
记住一句话:容器化不是目的,稳定高效地交付才是。咱们下章见。