首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >明明测试没问题,生产就翻车?老杨聊devops的血泪史

明明测试没问题,生产就翻车?老杨聊devops的血泪史

作者头像
IT运维技术圈
发布2025-10-09 12:22:08
发布2025-10-09 12:22:08
2600
代码可运行
举报
文章被收录于专栏:IT运维技术圈IT运维技术圈
运行总次数:0
代码可运行

0. 写在前面:为什么你需要“神器”而非“常用命令

大家好,欢迎来到干货、技术、专业全方位遥遥领先的老杨的博客.

帮老杨点赞、转发、在看以及打开小星标哦

攒今世之功德,修来世之福报


"测试环境跑得好好的,怎么一上生产就出幺蛾子?"这话老杨听了二十年,从当年的菜鸟工程师到现在的老油条,这个魔咒从来没有被打破过。

今天就来聊聊这个让无数技术人员头疼的话题。测试没问题,为什么上生产就总出问题.

环境差异这个老大难问题

版本差异的深水炸弹

别看都是Linux系统,开发环境用的Ubuntu 22.04和生产环境的CentOS 7.9之间的差别,有时候比你想象的要大得多。就拿Python来说,前者自带3.10,后者还在用3.6,这点小差别就能让你的脚本死得很难看。

老杨调出一个比较经典的错误.我这里有个哥们儿写了个部署脚本,用了subprocess.run()text参数,在开发机上跑得挺好:

代码语言:javascript
代码运行次数:0
运行
复制
#!/usr/bin/env python3
import subprocess
result = subprocess.run(['systemctl', 'status', 'nginx'], 
                       capture_output=True, text=True)
print(f"Exit code: {result.returncode}")

结果到了生产环境,Python 3.6根本不认识text这个参数,直接给你来个TypeError。踩这种坑不止一次,后来学乖了,兼容性处理必须做到位:

代码语言:javascript
代码运行次数:0
运行
复制
#!/usr/bin/env python3
import subprocess
import sys

def run_command(cmd):
    if sys.version_info >= (3, 7):
        result = subprocess.run(cmd, capture_output=True, text=True)
        return result.returncode, result.stdout, result.stderr
    else:
        result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        return result.returncode, result.stdout.decode('utf-8'), result.stderr.decode('utf-8')

returncode, stdout, stderr = run_command(['systemctl', 'status', 'nginx'])
print(f"Exit code: {returncode}")

依赖库版本的连环坑

生产服务器的依赖库版本往往比开发环境落后好几个版本,这时候各种奇怪的兼容性问题就出来了。你在开发环境用的是requests 2.31.0,生产环境可能还是2.25.1,SSL证书验证的行为就不一样了。

这种情况下,我一般会创建个虚拟环境,把依赖版本锁死:

代码语言:javascript
代码运行次数:0
运行
复制
cat > requirements.txt << 'EOF'
requests==2.25.1
urllib3==1.26.5
certifi==2021.5.25
charset-normalizer==2.0.4
idna==3.2
EOF

# 部署脚本
#!/bin/bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VENV_DIR="$SCRIPT_DIR/venv"

echo"=== 创建隔离的Python环境 ==="
if [ ! -d "$VENV_DIR" ]; then
    python3 -m venv "$VENV_DIR"
fi

source"$VENV_DIR/bin/activate"
pip install --no-deps -r requirements.txt

echo"=== 验证环境一致性 ==="
pip freeze | sort > deployed_versions.txt

python api_client.py

权限问题,这个隐形杀手

在测试环境,开发人员通常有sudo权限,想写哪儿就写哪儿。但生产环境不一样,权限控制严格得很,稍不注意就撞墙了。

这里老杨举一个权限检查脚本,每次部署前都会跑一遍,避免踩坑:

代码语言:javascript
代码运行次数:0
运行
复制
#!/bin/bash
# permission_check.sh - 生产环境权限预检

set -euo pipefail

echo"=== 系统权限诊断报告 ==="
echo"执行用户: $(whoami)"
echo"用户组: $(groups)"
echo"当前目录: $(pwd)"

declare -A critical_dirs=(
    ["/var/log"]="日志目录"
    ["/etc/systemd/system"]="系统服务配置"
    ["/opt/applications"]="应用部署目录"
    ["/tmp"]="临时文件目录"
)

fordirin"${!critical_dirs[@]}"; do
    echo"检查 ${critical_dirs[$dir]} ($dir):"
    if [ -d "$dir" ]; then
        ls -ld "$dir"
        if [ -w "$dir" ]; then
            echo"  ✅ 可写"
        else
            echo"  ❌ 不可写"
        fi
    else
        echo"  ❌ 目录不存在"
    fi
    echo
done

还有个更狠的,SELinux。这家伙在RHEL/CentOS系统里默默守护,很多时候你的脚本运行失败,根本想不到是它在作怪。检查SELinux状态,配置正确的文件上下文,这些都得考虑进去。

网络环境,看不见的拦路虎

