强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 14 章:Docker 中的 curl

第 14 章:Docker 中的 curl

在容器化时代,curl 是 Docker 健康检查、API 测试和容器间通信的瑞士军刀。


14.1 在 Docker 中安装 curl

各发行版镜像

# Alpine(推荐,最小镜像)
FROM alpine:3.19
RUN apk add --no-cache curl

# Debian slim
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# Ubuntu
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl ca-certificates jq && \
    rm -rf /var/lib/apt/lists/*

# 使用专用 curl 镜像(最小化)
FROM curlimages/curl:latest
# 仅包含 curl 二进制,无 shell

镜像大小对比

基础镜像+ curl总大小适用场景
alpine:3.19apk add curl~12 MB生产环境
curlimages/curl已包含~8 MB仅需 curl
debian:bookworm-slimapt install curl~95 MB需要更多工具
ubuntu:24.04apt install curl~120 MB开发调试
distroless手动复制~25 MB最安全

使用 curlimages/curl(最轻量)

# curlimages/curl 是官方提供的最小 curl 镜像
FROM curlimages/curl:latest

# 直接使用 curl 作为 ENTRYPOINT
ENTRYPOINT ["curl"]
CMD ["--help"]

# 在 docker-compose 中使用
# docker-compose.yml
services:
  api-test:
    image: curlimages/curl:latest
    command: >
      curl -sS --retry 5 --retry-delay 2
      http://api:8080/health
    networks:
      - app-net

14.2 Docker 健康检查

Dockerfile HEALTHCHECK

# 使用 curl 进行健康检查
FROM node:20-alpine
RUN apk add --no-cache curl
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000

# 健康检查:每 30 秒检查一次,超时 10 秒,3 次失败后标记为不健康
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "server.js"]

HEALTHCHECK 参数说明

参数默认值说明
--interval30s检查间隔
--timeout30s单次检查超时
--start-period0s启动宽限期(在此期间失败不计入重试次数)
--retries3失败重试次数
--start-interval5s启动期间的检查间隔(Docker 25+)

常见健康检查模式

# 模式 1:简单存活检查
HEALTHCHECK CMD curl -sf http://localhost:8080/ || exit 1

# 模式 2:检查特定端点
HEALTHCHECK CMD curl -sf http://localhost:8080/health || exit 1

# 模式 3:检查 JSON 响应中的状态字段
HEALTHCHECK CMD curl -sf http://localhost:8080/health | jq -e '.status == "ok"' || exit 1

# 模式 4:检查 HTTP 状态码
HEALTHCHECK CMD test "$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8080/health)" = "200"

# 模式 5:多端点检查
HEALTHCHECK CMD curl -sf http://localhost:8080/health && \
                 curl -sf http://localhost:8080/api/ping || exit 1

# 模式 6:带超时和重试的检查
HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
  CMD curl -sf --max-time 3 http://localhost:8080/health || exit 1

Docker Compose 健康检查

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 10s

  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  # 等待依赖服务健康后再启动
  worker:
    build: ./worker
    depends_on:
      api:
        condition: service_healthy
      db:
        condition: service_healthy

Kubernetes 就绪探针和存活探针

# kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      containers:
        - name: api
          image: myapp:latest
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            successThreshold: 1

14.3 容器间通信

Docker 网络基础

# 创建自定义网络
docker network create app-net

# 启动 API 服务
docker run -d --name api --network app-net myapi:latest

# 在同一网络中使用 curl 访问
docker run --rm --network app-net curlimages/curl \
  curl -s http://api:8080/health

# 容器间使用服务名作为主机名
docker run --rm --network app-net curlimages/curl \
  curl -s http://api:8080/users

# 注意:容器名即 DNS 名

Docker Compose 容器间通信

# docker-compose.yml
services:
  api:
    build: ./api
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: pass
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

  # 测试容器
  test:
    image: curlimages/curl:latest
    depends_on:
      api:
        condition: service_healthy
    command: >
      sh -c "
        echo '测试健康检查...'
        curl -sf http://api:8080/health &&
        echo '测试用户列表...'
        curl -sf http://api:8080/users | jq 'length > 0'
      "
    networks:
      - default

跨容器调试

# 从宿主机进入运行中的容器
docker exec -it api sh

# 在容器内安装 curl(临时)
docker exec api apk add --no-cache curl

# 直接从宿主机访问容器(端口映射)
curl http://localhost:8080/health

# 使用 docker exec 执行 curl
docker exec api curl -s http://localhost:8080/health

# 从一个容器访问另一个容器
docker exec api curl -s http://redis:6379  # Redis 不走 HTTP

# 检查容器网络连通性
docker run --rm --network app-net curlimages/curl \
  curl -s -o /dev/null -w "HTTP %{http_code}, 耗时 %{time_total}s\n" \
  http://api:8080/health

14.4 在容器中进行 API 测试

集成测试脚本

#!/bin/bash
# docker_integration_test.sh

set -e
NETWORK="test-net"
API_IMAGE="myapp:latest"

# 清理函数
cleanup() {
  docker rm -f api 2>/dev/null || true
  docker network rm "$NETWORK" 2>/dev/null || true
}
trap cleanup EXIT

# 启动测试环境
docker network create "$NETWORK"
docker run -d --name api --network "$NETWORK" -p 8080:8080 "$API_IMAGE"

# 等待服务就绪
echo "等待 API 服务启动..."
for i in $(seq 1 30); do
  if docker run --rm --network "$NETWORK" curlimages/curl \
    curl -sf http://api:8080/health > /dev/null 2>&1; then
    echo "✅ 服务已就绪"
    break
  fi
  if [ $i -eq 30 ]; then
    echo "❌ 服务启动超时"
    exit 1
  fi
  sleep 2
done

# 运行测试
run_test() {
  local desc="$1" expected="$2" actual="$3"
  if [ "$actual" = "$expected" ]; then
    echo "✅ PASS: $desc"
  else
    echo "❌ FAIL: $desc (expected $expected, got $actual)"
    exit 1
  fi
}

# 测试 1:健康检查
STATUS=$(docker run --rm --network "$NETWORK" curlimages/curl \
  curl -s -o /dev/null -w "%{http_code}" http://api:8080/health)
run_test "健康检查" "200" "$STATUS"

# 测试 2:创建用户
RESULT=$(docker run --rm --network "$NETWORK" curlimages/curl \
  curl -s -X POST http://api:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name":"测试用户"}')
USER_ID=$(echo "$RESULT" | docker run --rm -i jq -r '.id')
run_test "创建用户" "1" "$USER_ID"

# 测试 3:获取用户
USER_NAME=$(docker run --rm --network "$NETWORK" curlimages/curl \
  curl -s http://api:8080/users/1 | docker run --rm -i jq -r '.name')
run_test "获取用户" "测试用户" "$USER_NAME"

echo "所有测试通过!"

临时测试容器

# 一次性 curl 容器(运行后自动删除)
docker run --rm --network app-net curlimages/curl \
  curl -s http://api:8080/health

# 交互式调试容器
docker run -it --rm --network app-net \
  --entrypoint sh \
  curlimages/curl

# 带环境变量的测试
docker run --rm --network app-net \
  -e API_TOKEN=secret123 \
  curlimages/curl \
  curl -s -H "Authorization: Bearer $API_TOKEN" \
  http://api:8080/admin/users

14.5 CI/CD 中的 Docker curl

GitHub Actions 示例

# .github/workflows/docker-test.yml
name: Docker Integration Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build image
        run: docker build -t myapp:test .
      
      - name: Start services
        run: |
          docker network create test-net
          docker run -d --name app --network test-net \
            -p 8080:8080 myapp:test
      
      - name: Wait for service
        run: |
          for i in $(seq 1 30); do
            if curl -sf http://localhost:8080/health; then
              echo "Service ready"
              exit 0
            fi
            sleep 2
          done
          echo "Service failed to start"
          docker logs app
          exit 1
      
      - name: Run API tests
        run: |
          # 测试健康检查
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/health)
          [ "$STATUS" = "200" ] || (echo "Health check failed: $STATUS" && exit 1)
          
          # 测试 API 端点
          RESPONSE=$(curl -s http://localhost:8080/api/data)
          echo "$RESPONSE" | jq 'length > 0' || (echo "Empty response" && exit 1)
      
      - name: Collect logs
        if: failure()
        run: docker logs app > app.log 2>&1
      
      - name: Upload logs
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: app-logs
          path: app.log

14.6 容器网络调试

# 检查容器网络配置
docker network inspect app-net

# 查看容器 IP
docker inspect api | jq '.[0].NetworkSettings.Networks'

# 从容器内测试网络连通性
docker exec api ping -c 3 db
docker exec api nslookup api
docker exec api curl -sf http://localhost:8080/health

# 检查 DNS 解析
docker run --rm --network app-net alpine \
  sh -c "apk add --no-cache bind-tools && nslookup api"

# 端口扫描(从容器内部)
docker run --rm --network app-net alpine \
  sh -c "apk add --no-cache nmap && nmap -p 8080 api"

# 检查容器间路由
docker exec api ip route
docker exec api cat /etc/resolv.conf

# 抓包调试
docker exec api apk add --no-cache tcpdump
docker exec api tcpdump -i eth0 -w /tmp/capture.pcap host db and port 5432 &
docker exec api curl -s http://api:8080/users
# 停止抓包后导出
docker cp api:/tmp/capture.pcap ./capture.pcap

注意事项

  1. 镜像大小:生产环境使用 Alpine 基础镜像,最小化 curl 安装
  2. CA 证书:Alpine 需要 ca-certificates 包才能进行 HTTPS 请求
  3. 健康检查超时:确保 --timeout 大于实际响应时间
  4. DNS 解析:容器间通信使用服务名,不需要 IP 地址
  5. 安全扫描:定期更新 curl 和基础镜像,修复安全漏洞
# 生产级 Dockerfile
FROM alpine:3.19 AS base
RUN apk add --no-cache curl ca-certificates

FROM base AS production
COPY --from=builder /app/server /app/server
WORKDIR /app

HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \
  CMD curl -sf --max-time 3 http://localhost:8080/health || exit 1

EXPOSE 8080
CMD ["/app/server"]

扩展阅读


📖 下一章第 15 章:最佳实践 — 总结 curl 的安全、性能和生产力最佳实践。