ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Jenkins太重FTP不安全:小团队用rsync+SSH实现轻量自动化部署

2026/9/10 1:38:13 拓冰建站 浏览量
Jenkins太重FTP不安全:小团队用rsync+SSH实现轻量自动化部署 去年入秋的时候团队里一个做了三年PHP的老周跑来找我吐槽每次上线就是手动打包、开FileZilla往服务器传文件传一半断了还得从头来传漏了某个目录更是家常便饭。他花了一个周末把Jenkins装起来pipeline还没跑通光是Java版本、插件依赖、权限配置就把他劝退了。他问我一句话“我只是想简单地把代码发到服务器怎么这么难”这句话几乎是小团队的普遍困境。Jenkins功能强大但对三四个人、一台轻量云服务器的队伍来说它更像一台需要常驻保养的机器而你手头只需要一把顺手的螺丝刀。FTP虽然人人都会却撑不起任何一个“自动化”的定义。所以如果你也在“不想搭Jenkins和手动FTP”这个夹缝里纠结这篇文章应该能帮到你。我会先把两个方案的痛点掰开看再讲清楚轻量部署工具的共同原理随后给出几类可以直接上手的工具对比最后交给你一份能用很多年的rsync SSH自动化部署方案。全程不堆术语尽量说人话。1. Jenkins和手动FTP到底卡在哪先看清两个“敌人”1.1 Jenkins不是不好用而是太重了我见过不少小团队选择Jenkins的路径先在百度搜“jenkins安装部署”翻了几篇“jenkins菜鸟教程”然后稀里糊涂装了一台CentOS服务器接着开始折腾“jenkins用打包Java需要安装哪些插件”一类的问题。结果往往是一周过去了代码还是在命令行里mvn package之后手动拷包。Jenkins本身是个成熟的自动化调度平台问题是它把“通用”和“强大”放在第一位。你想让它做一件简单的事它先要求你理解节点、凭证、插件依赖、流水线语法。我们团队当年第一次配“jenkins可用环境变量”和“jenkins pipeline”时最大的感觉是每一步都有三个选项而你永远不知道哪个是对的。况且Jenkins运行起来是要吃资源的。一个安装了常用插件、偶尔跑构建的JenkinsJava进程轻松占到800MB以上内存。对于一台2C4G的小服务器来说这已经是相当沉重的负担。你还要定期升级插件修复SSL证书过期处理构建日志把磁盘塞满的问题。小团队通常没有专职运维这些维护成本最终会落在某个开发头上变成一肚子怨气。不是说Jenkins不能用于小团队而是说它默认给你的太多多到你不需要的东西反过来把你缠住了。1.2 FTP部署的每一个环节都是安全隐患再看手动FTP。这个方案看起来一步到位实际上每个环节都埋着雷。首先是传输本身。传统FTP是明文协议账号密码和文件内容在网络上裸奔。FileZilla连接服务器时经常弹出“不支持的服务器”或涉及“不安全的服务器”提示背后就是FTP over TLS协商失败的问题。如果服务器只支持纯FTP你的密码等于每次都在公网亮一次相这在今天的环境里几乎不可接受。其次是文件完整性。用FTP传几百个小文件、传一半网络闪断客户端有时只提示“ftp复制文件出错”你会发现服务器上多了一批残缺的PHP、JS文件可能只是某个函数少了几行。线上故障往往就是这么来的。再往后是版本和权限。FTP传上去的文件归你本机登录用户跟Web服务运行用户经常不一致。于是你会遇到“ftp可以登录无法传文件”或“如何让FTP上的一个文件夹可以读写”这类问题。排查半天发现是目录属主不对、权限不对。更麻烦的是一旦传坏了根本没有“回滚”这个概念。没有版本记录没有时间戳备份你只能靠记忆恢复原来的文件。很多时候团队不是因为FTP好用才用它而是因为暂时没被它坑到怀疑人生。1.3 小团队真正需要的部署工具长什么样把两边的痛点捋完就能总结出小团队对“轻量自动化部署工具”的核心诉求。一键触发从“我手上有新代码”到“服务器跑上新代码”中间最好只有一个操作。增量传输全量传十几个G的依赖、图片、node_modules既慢又蠢必须能按文件差异同步。可回滚新版出问题能迅速切回上一个可用版本而不是重新传一遍旧包。可观测至少能看到成功或失败失败时知道卡在哪一步。维护成本低没有独立服务、没有数据库、没有一堆插件要养。这个清单说穿了就是“自动化的核心价值”。轻量部署不是不做自动化而是把自动化的边界控制在“够用且好维护”的范围内。后面要讲的所有工具都是围绕这五个点展开的。2. 轻量部署工具的共同路径先搞懂代码是怎么上服务器的2.1 两种触发模式Push和Pull不管用什么工具部署的本质就是从代码仓库往服务器搬运“最新可用版本”。搬运的触发方式基本可以分为两类。Push模式开发者在本地或某个构建节点上把文件主动推到服务器。最典型的就是rsync -az --delete -e ssh ./ userhost:/var/www/html。这个方式非常直接链路短出问题容易排查适合服务器少、网络链路简单的场景。Pull模式服务器自己去代码仓库拉取最新内容。典型实现是服务器上挂一个Git Hook在git push到远程仓库后服务器收到通知自动git pull或者git fetch checkout。这种方式的好处是服务器不需要对外暴露太多端口只需要它能访问代码仓库即可。对比一下对比项Push模式Pull模式触发动作本地/CI机器主动执行命令仓库收到推送后回调服务器对服务器要求需开放SSH端口服务器需能访问仓库传输内容任意文件不止Git仓库以Git仓库内容为主回滚复杂度靠备份目录/标签切换靠git checkout切换历史提交典型工具rsync/scp脚本、DokkuGit Hooks、Webhook触发的一些脚本对于小团队来说Push模式通常是学习曲线最低的因为你永远知道“命令是我手动敲的它做了什么我看得见”。2.2 小团队部署的最小闭环不管是Push还是Pull一个完整的轻量部署闭环应该是开发者在本地提交并推送代码到仓库触发部署脚本手动执行、Git Hook或Webhook任一即可脚本把代码同步到服务器的发布目录脚本执行依赖安装/构建比如composer install、npm ci、pnpm build更新入口软链接或重启服务进程做一次健康检查curl一下健康检查接口失败则自动回滚或至少输出日志提示人工介入。别小看这个闭环。很多只做“文件同步”的团队把代码传上去就以为完事了结果vendor目录没装、环境变量没生效、进程没重启最后页面直接白屏。每一个环节都可能翻车所以“最小闭环”的意思是把最必要的步骤数清楚一个都不能省。2.3 为什么“脚本 SSH”是绝大多数轻量方案的底座你会发现无论Dokku、Piku、还是各种自定义脚本底层离不开SSH和Shell。SSH负责加密连接、身份认证、远程执行命令Shell负责在服务器端完成复制、解压、建软链、重启进程这些操作。理解了这一点你在选型时就不会慌。所谓轻量自动化部署工具很多只是“包装好的SSHShell”。既然底座是共通的那你可以先学会底层再用那些封装好的工具就会觉得“哦原来它只是替我做了这个”。这也是我建议小团队第一套方案先不要上太复杂框架的原因先把rsync、SSH、systemd这些基础玩明白后面即使换工具也能快速看出它为什么这么设计。3. 四类工具横评从“纯脚本”到“开箱即用”3.1 Git Hooks仓库自带的最小自动化如果你的项目已经用Git管理最简单粗暴的方式是“让仓库自己在收到推送时部署”。服务端的post-receiveHook会在每次接收完推送后执行一段脚本。假设你的服务器上有一个裸仓库/home/git/your-repo.git想让推送推到main分支时自动同步到/var/www/your-project你可以这样写#!/bin/bash TARGET/var/www/your-project GIT_DIR/home/git/your-repo.git BRANCHmain while read oldrev newrev ref do if [ $ref refs/heads/$BRANCH ]; then echo Deploying branch $BRANCH ... git --work-tree$TARGET --git-dir$GIT_DIR checkout -f $BRANCH cd $TARGET # 如果有Composer依赖取消下面这行注释 # composer install --no-dev --prefer-dist --optimize-autoloader echo Deploy finished. fi done这段脚本的核心是git --work-tree直接强制检出当前分支文件到目标目录。优点是零额外依赖、完全免费、改动很小缺点也很明显没有增量概念每次等同重新检出没有版本备份没有健康检查。它适合那种“静态站或纯PHP项目代码量不大对回滚要求不高”的场景。Git Hooks最大的问题是不太方便“按需部署”。它只认Git事件如果你想部署指定分支、指定目录或者需要多环境脚本逻辑会越来越复杂。这就像在自家厨房做饭可以用但别指望它是专业后厨。3.2 rsync SSH 脚本灵活度最高的万能方案rsync是目前我在小团队方案里最推荐的基础工具。它最大的特点是“增量同步”只传变化的部分配合SSH可以加密传输还能通过--exclude忽略不需要的文件。一条最常用的同步命令长这样rsync -az --delete \ -e ssh \ --exclude .git \ --exclude node_modules \ --exclude .env \ ./ useryour-server:/var/www/your-project解释一下几个参数-a归档模式保留权限、时间戳、软链接-z传输时压缩--delete删除服务器上本地已经不存在的文件保证两边严格一致。但同时也提醒一个坑加了--delete后服务器上多出来的文件会被删掉所以runtime、uploads这类需要保留的目录必须用--exclude排除。rsync可以很容易扩展成一键脚本#!/bin/bash set -e REMOTE_USERdeploy REMOTE_HOSTyour-server.com REMOTE_PATH/var/www/your-project RELEASE_BASE/var/www/your-project/releases CURRENT/var/www/your-project/current TIMESTAMP$(date %Y%m%d%H%M%S) # 1. 先保证远程目录结构存在 ssh $REMOTE_USER$REMOTE_HOST mkdir -p $RELEASE_BASE/$TIMESTAMP # 2. rsync 同步到新版本目录 rsync -az --delete \ -e ssh \ --exclude .git \ --exclude node_modules \ --exclude runtime/* \ --exclude .env \ ./ $REMOTE_USER$REMOTE_HOST:$RELEASE_BASE/$TIMESTAMP/ # 3. 在服务器上执行依赖安装和环境变量复制 ssh $REMOTE_USER$REMOTE_HOST cd $RELEASE_BASE/$TIMESTAMP composer install --no-dev --no-interaction # 4. 切换软链接 ssh $REMOTE_USER$REMOTE_HOST ln -sfn $RELEASE_BASE/$TIMESTAMP $CURRENT # 5. 健康检查 sleep 2 curl -fsS http://your-server.com/health echo Deploy OK || echo Health check failed这个脚本虽然粗糙但已经具备了“增量传输、独立版本目录、软链切换、健康检查”四个关键能力。回滚只需要把软链指向上一个版本目录ssh deployyour-server.com ln -sfn /var/www/your-project/releases/20250101120000 /var/www/your-project/current这样的方案足够满足大部分PHP、Node、Python、静态站项目的部署需求唯一的代价是你要自己维护脚本逻辑。但这点代价换来的是完全可控。3.3 Dokku / Piku一台VPS就能用的最小PaaS如果你不想维护一堆自研脚本又觉得云平台绑定太深Dokku是个很好的折中。Dokku可以看作“单机版Heroku”它基于Docker运行你用git push dokku main的方式就能部署。Dokku做了几件很讨人喜欢的事自动为应用创建容器管理端口映射支持app.json、Procfile定义启动命令提供插件体系PostgreSQL、Redis、Lets Encrypt证书都能一键安装回滚只需要dokku ps:rebuild app或dokku tags:deploy app tag。对三五人的团队来说Dokku的学习成本主要集中在Docker和插件配置上。一旦跑通日常发版就是一行git push dokku main甚至不需要SSH登服务器。比Dokku还要轻量的是Piku。它不需要Docker用Python写的直接依赖服务器上的语言环境和进程管理器适合内存特别紧张的情况。但它能自动化的范围也小一些更像是“带流程的git push部署脚本”。我在实际使用中的感受是如果你的项目已经容器化或者未来一定会上容器Dokku可以让你提前获得接近生产PaaS的体验如果项目只是传统LNMP结构Dokku反而会引入“为什么要用Docker”的学习成本。选它之前先想清楚团队的长期技术方向。3.4 托管平台与云服务不想管服务器时的另一个选择如果你的项目是纯前端静态站或者对运行环境没有强诉求完全可以用托管平台来省掉服务器管理。前端静态站可以直接推到Vercel、Netlify、Cloudflare Pages它们在收到Git推送后会自动构建、部署、回滚自带CDN和HTTPS个人和极小型项目甚至长期免费。你不用操心“怎么在资源管理器中打开FTP”也不用管“如何让FTP上的一个文件夹可以读写”因为你根本不需要服务器。Node.js、Python这类动态应用也可以用Render、Railway等平台它们支持从代码仓库部署并提供数据库等附加服务。好处是部署链路短、几乎零运维代价是费用随流量和实例时长上升而且数据和应用托管在第三方如果你有比较强的数据本地化要求这个方案要谨慎评估。这类平台胜在“开箱即用”但本质上是把服务器的控制权让渡出去了。小团队早期的复杂逻辑并不多用它们很合适一旦业务对定制化、内网访问、审计合规有要求迁回自建服务器的成本就会显现。3.5 为什么我不推荐一上来就用Ansible/Terraform很多文章一聊自动部署就推Ansible、Terraform理由是“基础设施即代码”。但老实说对小团队来说这两样东西的引入时机往往不是现在。Ansible的playbook写起来并不难但它需要一套严格控制Inventory、角色、变量、加密文件的管理方式。当你的服务器只有一台、部署流程只是“同步代码重启服务”时维护playbook的复杂度已经超过了手动脚本。Terraform更是面向云资源编排的适合管理VPC、实例、负载均衡这些基础设施而不是“把代码发上去”这件事。我的建议是先用rsync脚本或Git Hook把部署跑顺当你的服务器超过三五台、配置出现明显重复时再引入Ansible这类工具做统一管理。那才是它出场的时候。4. 直接能抄的作业用rsync SSH 做一套可持续用的自动部署4.1 服务端准备目录规划与免密登录既然rsync SSH是最稳妥的轻量方案我就把这套配置完整讲一遍。首先在服务器上创建一个专门用于部署的系统用户不要直接用root。因为你不会希望rsync脚本拥有删全盘的权限。sudo useradd -m -s /bin/bash deploy sudo mkdir -p /var/www/your-project sudo chown -R deploy:deploy /var/www/your-project然后在本地生成SSH密钥把公钥放到部署用户的authorized_keys里ssh-keygen -t ed25519 -C deploylocal ssh-copy-id deployyour-server.com之后测试ssh deployyour-server.com echo ok如果直接输出ok说明免密登录已经生效。目录规划我建议采用“发布目录 软链接”的结构/var/www/your-project/ ├── current - releases/20250101120000 ├── releases/ │ ├── 20250101100000/ │ ├── 20250101120000/ │ └── 20250102143000/ ├── shared/ │ ├── .env │ ├── runtime/ │ └── uploads/current始终指向当前生效的版本releases下按时间戳保留最近几个版本shared里放所有“不能每次发版都被覆盖”的内容比如环境变量文件、上传目录、运行日志目录。4.2 写一个带备份和回滚的部署脚本下面这个deploy.sh是在上一章那个粗糙版本上做了完善你可以直接复制改造。#!/bin/bash set -euo pipefail # 配置区 APP_NAMEyour-project REMOTE_USERdeploy REMOTE_HOSTyour-server.com REMOTE_BASE/var/www/${APP_NAME} RELEASE_DIR${REMOTE_BASE}/releases/$(date %Y%m%d%H%M%S) SHARED_DIR${REMOTE_BASE}/shared CURRENT_DIR${REMOTE_BASE}/current KEEP_RELEASES5 echo 1/5 创建远程发布目录 ssh ${REMOTE_USER}${REMOTE_HOST} mkdir -p ${RELEASE_DIR} echo 2/5 同步代码排除敏感文件和大目录 rsync -az --delete \ -e ssh \ --exclude .git \ --exclude .env \ --exclude node_modules \ --exclude runtime \ --exclude uploads \ ./ ${REMOTE_USER}${REMOTE_HOST}:${RELEASE_DIR}/ echo 3/5 链接共享文件 ssh ${REMOTE_USER}${REMOTE_HOST} ln -sfn ${SHARED_DIR}/.env ${RELEASE_DIR}/.env ln -sfn ${SHARED_DIR}/runtime ${RELEASE_DIR}/runtime ln -sfn ${SHARED_DIR}/uploads ${RELEASE_DIR}/uploads echo 4/5 远程安装依赖并切换软链接 ssh ${REMOTE_USER}${REMOTE_HOST} cd ${RELEASE_DIR} composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader ln -sfn ${RELEASE_DIR} ${CURRENT_DIR} echo 5/5 健康检查 sleep 3 curl -fsS http://your-server.com/health || echo Warning: health check failed echo 清理超过 ${KEEP_RELEASES} 个的旧版本 ssh ${REMOTE_USER}${REMOTE_HOST} cd ${REMOTE_BASE}/releases ls -1t | tail -n $((KEEP_RELEASES1)) | xargs -r rm -rf echo 部署完成这个脚本的核心是“绝不直接改current下的文件”。每次发版都在新目录里完成依赖安装再用ln -sfn原子切换软链接。即使新版本有问题旧版本目录也没有被动过回滚成本极低。回滚命令就是ssh deployyour-server.com ln -sfn ${REMOTE_BASE}/releases/20250102143000 ${CURRENT_DIR}记得在回滚后重启对应的PHP-FPM或Node进程并再跑一次健康检查。4.3 接上线上的“触发”Git钩子还是手动执行上面的脚本已经可以手动跑了但“自动化”还差一步怎么触发。最简单的方式是本地加一个Git别名或者Shell函数每次执行deploy就可以了。比如在~/.bashrc里加alias deploy~/deploy.sh但如果你想做到“本地git push后自动部署”可以选择本地pre-push钩子也可以选择服务端post-receive钩子。我建议小团队不要一开始就做全自动推送部署因为一不小心就会把“半成品代码”推到服务器。先用手动执行脚本等流程稳定到发布频次很高后再考虑接Webhook或者Git Hook。如果确实要接优先级是部署脚本中加入环境判断只有main分支允许部署脚本中加入构建动作避免本地代码和线上构建不一致增加一层审批部署前从命令行确认一次。自动化不是为了取消人的决定而是为了把重复动作交给机器把关键决策留给人。4.4 初始化线上环境时容易漏掉的几个点我踩过不少坑这里挑几个最典型的提醒。配置文件管理。.env文件不要进Git仓库也不要通过rsync同步。建议在shared里放一份部署时用软链链接。这样发版不会覆盖线上数据库密码、API Key等敏感信息。依赖安装位置。如果项目用了Composer最佳做法是在服务器上执行composer install。不要在本地把vendor目录一起rsync传上去因为本地系统跟服务器系统不同扩展版本、PHP版本都可能对不上。文件权限。rsync的-a参数会保留本地文件权限但本地开发环境的文件属主往往是你的Mac或Windows用户到Linux服务器上就会出现“nginx用户无法读取文件”的问题。部署后一定要执行sudo chown -R www-data:www-data ${RELEASE_DIR}或者让部署用户加入www-data组并确保目录是755、文件是644。进程重试。Node项目的npm install和pm2 reload要串行执行遇到pm2配置里用了相对路径时软链接切换后可能会出现进程指向旧目录的情况。最好用pm2 startOrReload ecosystem.config.js并在配置里写清楚cwd变量。健康检查接口。很多项目没有健康检查接口导致部署脚本里的curl形同虚设。可以加一个/healthz路由返回固定内容让脚本去判断服务是否真的可用。这个接口也可以带上数据库连接检查能在上线前发现数据库配置问题。5. 不同技术栈怎么选别再被“全家桶”绑架5.1 前端静态站最省钱省事的组合纯静态站是最适合“白嫖”托管平台的场景。Vercel、Netlify、Cloudflare Pages都支持Git推送自动构建自带HTTPS和CDN回滚也特别方便。如果团队有自建服务器要求也可以用rsync把dist目录推到Nginx的web目录脚本里只需要执行pnpm build再同步构建产物。静态站最容易犯的错误是把node_modules也传上服务器。千万记得用--exclude排除服务器上放一份nginx配置和一份dist目录就够了Node环境只在构建阶段存在。5.2 Node.js / Python 动态服务Process Manager Git hook如果是Node或Python写的后端服务部署不仅仅是同步代码还需要管理进程和依赖。Node常用pm2Python常用systemd或者gunicorn。推荐组合是“rsync pm2/systemd Git Hook”。部署脚本里执行cd ${CURRENT_DIR} npm ci --omitdev pm2 startOrReload ecosystem.config.jspm2的startOrReload会根据配置文件判断是首次启动还是重载比硬编码pm2 reload app更稳。Python项目则可以用systemctl reload myapp前提是systemd服务定义里使用软链接路径而不是具体版本路径否则每次都要改服务文件。5.3 PHP项目rsync composer 的经典组合PHP项目因为没有常驻进程部署相对简单但要重点处理composer依赖和权限。建议本地只写代码服务器上跑composer install --no-dev --optimize-autoloader。框架的runtime目录、uploads目录用shared软链统一管理。如果用了Laravelphp artisan migrate --force建议单独一步手动执行而不是混在自动部署里因为数据库变更比代码回滚难得多。5.4 如果哪天团队大了怎么平滑升级这套rsync方案撑到团队二十人左右完全没问题真正需要升级的信号有三个需要多个环境dev/staging/prod同时部署脚本开始出现大量环境判断团队成员规模变大部署权限、审批流程需要规范化自动测试、代码扫描、镜像构建开始出现在发布流程里。这时候再引入GitHub Actions或GitLab CI也不迟它们本质上是“帮你托管执行脚本的平台”比Jenkins更轻按分钟计费也不需要自己维护服务。再往后如果觉得GitAction Runner不够用再考虑Jenkins或Drone都不晚。轻量部署工具的选择永远不是“最强大”而是“最匹配团队当前阶段”。我个人的感受是小团队最该避免的是为了上一个“正规军工具”而把自己拖垮。工具是解决痛点的不是制造新痛点的。先把rsync和SSH玩明白部署这件事就会变得跟喝水一样自然。