企业内网环境复杂,防火墙规则、代理设置、DNS解析,任何一个环节出问题都能让你的脚本跑不起来。

这里老杨举例一个网络连通性检查脚本:

代码语言:javascript
代码运行次数:0
运行
复制
#!/bin/bash
# network_connectivity_check.sh

set -euo pipefail

declare -A endpoints=(
    ["registry.cn-hangzhou.aliyuncs.com:443"]="阿里云容器镜像服务"
    ["mirrors.aliyun.com:80"]="阿里云软件源"
    ["api.dingtalk.com:443"]="钉钉API"
    ["127.0.0.1:3306"]="本地MySQL"
    ["redis.internal.company.com:6379"]="内网Redis"
)

check_connectivity() {
    local endpoint=$1
    local description=$2
    local host port
    
    IFS=':'read -r host port <<< "$endpoint"
    
    echo"检查 $description ($endpoint):"
    
    # DNS解析检查
    if ! nslookup "$host" >/dev/null 2>&1; then
        echo"  ❌ DNS解析失败"
        return 1
    fi
    echo"  ✅ DNS解析正常"
    
    # 端口连通性检查
    iftimeout 5 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
        echo"  ✅ 端口 $port 可达"
    else
        echo"  ❌ 端口 $port 不可达"
        echo"  💡 建议检查防火墙规则:"
        echo"     sudo iptables -L | grep $port"
    fi
    echo
}

for endpoint in"${!endpoints[@]}"; do
    check_connectivity "$endpoint""${endpoints[$endpoint]}"
done

特别是企业代理环境,这个更是个大坑。所有外网访问都得走代理,脚本里不配置代理参数,连个包都下载不了。我一般会做个代理感知的处理:

代码语言:javascript
代码运行次数:0
运行
复制
setup_proxy() {
    echo"=== 代理环境配置 ==="
    
    local proxy_sources=(
        "$http_proxy"
        "$HTTP_PROXY"
        "http://proxy.company.com:8080"# 企业默认代理
    )
    
    local detected_proxy=""
    for proxy in"${proxy_sources[@]}"; do
        if [[ -n "$proxy" ]] && curl -s --proxy "$proxy" --connect-timeout 5 \
           "http://www.baidu.com" >/dev/null 2>&1; then
            detected_proxy="$proxy"
            break
        fi
    done
    
    if [[ -n "$detected_proxy" ]]; then
        echo"✅ 检测到可用代理: $detected_proxy"
        export http_proxy="$detected_proxy"
        export https_proxy="$detected_proxy"
    else
        echo"❌ 未检测到可用代理,使用直连模式"
    fi
}

配置管理的精细化工程

配置文件的管理更是门学问。不同环境用不同的配置,这个看起来简单,实际操作起来坑不少。配置文件的版本控制、语法验证、权限设置,每一步都不能马虎。

我习惯用Git管理配置,按环境分目录存放,部署的时候根据环境选择对应的配置:

