运行时环境:Python、Node、Go、Java
“本地能跑、服务器报错”的事故里,一大半是运行时版本不一致引起的。发行版自带的 Python 与 Node 往往版本偏旧,直接 apt install python3 上生产很快就会撞上语法或依赖不兼容。本章按语言给出可复制的安装方式、依赖锁定做法与产物目录规范。
各语言的推荐安装方式
| 语言 | 推荐方式 | 关键命令 | 不适合的方式 |
|---|---|---|---|
| Python | pyenv 管版本 + venv 管依赖 | pyenv install 3.12.6 | 用系统 Python 装业务依赖 |
| Node | nvm 管版本 + pnpm/npm 管依赖 | nvm install 20 | apt install nodejs 得到旧版本 |
| Go | 官方 tar 包解压到 /usr/local/go | tar -C /usr/local -xzf go*.tar.gz | 发行版包(版本落后、路径混乱) |
| Java | SDKMAN 装 JDK/Maven/Gradle | sdk install java 21.0.5-tem | 手工解压多版本互相干扰 |
Python:pyenv + venv
# 1) 编译 Python 需要的系统库,缺一个就会编译失败
sudo apt -y install make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev libffi-dev liblzma-dev libncursesw5-dev tk-dev
# 2) 安装 pyenv 并写入 shell 初始化(追加三行到 ~/.bashrc)
curl -fsSL https://pyenv.run | bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
exec "$SHELL" -l
pyenv install 3.12.6 && pyenv global 3.12.6
pyenv versions && python -V
# 4) 每个项目一个虚拟环境,依赖不互相污染
cd ~/apps/myapi && python -m venv .venv && source .venv/bin/activate
python -m pip install -U pip
# 生产用 lock 文件安装,保证可复现
pip install pip-tools && pip-compile requirements.in -o requirements.txt && pip-sync requirements.txt
deactivate
| 依赖管理工具 | 锁定文件 | 特点 | 建议 |
|---|---|---|---|
| pip + requirements.txt | requirements.txt | 最简单,版本约束靠手写 | 小项目够用,pip freeze 结果要人工清理 |
| pip-tools | requirements.in → requirements.txt | 分离直接依赖与全部锁定 | 传统项目的稳妥选择 |
| Poetry | poetry.lock | 依赖与打包一体 | 需要发布包时好用 |
| uv | uv.lock | 极快,兼容 pip 生态 | 新项目优先 |
Node:nvm + pnpm
# 国内网络慢就先用镜像
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
exec "$SHELL" -l
nvm install 20 && nvm alias default 20 && node -v && npm -v
# corepack 随 Node 自带,启用 pnpm 最省事
corepack enable && corepack prepare pnpm@9 --activate && pnpm -v
# 严格按锁定文件安装,绝不在服务器上更新依赖
pnpm install --frozen-lockfile # 等价于 npm ci
npm install 会按 package.json 重算依赖树并可能改写 package-lock.json;npm ci 与 pnpm install --frozen-lockfile 严格按锁文件安装,不一致时直接报错,生产构建必须用后者。
Go:官方 tar 与交叉编译
# 1) 安装官方工具链
wget https://go.dev/dl/go1.23.2.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.23.2.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
exec "$SHELL" -l && go version
# 2) 国内代理与模块缓存(缓存放数据盘,别挤占系统盘)
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GOMODCACHE=/data/go/pkg/mod
# 3) 安装小工具:go install 是 Go 特有的一行式安装
go install github.com/air-verse/air@latest
# 4) 交叉编译:CGO_ENABLED=0 才能产出静态二进制
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -ldflags "-s -w -X main.version=1.4.0" -o bin/app ./cmd/app
# 部署时只需要传这一个文件,服务器上不用装 Go
scp bin/app prod:~/apps/myapp/app
ssh prod 'chmod +x ~/apps/myapp/app && ~/apps/myapp/app -v'
Go 的最大优势就在这里:一个静态二进制就是全部产物,不会有“官方镜像里缺 so 库”的问题。
Java:SDKMAN 管理版本
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 21.0.5-tem
sdk install maven && sdk install gradle
java -version && mvn -v
用完整 JDK 而不是 JRE,是为了保留 jcmd、jstack 这些排障工具;容器里再换 eclipse-temurin:21-jre 精简体积即可。
构建产物目录规范
| 目录 | 内容 | 是否入库 |
|---|---|---|
~/apps/<name>/releases/<时间戳> | 每次发布的产物 | 不入库,服务器保留最近 3~5 个 |
~/apps/<name>/current | 指向当前生效版本的软链 | 不入库,切换靠改软链 |
~/apps/<name>/shared/.env | 环境配置与密钥 | 不入库,权限 600 |
/srv/www/<name> | 前端静态产物 | 不入库,由构建流程覆盖 |
/data/<name> | 数据库文件、上传文件 | 绝不入库,单独备份 |
/var/log/<name> | 应用日志 | 不入库,配 logrotate |
releases + current 软链是零停机发布与秒级回滚的基础,第 10 章会展开。
验证方法
python -V && pip -V && node -v && pnpm -v && go version && java -version
python -c "import sys; print(sys.executable)" # 期望 .venv/bin/python
pip check && pnpm list --depth 0 >/dev/null && echo deps-ok
file bin/app && ldd bin/app # 期望 statically linked
./bin/app --version
常见坑
| 坑 | 现象 | 做法 |
|---|---|---|
| 用系统 Python 装业务依赖 | 与系统工具冲突,升级即翻车 | venv 隔离,系统 Python 只给系统用 |
服务器上 npm install | 依赖漂移,锁文件被改写 | npm ci / pnpm install --frozen-lockfile |
| 在服务器上构建 | 内存吃满、磁盘被依赖撑爆 | 本地或 CI 构建,只上传产物 |
Go 忘了 CGO_ENABLED=0 | 二进制依赖服务器 glibc 版本 | 交叉编译时显式关闭 CGO |
| 多版本共存无切换机制 | 不同项目互相踩版本 | 用 pyenv/nvm/SDKMAN 统一管理 |
小结:运行时版本必须由版本管理工具锁定,Python 用 pyenv + venv、Node 用 nvm + pnpm、Go 用官方 tar 加交叉编译、Java 用 SDKMAN;依赖一律按锁定文件安装,生产构建不更新依赖;产物按 releases/current/shared 规范存放,Go 这类静态二进制可以直接跑,环境问题一次解决到位。