前言
在现代软件开发实践中,持续集成(CI)与持续部署(CD))已成为保障代码质量、提升发布效率的核心机制
常用CI/CD工具对比
| 工具 | 集成方式 | 适用场景 |
|---|---|---|
| GitHub Actions | 原生集成GitHub仓库 | 开源项目、中小型团队 |
| GitLab CI | 内置在GitLab中 | 企业级私有化部署 |
| Jenkins | 自托管,插件丰富 | 复杂定制化流程 |
CI/CD的基本组成
一个典型的CI/CD流水线包含以下关键环节:
- 代码提交触发:开发者推送代码至版本仓库(如GitHub、GitLab)时自动触发流水线
- 依赖安装
- 代码检测
- 单元测试
- 构建与部署
前置准备
- 触发条件:push 到 main 分支
- 动作:通过 SSH 将项目文件(HTML/JS/CSS)同步到服务器指定目录(如 /www/wwwroot/my-site)
- 前提:服务器已配置好 Web 服务(如 Nginx),并监听对应域名,服务器已安装好 rsync(检查命令:rsync --version)
- 备份!!!
- 服务器:Debian
- 代码仓库:GitHub
- 域名:my-site.com
- 网站目录:/www/wwwroot/my-site
创建用户
以 root 或 sudo 用户登录服务器
ssh root@server-ip
# 或
ssh admin-user@-server-ip
sudo -i
创建 deployer 用户(仅用于文件同步,禁止交互式登录)
1.创建用户
# 创建用户,指定 home 目录,并设置 shell 为 /bin/bash(必须!否则 rsync 无法执行)
useradd -m -s /bin/bash deployer
2.清理 profile 文件(防止输出污染协议)
# 确保 deployer 用户不会输出任何欢迎信息(避免污染 rsync 协议)
rm -f /home/deployer/.bashrc /home/deployer/.profile /home/deployer/.bash_logout
touch /home/deployer/.hushlogin # 抑制 motd
chown deployer:deployer /home/deployer/.hushlogin
生成密钥
在本地生成 SSH 密钥对(用于 GitHub)
# 生成无密码的专用密钥(名称随意,此处为 deploy_key)
ssh-keygen -t rsa -b 4096 -f ./deploy_key -N ""
这个命令会生成两个文件:
deploy_key → 私钥(给 GitHub Secrets)
-deploy_key.pub → 公钥(放到服务器的 authorized_keys)
-N "" 表示不设 passphrase,这样 GitHub Actions 才能自动使用

配置密钥认证
为 deployer 用户配置 SSH 密钥认证
1.创建 .ssh 目录并设置权限
mkdir -p /home/deployer/.ssh
chmod 700 /home/deployer/.ssh
chown deployer:deployer /home/deployer/.ssh
2.准备公钥,复制公钥(deploy_key.pub)内容
3.将公钥写入 authorized_keys
# 将公钥内容复制到剪贴板,执行以下命令粘贴:
cat > /home/deployer/.ssh/authorized_keys <<EOF
ssh-rsa AAAAB3NzaC1yc2E...(公钥内容)... deploy-github-action
EOF
4.设置正确权限
chmod 600 /home/deployer/.ssh/authorized_keys
chown deployer:deployer /home/deployer/.ssh/authorized_keys
设置网站目录权限
设置目录归属(假设原所有者用户为www)
chown -R deployer:www /www/wwwroot/my-site
设置安全的权限:
- 所有者(deployer):可读写、目录可进入(rwX)
- 组(www)和其他人:只读、目录可进入(rX)
chmod -R u=rwX,go=rX /www/wwwroot/my-site
chown: changing ownership of ‘.../.user.ini’: Operation not permitted
chmod: changing permissions of ‘.../.user.ini’: Operation not permitted
这是是因为 .user.ini 文件被 设置了不可变属性(immutable attribute)
命令验证:
lsattr /www/wwwroot/my-site/.user.ini
如果输出包含 ----i---------,说明文件被设为 不可变!
解决方案:
1.临时移除 immutable 属性
chattr -i /www/wwwroot/my-site/.user.ini
2.继续设置目录归属(假设原所有者用户为www)
chown -R deployer:www /www/wwwroot/my-site
设置安全的权限:
- 所有者(deployer):可读写、目录可进入(rwX)
- 组(www)和其他人:只读、目录可进入(rX)
chmod -R u=rwX,go=rX /www/wwwroot/my-site
测试连通性
在本地测试服务器rsync能否正常工作
使用Git Bash(Windows 命令行不支持,MAC或Linux可直接使用终端)(切换到deploy_key所在目录)
ssh -i ./deploy_key deployer@my-site.com "id"
#输出类似用户信息:uid=1004(deployer) gid=1004 groups=1004
ssh -i ./deploy_key deployer@my-site.com "rsync --version"
#应该返回版本信息
配置GitHub
将私钥添加到 GitHub Secrets
复制私钥内容
cat ./deploy_key
- 进入 GitHub 仓库 → Settings → Secrets and variables → Actions
- 点击 New repository secret
- 填入Name和Value
Name: DEPLOY_KEY
Value: 粘贴 deploy_key 的全部内容(包括 -----BEGIN RSA PRIVATE KEY----- 和 -----END RSA PRIVATE KEY-----)