代码语言:javascript
代码运行次数:0
运行
复制
#!/bin/bash
manage_configs() {
    localenv=$1# dev, test, prod
    
    echo"=== 环境配置管理 [$env] ==="
    
    # 克隆配置仓库
    if [ ! -d "/tmp/myapp-configs" ]; then
        git clone"$GIT_REPO" /tmp/myapp-configs
    fi
    
    cd /tmp/myapp-configs
    git pull origin main
    
    local config_source="configs/$env"
    if [ ! -d "$config_source" ]; then
        echo"❌ 环境配置目录不存在: $config_source"
        return 1
    fi
    
    # 备份现有配置
    if [ -d "$CONFIG_DIR" ] && [ "$(ls -A "$CONFIG_DIR" 2>/dev/null)" ]; then
        local backup_timestamp=$(date +"%Y%m%d_%H%M%S")
        local backup_path="$BACKUP_DIR/backup_$backup_timestamp"
        
        echo"📦 备份现有配置到: $backup_path"
        sudocp -r "$CONFIG_DIR""$backup_path"
    fi
    
    # 部署新配置
    sudocp -r "$config_source"/* "$CONFIG_DIR/"
    sudochown -R myapp:myapp "$CONFIG_DIR"
    
    echo"✅ 配置管理完成"
}

密钥管理这块儿更得小心,生产环境的密码、API密钥这些敏感信息,绝对不能明文存储。我一般用HashiCorp Vault或者至少做个加密存储:

代码语言:javascript
代码运行次数:0
运行
复制
encrypt_secrets() {
    local secrets_file=$1
    local encrypted_file="${secrets_file}.enc"
    
    echo"🔒 加密密钥文件: $secrets_file"
    
    # 使用GPG加密
    ifcommand -v gpg >/dev/null 2>&1; then
        gpg --symmetric --cipher-algo AES256 --output "$encrypted_file""$secrets_file"
        # 安全删除原文件
        shred -vfz -n 3 "$secrets_file" 2>/dev/null || rm -f "$secrets_file"
        echo"✅ 文件已加密: $encrypted_file"
    fi
}

资源限制的现实约束

生产环境的资源控制比开发环境严格多了,CPU、内存、磁盘I/O都有限制。你的脚本在开发机上跑得飞快,到了生产环境可能因为资源不够用而跑得慢如蜗牛,甚至直接被kill掉。

我经常用cgroup来做资源限制,确保应用不会因为资源抢占而影响其他服务:

代码语言:javascript
代码运行次数:0
运行
复制
setup_cgroup_limits() {
    local app_name=$1
    local memory_limit=$2    # 例如: 512m, 1g
    local cpu_limit=$3       # 例如: 1.0, 0.5
    
    echo"⚙️  配置cgroup资源限制: $app_name"
    
    # 创建cgroup
    local cgroup_path="/sys/fs/cgroup/myapps/$app_name"
    sudomkdir -p "$cgroup_path"
    
    # 内存限制
    echo"$memory_limit" | sudotee"$cgroup_path/memory.limit_in_bytes" > /dev/null
    echo"✅ 内存限制: $memory_limit"
    
    # CPU限制
    local cpu_period=100000
    local cpu_quota=$(echo"$cpu_limit * $cpu_period" | bc | cut -d. -f1)
    echo"$cpu_period" | sudotee"$cgroup_path/cpu.cfs_period_us" > /dev/null
    echo"$cpu_quota" | sudotee"$cgroup_path/cpu.cfs_quota_us" > /dev/null
    echo"✅ CPU限制: ${cpu_limit}核"
}

磁盘I/O也是个大头,特别是日志文件写入频繁的应用。我会做好日志轮转配置,避免磁盘被撑爆:

代码语言:javascript
代码运行次数:0
运行
复制
# logrotate配置示例
$log_dir/*.log {
    daily
    missingok
    rotate 7
    compress
    delaycompress
    notifempty
    create 644 $app_name$app_name
    
    postrotate
        if [ -f /var/run/${app_name}.pid ]; then
            kill -HUP $(cat /var/run/${app_name}.pid) 2>/dev/null || true
        fi
    endscript
}

服务依赖关系的复杂网络

现在的应用架构越来越复杂,微服务之间的依赖关系像蜘蛛网一样,任何一个服务挂了都可能引发连锁反应。数据库挂了,后端服务起不来;缓存服务异常,整个应用性能下降;消息队列堵塞,数据处理停滞。

我一般会建立服务依赖拓扑,按照依赖关系顺序启动服务:

代码语言:javascript
代码运行次数:0
运行
复制
# 服务依赖配置
{
"services": {
    "mysql": {
      "type": "database", 
      "dependencies": [],
      "health_check": "mysqladmin ping -h localhost",
      "start_timeout": 60
    },
    "redis": {
      "type": "cache",
      "dependencies": [],
      "health_check": "redis-cli ping",
      "start_timeout": 15
    },
    "app-backend": {
      "type": "application",
      "dependencies": ["mysql", "redis"],
      "health_check": "curl -f http://localhost:8080/api/health",
      "start_timeout": 45
    }
  }
}

健康检查也很重要,不能只看进程是否存在,还要确保服务真的可用。HTTP接口要能正常响应,数据库连接要正常,缓存要能读写,这些都得检查到位。

习惯性水结尾

运维人挑灯守夜,为亿万连接负重前行,机器轰鸣中,运维人以鲜血与诗意修补世界的脆弱,朝露未晞,使命已沉。 亿万方阵,任将帅坐帷帐指点江山,运维人护军旗立于风雨之夜.挽大厦于将倾,填江湖与决口.运维不死,只是慢慢凋零. 评论区等你们!

老杨时间

这里老杨先声明一下,日常生活中大家都叫老杨波哥,跟辈分没关系,主要是岁数大了.就一个代称而已. 老杨的00后小同事老杨喊都是带哥的.张哥,李哥的. 但是这个称呼呀,在线下参加一些活动时.金主爸爸也这么叫就显的不太合适. 比如上次某集团策划总监,公司开大会来一句:“今个咱高兴!有请IT运维技术圈的波哥讲两句“ 这个氛围配这个称呼在互联网这行来讲就有点对不齐! 每次遇到这个情况老杨老杨周末浅聊服务器开在公网的那些坑老杨干了,你们随意!” 所以以后咱们改叫老杨,即市井又低调.还挺亲切,老杨觉得挺好.

运维X档案系列文章:

从告警到CTO:一个P0故障的11小时生死时速

企业级 Kubernetes 集群安全加固全攻略( 附带一键检查脚本)

看完别走.修行在于点赞、转发、在看.攒今世之功德,修来世之福报

老杨AI的号: 98dev

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT运维技术圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 环境差异这个老大难问题
    • 版本差异的深水炸弹
    • 依赖库版本的连环坑
  • 权限问题,这个隐形杀手
  • 网络环境,看不见的拦路虎
  • 配置管理的精细化工程
  • 资源限制的现实约束
  • 服务依赖关系的复杂网络
  • 习惯性水结尾
  • 老杨时间
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档