强曰为道

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

第十一章: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/O0-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 mountI/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/CDGitHub Actions / GitLab CI 完全支持
资源限制使用 --cpus--memory 精确控制
编排Docker Compose 简化多容器测试

扩展阅读