本章概述
Helm 提供了强大的测试框架,帮助我们验证 Chart 的正确性和应用的健康状态。通过合理的测试策略,我们可以确保 Chart 在各种环境中都能正常工作,提高部署的可靠性和稳定性。本章将深入探讨 Helm 测试的各种方法和最佳实践。
学习目标
- 理解 Helm 测试框架的概念和架构
- 掌握 Chart 测试的编写方法
- 学会使用 helm test 命令进行测试
- 了解不同类型的测试策略
- 掌握测试的调试和故障排除
- 学习 CI/CD 中的 Chart 测试集成
- 了解测试的最佳实践和规范
6.1 Helm 测试框架概述
6.1.1 测试框架架构
graph TB
A[helm test] --> B[Test Hooks]
B --> C[Test Pods]
C --> D[Test Execution]
D --> E[Result Collection]
E --> F[Test Report]
subgraph "测试类型"
G[Unit Tests]
H[Integration Tests]
I[End-to-End Tests]
J[Health Checks]
K[Performance Tests]
end
subgraph "测试阶段"
L[Pre-deployment]
M[Post-deployment]
N[Runtime]
O[Pre-upgrade]
P[Post-upgrade]
end
6.1.2 测试的优势
- 质量保证:确保 Chart 的正确性和稳定性
- 自动化验证:自动验证应用的功能和性能
- 回归测试:防止新版本引入问题
- 环境验证:验证不同环境下的兼容性
- 持续集成:集成到 CI/CD 流程中
6.2 基础测试编写
6.2.1 简单的健康检查测试
# templates/tests/health-check.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-health-test"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: health-check
image: curlimages/curl:7.85.0
command:
- /bin/sh
- -c
- |
echo "Starting health check test..."
# 基本健康检查
echo "Testing health endpoint..."
curl -f http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health
if [ $? -eq 0 ]; then
echo "✓ Health check passed"
else
echo "✗ Health check failed"
exit 1
fi
echo "Health check test completed successfully"
6.2.2 API 功能测试
# templates/tests/api-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-api-test"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "1"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: api-test
image: curlimages/curl:7.85.0
command:
- /bin/sh
- -c
- |
echo "Starting API functionality test..."
BASE_URL="http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}"
# 测试 API 版本信息
echo "Testing API version endpoint..."
VERSION_RESPONSE=$(curl -s $BASE_URL/api/version)
echo "Version response: $VERSION_RESPONSE"
if echo "$VERSION_RESPONSE" | grep -q "version"; then
echo "✓ Version endpoint test passed"
else
echo "✗ Version endpoint test failed"
exit 1
fi
# 测试用户列表 API
echo "Testing users list endpoint..."
USERS_RESPONSE=$(curl -s -w "%{http_code}" $BASE_URL/api/users)
HTTP_CODE=${USERS_RESPONSE: -3}
if [ "$HTTP_CODE" = "200" ]; then
echo "✓ Users endpoint test passed"
else
echo "✗ Users endpoint test failed (HTTP $HTTP_CODE)"
exit 1
fi
# 测试创建用户 API
echo "Testing user creation endpoint..."
CREATE_RESPONSE=$(curl -s -w "%{http_code}" -X POST \
-H "Content-Type: application/json" \
-d '{"name":"test-user","email":"test@example.com"}' \
$BASE_URL/api/users)
HTTP_CODE=${CREATE_RESPONSE: -3}
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "✓ User creation test passed"
else
echo "✗ User creation test failed (HTTP $HTTP_CODE)"
exit 1
fi
echo "API functionality test completed successfully"
6.2.3 数据库连接测试
# templates/tests/database-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-database-test"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "2"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: database-test
image: postgres:13
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.database.secretName }}
key: password
- name: PGHOST
value: {{ .Values.database.host }}
- name: PGPORT
value: "{{ .Values.database.port }}"
- name: PGUSER
value: {{ .Values.database.username }}
- name: PGDATABASE
value: {{ .Values.database.name }}
command:
- /bin/bash
- -c
- |
echo "Starting database connectivity test..."
# 测试数据库连接
echo "Testing database connection..."
pg_isready -h $PGHOST -p $PGPORT -U $PGUSER
if [ $? -eq 0 ]; then
echo "✓ Database connection test passed"
else
echo "✗ Database connection test failed"
exit 1
fi
# 测试数据库查询
echo "Testing database query..."
RESULT=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -c "SELECT 1;")
if [ "$(echo $RESULT | tr -d ' ')" = "1" ]; then
echo "✓ Database query test passed"
else
echo "✗ Database query test failed"
exit 1
fi
# 测试表结构
echo "Testing table structure..."
TABLE_COUNT=$(psql -h $PGHOST -p $PGPORT -U $PGUSER -d $PGDATABASE -t -c \
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public';")
if [ "$(echo $TABLE_COUNT | tr -d ' ')" -gt "0" ]; then
echo "✓ Table structure test passed ($TABLE_COUNT tables found)"
else
echo "✗ Table structure test failed (no tables found)"
exit 1
fi
echo "Database connectivity test completed successfully"
6.2.4 性能测试
# templates/tests/performance-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-performance-test"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "3"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: performance-test
image: alpine:3.16
command:
- /bin/sh
- -c
- |
echo "Starting performance test..."
# 安装必要工具
apk add --no-cache curl time
BASE_URL="http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}"
# 响应时间测试
echo "Testing response time..."
RESPONSE_TIME=$(curl -o /dev/null -s -w "%{time_total}" $BASE_URL/health)
echo "Response time: ${RESPONSE_TIME}s"
# 检查响应时间是否在可接受范围内 (< 2秒)
if [ "$(echo "$RESPONSE_TIME < 2.0" | bc -l 2>/dev/null || echo 0)" = "1" ]; then
echo "✓ Response time test passed"
else
echo "✗ Response time test failed (${RESPONSE_TIME}s > 2.0s)"
exit 1
fi
# 并发测试
echo "Testing concurrent requests..."
# 创建并发请求函数
test_concurrent() {
for i in $(seq 1 10); do
curl -s $BASE_URL/health > /dev/null &
done
wait
}
# 执行并发测试
START_TIME=$(date +%s.%N)
test_concurrent
END_TIME=$(date +%s.%N)
DURATION=$(echo "$END_TIME - $START_TIME" | bc -l 2>/dev/null || echo "1")
echo "Concurrent test duration: ${DURATION}s"
# 检查并发性能
if [ "$(echo "$DURATION < 5.0" | bc -l 2>/dev/null || echo 0)" = "1" ]; then
echo "✓ Concurrent test passed"
else
echo "✗ Concurrent test failed (${DURATION}s > 5.0s)"
exit 1
fi
echo "Performance test completed successfully"
6.3 高级测试策略
6.3.1 集成测试
# templates/tests/integration-test.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ include "myapp.fullname" . }}-integration-test"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: integration-test
image: node:16-alpine
workingDir: /app
command:
- /bin/sh
- -c
- |
echo "Starting integration test..."
# 安装测试依赖
npm install -g newman
# 创建 Postman 集合
cat > collection.json << 'EOF'
{
"info": {
"name": "{{ include "myapp.fullname" . }} Integration Tests",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Health Check",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/health",
"host": ["{{base_url}}"],
"path": ["health"]
}
},
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 200', function () {",
" pm.response.to.have.status(200);",
"});",
"pm.test('Response contains status', function () {",
" pm.expect(pm.response.text()).to.include('status');",
"});"
]
}
}
]
},
{
"name": "User CRUD Operations",
"item": [
{
"name": "Create User",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"name\": \"Integration Test User\",\n \"email\": \"integration@test.com\"\n}"
},
"url": {
"raw": "{{base_url}}/api/users",
"host": ["{{base_url}}"],
"path": ["api", "users"]
}
},
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('User created successfully', function () {",
" pm.response.to.have.status(201);",
"});",
"pm.test('Response contains user ID', function () {",
" const response = pm.response.json();",
" pm.expect(response).to.have.property('id');",
" pm.globals.set('user_id', response.id);",
"});"
]
}
}
]
},
{
"name": "Get User",
"request": {
"method": "GET",
"url": {
"raw": "{{base_url}}/api/users/{{user_id}}",
"host": ["{{base_url}}"],
"path": ["api", "users", "{{user_id}}"]
}
},
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('User retrieved successfully', function () {",
" pm.response.to.have.status(200);",
"});",
"pm.test('User data is correct', function () {",
" const response = pm.response.json();",
" pm.expect(response.email).to.eql('integration@test.com');",
"});"
]
}
}
]
}
]
}
]
}
EOF
# 创建环境变量
cat > environment.json << EOF
{
"id": "integration-env",
"name": "Integration Environment",
"values": [
{
"key": "base_url",
"value": "http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}",
"enabled": true
}
]
}
EOF
# 运行集成测试
echo "Running Newman collection..."
newman run collection.json -e environment.json --reporters cli,json --reporter-json-export results.json
# 检查测试结果
if [ $? -eq 0 ]; then
echo "✓ Integration test passed"
else
echo "✗ Integration test failed"
cat results.json
exit 1
fi
echo "Integration test completed successfully"
6.3.2 端到端测试
# templates/tests/e2e-test.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ include "myapp.fullname" . }}-e2e-test"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: e2e-test
image: cypress/included:10.11.0
workingDir: /app
command:
- /bin/sh
- -c
- |
echo "Starting end-to-end test..."
# 创建 Cypress 配置
cat > cypress.config.js << 'EOF'
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}',
supportFile: false,
video: false,
screenshot: false,
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
})
EOF
# 创建测试目录
mkdir -p cypress/e2e
# 创建 E2E 测试
cat > cypress/e2e/app.cy.js << 'EOF'
describe('Application E2E Tests', () => {
beforeEach(() => {
cy.visit('/')
})
it('should load the homepage', () => {
cy.get('body').should('be.visible')
cy.title().should('not.be.empty')
})
it('should navigate to user management', () => {
cy.get('[data-cy="users-link"]').click()
cy.url().should('include', '/users')
cy.get('[data-cy="users-table"]').should('be.visible')
})
it('should create a new user', () => {
cy.get('[data-cy="users-link"]').click()
cy.get('[data-cy="add-user-btn"]').click()
cy.get('[data-cy="user-name-input"]').type('E2E Test User')
cy.get('[data-cy="user-email-input"]').type('e2e@test.com')
cy.get('[data-cy="save-user-btn"]').click()
cy.get('[data-cy="success-message"]').should('contain', 'User created successfully')
cy.get('[data-cy="users-table"]').should('contain', 'E2E Test User')
})
it('should handle API errors gracefully', () => {
// 模拟网络错误
cy.intercept('POST', '/api/users', { forceNetworkError: true }).as('createUserError')
cy.get('[data-cy="users-link"]').click()
cy.get('[data-cy="add-user-btn"]').click()
cy.get('[data-cy="user-name-input"]').type('Error Test User')
cy.get('[data-cy="user-email-input"]').type('error@test.com')
cy.get('[data-cy="save-user-btn"]').click()
cy.wait('@createUserError')
cy.get('[data-cy="error-message"]').should('be.visible')
})
it('should be responsive on mobile', () => {
cy.viewport('iphone-6')
cy.get('[data-cy="mobile-menu-btn"]').should('be.visible')
cy.get('[data-cy="mobile-menu-btn"]').click()
cy.get('[data-cy="mobile-nav"]').should('be.visible')
})
})
EOF
# 等待应用就绪
echo "Waiting for application to be ready..."
until curl -f http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health; do
echo "Waiting for application..."
sleep 5
done
# 运行 Cypress 测试
echo "Running Cypress E2E tests..."
npx cypress run --headless --browser chrome
if [ $? -eq 0 ]; then
echo "✓ E2E test passed"
else
echo "✗ E2E test failed"
exit 1
fi
echo "End-to-end test completed successfully"
6.3.3 安全测试
# templates/tests/security-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-security-test"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "4"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: security-test
image: alpine:3.16
command:
- /bin/sh
- -c
- |
echo "Starting security test..."
# 安装必要工具
apk add --no-cache curl nmap
BASE_URL="http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}"
# 测试 HTTPS 重定向
echo "Testing HTTPS redirect..."
HTTP_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $BASE_URL)
{{- if .Values.ingress.tls }}
if [ "$HTTP_RESPONSE" = "301" ] || [ "$HTTP_RESPONSE" = "302" ]; then
echo "✓ HTTPS redirect test passed"
else
echo "✗ HTTPS redirect test failed (HTTP $HTTP_RESPONSE)"
exit 1
fi
{{- else }}
echo "ℹ HTTPS not configured, skipping redirect test"
{{- end }}
# 测试安全头
echo "Testing security headers..."
HEADERS=$(curl -s -I $BASE_URL/health)
# 检查 X-Frame-Options
if echo "$HEADERS" | grep -i "x-frame-options"; then
echo "✓ X-Frame-Options header found"
else
echo "⚠ X-Frame-Options header missing"
fi
# 检查 X-Content-Type-Options
if echo "$HEADERS" | grep -i "x-content-type-options"; then
echo "✓ X-Content-Type-Options header found"
else
echo "⚠ X-Content-Type-Options header missing"
fi
# 检查 X-XSS-Protection
if echo "$HEADERS" | grep -i "x-xss-protection"; then
echo "✓ X-XSS-Protection header found"
else
echo "⚠ X-XSS-Protection header missing"
fi
# 测试敏感信息泄露
echo "Testing for information disclosure..."
# 检查服务器信息泄露
if echo "$HEADERS" | grep -i "server:" | grep -v "nginx\|apache"; then
echo "⚠ Server information may be disclosed"
else
echo "✓ Server information disclosure test passed"
fi
# 测试常见漏洞端点
echo "Testing common vulnerability endpoints..."
VULN_ENDPOINTS=(
"/admin"
"/.env"
"/config"
"/debug"
"/actuator"
)
for endpoint in "${VULN_ENDPOINTS[@]}"; do
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" $BASE_URL$endpoint)
if [ "$RESPONSE" = "200" ]; then
echo "⚠ Potentially sensitive endpoint accessible: $endpoint"
else
echo "✓ Endpoint $endpoint properly protected"
fi
done
# 测试 SQL 注入防护
echo "Testing SQL injection protection..."
INJECTION_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
"$BASE_URL/api/users?id=1' OR '1'='1")
if [ "$INJECTION_RESPONSE" = "400" ] || [ "$INJECTION_RESPONSE" = "403" ]; then
echo "✓ SQL injection protection test passed"
else
echo "⚠ SQL injection protection may be insufficient"
fi
echo "Security test completed"
6.4 测试配置和管理
6.4.1 测试配置文件
# values.yaml 中的测试配置
testing:
enabled: true
# 测试超时设置
timeout: 300
# 测试并行度
parallelism: 1
# 测试环境配置
environment:
testData:
users:
- name: "Test User 1"
email: "test1@example.com"
- name: "Test User 2"
email: "test2@example.com"
# 测试数据库配置
database:
host: "test-db"
port: 5432
name: "testdb"
username: "testuser"
secretName: "test-db-secret"
# 性能测试配置
performance:
responseTimeThreshold: 2.0
concurrentRequests: 10
testDuration: 60
# 安全测试配置
security:
enableVulnerabilityScanning: true
checkSecurityHeaders: true
testCommonVulnerabilities: true
# 集成测试配置
integration:
postmanCollection: "integration-tests.json"
environment: "test-environment.json"
# E2E 测试配置
e2e:
browser: "chrome"
headless: true
viewport:
width: 1280
height: 720
6.4.2 条件测试
# templates/tests/conditional-test.yaml
{{- if .Values.testing.enabled }}
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-conditional-test"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
activeDeadlineSeconds: {{ .Values.testing.timeout | default 300 }}
containers:
- name: conditional-test
image: alpine:3.16
command:
- /bin/sh
- -c
- |
echo "Running conditional tests..."
{{- if .Values.database.enabled }}
echo "Database is enabled, running database tests..."
# 数据库相关测试
{{- end }}
{{- if .Values.redis.enabled }}
echo "Redis is enabled, running cache tests..."
# Redis 相关测试
{{- end }}
{{- if .Values.ingress.enabled }}
echo "Ingress is enabled, running ingress tests..."
# Ingress 相关测试
{{- end }}
{{- if .Values.monitoring.enabled }}
echo "Monitoring is enabled, running monitoring tests..."
# 监控相关测试
{{- end }}
echo "Conditional tests completed"
{{- end }}
6.4.3 测试数据管理
# templates/tests/test-data-setup.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ include "myapp.fullname" . }}-test-data-setup"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "-10"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: test-data-setup
image: postgres:13
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.testing.environment.database.secretName }}
key: password
command:
- /bin/bash
- -c
- |
echo "Setting up test data..."
# 等待数据库就绪
until pg_isready -h {{ .Values.testing.environment.database.host }} \
-p {{ .Values.testing.environment.database.port }} \
-U {{ .Values.testing.environment.database.username }}; do
echo "Waiting for test database..."
sleep 2
done
# 创建测试数据
psql -h {{ .Values.testing.environment.database.host }} \
-p {{ .Values.testing.environment.database.port }} \
-U {{ .Values.testing.environment.database.username }} \
-d {{ .Values.testing.environment.database.name }} << 'EOF'
-- 清理现有测试数据
DELETE FROM users WHERE email LIKE '%@example.com';
-- 插入测试用户
{{- range .Values.testing.environment.testData.users }}
INSERT INTO users (name, email, created_at)
VALUES ('{{ .name }}', '{{ .email }}', NOW())
ON CONFLICT (email) DO NOTHING;
{{- end }}
-- 创建测试角色
INSERT INTO roles (name, description)
VALUES ('test-role', 'Test Role for Integration Tests')
ON CONFLICT (name) DO NOTHING;
-- 设置测试权限
INSERT INTO permissions (name, resource, action)
VALUES
('test-read', 'users', 'read'),
('test-write', 'users', 'write')
ON CONFLICT (name) DO NOTHING;
EOF
echo "Test data setup completed"
6.5 测试执行和管理
6.5.1 基本测试命令
# 运行所有测试
helm test myapp
# 运行测试并显示详细输出
helm test myapp --logs
# 运行特定测试
helm test myapp --filter name=health-test
# 运行测试并保持 Pod(用于调试)
helm test myapp --timeout 600s
# 清理测试资源
kubectl delete pods -l "helm.sh/hook=test"
6.5.2 测试脚本
#!/bin/bash
# scripts/run-tests.sh
set -e
RELEASE_NAME=${1:-myapp}
NAMESPACE=${2:-default}
TIMEOUT=${3:-300s}
echo "Running Helm tests for release: $RELEASE_NAME"
echo "Namespace: $NAMESPACE"
echo "Timeout: $TIMEOUT"
# 检查 release 状态
echo "Checking release status..."
helm status $RELEASE_NAME -n $NAMESPACE
if [ $? -ne 0 ]; then
echo "Error: Release $RELEASE_NAME not found or not deployed"
exit 1
fi
# 等待所有 Pod 就绪
echo "Waiting for pods to be ready..."
kubectl wait --for=condition=ready pod \
-l "app.kubernetes.io/instance=$RELEASE_NAME" \
-n $NAMESPACE \
--timeout=$TIMEOUT
# 运行测试
echo "Running tests..."
helm test $RELEASE_NAME -n $NAMESPACE --timeout $TIMEOUT --logs
TEST_RESULT=$?
# 收集测试结果
echo "Collecting test results..."
mkdir -p test-results
# 获取测试 Pod 日志
for pod in $(kubectl get pods -l "helm.sh/hook=test" -n $NAMESPACE -o name); do
pod_name=$(basename $pod)
echo "Collecting logs for $pod_name..."
kubectl logs $pod -n $NAMESPACE > test-results/${pod_name}.log
done
# 获取测试 Pod 状态
kubectl get pods -l "helm.sh/hook=test" -n $NAMESPACE -o yaml > test-results/test-pods.yaml
# 生成测试报告
echo "Generating test report..."
cat > test-results/test-report.md << EOF
# Test Report for $RELEASE_NAME
**Date:** $(date)
**Release:** $RELEASE_NAME
**Namespace:** $NAMESPACE
**Result:** $([ $TEST_RESULT -eq 0 ] && echo "PASSED" || echo "FAILED")
## Test Summary
\`\`\`
$(kubectl get pods -l "helm.sh/hook=test" -n $NAMESPACE)
\`\`\`
## Test Details
EOF
# 添加每个测试的详细信息
for pod in $(kubectl get pods -l "helm.sh/hook=test" -n $NAMESPACE -o jsonpath='{.items[*].metadata.name}'); do
echo "### $pod" >> test-results/test-report.md
echo "" >> test-results/test-report.md
echo "\`\`\`" >> test-results/test-report.md
kubectl logs $pod -n $NAMESPACE >> test-results/test-report.md
echo "\`\`\`" >> test-results/test-report.md
echo "" >> test-results/test-report.md
done
# 清理测试资源(可选)
if [ "$CLEANUP_TESTS" = "true" ]; then
echo "Cleaning up test resources..."
kubectl delete pods -l "helm.sh/hook=test" -n $NAMESPACE
fi
echo "Test execution completed. Results saved in test-results/"
if [ $TEST_RESULT -eq 0 ]; then
echo "✓ All tests passed"
exit 0
else
echo "✗ Some tests failed"
exit 1
fi
6.5.3 CI/CD 集成
# .github/workflows/helm-test.yml
name: Helm Chart Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Kubernetes
uses: helm/kind-action@v1.4.0
with:
cluster_name: test-cluster
- name: Setup Helm
uses: azure/setup-helm@v3
with:
version: '3.10.0'
- name: Add Helm repositories
run: |
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
- name: Install dependencies
run: |
helm dependency update
- name: Lint Chart
run: |
helm lint .
- name: Template Chart
run: |
helm template test-release . --debug
- name: Install Chart
run: |
helm install test-release . \
--wait \
--timeout 300s \
--set testing.enabled=true
- name: Run Tests
run: |
chmod +x scripts/run-tests.sh
./scripts/run-tests.sh test-release default 600s
- name: Upload Test Results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: test-results/
- name: Cleanup
if: always()
run: |
helm uninstall test-release || true
kubectl delete pods -l "helm.sh/hook=test" || true
6.6 测试调试和故障排除
6.6.1 测试调试技巧
# 查看测试 Pod 状态
kubectl get pods -l "helm.sh/hook=test"
# 查看测试 Pod 详细信息
kubectl describe pod myapp-health-test
# 查看测试日志
kubectl logs myapp-health-test
# 进入测试 Pod 进行调试
kubectl exec -it myapp-health-test -- /bin/sh
# 手动运行测试命令
kubectl run debug-test --rm -it --image=curlimages/curl -- /bin/sh
6.6.2 常见问题解决
# templates/tests/debug-helper.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-debug-helper"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "-1"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
restartPolicy: Never
containers:
- name: debug-helper
image: alpine:3.16
command:
- /bin/sh
- -c
- |
echo "Starting debug helper..."
# 安装调试工具
apk add --no-cache curl dig nmap netcat-openbsd
echo "=== Network Connectivity Tests ==="
# 测试 DNS 解析
echo "Testing DNS resolution..."
nslookup {{ include "myapp.fullname" . }}
# 测试端口连通性
echo "Testing port connectivity..."
nc -zv {{ include "myapp.fullname" . }} {{ .Values.service.port }}
# 测试服务发现
echo "Testing service discovery..."
dig {{ include "myapp.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local
echo "=== Application Status ==="
# 检查应用健康状态
echo "Checking application health..."
curl -v http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/health || true
# 检查应用版本
echo "Checking application version..."
curl -s http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}/version || true
echo "=== Kubernetes Resources ==="
# 检查相关资源
echo "Checking pods..."
kubectl get pods -l "app.kubernetes.io/instance={{ .Release.Name }}" -o wide
echo "Checking services..."
kubectl get services -l "app.kubernetes.io/instance={{ .Release.Name }}"
echo "Checking endpoints..."
kubectl get endpoints {{ include "myapp.fullname" . }}
echo "=== Environment Information ==="
echo "Release Name: {{ .Release.Name }}"
echo "Release Namespace: {{ .Release.Namespace }}"
echo "Chart Version: {{ .Chart.Version }}"
echo "App Version: {{ .Chart.AppVersion }}"
echo "Debug helper completed"
6.6.3 性能分析
# templates/tests/performance-analysis.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-performance-analysis"
labels:
{{- include "myapp.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-weight": "20"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
restartPolicy: Never
containers:
- name: performance-analysis
image: alpine:3.16
command:
- /bin/sh
- -c
- |
echo "Starting performance analysis..."
# 安装工具
apk add --no-cache curl apache2-utils bc
BASE_URL="http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}"
echo "=== Response Time Analysis ==="
# 单次请求分析
echo "Single request analysis:"
curl -w "\nTime breakdown:\n DNS lookup: %{time_namelookup}s\n Connect: %{time_connect}s\n Pre-transfer: %{time_pretransfer}s\n Start transfer:%{time_starttransfer}s\n Total: %{time_total}s\n" \
-o /dev/null -s $BASE_URL/health
echo "\n=== Load Testing ==="
# 使用 ab 进行负载测试
echo "Running load test (100 requests, 10 concurrent):"
ab -n 100 -c 10 -g results.tsv $BASE_URL/health
# 分析结果
echo "\n=== Performance Metrics ==="
# 计算平均响应时间
AVG_TIME=$(awk 'NR>1 {sum+=$9; count++} END {print sum/count}' results.tsv)
echo "Average response time: ${AVG_TIME}ms"
# 计算 95th 百分位
P95_TIME=$(awk 'NR>1 {print $9}' results.tsv | sort -n | awk '{a[NR]=$1} END {print a[int(NR*0.95)]}')
echo "95th percentile: ${P95_TIME}ms"
# 检查性能阈值
THRESHOLD={{ .Values.testing.performance.responseTimeThreshold | default 2000 }}
if [ "$(echo "$AVG_TIME < $THRESHOLD" | bc -l)" = "1" ]; then
echo "✓ Performance test passed (avg: ${AVG_TIME}ms < ${THRESHOLD}ms)"
else
echo "✗ Performance test failed (avg: ${AVG_TIME}ms >= ${THRESHOLD}ms)"
exit 1
fi
echo "Performance analysis completed"
6.7 本章小结
核心概念回顾
- Helm 测试框架:基于 Kubernetes Pod 的测试执行环境
- 测试类型:健康检查、API 测试、集成测试、E2E 测试、安全测试、性能测试
- 测试 Hook:使用
helm.sh/hook: test
注解标识测试资源 - 测试权重:控制测试执行顺序
- 测试管理:测试配置、执行、调试和结果收集
技术要点总结
- 测试通过特殊的 Pod 资源实现
- 支持多种测试策略和场景
- 可以集成到 CI/CD 流程中
- 提供丰富的调试和故障排除工具
- 支持条件测试和参数化配置
最佳实践
- 分层测试:从单元测试到集成测试再到 E2E 测试
- 测试隔离:确保测试之间不相互影响
- 测试数据管理:合理管理测试数据的创建和清理
- 性能监控:监控测试执行时间和资源使用
- 持续集成:将测试集成到 CI/CD 流程中
- 文档化:详细记录测试用例和预期结果
下一章预告
下一章我们将学习「Chart 仓库与分发」,探讨如何创建和管理 Helm Chart 仓库,包括公共仓库、私有仓库、Chart 打包、版本管理、安全签名等内容,以及如何构建企业级的 Chart 分发体系。