HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • DevOps与CI/CD

    • DevOps & CI/CD实践手册
    • 第1章:CI/CD流程设计
    • 第2章:Jenkins与GitLab CI
    • 第3章:Docker容器化
    • 第4章:Kubernetes编排
    • 第5章:GitOps与自动化部署

第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 --from=builder /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 --from=builder /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 --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /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 --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# 复制二进制文件
COPY --from=builder /build/myapp /myapp

# 复制配置文件
COPY config.yaml /config.yaml

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  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 --from=builder /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 ComposeKubernetes
定位单机编排集群编排
规模小(< 10个容器)大(1000+容器)
高可用
自动扩容
服务发现简单(DNS)完善(Service)
配置docker-compose.ymlYAML资源清单
学习曲线低高

使用场景:

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

参考资料

  • Docker Documentation
  • Dockerfile Best Practices
  • Docker Compose
  • Harbor
  • Trivy Security Scanner
Prev
第2章:Jenkins与GitLab CI
Next
第4章:Kubernetes编排