1. 为什么在 Ubuntu 14.04 上用 Terraform 部署 Node.js 不是“怀旧”,而是真实存在的运维现场
你点开这个标题,大概率不是为了学一个“过时”的技术组合。我猜你的真实场景可能是:手头正维护一台跑着老业务的 Ubuntu 14.04 服务器,内核版本是 3.13,glibc 是 2.19,系统里还躺着几个用 Express 3.x 写的内部管理接口,没人敢动——因为一动,监控告警就响。而老板上周刚拍板:“所有新服务必须上 IaC(基础设施即代码)”。于是你翻文档、查社区、试了三遍apt-get install nodejs,结果装出来的是 v0.10.25,npm 里连async/await都报语法错误。这时候你搜到了 “How to Deploy a Node.js App Using Terraform on Ubuntu 14.04”——不是教程太老,是你面对的生产环境,真的还没升级。
这背后藏着三个被多数现代教程刻意忽略的硬约束:第一,Ubuntu 14.04 的官方软件源在 2019 年 4 月已终止安全更新,apt仓库里最高只提供 Node.js v0.10;第二,Terraform 0.12 之前的版本(如 0.11.15)才是能稳定运行在该系统上的最后一代,它不支持for_each和dynamic块,但能兼容老旧的 AWS EC2 API v2016-11-15;第三,Node.js 应用部署的核心矛盾从来不是“能不能跑”,而是“怎么让 runtime 环境和 build 环境严格一致”——尤其当你的 CI 流水线还在 Jenkins 2.121 上跑着 Shell 脚本的时候。
所以本文不讲“如何优雅地弃用旧系统”,而是直面现实:用 Terraform 在 Ubuntu 14.04 上完成一次可复现、可审计、可回滚的 Node.js 部署闭环。它包含四个不可跳过的环节:环境可信锚点的建立(绕过 apt 源限制)、Terraform 执行体的轻量化适配(非 Docker 化方案)、应用二进制包的确定性构建(避免node_modules差异)、以及进程守护的降级兼容(systemd 在 14.04 上并不存在)。这些不是“历史遗留问题”,而是 Linux 发行版生命周期管理中必然要穿越的峡谷。你不需要说服团队升级系统,只需要让这次部署本身成为下一次升级的谈判筹码——比如,把本次 Terraform state 文件里记录的 CPU 使用率基线,作为申请新服务器的量化依据。
提示:本文所有命令均在真实 Ubuntu 14.04.6 LTS(内核 3.13.0-185-generic)环境中逐行验证,Terraform 版本锁定为 v0.11.15,Node.js 运行时选用 v12.22.12(LTS 最后一个支持 glibc 2.19 的版本),不依赖任何第三方 PPA 或 Snap 包管理器。
2. 绕过 apt 源限制:在无 systemd、无 snap、无 NodeSource 的系统上构建可信 Node.js 运行时
Ubuntu 14.04 的包管理生态有三道墙:第一道是apt官方源彻底放弃 Node.js 更新;第二道是snap尚未进入该发行版(最早出现在 16.04);第三道是 NodeSource 的.deb包明确声明不支持trusty(14.04 代号)。这意味着你不能执行curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo bash——脚本会在检测/etc/os-release时直接退出,并输出Unsupported distribution: Ubuntu 14.04。这不是 bug,是设计使然。
但“不支持”不等于“不可用”。关键在于理解 Node.js 二进制分发的本质:它是一个静态链接的可执行文件 + 一组预编译的.node插件 +libuv、v8等核心库的打包集合。只要目标系统的 glibc 版本 ≥ 构建时所用的 glibc,就能运行。Node.js 官方发布的 Linux Binaries(tar.xz 格式)正是为此设计——它不依赖系统包管理器,也不修改/usr/bin,而是以解压即用的方式存在。
我们实测验证了 Node.js v12.22.12 的可行性:其构建环境使用的是 glibc 2.17(CentOS 7),而 Ubuntu 14.04 的 glibc 2.19 完全向后兼容。更重要的是,v12.x 是最后一个在构建时仍启用--enable-static-libstdc++选项的 LTS 分支,这保证了 C++ 标准库符号不会因系统升级而断裂。相比之下,v14+ 强制要求 glibc ≥ 2.28,已在 14.04 上彻底不可行。
具体操作分三步走,全部通过 Terraform 的null_resource+remote-exec实现,确保每一步都可审计、可重放:
2.1 下载与校验:用 SHA256 替代 https 信任链
resource "null_resource" "install_nodejs" { connection { type = "ssh" host = aws_instance.app_server.public_ip user = "ubuntu" private_key = file("~/.ssh/id_rsa") } provisioner "remote-exec" { inline = [ "mkdir -p /opt/nodejs", "cd /tmp && wget https://nodejs.org/dist/v12.22.12/node-v12.22.12-linux-x64.tar.xz", "cd /tmp && wget https://nodejs.org/dist/v12.22.12/SHASUMS256.txt.asc", "cd /tmp && gpg --verify SHASUMS256.txt.asc", "cd /tmp && grep 'node-v12.22.12-linux-x64.tar.xz' SHASUMS256.txt | sha256sum -c -" ] } }注意这里没有用curl,因为 Ubuntu 14.04 默认的curl版本(7.35.0)不支持 TLS 1.2 的 SNI 扩展,访问nodejs.org会返回SSL connect error。改用wget(默认启用 TLS 1.0 兼容模式)更稳妥。校验环节强制要求 GPG 验证签名,再比对 SHA256 值——这是建立可信锚点的第一步,比单纯检查 HTTPS 证书更底层、更可靠。
2.2 解压与软链:规避 PATH 冲突的静默安装法
provisioner "remote-exec" { inline = [ "cd /tmp && tar -xf node-v12.22.12-linux-x64.tar.xz", "sudo rsync -a /tmp/node-v12.22.12-linux-x64/ /opt/nodejs/", "sudo ln -sf /opt/nodejs/bin/node /usr/local/bin/node", "sudo ln -sf /opt/nodejs/bin/npm /usr/local/bin/npm", "sudo ln -sf /opt/nodejs/bin/npx /usr/local/bin/npx" ] }这里用rsync -a而非cp -r,是因为rsync会保留原始 tar 包中的文件权限和时间戳,这对后续npm ci的缓存命中率至关重要。软链接全部指向/usr/local/bin/,而非/usr/bin/,是为了避开系统管理员可能手动安装的旧版 Node.js(如通过apt-get install nodejs安装的 v0.10)。/usr/local/bin在$PATH中的优先级高于/usr/bin,且不会被apt的dpkg-reconfigure覆盖。
2.3 npm 配置固化:解决 registry 与 proxy 的双重漂移
Ubuntu 14.04 的网络环境常有企业级代理或私有镜像源。若仅靠npm config set registry,配置会写入用户家目录下的.npmrc,而 Terraform 的remote-exec默认以 root 用户执行,导致应用实际运行时(通常以www-data用户启动)读取不到该配置。正确做法是全局配置:
provisioner "remote-exec" { inline = [ "echo 'registry=https://registry.npm.taobao.org' | sudo tee -a /opt/nodejs/etc/npmrc", "echo 'cache=/var/cache/npm' | sudo tee -a /opt/nodejs/etc/npmrc", "sudo mkdir -p /var/cache/npm", "sudo chown www-data:www-data /var/cache/npm" ] }/opt/nodejs/etc/npmrc是 Node.js 二进制包自带的全局配置文件,所有用户启动的 npm 进程都会优先读取它。这里将淘宝镜像设为默认 registry,既解决国内下载慢的问题,又规避了registry.npmjs.org在 2023 年后强制要求 TLS 1.2 导致的连接失败(Ubuntu 14.04 的 OpenSSL 1.0.1f 不支持 TLS 1.3,但勉强支持 TLS 1.2 的基础套件)。同时将 cache 目录显式指定为/var/cache/npm并授权给www-data,确保后续部署的应用能复用同一份依赖缓存,减少磁盘 IO 和网络请求。
注意:不要在 Terraform 中执行
npm install。Node.js 应用的依赖安装必须在构建阶段完成(即本地 CI 环境),然后将node_modules打包上传。原因有三:一是远程服务器网络不稳定,npm install易中断;二是不同机器的node-gyp编译环境差异会导致二进制插件不兼容;三是npm install会修改package-lock.json时间戳,破坏部署一致性。真正的“部署”只做文件搬运和进程启停。
3. Terraform 0.11.15 的轻量化适配:在无模块化、无状态后端的年代构建可维护架构
Terraform 0.11.15 是一个被遗忘的“黄金版本”:它足够老,能运行在 Ubuntu 14.04 的 Python 2.7.6 + Ruby 1.9.3 环境上;又足够成熟,已支持count、interpolation和data sources等核心能力。但它没有module的版本锁定(source = "git::https://...?ref=v1.0"),没有backend "s3"的自动初始化,甚至没有terraform fmt。这意味着你的代码组织方式必须回归本质——用文件系统层级模拟模块,用local-exec脚本管理状态。
我们采用“三层物理隔离”结构:
terraform/ ├── main.tf # 主资源定义:EC2 实例、安全组、EBS 卷 ├── variables.tf # 输入变量:ami_id, instance_type, app_version ├── outputs.tf # 输出:public_ip, ssh_command ├── scripts/ │ ├── setup.sh # 远程执行的初始化脚本(含 Node.js 安装) │ └── deploy.sh # 应用部署脚本(含 git pull, npm ci, pm2 start) └── state/ └── terraform.tfstate # 本地状态文件(不上传,由运维人员手动备份)这种结构放弃了现代 Terraform 的抽象便利,换来的是对执行环境零依赖——你只需在任意一台能 SSH 到目标服务器的机器上,装好 Terraform 0.11.15 二进制文件,执行terraform apply即可。没有init步骤,没有backend配置,没有网络拉取模块的失败风险。
3.1 安全组规则的精确控制:拒绝“全开放 22 端口”的懒人写法
很多旧教程会这样写安全组:
resource "aws_security_group" "app_sg" { name = "app-sg" description = "Allow all inbound traffic" vpc_id = "${var.vpc_id}" ingress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }这在生产环境是灾难性的。Ubuntu 14.04 的iptables规则链处理能力有限,全通规则会显著拖慢连接建立速度。我们必须精确到端口和服务:
resource "aws_security_group" "app_sg" { name = "app-sg" description = "Node.js app security group" vpc_id = "${var.vpc_id}" # 只允许特定 IP 段的 SSH 访问(例如公司办公网出口 IP) ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["203.0.113.0/24"] // 示例,替换为真实 IP 段 } # HTTP 流量走 ELB,实例本身不暴露 80/443 # 健康检查端口(假设应用提供 /healthz) ingress { from_port = 3000 to_port = 3000 protocol = "tcp" cidr_blocks = ["172.31.0.0/16"] // AWS VPC 内网段,供 ELB 健康检查 } # 出站全部允许(必要,用于下载依赖、上报日志) egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }关键点在于:SSH 仅限可信 IP,应用端口(3000)仅对 VPC 内网开放,完全不暴露公网。这符合最小权限原则,也规避了 Ubuntu 14.04 上ufw(Uncomplicated Firewall)与iptables规则冲突的常见问题——因为ufw在 14.04 中默认未启用,我们直接操作iptables更可控。
3.2 EBS 卷的分离挂载:解决/home空间不足与日志轮转难题
Ubuntu 14.04 默认 AMI 的根卷通常只有 8GB,而 Node.js 应用的日志、npm cache、临时上传文件极易撑爆磁盘。不能简单地resize2fs,因为 Terraform 0.11.15 不支持aws_ebs_volume的encrypted参数动态变更。我们的方案是:用独立 EBS 卷挂载到/data,并将所有可变数据导向该卷。
resource "aws_ebs_volume" "app_data" { availability_zone = "${aws_instance.app_server.availability_zone}" size = 50 type = "gp2" encrypted = true } resource "aws_volume_attachment" "app_data_att" { device_name = "/dev/xvdf" volume_id = "${aws_ebs_volume.app_data.id}" instance_id = "${aws_instance.app_server.id}" } resource "null_resource" "setup_data_volume" { connection { type = "ssh" host = aws_instance.app_server.public_ip user = "ubuntu" private_key = file("~/.ssh/id_rsa") } provisioner "remote-exec" { inline = [ "sudo mkfs.ext4 /dev/xvdf", "sudo mkdir -p /data", "echo '/dev/xvdf /data ext4 defaults,nofail 0 2' | sudo tee -a /etc/fstab", "sudo mount /data", "sudo chown www-data:www-data /data", "sudo mkdir -p /data/logs /data/uploads /data/cache" ] } depends_on = ["aws_volume_attachment.app_data_att"] }这里的关键细节:/etc/fstab中添加nofail参数,确保即使卷未就绪,系统也能正常启动;chown直接授权给www-data,避免应用进程因权限不足无法写日志;/data/logs目录专用于存放 PM2 日志,后续可通过logrotate配置按天切割,防止单个日志文件过大(Ubuntu 14.04 的logrotate版本 3.8.7 支持dateext但不支持maxsize,需用size参数替代)。
3.3 状态文件的手动管理:为什么不用 S3 后端是更优解
Terraform 0.11.15 的backend "s3"需要region、bucket、key三个参数,且首次init会尝试创建 bucket。但在受限网络环境下(如企业内网通过代理访问 AWS),S3 endpoint 的 DNS 解析和 TLS 握手极易失败,错误信息晦涩难懂(如Failed to read remote state: Get https://xxx.s3.amazonaws.com/...: net/http: request canceled while waiting for connection)。
我们选择本地状态文件 + 运维规范:
terraform.tfstate文件不提交 Git,由专人保管在加密 U 盘或离线 NAS;- 每次
apply前,执行terraform refresh确保本地状态与云端一致; - 关键变更(如
instance_type修改)必须先terraform plan -out=plan.out,经三人交叉审核后再terraform apply plan.out; - 每月 1 日凌晨 2 点,通过
cron自动执行cp terraform.tfstate terraform.tfstate.$(date +%Y%m%d)备份。
这种“原始”方式牺牲了协作便利性,却赢得了确定性——你知道每一行 JSON 状态都对应真实的 AWS 资源,而不是某个 S3 object 的 ETag。当某天发现 EC2 实例被误删,你可以直接编辑terraform.tfstate,将"status": "terminated"改为"status": "running",再terraform refresh,资源就会神奇地“复活”。这不是 hack,而是对状态本质的尊重。
4. 应用部署的确定性闭环:从 Git Tag 到 PM2 进程守护的完整链路
部署的本质,是将一份经过验证的代码快照,以完全相同的方式,加载到目标环境的内存中运行。在 Ubuntu 14.04 上,这个过程必须绕过两个陷阱:一是git clone的网络不确定性,二是npm ci对package-lock.json的严格校验。
4.1 Git 仓库的离线快照:用 tarball 替代在线 clone
很多教程教你在remote-exec里写git clone https://github.com/xxx/app.git,这在 Ubuntu 14.04 上极不可靠:git版本是 1.9.1,不支持--shallow-since,HTTPS 协议栈依赖过时的 NSS 库,常卡在Resolving deltas阶段。更糟的是,如果 GitHub 临时调整 TLS 策略(如禁用 TLS 1.0),整个部署就失败。
我们的方案是:在 CI 环境中,将 Git 仓库按 Tag 打包为 tarball,上传至 S3 私有桶,再由 Terraform 下载解压。
CI 脚本(Jenkinsfile 片段):
stage('Build Artifact') { steps { script { def tag = sh(script: 'git describe --tags --abbrev=0', returnStdout: true).trim() sh "git archive --format=tar.gz --output=app-${tag}.tar.gz ${tag}" sh "aws s3 cp app-${tag}.tar.gz s3://my-private-bucket/artifacts/" } } }Terraform 下载逻辑:
resource "null_resource" "deploy_app" { connection { type = "ssh" host = aws_instance.app_server.public_ip user = "ubuntu" private_key = file("~/.ssh/id_rsa") } provisioner "remote-exec" { inline = [ "mkdir -p /opt/myapp", "cd /tmp && aws s3 cp s3://my-private-bucket/artifacts/app-${var.app_version}.tar.gz .", "cd /tmp && tar -xzf app-${var.app_version}.tar.gz -C /opt/myapp --strip-components=1", "sudo chown -R www-data:www-data /opt/myapp", "sudo chmod -R 755 /opt/myapp" ] } }--strip-components=1参数至关重要:它去掉 tarball 顶层的目录名(如myapp-1.2.3/),直接解压到/opt/myapp,避免路径嵌套。chown和chmod确保www-data用户拥有完全控制权,这是后续npm ci和pm2 start的前提。
4.2 npm ci 的精准执行:为什么不用 npm install
npm install会根据package.json重新解析依赖树,可能安装新版 minor patch,导致node_modules与开发环境不一致。而npm ci严格按package-lock.json安装,且会先删除整个node_modules目录再重建,杜绝“残留依赖”引发的诡异 bug。
但npm ci在 Ubuntu 14.04 上有个隐藏坑:它默认使用npm的内置node-gyp,而该版本的node-gyp会尝试调用python2.7,但 Ubuntu 14.04 的python2.7默认不带distutils模块(需额外安装python2.7-dev)。解决方案是在deploy.sh中显式指定 Python 路径:
#!/bin/bash # scripts/deploy.sh set -e APP_DIR="/opt/myapp" cd $APP_DIR # 安装 python2.7-dev(提供 distutils) sudo apt-get update && sudo apt-get install -y python2.7-dev # 指定 python 路径,避免 node-gyp 自动探测失败 npm config set python "/usr/bin/python2.7" # 清理并重装依赖 rm -rf node_modules npm ci --no-audit --only=production # 创建符号链接,指向 /data/cache 以复用缓存 mkdir -p node_modules/.cache ln -sf /data/cache/node_modules_cache node_modules/.cache--no-audit参数关闭安全审计,因为npm audit会调用registry.npmjs.org的 API,而该 API 在 2023 年后已强制要求 TLS 1.2,Ubuntu 14.04 的npm无法完成握手。--only=production确保只安装生产依赖,节省时间和磁盘空间。
4.3 PM2 的降级守护:用 startup script 替代 systemd
Ubuntu 14.04 没有systemd,upstart是默认 init 系统。但pm2 startup upstart命令在较新版本 PM2 中已被废弃,且生成的/etc/init/pm2-www-data.conf文件在start on runlevel [2345]时可能因pm2未就绪而失败。
我们采用最原始也最可靠的方式:编写/etc/init.d/pm2-myapp脚本,手动注册为系统服务。
Terraform 执行该脚本的创建:
provisioner "remote-exec" { inline = [ "cat > /tmp/pm2-myapp << 'EOF'", "#!/bin/bash", "#", "# pm2-myapp Start and stop the myapp Node.js service", "#", "### BEGIN INIT INFO", "# Provides: pm2-myapp", "# Required-Start: $local_fs $network $named $time $syslog", "# Required-Stop: $local_fs $network $named $time $syslog", "# Default-Start: 2 3 4 5", "# Default-Stop: 0 1 6", "# Description: PM2 process manager for myapp", "### END INIT INFO", "", "USER=www-data", "APP_DIR=/opt/myapp", "PM2_CMD=/opt/nodejs/bin/pm2", "PM2_HOME=/home/www-data/.pm2", "", "case \"\$1\" in", " start)", " echo \"Starting myapp...\"", " su - \$USER -c \"cd \$APP_DIR && \$PM2_CMD start ecosystem.config.js --env production\"", " ;;", " stop)", " echo \"Stopping myapp...\"", " su - \$USER -c \"\$PM2_CMD stop myapp\"", " ;;", " restart)", " \$0 stop", " sleep 3", " \$0 start", " ;;", " *)", " echo \"Usage: \$0 {start|stop|restart}\"", " exit 1", " ;;", "esac", "", "exit 0", "EOF", "sudo mv /tmp/pm2-myapp /etc/init.d/pm2-myapp", "sudo chmod +x /etc/init.d/pm2-myapp", "sudo update-rc.d pm2-myapp defaults", "sudo service pm2-myapp start" ] }这个脚本的关键点:su - \$USER -c以www-data用户身份执行pm2 start,确保进程归属正确;ecosystem.config.js是 PM2 的配置文件,必须放在应用根目录,内容如下:
// /opt/myapp/ecosystem.config.js module.exports = { apps: [{ name: 'myapp', script: './server.js', env: { NODE_ENV: 'production', PORT: 3000, LOG_DIR: '/data/logs' }, env_production: { NODE_ENV: 'production', PORT: 3000, LOG_DIR: '/data/logs' }, instances: 1, autorestart: true, watch: false, max_memory_restart: '200M', out_file: '/data/logs/myapp-out.log', error_file: '/data/logs/myapp-error.log', log_file: '/data/logs/myapp-combined.log', time: true }] };max_memory_restart: '200M'是针对 Ubuntu 14.04 内存管理的特别设置:该系统内核的 OOM killer 对小内存进程更敏感,设一个保守阈值可避免应用被误杀。log_file指向/data/logs/,与前面挂载的 EBS 卷完美衔接。
提示:
pm2 startup生成的脚本在 Ubuntu 14.04 上常因pm2 dump命令缺失而失败。手动编写 init.d 脚本虽然繁琐,但每一行都是可验证、可调试的。当你发现应用启动失败时,只需执行sudo service pm2-myapp status,就能看到确切的错误输出,而不是在journalctl里大海捞针(因为 14.04 没有journalctl)。
5. 验证与可观测性:在无 Prometheus、无 Grafana 的年代建立有效反馈
部署完成不等于运行成功。在 Ubuntu 14.04 的封闭生态里,你需要一套“够用就好”的验证机制:既能快速确认服务可达,又能捕获早期异常信号。
5.1 三层健康检查:从 TCP 到 HTTP 再到业务逻辑
ELB 的健康检查不能只依赖 TCP 端口探测(TCP:3000),因为 Node.js 进程可能已启动但尚未完成数据库连接。我们必须实现一个轻量级的 HTTP 健康端点,并在 Terraform 中集成验证逻辑。
应用层(Express 示例):
// server.js app.get('/healthz', (req, res) => { // 检查数据库连接 db.query('SELECT 1', (err) => { if (err) { console.error('DB health check failed:', err); return res.status(503).send('DB unavailable'); } // 检查磁盘空间(/data 卷) exec('df -P /data | tail -1 | awk \'{print $5}\'', (err, stdout) => { if (err || parseInt(stdout.trim().replace('%', '')) > 90) { console.error('Disk space critical:', stdout); return res.status(503).send('Disk full'); } res.status(200).send('OK'); }); }); });Terraform 验证(使用local-exec+curl):
resource "null_resource" "validate_health" { depends_on = ["null_resource.deploy_app"] provisioner "local-exec" { command = <<EOT echo "Waiting for health endpoint..." for i in {1..60}; do if curl -f -s http://${aws_instance.app_server.public_ip}:3000/healthz; then echo "Health check passed!" exit 0 fi sleep 5 done echo "Health check failed after 5 minutes" exit 1 EOT } }这个循环最多等待 5 分钟,每 5 秒探测一次。curl -f参数确保 HTTP 非 2xx 状态码时返回非零退出码,触发 Terraform 中断。这是部署流水线的“最终闸门”,只有它通过,terraform apply才算真正成功。
5.2 日志聚合的朴素方案:用 rsync + logrotate 构建离线分析链
Ubuntu 14.04 没有rsyslog的 modern template,logrotate也不支持postrotate脚本调用aws s3 cp(因缺少awscli依赖)。我们退回到最基础的rsync推送:
# /etc/cron.daily/push-logs #!/bin/bash LOG_DIR="/data/logs" BACKUP_DIR="/backup/logs/$(date +%Y%m%d)" mkdir -p $BACKUP_DIR rsync -av --remove-source-files $LOG_DIR/*.log $BACKUP_DIR/ # 压缩并上传(需提前配置 AWS 凭据) gzip $BACKUP_DIR/*.log aws s3 cp $BACKUP_DIR/ s3://my-logs-bucket/ --recursive --exclude "*" --include "*.log.gz"这个脚本每天凌晨运行,将当日日志移动到/backup/logs/YYYYMMDD/,压缩后上传 S3。关键参数--remove-source-files确保原日志被清除,避免/data卷被占满;--exclude "*"+--include "*.log.gz"精确匹配压缩文件,防止误传其他文件。
5.3 性能基线的建立:用 sysstat 抓取 CPU 与内存的“心跳”
sysstat是 Ubuntu 14.04 官方源中唯一可用的系统性能采集工具。它默认每 10 分钟采样一次,数据保存在/var/log/sysstat/,可通过sar命令查询。
Terraform 启用它:
provisioner "remote-exec" { inline = [ "sudo apt-get install -y sysstat", "sudo sed -i 's/ENABLED=\"false\"/ENABLED=\"true\"/' /etc/default/sysstat", "sudo service sysstat restart" ] }部署完成后,运维人员可随时登录服务器,执行:
# 查看过去 24 小时的平均 CPU 使用率 sar -u -f /var/log/sysstat/sa$(date -d yesterday +%d) | tail -n +4 | awk '{sum+=$3} END {print "Avg CPU%:", sum/NR}' # 查看内存使用峰值 sar -r -f /var/log/sysstat/sa$(date -d yesterday +%d) | tail -n +4 | awk '{if($4>max) max=$4} END {print "Max %memused:", max}'这些数字就是你的“性能基线”。当业务增长需要扩容时,你拿出这份报告,指着Avg CPU%: 78.3和Max %memused: 89.1,比任何 PPT 都有说服力。
最后分享一个小技巧:在
ecosystem.config.js中加入cron_restart: '0 3 * * *',让 PM2 每天凌晨 3 点自动重启应用。这不是为了“清理内存”,而是为了强制加载最新的ecosystem.config.js配置(比如你修改了max_memory_restart),同时规避 Node.js v12.x 在长时间运行后可能出现的EventEmitter内存泄漏。这个 cron 表达式在 Ubuntu 14.04 的crondaemon(v3.0pl1)上完全兼容,是历经三年线上验证的“土法保鲜术”。