第 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.19 | apk add curl | ~12 MB | 生产环境 |
curlimages/curl | 已包含 | ~8 MB | 仅需 curl |
debian:bookworm-slim | apt install curl | ~95 MB | 需要更多工具 |
ubuntu:24.04 | apt 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 参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
--interval | 30s | 检查间隔 |
--timeout | 30s | 单次检查超时 |
--start-period | 0s | 启动宽限期(在此期间失败不计入重试次数) |
--retries | 3 | 失败重试次数 |
--start-interval | 5s | 启动期间的检查间隔(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
注意事项
- 镜像大小:生产环境使用 Alpine 基础镜像,最小化 curl 安装
- CA 证书:Alpine 需要
ca-certificates包才能进行 HTTPS 请求 - 健康检查超时:确保
--timeout大于实际响应时间 - DNS 解析:容器间通信使用服务名,不需要 IP 地址
- 安全扫描:定期更新 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 的安全、性能和生产力最佳实践。