Shell脚本配置实战指南
目录导读
- 为什么要做容器启动前检查?
- Shell脚本核心检查模块设计
- 环境依赖检查实战
- 网络与端口连通性检测
- 配置文件与资源就绪校验
- 优雅超时与重试机制
- 集成到容器编排工具(Docker/K8s)
- 常见问题FAQ
为什么要做容器启动前检查?
在容器化部署中,应用启动失败常源于环境未就绪:数据库未连接、配置文件缺失、依赖服务未启动。启动前检查(Pre-start Check)通过Shell脚本在容器主进程启动前执行一系列验证,确保所有必备条件满足后才启动应用,这能避免:

- 应用进程启动后因依赖缺失崩溃
- 容器反复重启(CrashLoopBackOff)
- 日志中出现大量连接超时错误
问答:启动前检查与健康检查的区别?
健康检查(如Docker HEALTHCHECK)是容器运行期间周期性检测,而启动前检查仅在容器启动前执行一次,前者用于运行时暂停流量,后者用于阻止因环境问题导致的启动失败。
Shell脚本核心检查模块设计
一个健壮的检查脚本应包含以下模块:
#!/bin/bash
set -euo pipefail # 严格模式:任何错误即退出
# 配置变量
TIMEOUT=30 # 总超时时间
RETRY_INTERVAL=2 # 重试间隔
# 错误捕获
trap 'log "ERROR" "检查失败,停止启动"' ERR
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$1] $2"
}
设计原则:
- 使用
set -e确保任何命令失败即停止脚本 - 独立检查函数,每个函数只做一件事
- 统一日志格式,便于排查
问答:为什么用set -euo pipefail?
-e遇到错误退出;-u防止未定义变量;-o pipefail管道中任一命令失败则全管道失败,三者结合避免“静默失败”。
环境依赖检查实战
1 必需环境变量检查
check_env_vars() {
local required_vars=( "DB_HOST" "DB_PORT" "APP_SECRET" )
local missing=()
for var in "${required_vars[@]}"; do
if [[ -z "${!var:-}" ]]; then
missing+=("$var")
fi
done
if [[ ${#missing[@]} -gt 0 ]]; then
log "ERROR" "Missing required env vars: ${missing[*]}"
return 1
fi
log "INFO" "All required env vars are set"
}
2 文件与目录权限检查
check_file_permissions() {
local files=( "/app/config.yml" "/data/logs" )
for f in "${files[@]}"; do
if [[ -f "$f" && ! -r "$f" ]]; then
log "ERROR" "File $f is not readable"
return 1
fi
if [[ -d "$f" && ! -w "$f" ]]; then
log "ERROR" "Directory $f is not writable"
return 1
fi
done
log "INFO" "File permissions OK"
}
问答:环境变量检查一定要用
${!var:-}吗?
是的。${!var}间接引用变量值, 提供默认空值,防止-u模式未定义变量报错。
网络与端口连通性检测
1 TCP端口检测(无需额外工具)
check_tcp_port() {
local host="$1" port="$2"
timeout 5 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null && {
log "INFO" "Port $host:$port reachable"
return 0
}
log "WARN" "Port $host:$port unreachable"
return 1
}
2 HTTP健康端点检测(需curl)
check_http_endpoint() {
local url="$1"
local expected_code="${2:-200}"
local code
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null) || true
if [[ "$code" == "$expected_code" ]]; then
log "INFO" "HTTP endpoint $url returns $expected_code"
return 0
fi
log "ERROR" "HTTP $url returned $code (expected $expected_code)"
return 1
}
问答:容器内没有curl/netcat怎么办?
使用Bash内置的/dev/tcp(需编译支持)或用wget、python的urllib,推荐脚本开头检查工具存在性。
配置文件与资源就绪校验
1 配置文件语法检查(以YAML为例)
check_yaml_syntax() {
local file="$1"
python3 -c "import yaml; yaml.safe_load(open('$file'))" 2>/dev/null || {
log "ERROR" "YAML syntax error in $file"
return 1
}
}
2 数据库连接检查(MySQL示例)
check_db_connection() {
local host="$DB_HOST" user="$DB_USER" pass="$DB_PASS"
timeout 10 mysql -h "$host" -u "$user" -p"$pass" -e "SELECT 1" 2>/dev/null || {
log "ERROR" "Cannot connect to MySQL at $host"
return 1
}
log "INFO" "MySQL connection successful"
}
问答:如果数据库密码含特殊字符怎么办?
使用环境变量时用双引号包裹变量,或写入临时配置文件用--defaults-extra-file,避免密码直接暴露在命令中。
优雅超时与重试机制
1 带重试的通用检查函数
retry_check() {
local check_func="$1" max_retries="${2:-5}" delay="${3:-2}"
local attempt=1
while [[ $attempt -le $max_retries ]]; do
if $check_func; then
return 0
fi
log "WARN" "Attempt $attempt/$max_retries failed, retrying in ${delay}s"
sleep "$delay"
((attempt++))
done
log "ERROR" "All $max_retries attempts failed"
return 1
}
# 使用示例:重试5次连接数据库
retry_check "check_db_connection" 5 3
2 全局超时控制
# 在脚本开头设置
total_start=$(date +%s)
if [[ $(($(date +%s) - total_start)) -gt $TIMEOUT ]]; then
log "ERROR" "Total check time exceeded ${TIMEOUT}s, aborting"
exit 1
fi
问答:重试频率和次数如何设计?
典型值:每2秒重试,最多10次(约20秒),对于数据库启动较慢的场景,可延长至30秒,但避免超过业务SLA。
集成到容器编排工具
1 Dockerfile集成
FROM alpine:latest COPY check.sh /check.sh RUN chmod +x /check.sh # 使用ENTRYPOINT将检查作为前置步骤 ENTRYPOINT ["/bin/sh", "-c", "/check.sh && exec /app/entrypoint.sh"]
2 Kubernetes Init Container
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
initContainers:
- name: pre-check
image: alpine:latest
command: ['sh', '-c', '/check.sh']
env:
- name: DB_HOST
value: "mysql-service"
containers:
- name: app
image: my-app:latest
问答:Init Container与Entrypoint脚本哪个更好?
Init Container更容器原生,隔离检查逻辑,且可复用镜像;Entrypoint脚本更轻量,适合单体容器,K8s环境推荐Init Container。
常见问题FAQ
Q1:检查脚本执行失败会不会让容器一直重启?
A:会,脚本通过exit 1返回错误,Docker/K8s会按重启策略(如Always)重启容器,直到依赖就绪。
Q2:如何调试检查脚本?
A:在容器内直接运行/check.sh查看输出,或设置set -x打印每条命令,生产环境建议将日志输出到Stdout,便于收集。
Q3:检查脚本本身有bug怎么办?
A:为检查脚本编写单元测试(如Bats测试框架),并在CI/CD中执行,考虑为脚本设置独立的超时和失败阈值。
Q4:不同环境(开发/生产)检查项不同如何处理?
A:使用环境变量控制启用哪些检查,例如ENABLE_DB_CHECK=true,或者准备多个检查脚本,通过ConfigMap挂载。
Q5:检查脚本运行时间过长有什么影响?
A:容器启动延迟,可能导致服务注册超时,建议总检查时间不超过30秒,并使用异步检查或并行检查优化。
通过以上步骤,你可以构建一个健壮的容器启动前检查体系,关键在于明确依赖清单、实现幂等复检(重入不产生副作用)、严格超时控制,良好的启动前检查能显著提升容器化应用的启动成功率和运维稳定性。