GitHub Actions 实践指南:从零到部署 ishwe
什么是 GitHub ActionsGitHub Actions 是 GitHub 内置的 CI/CD 平台。简单来说你 push 代码 → GitHub 自动帮你执行预定义的命令对于 ishwe 项目流程是git push origin master ↓ GitHub 检测到代码变更 ↓ 自动 SSH 到你的服务器 ↓ 执行 git pull docker compose up -d --build ↓ 服务器上的容器自动更新你不需要手动 SSH 到服务器不需要手动执行任何部署命令。推送代码 部署。2. 核心概念2.1 Workflow工作流工作流就是一个 YAML 文件放在.github/workflows/目录下。GitHub 会自动读取并执行。.github/ workflows/ deploy.yml ← 这就是工作流ishwe 的工作流文件.github/workflows/deploy.yml2.2 Trigger触发器工作流什么时候执行由on字段定义on: push: branches: [master] # 推送到 master 分支时触发 paths: # 只有这些路径下的文件变更才触发 - backend/** - frontend/** - docker-compose.yml为什么用paths过滤如果你只改了 README.md没必要重新部署。只有前后端代码或 Docker 配置变了才触发部署。2.3 Job任务一个工作流可以包含多个 Job默认并行执行。每个 Job 运行在一个独立的虚拟机上称为 Runner。jobs: deploy: # Job 名称 runs-on: ubuntu-latest # 使用 Ubuntu 虚拟机 steps: # 该 Job 包含的步骤 - name: Deploy run: echo deploying...2.4 Step步骤每个 Job 由多个 Step 组成按顺序执行。steps: - name: 第一步 run: echo step 1 - name: 第二步 run: echo step 2 # 第一步完成后才执行2.5 Action动作Action 是可复用的步骤别人写好的你直接用。格式是用户名/仓库名版本。steps: - uses: actions/checkoutv4 # 官方的拉取代码 - uses: appleboy/ssh-actionv1 # 第三方的SSH 执行命令2.6 Secrets密钥敏感信息密码、API Key不能写在 YAML 里仓库是公开的。GitHub 提供了 Secrets 功能在仓库 Settings → Secrets and variables → Actions 中添加在 YAML 中通过${{ secrets.名称 }}引用日志中会自动显示为***3. ishwe 的 CI/CD 架构最终方案┌─────────────────────────────────────────────────────┐ │ GitHub 仓库 │ │ │ │ 代码变更 → push to master → 触发 deploy.yml │ └──────────────────────┬──────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ GitHub Actions Runner │ │ (GitHub 提供的虚拟机) │ │ │ │ 1. SSH 连接到你的服务器 │ │ 2. 执行部署命令 │ └──────────────────────┬──────────────────────────────┘ │ SSH ▼ ┌─────────────────────────────────────────────────────┐ │ 你的服务器 (xxx.xx.xxx.xx) │ │ │ │ git fetch origin master │ │ git reset --hard FETCH_HEAD │ │ docker compose up -d --build --force-recreate │ │ │ │ ┌─────────┐ ┌─────────┐ ┌──────────────┐ │ │ │ backend │ │frontend │ │ OpenResty │ │ │ │ :8000 │ │ :3000 │ │ (Nginx) │ │ │ └─────────┘ └─────────┘ │ :8080 │ │ │ └──────────────┘ │ └─────────────────────────────────────────────────────┘为什么不需要单独构建镜像你可能会问CI/CD 不是应该先构建镜像再推送到镜像仓库吗这是两种不同的部署模式模式流程适用场景镜像仓库模式CI 构建镜像 → 推送到 GHCR/Docker Hub → 服务器拉取大型项目、多服务器、需要版本管理源码构建模式服务器 git pull → docker compose build → 重启小型项目、单服务器、简单直接ishwe 用的是源码构建模式。GitHub Actions 的 Runner 只是一个遥控器通过 SSH 在你的服务器上执行命令。真正的构建发生在你的服务器上。完整的 deploy.ymlname: Build Deploy on: push: branches: [master] paths: - backend/** - frontend/** - docker-compose.yml - .github/workflows/deploy.yml jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy to server uses: appleboy/ssh-actionv1 with: host: ${{ secrets.SERVER_HOST }} username: ${{ secrets.SERVER_USER }} password: ${{ secrets.SERVER_PASSWORD }} script: | cd /home/ubuntu/ishwe git config --global http.version HTTP/1.1 git fetch origin master git reset --hard FETCH_HEAD sudo docker compose up -d --build --force-recreate sudo docker image prune -f逐行解释on: push: branches: [master] # 只在推送到 master 时触发 paths: # 只在这些文件变更时触发 - backend/** # backend 目录下的任何文件 - frontend/** # frontend 目录下的任何文件 - docker-compose.yml - .github/workflows/deploy.ymlsteps: - uses: appleboy/ssh-actionv1 # 使用 SSH 执行远程命令的 Action with: host: ${{ secrets.SERVER_HOST }} # 从 Secrets 读取服务器 IP username: ${{ secrets.SERVER_USER }} # SSH 用户名 password: ${{ secrets.SERVER_PASSWORD }} # SSH 密码script: | cd /home/ubuntu/ishwe # 进入项目目录 git config --global http.version HTTP/1.1 # 修复 TLS 问题 git fetch origin master # 拉取最新代码 git reset --hard FETCH_HEAD # 强制同步到最新版本 sudo docker compose up -d --build --force-recreate # 重建并启动 sudo docker image prune -f # 清理旧镜像释放磁盘4. 从零开始配置4.1 准备工作你需要一个 GitHub 仓库ishwe 代码已推送一台 Linux 服务器已安装 Docker服务器上已 clone 了仓库4.2 配置 GitHub Secrets这是最关键的一步。在浏览器中操作打开 GitHub 仓库页面如https://github.com/Pronting/eisenhower点击Settings顶部标签栏最右边左侧菜单找到Secrets and variables→ 点击Actions点击New repository secret逐个添加NameValue说明SERVER_HOSTxxx.xx.xxx.xx你的服务器 IPSERVER_USERubuntuSSH 用户名SERVER_PASSWORD你的密码SSH 密码每添加一个点Add secret。添加完成后这些值会被加密存储YAML 中通过${{ secrets.SERVER_HOST }}引用。4.3 设置工作流权限Settings→Actions→General往下滚到Workflow permissions选择Read and write permissions点Save这一步允许 GitHub Actions 对仓库进行写操作。如果以后需要推送构建产物到仓库如 GHCR 镜像需要这个权限。4.4 创建工作流文件在项目根目录创建.github/ workflows/ deploy.yml写入上面的完整 YAML 内容。4.5 提交并推送git add .github/workflows/deploy.yml git commit -m ci: 添加 GitHub Actions 自动部署 git push origin master4.6 查看部署状态打开 GitHub 仓库页面点击顶部Actions标签你会看到一个正在运行的工作流黄色圆圈 运行中点进去可以看实时日志绿色勾 成功红色叉 失败从推送代码到部署完成通常需要 2-5 分钟主要是docker compose build中pip install耗时。5. 踩坑记录与解决方案以下是我们在部署 ishwe 过程中遇到的所有问题按出现顺序记录。5.1 SSH 权限不足现象permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock原因SSH 登录的用户ubuntu没有 Docker 权限。Docker daemon 的 socket 文件只有 root 和 docker 组用户才能访问。解决在docker compose命令前加sudo。# 错误 docker compose up -d --build # 正确 sudo docker compose up -d --build永久解决可选在服务器上执行sudo usermod -aG docker $USER然后重新登录。5.2 git pull 失败 — TLS 协议错误现象fatal: unable to access https://github.com/Pronting/eisenhower.git/: GnuTLS recv error (-110): The TLS connection was non-properly terminated.原因服务器的 git 使用 HTTP/2 协议与 GitHub 通信某些网络环境下 HTTP/2 不稳定尤其在国内服务器。解决在git pull或git fetch前设置 git 使用 HTTP/1.1。script: | git config --global http.version HTTP/1.1 # 加这一行 git fetch origin master注意git config --global修改的是~/.gitconfig文件对当前用户永久生效。但通过 SSH 非交互式执行时某些环境下配置可能不持久。建议每次都设置一次。5.3 git pull 冲突现象error: Your local changes to the following files would be overwritten by merge原因服务器上有一些本地修改比如手动改过 docker-compose.yml导致git pull失败。解决使用git fetchgit reset --hard替代git pull。# 不推荐可能冲突 git pull origin master # 推荐强制同步不会冲突 git fetch origin master git reset --hard FETCH_HEAD区别git pullgit fetchgit mergemerge 可能冲突git fetchgit reset --hard 直接覆盖本地代码不会冲突注意git reset --hard会丢弃服务器上的所有本地修改。确保服务器上没有需要保留的改动。5.4 pip 依赖版本冲突现象ERROR: Cannot install pydantic2.5.2 because these package versions have conflicting dependencies. langchain 1.2.15 depends on pydantic2.7.4 pydantic-settings 2.1.0 depends on pydantic2.3.0原因requirements.txt用锁定了旧版本但 langchain 的新版本需要更高版本的 pydantic。这是最常见的 Python 项目依赖问题。根本原因是requirements.txt中的版本组合本身就有内在冲突只是之前构建的 Docker 镜像缓存了旧依赖没暴露出来。解决将改为让 pip 自动解析兼容版本。# 错误锁定旧版本 pydantic2.5.2 pydantic-settings2.1.0 langchain1.2.15 # 正确允许升级 pydantic2.7.4 pydantic-settings2.10.1 langchain1.2.15如何找到正确的版本查看服务器上正在运行的容器里实际安装的版本docker exec 容器名 pip list | grep 包名