更新 GitHub Actions YAML
创建.github/workflows/ci-cd.yml文件(若无)
# 工作流的名称,在 GitHub Actions 页面中显示
name: CICD to Server
# 触发条件:定义在什么情况下运行此工作流
on:
# 当向 `main` 分支推送代码时自动触发
push:
branches: [ main ]
# 允许在 GitHub 网页上手动点击“Run workflow”来触发部署(用于紧急发布或测试)
workflow_dispatch:
# 定义一个名为 `deploy` 的作业(Job)
jobs:
deploy:
# 在 GitHub Actions 界面中显示的作业名称
name: Deploy via rsync
# 指定使用 Ubuntu 最新版本的虚拟机来运行此作业(Linux 环境,自带 rsync/ssh)
runs-on: ubuntu-latest
# 定义该作业的具体执行步骤
steps:
# 第一步:从 GitHub 拉取当前仓库的源代码到运行环境中
- name: 📥 Checkout code
uses: actions/checkout@v4 # 使用官方 checkout 动作获取代码
# 第二步:配置 SSH 私钥,使后续的 rsync 能通过 SSH 免密登录你的服务器
- name: 🔑 Setup SSH key for deployment
uses: webfactory/ssh-agent@v0.9.0 # 使用社区维护的 ssh-agent 动作
with:
# 从 GitHub Secrets 中读取名为 DEPLOY_KEY 的私钥内容
# 注意:这不是文件路径,而是直接传入私钥文本
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
# 第三步:使用 rsync 命令将本地所有文件同步到你的生产服务器
# 执行 rsync 同步命令,参数说明如下:
# -a: 归档模式(保留权限/时间等);-v: 显示过程;-z: 压缩传输;--delete: 删除服务器上已不存在的文件
# 通过 SSH 传输,并跳过主机密钥检查(避免首次连接交互)
# 不同步时间(--no-times)
# 不同步权限(--no-perms)
# 不同步用户/组(--no-group)
# 排除 .git 目录(版本控制文件,无需部署)
# 排除 GitHub Actions 配置目录
# 排除宝塔面板生成的 PHP 配置文件(常带不可变属性,避免权限错误)
# 排除说明文档
# 排除 Git 忽略规则文件
# 同步当前目录(即整个项目根目录)下的所有内容
# 目标地址:用户名@域名:远程路径
- name: 🚀 Sync files to production server
run: |
rsync -avz --delete \
-e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=QUIET" \
--rsync-path="rsync" \
--no-times \
--no-perms \
--no-group \
--exclude='.git/' \
--exclude='.github/' \
--exclude='.user.ini' \
--exclude='README.md' \
--exclude='.gitignore' \
./ \
deployer@my-site.com:/www/wwwroot/my-site/
最终测试
提交代码到GitHub仓库,合并到主分支,然后触发动作(自动/可手动触发),更新网站代码
