Shell脚本如何配置容器启动前检查

wen 实用脚本 2

Shell脚本配置实战指南

目录导读

  1. 为什么要做容器启动前检查?
  2. Shell脚本核心检查模块设计
  3. 环境依赖检查实战
  4. 网络与端口连通性检测
  5. 配置文件与资源就绪校验
  6. 优雅超时与重试机制
  7. 集成到容器编排工具(Docker/K8s)
  8. 常见问题FAQ

为什么要做容器启动前检查?

在容器化部署中,应用启动失败常源于环境未就绪:数据库未连接、配置文件缺失、依赖服务未启动。启动前检查(Pre-start Check)通过Shell脚本在容器主进程启动前执行一系列验证,确保所有必备条件满足后才启动应用,这能避免:

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(需编译支持)或用wgetpythonurllib,推荐脚本开头检查工具存在性。


配置文件与资源就绪校验

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秒,并使用异步检查或并行检查优化。


通过以上步骤,你可以构建一个健壮的容器启动前检查体系,关键在于明确依赖清单、实现幂等复检(重入不产生副作用)、严格超时控制,良好的启动前检查能显著提升容器化应用的启动成功率运维稳定性

抱歉,评论功能暂时关闭!