第12章:CI/CDパイプラインの構築
本章の意義と学習目標
なぜパフォーマンスチューニングが重要なのか
コンテナ技術の採用により、アプリケーションの展開は簡単になりましたが、最適なパフォーマンスを得るには適切なチューニングが不可欠です:
- リソース効率の最大化: 限られたハードウェアで最大の価値を提供
- レスポンス時間の短縮: ユーザー体験の向上
- コスト削減: クラウド環境でのリソース使用量削減
- スケーラビリティ確保: 負荷増大への対応力向上
本章では、Podmanコンテナのパフォーマンスを最大限に引き出す技術を習得します。
12.1 パフォーマンス分析の基礎
12.1.1 メトリクス収集と監視
なぜメトリクス収集が重要か
「測定できないものは改善できない」という原則に基づき、適切なメトリクス収集は最適化の第一歩です。
基本的なメトリクス収集
# リアルタイムモニタリング
podman stats
# 特定コンテナの詳細統計
podman stats --format "table \{\{.Container\}\}\t\{\{.CPUPerc\}\}\t\{\{.MemUsage\}\}\t\{\{.NetIO\}\}\t\{\{.BlockIO\}\}"
# JSON形式での出力(自動化用)
podman stats --no-stream --format json > metrics.json
高度なメトリクス収集スクリプト
#!/bin/bash
# performance-monitor.sh
CONTAINER=$1
DURATION=${2:-300} # デフォルト5分
INTERVAL=1
echo "Monitoring container: $CONTAINER for $DURATION seconds"
echo "Timestamp,CPU%,Memory(MB),NetRx(KB),NetTx(KB),BlockRead(KB),BlockWrite(KB)" > metrics.csv
end=$((SECONDS+DURATION))
while [ $SECONDS -lt $end ]; do
STATS=$(podman stats --no-stream --format json $CONTAINER | jq -r '.[0]')
TIMESTAMP=$(date +%s)
CPU=$(echo $STATS | jq -r '.CPUPerc' | sed 's/%//')
MEM=$(echo $STATS | jq -r '.MemUsage' | awk '{print $1}' | sed 's/MiB//')
NET_RX=$(echo $STATS | jq -r '.NetIO' | awk '{print $1}' | sed 's/kB//')
NET_TX=$(echo $STATS | jq -r '.NetIO' | awk '{print $3}' | sed 's/kB//')
BLOCK_READ=$(echo $STATS | jq -r '.BlockIO' | awk '{print $1}' | sed 's/kB//')
BLOCK_WRITE=$(echo $STATS | jq -r '.BlockIO' | awk '{print $3}' | sed 's/kB//')
echo "$TIMESTAMP,$CPU,$MEM,$NET_RX,$NET_TX,$BLOCK_READ,$BLOCK_WRITE" >> metrics.csv
sleep $INTERVAL
done
echo "Metrics saved to metrics.csv"
12.1.2 プロファイリング技術
システムコールレベルの分析
# straceによるシステムコール追跡
podman run --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined \
alpine strace -c -f /bin/ls
# 特定のシステムコールのみ追跡
podman run --cap-add SYS_PTRACE \
alpine strace -e trace=open,read,write /app/myapp
メモリプロファイリング
# メモリマップの確認
podman exec mycontainer cat /proc/1/maps
# メモリ使用詳細
podman exec mycontainer cat /proc/1/status | grep -E "Vm|Rss"
12.2 起動時間の最適化
12.2.1 イメージサイズの削減
なぜイメージサイズが起動時間に影響するか
- イメージのダウンロード時間
- ディスクI/O
- レイヤーの展開時間
最適化前後の比較
# 最適化前 (1.2GB)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
build-essential \
git
RUN pip install flask numpy pandas scikit-learn
COPY . /app
CMD ["python3", "/app/main.py"]
# 最適化後 (150MB)
FROM python:3.11-alpine AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.11-alpine
RUN apk add --no-cache libstdc++
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "main.py"]
12.2.2 起動プロセスの最適化
# 起動時間の測定
time podman run --rm myapp echo "Started"
# 起動最適化の技術
# 1. 事前プル
podman pull myapp:latest &
# 2. コンテナの事前作成
podman create --name myapp-standby myapp:latest
# 3. systemdソケットアクティベーション
systemctl --user enable podman.socket
12.3 CPU最適化
12.3.1 CPU制限とスケジューリング
CPUリソースの適切な割り当て
# CPU制限の設定
podman run -d \
--name cpu-limited \
--cpus="2.5" \ # 2.5CPU相当
--cpu-shares=2048 \ # 相対的優先度(デフォルト1024)
--cpu-period=100000 \ # スケジューリング周期(μs)
--cpu-quota=250000 \ # 周期内の使用時間(μs)
myapp
# CPU親和性の設定
podman run -d \
--cpuset-cpus="0-3" \ # CPU 0-3のみ使用
--cpuset-mems="0" \ # NUMAノード0のメモリ使用
myapp
12.3.2 マルチスレッドアプリケーションの最適化
# cpu_optimization.py
import os
import multiprocessing
def optimize_cpu_usage():
# コンテナ内のCPU数を取得
cpu_count = len(os.sched_getaffinity(0))
# ワーカー数の最適化
optimal_workers = min(cpu_count, multiprocessing.cpu_count())
# スレッドプールの設定
os.environ['OMP_NUM_THREADS'] = str(optimal_workers)
os.environ['OPENBLAS_NUM_THREADS'] = str(optimal_workers)
os.environ['MKL_NUM_THREADS'] = str(optimal_workers)
return optimal_workers
12.4 メモリ最適化
12.4.1 メモリ制限と管理
# メモリ制限の設定
podman run -d \
--name memory-optimized \
--memory=2g \ # ハードリミット
--memory-reservation=1g \ # ソフトリミット
--memory-swap=3g \ # スワップを含む総メモリ
--oom-kill-disable=false \ # OOMキラー有効
myapp
# メモリ使用状況の確認
podman exec memory-optimized cat /sys/fs/cgroup/memory/memory.usage_in_bytes
podman exec memory-optimized cat /sys/fs/cgroup/memory/memory.limit_in_bytes
12.4.2 メモリリークの検出と対策
# valgrindによるメモリリーク検出
podman run --cap-add SYS_PTRACE \
-v $(pwd):/app \
alpine sh -c "
apk add --no-cache valgrind
valgrind --leak-check=full --show-leak-kinds=all /app/myapp
"
# jemalloc使用による最適化
FROM alpine
RUN apk add --no-cache jemalloc
ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2
ENV MALLOC_CONF=stats_print:true
12.5 ネットワーク最適化
12.5.1 ネットワークドライバーの選択
パフォーマンス特性の比較
#!/bin/bash
# network-benchmark.sh
echo "Testing network performance..."
# Bridge network
podman network create test-bridge
podman run -d --name iperf-server --network test-bridge \
networkstatic/iperf3 -s
podman run --rm --network test-bridge \
networkstatic/iperf3 -c iperf-server -t 10
podman stop iperf-server && podman rm iperf-server
podman network rm test-bridge
# Host network
podman run -d --name iperf-server --network host \
networkstatic/iperf3 -s -p 5201
podman run --rm --network host \
networkstatic/iperf3 -c localhost -p 5201 -t 10
podman stop iperf-server && podman rm iperf-server
12.5.2 TCP/IPチューニング
# TCP最適化パラメータ
podman run -d \
--sysctl net.core.somaxconn=65535 \
--sysctl net.ipv4.tcp_max_syn_backlog=65535 \
--sysctl net.ipv4.tcp_tw_reuse=1 \
--sysctl net.ipv4.tcp_fin_timeout=15 \
--sysctl net.core.netdev_max_backlog=16384 \
high-performance-app
12.6 ストレージ最適化
12.6.1 ストレージドライバーの最適化
# ストレージドライバーのパフォーマンステスト
#!/bin/bash
# overlayファイルシステムの最適化
cat >> ~/.config/containers/storage.conf << EOF
[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"
mountopt = "nodev,metacopy=on,volatile"
EOF
# Direct I/Oの使用
podman run -v /fast-storage:/data:O myapp
12.6.2 I/Oパフォーマンスの向上
# I/Oベンチマーク
podman run --rm -v test-vol:/data \
alpine dd if=/dev/zero of=/data/test bs=1M count=1000
# I/O制限の設定
podman run -d \
--device-read-bps /dev/sda:100mb \
--device-write-bps /dev/sda:100mb \
--device-read-iops /dev/sda:1000 \
--device-write-iops /dev/sda:1000 \
myapp
12.7 総合的な最適化戦略
12.7.1 パフォーマンステストフレームワーク
#!/usr/bin/env python3
# performance_test_framework.py
import subprocess
import json
import time
import statistics
from datetime import datetime
class PerformanceTestFramework:
def __init__(self, image_name):
self.image_name = image_name
self.results = {}
def test_startup_time(self, iterations=10):
"""起動時間のベンチマーク"""
times = []
for _ in range(iterations):
start = time.time()
container = subprocess.run(
['podman', 'run', '-d', '--rm', self.image_name, 'sleep', '10'],
capture_output=True, text=True
).stdout.strip()
# 起動完了を待つ
while True:
result = subprocess.run(
['podman', 'inspect', container, '--format', '\{\{.State.Status\}\}'],
capture_output=True, text=True
)
if result.stdout.strip() == 'running':
break
time.sleep(0.01)
elapsed = time.time() - start
times.append(elapsed)
subprocess.run(['podman', 'stop', container], capture_output=True)
self.results['startup_time'] = {
'mean': statistics.mean(times),
'stdev': statistics.stdev(times) if len(times) > 1 else 0,
'min': min(times),
'max': max(times)
}
def test_throughput(self):
"""スループットテスト"""
# アプリケーション固有のスループットテストを実装
pass
def test_resource_efficiency(self):
"""リソース効率のテスト"""
container = subprocess.run(
['podman', 'run', '-d', '--name', 'perf-test', self.image_name],
capture_output=True, text=True
).stdout.strip()
time.sleep(5) # 安定化を待つ
# リソース使用量を収集
stats = subprocess.run(
['podman', 'stats', '--no-stream', '--format', 'json', 'perf-test'],
capture_output=True, text=True
)
stats_data = json.loads(stats.stdout)[0]
self.results['resource_efficiency'] = {
'cpu_percent': float(stats_data['CPUPerc'].rstrip('%')),
'memory_usage': stats_data['MemUsage'],
'memory_percent': float(stats_data['MemPerc'].rstrip('%'))
}
subprocess.run(['podman', 'rm', '-f', 'perf-test'])
def generate_report(self):
"""パフォーマンスレポート生成"""
report = {
'timestamp': datetime.now().isoformat(),
'image': self.image_name,
'results': self.results
}
with open('performance_report.json', 'w') as f:
json.dump(report, f, indent=2)
print("Performance Test Report")
print("=" * 50)
print(f"Image: {self.image_name}")
for test, results in self.results.items():
print(f"\n{test}:")
for metric, value in results.items():
print(f" {metric}: {value}")
12.7.2 継続的パフォーマンス改善
# .gitlab-ci.yml での自動パフォーマンステスト
performance_test:
stage: test
script:
- podman build -t $CI_PROJECT_NAME:$CI_COMMIT_SHA .
- python3 performance_test_framework.py $CI_PROJECT_NAME:$CI_COMMIT_SHA
- |
# パフォーマンス基準値との比較
STARTUP_TIME=$(jq -r '.results.startup_time.mean' performance_report.json)
if (( $(echo "$STARTUP_TIME > 2.0" | bc -l) )); then
echo "起動時間が基準値を超えています: ${STARTUP_TIME}秒"
exit 1
fi
artifacts:
reports:
performance: performance_report.json
12.8 実践演習
演習1: アプリケーションのパフォーマンスプロファイル作成
目標: 実際のアプリケーションのパフォーマンス特性を把握し、最適化ポイントを特定する
手順:
- ベースラインメトリクスの収集
- ボトルネックの特定
- 最適化の実施
- 結果の検証
演習2: 負荷テストと最適化
目標: 高負荷環境でのパフォーマンス改善
手順:
- 負荷生成ツール(Apache Bench、JMeter)の準備
- 段階的な負荷増加テスト
- リソース制限の調整
- スケーリング戦略の検証