第3章:Docker容器化
Docker核心概念
1. 镜像、容器、仓库
┌──────────────────────────────────────┐
│ Docker Registry │
│ (镜像仓库:Docker Hub、Harbor) │
└──────────────────────────────────────┘
↓ push/pull
┌──────────────────────────────────────┐
│ Docker Image │
│ (镜像:只读模板) │
│ - 包含应用代码 │
│ - 包含依赖库 │
│ - 包含运行时环境 │
└──────────────────────────────────────┘
↓ run
┌──────────────────────────────────────┐
│ Docker Container │
│ (容器:镜像的运行实例) │
│ - 可读写层 │
│ - 独立的文件系统 │
│ - 隔离的进程空间 │
└──────────────────────────────────────┘
形象比喻:
镜像 = 类(Class)
容器 = 对象(Instance)
Dockerfile = 类定义
docker build = 编译
docker run = 实例化
2. Docker架构
┌──────────────────────────────────────────┐
│ Docker Client │
│ (docker命令行工具) │
└──────────────────────────────────────────┘
↓ REST API
┌──────────────────────────────────────────┐
│ Docker Daemon │
│ (dockerd,Docker守护进程) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Images(镜像管理) │ │
│ └────────────────────────────────────┘ │
│ ┌────────────────────────────────────┐ │
│ │ Containers(容器管理) │ │
│ └────────────────────────────────────┘ │
│ ┌────────────────────────────────────┐ │
│ │ Networks(网络管理) │ │
│ └────────────────────────────────────┘ │
│ ┌────────────────────────────────────┐ │
│ │ Volumes(存储管理) │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
↓
┌──────────────────────────────────────────┐
│ Docker Registry │
│ (镜像仓库) │
└──────────────────────────────────────────┘
3. Docker生命周期
docker build 创建镜像
↓
docker images 查看镜像
↓
docker run 运行容器
↓
docker ps 查看运行中的容器
↓
docker stop 停止容器
↓
docker start 启动容器
↓
docker rm 删除容器
↓
docker rmi 删除镜像
Dockerfile最佳实践
1. 基础Dockerfile
Go应用示例:
# 方式1:单阶段构建(不推荐)
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o myapp
CMD ["./myapp"]镜像过大(800MB+)
# 方式2:多阶段构建(推荐)
# 第一阶段:构建
FROM golang:1.21 AS builder
WORKDIR /app
# 复制依赖文件
COPY go.mod go.sum ./
RUN go mod download
# 复制源码并构建
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapp .
# 第二阶段:运行
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# 只复制二进制文件
COPY /app/myapp .
# 暴露端口
EXPOSE 8080
# 运行应用
CMD ["./myapp"]
# 镜像大小:15MB
2. Java应用示例
# 多阶段构建
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
# 复制pom.xml并下载依赖(利用缓存)
COPY pom.xml .
RUN mvn dependency:go-offline
# 复制源码并构建
COPY src ./src
RUN mvn package -DskipTests
# 运行阶段
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 复制JAR文件
COPY /app/target/*.jar app.jar
# 暴露端口
EXPOSE 8080
# JVM参数优化
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"
# 运行应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
3. Node.js应用示例
# 多阶段构建
FROM node:18-alpine AS builder
WORKDIR /app
# 复制package.json并安装依赖
COPY package*.json ./
RUN npm ci --only=production
# 复制源码并构建
COPY . .
RUN npm run build
# 运行阶段
FROM node:18-alpine
WORKDIR /app
# 创建非root用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# 复制构建产物
COPY /app/node_modules ./node_modules
COPY /app/dist ./dist
COPY /app/package.json ./
# 切换到非root用户
USER nodejs
# 暴露端口
EXPOSE 3000
# 运行应用
CMD ["node", "dist/index.js"]
4. Dockerfile优化技巧
技巧1:利用构建缓存:
# 错误:每次代码变更都会重新下载依赖
COPY . .
RUN go mod download
# 正确:依赖文件未变化时复用缓存
COPY go.mod go.sum ./
RUN go mod download
COPY . .
技巧2:减少层数:
# 错误:多个RUN命令创建多个层
RUN apt-get update
RUN apt-get install -y git
RUN apt-get install -y curl
RUN apt-get clean
# 正确:合并RUN命令
RUN apt-get update && \
apt-get install -y git curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
技巧3:使用.dockerignore:
# .dockerignore
.git
.gitignore
README.md
.env
.vscode
node_modules
*.log
技巧4:最小化镜像:
# 基础镜像:ubuntu(70MB)
FROM ubuntu:22.04
# 基础镜像:alpine(5MB)
FROM alpine:latest
# 更好:scratch(0MB,仅适用于静态编译)
FROM scratch
COPY myapp /myapp
CMD ["/myapp"]
5. 完整的生产级Dockerfile
# syntax=docker/dockerfile:1
# 构建阶段
FROM golang:1.21-alpine AS builder
# 安装依赖
RUN apk add --no-cache git make
# 设置工作目录
WORKDIR /build
# 复制依赖文件
COPY go.mod go.sum ./
RUN go mod download
# 复制源码
COPY . .
# 构建(禁用CGO,静态编译)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -a -installsuffix cgo \
-ldflags="-w -s -X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME}" \
-o myapp .
# 运行阶段
FROM scratch
# 复制CA证书(用于HTTPS请求)
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 复制二进制文件
COPY /build/myapp /myapp
# 复制配置文件
COPY config.yaml /config.yaml
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK \
CMD ["/myapp", "healthcheck"]
# 运行应用
ENTRYPOINT ["/myapp"]
Docker Compose
1. 基础配置
docker-compose.yml:
version: '3.8'
services:
# Web应用
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
restart: unless-stopped
# PostgreSQL数据库
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
# Redis缓存
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
postgres_data:
redis_data:
使用:
# 启动所有服务
docker-compose up -d
# 查看日志
docker-compose logs -f web
# 停止所有服务
docker-compose down
# 重启服务
docker-compose restart web
# 查看服务状态
docker-compose ps
2. 完整的微服务示例
version: '3.8'
services:
# Nginx反向代理
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- api
- frontend
restart: unless-stopped
# 前端
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
expose:
- "3000"
restart: unless-stopped
# API服务
api:
build:
context: ./backend
dockerfile: Dockerfile
environment:
- DATABASE_URL=postgres://postgres:password@postgres:5432/myapp
- REDIS_URL=redis://redis:6379/0
- JWT_SECRET=${JWT_SECRET}
expose:
- "8080"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
restart: unless-stopped
# PostgreSQL
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# Redis
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
restart: unless-stopped
# Prometheus监控
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
restart: unless-stopped
# Grafana可视化
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
depends_on:
- prometheus
restart: unless-stopped
volumes:
postgres_data:
redis_data:
prometheus_data:
grafana_data:
networks:
default:
driver: bridge
3. 环境变量管理
.env文件:
# .env
# 数据库
POSTGRES_DB=myapp
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_secure_password
# Redis
REDIS_PASSWORD=your_redis_password
# JWT
JWT_SECRET=your_jwt_secret_key
# Grafana
GRAFANA_PASSWORD=admin_password
使用环境变量:
services:
api:
environment:
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
镜像仓库
1. Docker Hub
登录:
docker login
Username: your_username
Password: your_password
推送镜像:
# 1. 构建镜像
docker build -t myapp:v1.0.0 .
# 2. 打标签
docker tag myapp:v1.0.0 your_username/myapp:v1.0.0
docker tag myapp:v1.0.0 your_username/myapp:latest
# 3. 推送
docker push your_username/myapp:v1.0.0
docker push your_username/myapp:latest
2. Harbor私有仓库
安装Harbor:
# 1. 下载Harbor
wget https://github.com/goharbor/harbor/releases/download/v2.9.0/harbor-offline-installer-v2.9.0.tgz
tar xzvf harbor-offline-installer-v2.9.0.tgz
cd harbor
# 2. 配置harbor.yml
cp harbor.yml.tmpl harbor.yml
vim harbor.yml
# harbor.yml配置
hostname: harbor.example.com
http:
port: 80
https:
port: 443
certificate: /path/to/cert.crt
private_key: /path/to/cert.key
harbor_admin_password: Harbor12345
database:
password: root123
max_idle_conns: 50
max_open_conns: 1000
# 3. 安装
sudo ./install.sh --with-trivy --with-chartmuseum
# 4. 启动/停止
docker-compose up -d
docker-compose down
使用Harbor:
# 1. 登录Harbor
docker login harbor.example.com
Username: admin
Password: Harbor12345
# 2. 创建项目(通过Web UI)
# https://harbor.example.com
# 3. 推送镜像
docker tag myapp:v1.0.0 harbor.example.com/myproject/myapp:v1.0.0
docker push harbor.example.com/myproject/myapp:v1.0.0
# 4. 拉取镜像
docker pull harbor.example.com/myproject/myapp:v1.0.0
3. CI/CD集成Harbor
GitLab CI集成:
variables:
DOCKER_REGISTRY: harbor.example.com
DOCKER_IMAGE: ${DOCKER_REGISTRY}/myproject/myapp
docker-build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- echo ${HARBOR_PASSWORD} | docker login ${DOCKER_REGISTRY} -u ${HARBOR_USERNAME} --password-stdin
script:
- docker build -t ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA} .
- docker tag ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA} ${DOCKER_IMAGE}:latest
- docker push ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA}
- docker push ${DOCKER_IMAGE}:latest
Docker安全
1. 镜像安全
安全扫描(Trivy):
# 安装Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# 扫描镜像
trivy image myapp:v1.0.0
# 输出示例:
# Total: 23 (UNKNOWN: 0, LOW: 5, MEDIUM: 10, HIGH: 6, CRITICAL: 2)
在CI/CD中集成扫描:
security:scan:
stage: security
image: aquasec/trivy:latest
script:
- trivy image --severity HIGH,CRITICAL --exit-code 1 ${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA}
allow_failure: false # 发现高危漏洞则失败
2. 运行时安全
最小权限原则:
# 创建非root用户
FROM alpine:latest
RUN addgroup -g 1001 -S appuser && \
adduser -S appuser -u 1001
# 切换到非root用户
USER appuser
# 运行应用
CMD ["./myapp"]
只读文件系统:
docker run --read-only --tmpfs /tmp myapp:v1.0.0
限制资源:
docker run \
--memory="512m" \
--cpus="0.5" \
--pids-limit 100 \
myapp:v1.0.0
Docker Compose资源限制:
services:
api:
image: myapp:v1.0.0
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
3. 网络安全
自定义网络:
# 创建自定义网络
docker network create --driver bridge myapp-network
# 运行容器并连接到网络
docker run -d --name api --network myapp-network myapp:v1.0.0
docker run -d --name db --network myapp-network postgres:15
网络隔离:
version: '3.8'
services:
frontend:
networks:
- frontend-network
api:
networks:
- frontend-network
- backend-network
db:
networks:
- backend-network # 数据库只能被API访问
networks:
frontend-network:
backend-network:
4. 密钥管理
Docker Secrets(Swarm):
# 创建secret
echo "my_db_password" | docker secret create db_password -
# 使用secret
docker service create \
--name myapp \
--secret db_password \
myapp:v1.0.0
# 在容器内读取secret
# Secret存储在:/run/secrets/db_password
环境变量(开发环境):
# 不要硬编码密码
docker run -e DATABASE_URL=postgres://user:password@db:5432/mydb myapp
# 使用.env文件
docker run --env-file .env myapp
面试问答
Docker容器和虚拟机有什么区别?
答案:
┌────────────────────────────────────────┐
│ 虚拟机(VM) │
├────────────────────────────────────────┤
│ App A │ App B │ App C │
├─────────┴─────────┴───────────────────┤
│ Guest OS │ Guest OS │ Guest OS │
├────────────────────────────────────────┤
│ Hypervisor │
├────────────────────────────────────────┤
│ Host OS │
├────────────────────────────────────────┤
│ Hardware │
└────────────────────────────────────────┘
┌────────────────────────────────────────┐
│ 容器(Container) │
├────────────────────────────────────────┤
│ App A │ App B │ App C │
├────────────────────────────────────────┤
│ Docker Engine │
├────────────────────────────────────────┤
│ Host OS │
├────────────────────────────────────────┤
│ Hardware │
└────────────────────────────────────────┘
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 启动速度 | 分钟级 | 秒级 |
| 资源占用 | GB级(需要完整OS) | MB级 |
| 隔离性 | 强(硬件级别) | 中(进程级别) |
| 性能 | 有损耗 | 接近原生 |
| 可移植性 | 弱 | 强 |
形象比喻:
虚拟机 = 独立的房子(完整的基础设施)
容器 = 集装箱(共享船只,独立货物)
如何优化Docker镜像大小?
答案:
优化策略:
1. 使用小基础镜像:
# 大镜像:ubuntu(70MB)
FROM ubuntu:22.04
# 小镜像:alpine(5MB)
FROM alpine:latest
# 更小:scratch(0MB)
FROM scratch
2. 多阶段构建:
# 多阶段构建
FROM golang:1.21 AS builder
RUN go build -o myapp
FROM alpine:latest
COPY /app/myapp .
# 优化前:800MB
# 优化后:15MB
3. 合并RUN命令:
# 多层
RUN apt-get update
RUN apt-get install -y git
RUN apt-get clean
# 单层
RUN apt-get update && \
apt-get install -y git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
4. 使用.dockerignore:
.git
node_modules
*.log
README.md
效果对比:
优化前:
- 基础镜像:ubuntu(70MB)
- 构建工具:Maven、JDK(500MB)
- 总大小:800MB
优化后:
- 基础镜像:alpine(5MB)
- 多阶段构建(只保留JAR)
- 总大小:100MB
减少:87.5%
Docker的网络模式有哪些?
答案:
1. Bridge(默认):
docker run --network bridge myapp
特点:
- 容器通过虚拟网桥通信
- 容器有独立IP
- 需要端口映射访问外部
2. Host:
docker run --network host myapp
特点:
- 容器使用宿主机网络
- 无网络隔离
- 性能最好
3. None:
docker run --network none myapp
特点:
- 无网络
- 完全隔离
- 适合安全要求高的场景
4. Overlay(Swarm/Kubernetes):
docker network create --driver overlay myapp-network
特点:
- 跨主机通信
- 分布式网络
- 适合集群
选择建议:
开发环境:Bridge(默认)
生产环境:
- 单机:Bridge
- 集群:Overlay
- 高性能:Host
Docker Compose和Kubernetes有什么区别?
答案:
| 维度 | Docker Compose | Kubernetes |
|---|---|---|
| 定位 | 单机编排 | 集群编排 |
| 规模 | 小(< 10个容器) | 大(1000+容器) |
| 高可用 | ||
| 自动扩容 | ||
| 服务发现 | 简单(DNS) | 完善(Service) |
| 配置 | docker-compose.yml | YAML资源清单 |
| 学习曲线 | 低 | 高 |
使用场景:
Docker Compose:
开发环境
单机部署
简单微服务(< 5个)
Kubernetes:
生产环境
大规模集群
高可用要求
自动扩缩容
从Compose迁移到K8s:
# 1. 使用kompose转换
kompose convert -f docker-compose.yml
# 2. 生成Kubernetes YAML
# deployment.yaml
# service.yaml
# 3. 部署到K8s
kubectl apply -f .
如何保证Docker容器的安全?
答案:
安全措施:
1. 镜像安全:
# 使用可信镜像源
FROM golang:1.21-alpine # 官方镜像
# 镜像扫描
trivy image myapp:v1.0.0 --severity HIGH,CRITICAL
# 镜像签名
export DOCKER_CONTENT_TRUST=1
docker push myapp:v1.0.0
2. 运行时安全:
# 使用非root用户
FROM alpine
RUN addgroup -g 1001 appuser && \
adduser -S appuser -u 1001
USER appuser
CMD ["./myapp"]
# 只读文件系统
docker run --read-only --tmpfs /tmp myapp
# 限制资源
docker run --memory="512m" --cpus="0.5" myapp
# 禁用特权模式
docker run --privileged=false myapp
3. 网络安全:
# 网络隔离
services:
api:
networks:
- frontend
- backend
db:
networks:
- backend # 数据库不暴露到frontend
4. 密钥管理:
# 使用Docker Secrets
echo "password" | docker secret create db_password -
docker service create --secret db_password myapp
# 不要硬编码密码
docker run -e DB_PASSWORD=${DB_PASSWORD} myapp
5. 最小权限:
# Kubernetes Security Context
securityContext:
runAsNonRoot: true
runAsUser: 1001
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false