第十一章:Docker 环境测试
第十一章:Docker 环境测试
11.1 概述
在容器化时代,Docker 已成为部署和测试的标准环境。本章介绍如何在 Docker 中运行 Sysbench 测试,包括直接测试、数据库容器测试、Docker Compose 编排测试,以及容器性能与裸机性能的对比。
11.1.1 适用场景
| 场景 | 说明 |
|---|
| CI/CD 集成 | 在自动化流水线中运行性能测试 |
| 开发环境 | 快速搭建测试环境,无需安装数据库 |
| 性能基线 | 对比容器 vs 裸机的性能差异 |
| 多版本测试 | 同时测试不同版本的数据库 |
| 环境一致性 | 确保所有开发者使用相同的测试环境 |
11.2 直接使用 Sysbench Docker 镜像
11.2.1 可用镜像
| 镜像 | 说明 |
|---|
severalnines/sysbench | 社区常用,包含 MySQL/PostgreSQL 客户端 |
sysbench/sysbench | 较新的镜像 |
custom build | 自定义镜像 |
11.2.2 基本用法
# 拉取镜像
docker pull severalnines/sysbench
# CPU 测试
docker run --rm severalnines/sysbench \
sysbench cpu --threads=4 --time=30 run
# 内存测试
docker run --rm severalnines/sysbench \
sysbench memory --threads=4 --time=30 run
# 文件 I/O 测试(使用临时卷)
docker run --rm -v /tmp/sysbench-data:/data severalnines/sysbench \
sh -c "cd /data && sysbench fileio --file-total-size=1G prepare && \
sysbench fileio --file-test-mode=rndrw --time=30 run && \
sysbench fileio --file-total-size=1G cleanup"
11.2.3 连接宿主机数据库
# 连接宿主机的 MySQL(使用宿主机网络)
docker run --rm --network host severalnines/sysbench \
sysbench oltp_read_write \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=secret \
--tables=10 \
--table-size=100000 \
--threads=8 \
--time=60 \
run
# 使用 Docker 内部网络(假设 MySQL 容器名为 mysql-server)
docker run --rm --network my-network severalnines/sysbench \
sysbench oltp_read_write \
--mysql-host=mysql-server \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=secret \
--tables=10 \
--table-size=100000 \
--threads=8 \
--time=60 \
run
11.3 Docker Compose 编排测试
11.3.1 完整的 Docker Compose 配置
# docker-compose.yml
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: sb-mysql
environment:
MYSQL_ROOT_PASSWORD: testpass123
MYSQL_DATABASE: sbtest
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./mysql-conf:/etc/mysql/conf.d
command: >
--innodb-buffer-pool-size=1G
--innodb-flush-log-at-trx-commit=1
--max-connections=500
--default-authentication-plugin=mysql_native_password
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10
postgres:
image: postgres:16
container_name: sb-postgres
environment:
POSTGRES_PASSWORD: testpass123
POSTGRES_DB: sbtest
ports:
- "5432:5432"
volumes:
- pg-data:/var/lib/postgresql/data
command: >
postgres
-c shared_buffers=1GB
-c effective_cache_size=2GB
-c work_mem=64MB
-c max_connections=500
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10
sysbench:
image: severalnines/sysbench
container_name: sb-runner
depends_on:
mysql:
condition: service_healthy
postgres:
condition: service_healthy
volumes:
- ./scripts:/scripts
- ./results:/results
entrypoint: ["sleep", "infinity"]
volumes:
mysql-data:
pg-data:
11.3.2 运行测试
# 启动服务
docker compose up -d
# 在 sysbench 容器中运行 MySQL 测试
docker exec -it sb-runner sysbench oltp_read_write \
--mysql-host=mysql \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=testpass123 \
--mysql-db=sbtest \
--tables=16 \
--table-size=100000 \
prepare
docker exec -it sb-runner sysbench oltp_read_write \
--mysql-host=mysql \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=testpass123 \
--mysql-db=sbtest \
--tables=16 \
--table-size=100000 \
--threads=16 \
--time=300 \
run
# 在 sysbench 容器中运行 PostgreSQL 测试
docker exec -it sb-runner sysbench oltp_read_write \
--db-driver=pgsql \
--pgsql-host=postgres \
--pgsql-port=5432 \
--pgsql-user=postgres \
--pgsql-password=testpass123 \
--pgsql-db=sbtest \
--tables=16 \
--table-size=100000 \
prepare
docker exec -it sb-runner sysbench oltp_read_write \
--db-driver=pgsql \
--pgsql-host=postgres \
--pgsql-port=5432 \
--pgsql-user=postgres \
--pgsql-password=testpass123 \
--pgsql-db=sbtest \
--tables=16 \
--table-size=100000 \
--threads=16 \
--time=300 \
run
# 清理
docker compose down -v
11.4 自定义 Dockerfile
11.4.1 完整功能镜像
# Dockerfile
FROM ubuntu:22.04
# 避免交互式提示
ENV DEBIAN_FRONTEND=noninteractive
# 安装依赖
RUN apt-get update && apt-get install -y \
sysbench \
mysql-client \
postgresql-client \
jq \
bc \
curl \
gnupg2 \
&& rm -rf /var/lib/apt/lists/*
# 添加自定义脚本
COPY scripts/ /opt/sysbench/scripts/
RUN chmod +x /opt/sysbench/scripts/*.sh
WORKDIR /opt/sysbench
ENTRYPOINT ["sysbench"]
CMD ["--help"]
11.4.2 构建和使用
# 构建镜像
docker build -t my-sysbench .
# 运行
docker run --rm my-sysbench cpu --threads=4 --time=30 run
11.4.3 带自定义脚本的镜像
# Dockerfile.with-scripts
FROM severalnines/sysbench
COPY scripts/ /scripts/
RUN chmod +x /scripts/*.sh
ENTRYPOINT ["/scripts/entrypoint.sh"]
#!/bin/bash
# scripts/entrypoint.sh
set -euo pipefail
# 等待数据库就绪
wait_for_db() {
local host=$1 port=$2 max_wait=${3:-60}
local waited=0
echo "Waiting for database at $host:$port..."
while ! nc -z $host $port && [ $waited -lt $max_wait ]; do
sleep 1
waited=$((waited + 1))
done
if [ $waited -ge $max_wait ]; then
echo "ERROR: Database not ready after ${max_wait}s"
exit 1
fi
echo "Database ready after ${waited}s"
}
# 执行测试
run_test() {
local test_name=$1
shift
echo ""
echo "========================================="
echo "Running: $test_name"
echo "Time: $(date)"
echo "========================================="
sysbench "$@" run
}
# 主逻辑
case "${1:-help}" in
mysql-test)
wait_for_db ${MYSQL_HOST:-mysql} ${MYSQL_PORT:-3306}
sysbench oltp_read_write \
--mysql-host=${MYSQL_HOST:-mysql} \
--mysql-port=${MYSQL_PORT:-3306} \
--mysql-user=${MYSQL_USER:-root} \
--mysql-password=${MYSQL_PASSWORD:-secret} \
--tables=${TABLES:-16} \
--table-size=${TABLE_SIZE:-100000} \
--threads=${THREADS:-16} \
--time=${TIME:-300} \
run
;;
pgsql-test)
wait_for_db ${PG_HOST:-postgres} ${PG_PORT:-5432}
sysbench oltp_read_write \
--db-driver=pgsql \
--pgsql-host=${PG_HOST:-postgres} \
--pgsql-port=${PG_PORT:-5432} \
--pgsql-user=${PG_USER:-postgres} \
--pgsql-password=${PG_PASSWORD:-secret} \
--tables=${TABLES:-16} \
--table-size=${TABLE_SIZE:-100000} \
--threads=${THREADS:-16} \
--time=${TIME:-300} \
run
;;
*)
exec sysbench "$@"
;;
esac
11.5 容器 vs 裸机性能对比
11.5.1 对比测试脚本
#!/bin/bash
# container_vs_baremetal.sh
echo "=== 容器 vs 裸机性能对比 ==="
# 裸机测试
echo ""
echo "--- 裸机 CPU 测试 ---"
sysbench cpu --threads=8 --time=30 run 2>&1 | grep "events per second"
echo ""
echo "--- 裸机内存测试 ---"
sysbench memory --memory-block-size=1M --threads=8 --time=30 run 2>&1 | grep "MiB/sec"
echo ""
echo "--- 裸机文件 I/O 测试 ---"
cd /tmp && sysbench fileio --file-total-size=2G prepare > /dev/null 2>&1
sysbench fileio --file-total-size=2G --file-test-mode=rndrw --threads=8 --time=30 run 2>&1 | \
grep -E "(reads/s|writes/s)"
sysbench fileio --file-total-size=2G cleanup > /dev/null 2>&1
# 容器测试
echo ""
echo "--- 容器 CPU 测试 ---"
docker run --rm severalnines/sysbench \
sysbench cpu --threads=8 --time=30 run 2>&1 | grep "events per second"
echo ""
echo "--- 容器内存测试 ---"
docker run --rm severalnines/sysbench \
sysbench memory --memory-block-size=1M --threads=8 --time=30 run 2>&1 | grep "MiB/sec"
echo ""
echo "--- 容器文件 I/O 测试 ---"
docker run --rm -v /tmp/sb-data:/data severalnines/sysbench \
sh -c "cd /data && sysbench fileio --file-total-size=2G prepare > /dev/null 2>&1 && \
sysbench fileio --file-total-size=2G --file-test-mode=rndrw --threads=8 --time=30 run && \
sysbench fileio --file-total-size=2G cleanup" 2>&1 | \
grep -E "(reads/s|writes/s)"
11.5.2 预期差异
| 测试项 | 容器开销 | 说明 |
|---|
| CPU | < 2% | Docker 使用原生 Linux 内核,CPU 开销极小 |
| 内存 | < 2% | 内存访问不受容器影响 |
| 文件 I/O | 0-5% | 使用 overlay2 时有少量开销,使用 volume 时几乎无开销 |
| 网络 | 5-15% | bridge 网络有开销,host 网络无开销 |
结论:Docker 容器对 CPU 和内存测试几乎没有性能影响。文件 I/O 使用 volume 时也几乎没有开销。网络开销可通过 --network host 消除。
11.6 CI/CD 集成
11.6.1 GitHub Actions
# .github/workflows/performance-test.yml
name: Performance Test
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * 1' # 每周一凌晨 2 点
jobs:
cpu-benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run CPU benchmark
run: |
docker run --rm severalnines/sysbench \
sysbench cpu --threads=4 --time=60 run | tee cpu_result.txt
- name: Check performance threshold
run: |
EPS=$(grep "events per second" cpu_result.txt | awk '{print $4}')
echo "CPU events/sec: $EPS"
THRESHOLD=3000
if (( $(echo "$EPS < $THRESHOLD" | bc -l) )); then
echo "ERROR: CPU performance below threshold ($THRESHOLD)"
exit 1
fi
mysql-benchmark:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: testpass
MYSQL_DATABASE: sbtest
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost"
--health-interval=5s
--health-timeout=5s
--health-retries=10
steps:
- uses: actions/checkout@v4
- name: Prepare data
run: |
docker run --rm --network host severalnines/sysbench \
sysbench oltp_read_write \
--mysql-host=127.0.0.1 --mysql-port=3306 \
--mysql-user=root --mysql-password=testpass \
--tables=10 --table-size=50000 prepare
- name: Run benchmark
run: |
docker run --rm --network host severalnines/sysbench \
sysbench oltp_read_write \
--mysql-host=127.0.0.1 --mysql-port=3306 \
--mysql-user=root --mysql-password=testpass \
--tables=10 --table-size=50000 \
--threads=8 --time=120 \
--json=result.json run
- name: Parse results
run: |
TPS=$(docker run --rm -v $PWD:/data --network host severalnines/sysbench \
jq '.transactions.per_second' /data/result.json)
echo "TPS: $TPS"
echo "TPS=$TPS" >> $GITHUB_OUTPUT
- name: Upload results
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: result.json
11.6.2 GitLab CI
# .gitlab-ci.yml
stages:
- build
- test
- benchmark
variables:
MYSQL_ROOT_PASSWORD: testpass
MYSQL_DATABASE: sbtest
mysql-benchmark:
stage: benchmark
image: severalnines/sysbench
services:
- name: mysql:8.0
alias: mysql
variables:
MYSQL_HOST: mysql
MYSQL_PORT: "3306"
script:
- |
sysbench oltp_read_write \
--mysql-host=$MYSQL_HOST --mysql-port=$MYSQL_PORT \
--mysql-user=root --mysql-password=$MYSQL_ROOT_PASSWORD \
--mysql-db=$MYSQL_DATABASE \
--tables=10 --table-size=50000 prepare
sysbench oltp_read_write \
--mysql-host=$MYSQL_HOST --mysql-port=$MYSQL_PORT \
--mysql-user=root --mysql-password=$MYSQL_ROOT_PASSWORD \
--mysql-db=$MYSQL_DATABASE \
--tables=10 --table-size=50000 \
--threads=8 --time=120 --json=result.json run
TPS=$(jq '.transactions.per_second' result.json)
echo "TPS: $TPS"
artifacts:
paths:
- result.json
expire_in: 30 days
11.7 容器资源限制测试
11.7.1 CPU 限制
# 限制容器使用 2 个 CPU
docker run --rm --cpus=2 severalnines/sysbench \
sysbench cpu --threads=4 --time=30 run
# 限制 CPU shares(相对权重)
docker run --rm --cpu-shares=512 severalnines/sysbench \
sysbench cpu --threads=4 --time=30 run
# 对比不同 CPU 限制
echo "=== 无限制 ==="
docker run --rm severalnines/sysbench \
sysbench cpu --threads=4 --time=10 run 2>&1 | grep "events per second"
echo "=== 限制 1 CPU ==="
docker run --rm --cpus=1 severalnines/sysbench \
sysbench cpu --threads=4 --time=10 run 2>&1 | grep "events per second"
echo "=== 限制 2 CPU ==="
docker run --rm --cpus=2 severalnines/sysbench \
sysbench cpu --threads=4 --time=10 run 2>&1 | grep "events per second"
11.7.2 内存限制
# 限制容器内存
docker run --rm --memory=512m severalnines/sysbench \
sysbench memory --memory-total-size=1G --memory-block-size=1M run
# 内存限制 + OOM 控制
docker run --rm --memory=256m --oom-kill-disable severalnines/sysbench \
sysbench memory --memory-total-size=10G run # 可能会失败
11.7.3 综合资源限制
# docker-compose-limited.yml
services:
sysbench-limited:
image: severalnines/sysbench
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '1.0'
memory: 1G
command: >
sysbench oltp_read_write
--mysql-host=mysql
--mysql-user=root
--mysql-password=secret
--tables=10
--table-size=50000
--threads=8
--time=300
run
11.8 多容器并行测试
11.8.1 并行运行多个 Sysbench 实例
#!/bin/bash
# parallel_benchmark.sh - 多容器并行测试
INSTANCES=4
THREADS_PER_INSTANCE=8
DURATION=300
echo "Starting $INSTANCES parallel benchmark instances..."
for i in $(seq 1 $INSTANCES); do
docker run --rm \
--name "sysbench-$i" \
--network host \
severalnines/sysbench \
sysbench oltp_read_write \
--mysql-host=127.0.0.1 \
--mysql-user=root \
--mysql-password=secret \
--tables=16 \
--table-size=100000 \
--threads=$THREADS_PER_INSTANCE \
--time=$DURATION \
--json="/tmp/result_$i.json" \
run &
done
echo "All instances started, waiting for completion..."
wait
echo ""
echo "=== Results ==="
total_tps=0
for i in $(seq 1 $INSTANCES); do
tps=$(cat /tmp/result_$i.json | jq '.transactions.per_second')
echo "Instance $i: TPS = $tps"
total_tps=$(echo "$total_tps + $tps" | bc)
done
echo ""
echo "Total TPS: $total_tps"
echo "Total QPS: $(echo "$total_tps * 20" | bc)" # 每事务约 20 查询
11.9 最佳实践
11.9.1 Docker 环境优化
| 优化项 | 说明 |
|---|
使用 --network host | 消除网络桥接开销 |
| 使用 volume 而非 bind mount | I/O 性能更好 |
| 限制资源 | 使用 --cpus 和 --memory 控制资源 |
| 健康检查 | 确保数据库就绪后再测试 |
| 日志管理 | 限制容器日志大小 |
11.9.2 注意事项
| 事项 | 说明 |
|---|
| 存储驱动 | overlay2 有少量 I/O 开销,volume 几乎无开销 |
| 网络模式 | bridge 有 5-15% 开销,host 模式无开销 |
| 资源限制 | CPU/内存限制会影响测试结果,需要根据场景调整 |
| 安全性 | 生产环境避免使用 --privileged |
| 时钟同步 | 容器与宿主机共享时钟,无需额外同步 |
11.10 小结
| 要点 | 说明 |
|---|
| 性能影响 | CPU/内存开销 < 2%,I/O 开销 < 5% |
| 网络优化 | 使用 --network host 消除网络开销 |
| CI/CD | GitHub Actions / GitLab CI 完全支持 |
| 资源限制 | 使用 --cpus 和 --memory 精确控制 |
| 编排 | Docker Compose 简化多容器测试 |
扩展阅读