第二章:容器技术基础
好,咱们进入第二章。说实话,容器技术是整个云原生大厦的基石。你想想看,没有容器,微服务怎么跑?CI/CD 怎么玩?Kubernetes 调度什么?所以这一章,我带你从零开始,把 Docker 这套东西彻底搞明白。
2.1 Docker 安装与配置
安装 Docker 其实不复杂,但不同系统有不同坑。我个人习惯在 Linux 上搞,毕竟生产环境基本都是 Linux。
2.1.1 在 Ubuntu 上安装
官方推荐用 apt 仓库安装,别去下载什么 .deb 包手动装,那是在给自己找麻烦。
# 卸载旧版本
sudo apt-get remove docker docker-engine docker.io containerd runc
# 安装依赖
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release
# 添加 Docker 官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 设置稳定版仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
装完之后,记得把当前用户加到 docker 组里,不然每次都要 sudo,烦得很。
sudo usermod -aG docker $USER
newgrp docker
2.1.2 验证安装
docker --version
docker run hello-world
看到 "Hello from Docker!" 就说明成了。嗯,这里要注意,如果拉镜像慢,后面我会讲怎么配加速器。
2.1.3 Docker 配置优化
我个人习惯装完先改几个配置,不然用起来会有点别扭。
配置镜像加速器——国内你懂的,不配加速器,拉个 nginx 都能等到你怀疑人生。
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://你的加速器地址.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
配置日志轮转——这个坑我踩过。默认情况下,容器日志会无限增长,直到把磁盘撑爆。我在生产环境见过一个容器日志占了 200GB,那叫一个酸爽。
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
2.2 Dockerfile 编写最佳实践
Dockerfile 怎么写?说白了就是告诉 Docker 怎么把你的应用打包成镜像。但怎么写得好,这里面门道不少。
2.2.1 基础镜像选择
别上来就 FROM ubuntu:latest,那玩意 700MB,你部署一次试试?我建议用 Alpine,才 5MB 左右。
# 不推荐
FROM ubuntu:20.04
# 推荐
FROM alpine:3.18
当然,如果你的应用依赖 glibc,Alpine 可能不太行,那就用 slim 版本。
FROM node:18-slim
2.2.2 分层构建与缓存
Docker 构建镜像是一层一层叠上去的。每一行 RUN、COPY 都会产生一个新层。我见过有人把所有命令写在一行,也有人每步都写一行。都不对。
正确的做法是:把不常变动的指令放前面,频繁变动的放后面。这样能充分利用缓存。
# 好例子
FROM node:18-alpine
WORKDIR /app
# 先复制依赖文件
COPY package.json package-lock.json ./
RUN npm install
# 再复制源码
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
为什么这样写?因为源码经常改,但 package.json 不常改。如果先复制源码,每次改代码都要重新 npm install,那构建速度就慢得离谱了。
2.2.3 多阶段构建
这个技巧我强烈推荐。说白了就是:一个 Dockerfile 里写多个 FROM,前面的用来编译,后面的用来运行。最终镜像只包含运行所需的东西。
# 第一阶段:编译
FROM golang:1.20 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp
# 第二阶段:运行
FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
你看,最终镜像只有 alpine + 一个二进制文件,可能就 20MB。如果不用多阶段构建,光 golang 镜像就 800MB 了。
2.2.4 减少层数
虽然 Docker 层数理论上没限制,但层太多会影响性能。我建议把相关的 RUN 命令合并起来。
# 不推荐
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN rm -rf /var/lib/apt/lists/*
# 推荐
RUN apt-get update && \
apt-get install -y curl vim && \
rm -rf /var/lib/apt/lists/*
为什么要删 apt 缓存?因为那些文件只在安装时需要,运行时不依赖。不删的话,它们会留在镜像里,白白增加体积。
2.3 镜像构建与优化
镜像构建不只是 docker build 那么简单。怎么构建得快、怎么让镜像小、怎么保证安全,都是学问。
2.3.1 构建命令
docker build -t myapp:v1.0 -f Dockerfile.prod .
-t 是打标签,-f 指定 Dockerfile 文件名(默认是 Dockerfile),最后的 . 是构建上下文。
这里有个坑:构建上下文不要放无关文件。Docker 会把整个上下文发给守护进程,如果你在项目根目录执行 docker build,而项目里有 node_modules 或者 .git,那构建速度会慢到你想哭。
解决方案:写个 .dockerignore 文件。
node_modules
.git
*.log
.env
dist
2.3.2 镜像优化技巧
| 优化项 | 说明 | 效果 |
|---|---|---|
| 选择小基础镜像 | Alpine / slim 替代 full | 体积减少 80% |
| 多阶段构建 | 编译和运行分离 | 体积减少 90% |
| 合并 RUN 命令 | 减少层数 | 构建更快 |
| 清理缓存 | apt/yum/npm 缓存 | 体积减少 10-30% |
| 使用 .dockerignore | 排除无关文件 | 构建更快 |
2.3.3 镜像安全
别用 root 用户运行容器。这是个基本的安全原则。如果容器被攻破,root 权限意味着攻击者可以为所欲为。
FROM node:18-alpine
# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
CMD ["node", "app.js"]
2.4 Docker Compose
单容器好办,但实际项目哪有只跑一个容器的?前端、后端、数据库、缓存、消息队列...一个个手动 docker run 会疯掉的。这时候就需要 Docker Compose 了。
2.4.1 什么是 Docker Compose
说白了,就是用 YAML 文件定义一组容器,然后一条命令全部启动。我最早接触 Compose 时觉得这玩意就是「一键部署」的雏形。
2.4.2 编写 docker-compose.yml
来个实际例子:一个 Node.js 应用 + Redis + MySQL。
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
volumes:
- .:/app
- /app/node_modules
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: myapp
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
mysql_data:
你看,三个服务,一个文件搞定。每个服务可以指定镜像、端口、环境变量、数据卷、依赖关系。
2.4.3 常用命令
# 启动所有服务
docker compose up -d
# 查看日志
docker compose logs -f
# 停止并删除
docker compose down
# 重新构建并启动
docker compose up -d --build
# 查看运行状态
docker compose ps
-d 是后台运行,不加的话日志会直接打印在终端上,调试时挺有用。
2.4.4 网络与数据持久化
Compose 会自动创建一个网络,所有服务可以通过服务名互相访问。比如 app 服务里连接数据库,直接用 mysql:3306 就行,不用写 IP。
数据持久化用 volumes。我上面例子里的 mysql_data 就是命名卷,数据会保存在宿主机上,容器删了数据还在。
2.4.5 环境变量管理
敏感信息别硬编码在 docker-compose.yml 里。我习惯用 .env 文件。
# .env 文件
DB_PASSWORD=root123
REDIS_PASSWORD=redis123
# docker-compose.yml 中引用
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
这样 .env 文件可以加到 .gitignore 里,避免密码泄露。
小结
这一章内容不少,从 Docker 安装到 Dockerfile 编写,再到镜像优化和 Compose 编排。说实话,这些是容器技术的核心基本功。你把这些搞透了,后面学 Kubernetes 会轻松很多。
下一章我们讲容器编排,也就是 Kubernetes。到时候你会发现,很多概念其实在 Docker 里已经见过了。