hyperf对接 项目接入 Jenkins 国内 CI/CD 实践
整体架构 开发者 push/PR ↓ esc to interrupt Gitee 私有仓库 ↓ Webhook Jenkins自建 ↓ ┌───────────────────────────────┐ │ Stage1: 拉取代码 │ │ Stage2: Composer 安装依赖 │ │ Stage3: 静态分析 单元测试 │ │ Stage4: 构建 Docker 镜像 │ │ Stage5: 推送 ACR/Harbor │ │ Stage6: SSH 部署到目标服务器 │ │ Stage7: 通知钉钉/企业微信 │ └───────────────────────────────┘ --- 第一步安装 JenkinsDocker 方式国内加速1.1docker-compose.yml version:3.8services: jenkins: image: jenkins/jenkins:lts-jdk17 container_name: jenkins restart: unless-stopped privileged:trueuser: root ports: -8080:8080-50000:50000volumes: - /data/jenkins:/var/jenkins_home - /var/run/docker.sock:/var/run/docker.sock# Docker-outside-Docker- /usr/bin/docker:/usr/bin/docker environment: -TZAsia/Shanghai -JAVA_OPTS-Duser.timezoneAsia/Shanghai-Xmx2gdocker-composeup-d# 获取初始密码dockerexecjenkinscat/var/jenkins_home/secrets/initialAdminPassword1.2切换国内插件源解决插件下载慢 Jenkins 启动后进入 Manage Jenkins → Plugin Manager → Advanced将 Update Site URL 替换为清华镜像 https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json 或者直接修改配置文件# 进入 Jenkins 数据目录sed-is|https://updates.jenkins.io/update-center.json|https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-ce nter.json|g\/data/jenkins/hudson.model.UpdateCenter.xml1.3必装插件清单 在 Manage Jenkins → Plugin Manager → Available 搜索安装 ┌─────────────────────┬───────────────────────────────────┐ │ 插件名 │ 用途 │ ├─────────────────────┼───────────────────────────────────┤ │ Gitee │ Gitee Webhook 触发 构建状态回写 │ ├─────────────────────┼───────────────────────────────────┤ │ Pipeline │ Jenkinsfile 声明式流水线 │ ├─────────────────────┼───────────────────────────────────┤ │ Git │ 拉取 Git 仓库 │ ├─────────────────────┼───────────────────────────────────┤ │ Publish Over SSH │ SSH 部署到远程服务器 │ ├─────────────────────┼───────────────────────────────────┤ │ Credentials Binding │ 安全注入密钥/密码 │ ├─────────────────────┼───────────────────────────────────┤ │ AnsiColor │ 彩色日志输出 │ ├─────────────────────┼───────────────────────────────────┤ │ DingTalk │ 钉钉通知 │ ├─────────────────────┼───────────────────────────────────┤ │ Docker Pipeline │ Pipeline 内构建推送镜像 │ ├─────────────────────┼───────────────────────────────────┤ │ Workspace Cleanup │ 构建前清理工作区 │ └─────────────────────┴───────────────────────────────────┘ --- 第二步Jenkins 全局配置2.1配置 Gitee 连接 Manage Jenkins → Configure System → Gitee 配置 - Gitee 链接名称gitee - Gitee 域名https://gitee.com - 证书令牌添加 → Jenkins → 类型选 Gitee API 令牌 → 填入 Gitee 私人令牌2.2配置 SSH 远程服务器Publish Over SSH Manage Jenkins → Configure System → Publish over SSH Name: production-server Hostname:192.168.1.100 Username: deploy Remote Directory: /data/www 私钥粘贴到 Key 字段Jenkins 服务器的 ~/.ssh/id_rsa 内容。2.3配置 Credentials凭据 Manage Jenkins → Credentials → System → Global → Add Credentials ┌─────────────────┬───────────────────────────────┬─────────────────────┐ │ ID │ 类型 │ 用途 │ ├─────────────────┼───────────────────────────────┼─────────────────────┤ │ gitee-ssh-key │ SSH Username with private key │ 拉取 Gitee 私有仓库 │ ├─────────────────┼───────────────────────────────┼─────────────────────┤ │ composer-auth │ Secret text │ COMPOSER_AUTH JSON │ ├─────────────────┼───────────────────────────────┼─────────────────────┤ │ acr-credentials │ Username/Password │ 推送 Docker 镜像 │ ├─────────────────┼───────────────────────────────┼─────────────────────┤ │ dingtalk-token │ Secret text │ 钉钉机器人 Token │ └─────────────────┴───────────────────────────────┴─────────────────────┘ --- 第三步Hyperf 项目配置3.1项目根目录添加 Jenkinsfile // Jenkinsfile pipeline{agent any // 环境变量 environment{APP_NAMEhyperf-appDEPLOY_PATH/data/www/hyperf-appPHP_BIN/usr/local/php/bin/phpCOMPOSER_BIN/usr/local/bin/composer// Docker 镜像仓库阿里云 ACR IMAGE_REGISTRYregistry.cn-hangzhou.aliyuncs.comIMAGE_NAMESPACEyour-namespaceIMAGE_TAG${IMAGE_REGISTRY}/${IMAGE_NAMESPACE}/${APP_NAME}:${BUILD_NUMBER}// 注入 Composer 私有仓库认证 COMPOSER_AUTHcredentials(composer-auth)}// 触发条件 triggers{// Gitee 插件触发push 到 main/master 分支 GenericTrigger(genericVariables:[[key:ref, value:$.ref]], causeString:Triggered by Gitee push to $ref, token:your-webhook-token, regexpFilterText:$ref, regexpFilterExpression:refs/heads/(main|master|release/.*))}options{// 保留最近10次构建记录 buildDiscarder(logRotator(numToKeepStr:10))// 超时30分钟 timeout(time:30, unit:MINUTES)// 彩色日志 ansiColor(xterm)// 不允许并发构建 disableConcurrentBuilds()}stages{// ── Stage1: 清理工作区 ────────────────────────────── stage(Prepare){steps{cleanWs()checkout scmecho✅ 代码拉取完成分支:${env.GIT_BRANCH}Commit:${env.GIT_COMMIT[0..7]}}}// ── Stage2: 安装 Composer 依赖 ───────────────────── stage(Composer Install){steps{sh${PHP_BIN}${COMPOSER_BIN}install\--no-dev\--optimize-autoloader\--no-interaction\--prefer-dist}}// ── Stage3: 代码质量检查 ──────────────────────────── stage(Code Quality){parallel{stage(PHPStan){steps{sh${PHP_BIN}vendor/bin/phpstan analyse\--level5\--no-progress\app/}}stage(Unit Tests){steps{sh${PHP_BIN}vendor/bin/phpunit\--configurationphpunit.xml\--log-junit reports/junit.xml\--coverage-clover reports/coverage.xml}post{always{// 发布测试报告 junitreports/junit.xml}}}}}// ── Stage4: 生成 Hyperf 注解缓存 ─────────────────── stage(Build Cache){steps{sh${PHP_BIN} bin/hyperf.php di:init-proxy}}// ── Stage5: 构建 Docker 镜像 ──────────────────────── stage(Docker Build){when{// 只有 main/master/release 分支才构建镜像 anyOf{branchmainbranchmasterbranch pattern:release/.*, comparator:REGEXP}}steps{withCredentials([usernamePassword(credentialsId:acr-credentials, usernameVariable:ACR_USER, passwordVariable:ACR_PASS)]){shdockerlogin${IMAGE_REGISTRY}-u${ACR_USER}-p${ACR_PASS}dockerbuild\--build-argAPP_ENVproduction\--labelgit-commit${GIT_COMMIT}\--labelbuild-number${BUILD_NUMBER}\-t${IMAGE_TAG}\-t${IMAGE_REGISTRY}/${IMAGE_NAMESPACE}/${APP_NAME}:latest\.dockerpush${IMAGE_TAG}dockerpush${IMAGE_REGISTRY}/${IMAGE_NAMESPACE}/${APP_NAME}:latestdockerrmi${IMAGE_TAG}||true}}}// ── Stage6: 部署到测试环境 ────────────────────────── stage(Deploy Staging){when{branchmain}steps{sshPublisher(publishers:[sshPublisherDesc(configName:staging-server, transfers:[sshTransfer(// 不传文件只执行远程脚本 execCommand:cd/data/www/hyperf-app-staginggitpull origin main /usr/local/php/bin/php /usr/local/bin/composerinstall--no-dev --optimize-autoloader /usr/local/php/bin/php bin/hyperf.php di:init-proxy# 平滑重启 Worker不断开连接PID\$(catruntime/hyperf.pid2/dev/null)if[-n\$PID];thenkill-USR1\$PIDelsesupervisorctl restart hyperf-stagingfi, execTimeout:120000)], failOnError:true)])}}// ── Stage7: 部署到生产环境需人工确认──────────── stage(Deploy Production){when{branchmaster}input{message确认部署到生产环境ok确认部署submitteradmin,ops-teamparameters{choice(name:DEPLOY_MODE, choices:[rolling,restart], description:部署方式)}}steps{sshPublisher(publishers:[sshPublisherDesc(configName:production-server, transfers:[sshTransfer(execCommand:set-ecd/data/www/hyperf-appecho 备份当前版本...cp-r./data/backup/hyperf-app-\$(date%Y%m%d%H%M%S)2/dev/null||trueecho 拉取最新代码...gitpull origin masterecho 安装依赖.../usr/local/php/bin/php /usr/local/bin/composerinstall--no-dev --optimize-autoloaderecho 重建注解缓存.../usr/local/php/bin/php bin/hyperf.php di:init-proxyecho 平滑重启...PID\$(catruntime/hyperf.pid2/dev/null)if[${DEPLOY_MODE}rolling][-n\$PID];thenkill-USR1\$PIDecho SIGUSR1 已发送Worker 平滑重启中...elsesupervisorctl restart hyperf-appfiecho 部署完成 ✅, execTimeout:180000)], failOnError:true)])}}}// ── 构建后通知 ─────────────────────────────────────────── post{success{script{dingTalk(robot:dingtalk-robot-id, type:MARKDOWN, title:✅ 构建成功 -${APP_NAME}, text:[## ✅ 构建成功,- **项目**:${APP_NAME},- **分支**:${env.GIT_BRANCH},- **Commit**:${env.GIT_COMMIT[0..7]},- **构建号**: #${BUILD_NUMBER},- **耗时**:${currentBuild.durationString}, [查看构建详情](${BUILD_URL})])}}failure{script{dingTalk(robot:dingtalk-robot-id, type:MARKDOWN, title:❌ 构建失败 -${APP_NAME}, text:[## ❌ 构建失败,- **项目**:${APP_NAME},- **分支**:${env.GIT_BRANCH},- **失败阶段**:${env.STAGE_NAME}, [查看失败日志](${BUILD_URL}console)], at:[all])}}always{// 清理 Docker 悬空镜像shdocker image prune -f || true}}}--- 第四步Gitee 配置 Webhook 在 Gitee 仓库 → 管理 → WebHooks → 添加 URL: http://your-jenkins.com:8080/gitee-project/hyperf-app 密码: your-webhook-token与 Jenkinsfile 中 token 一致 触发事件: ✅ Push ✅ Pull Request Jenkins 任务配置中勾选 - 构建触发器 → Gitee webhook 触发构建 - 填写 Webhook 密码 --- 第五步多分支流水线推荐 比单任务更强大自动发现所有分支和 PR。 New Item → Multibranch Pipeline // 分支源配置Jenkins UI 中填写 // Branch Sources → Gitee // Credentials: gitee-ssh-key // Owner: your-org // Repository: hyperf-app // 扫描触发Gitee Webhook 推送时自动扫描 // Orphaned Item Strategy: 保留3天 分支策略在 Jenkinsfile 中用 when 控制 ┌───────────┬─────────────────┬──────────────────────┐ │ 分支 │ 触发动作 │ 部署目标 │ ├───────────┼─────────────────┼──────────────────────┤ │ feature/* │ 只跑测试 │ 无 │ ├───────────┼─────────────────┼──────────────────────┤ │ main │ 测试 构建镜像 │ 测试环境自动 │ ├───────────┼─────────────────┼──────────────────────┤ │ master │ 测试 构建镜像 │ 生产环境人工确认 │ ├───────────┼─────────────────┼──────────────────────┤ │ release/* │ 测试 构建镜像 │ 预发布环境 │ └───────────┴─────────────────┴──────────────────────┘ --- 第六步Hyperf 项目适配 phpunit.xml测试配置?xmlversion1.0encodingUTF-8?phpunit xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:noNamespaceSchemaLocationvendor/phpunit/phpunit/phpunit.xsdbootstrapvendor/autoload.phpcolorstruetestsuitestestsuitenameUnitdirectorytest/Unit/directory/testsuitetestsuitenameFeaturedirectorytest/Feature/directory/testsuite/testsuitescoverageincludedirectoryapp/directory/include/coveragephpenvnameAPP_ENVvaluetesting/envnameDB_DRIVERvaluesqlite/envnameDB_DATABASEvalue:memory://php/phpunitphpstan.neon静态分析 parameters: level:5paths: - app excludePaths: - app/Exception/Handler ignoreErrors: -#Call to an undefined method Hyperf\\Di\\Container#.env.testingCI 测试环境变量APP_ENVtestingAPP_DEBUGfalseDB_DRIVERsqliteDB_DATABASE:memory:REDIS_HOST127.0.0.1CACHE_DRIVERarray --- 第七步Jenkins Agent 安装 PHP 环境 如果 Jenkins 运行在 Docker 容器内需要在容器中安装 PHP Swoole# 自定义 Jenkins 镜像FROM jenkins/jenkins:lts-jdk17USERroot# 安装 PHP 8.1 必要扩展RUNapt-getupdateapt-getinstall-y\php8.1-cli php8.1-mbstring php8.1-xml\php8.1-curl php8.1-zip php8.1-redis\php8.1-pdo php8.1-mysqlgitunzip\rm-rf/var/lib/apt/lists/*# 安装 ComposerRUNcurl-sShttps://getcomposer.org/installer|php\mvcomposer.phar /usr/local/bin/composer\composerconfig-grepo.packagistcomposer\https://mirrors.aliyun.com/composer/# 安装 SwoolePHPStan/测试不需要但如果要跑集成测试需要RUN peclinstallswoole\echoextensionswoole.so/etc/php/8.1/cli/php.ini\echoswoole.use_shortnameOff/etc/php/8.1/cli/php.iniUSERjenkins --- 关键注意事项 ┌──────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────┐ │ 问题 │ 解决方案 │ ├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┤ │ Gitee Webhook │ Jenkins 必须有公网 IP 或内网穿透frp/ngrok │ │ 国内延迟 │ │ ├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┤ │ Composer 下载慢 │ 全局配置阿里云镜像composer config-grepo.packagistcomposer│ │ │ https://mirrors.aliyun.com/composer/ │ ├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┤ │ 生产部署不中断 │ 用kill-USR1$PID触发 Hyperf Worker 平滑重启 │ ├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┤ │ 凭据安全 │ 所有密码/Token 存 Jenkins CredentialsJenkinsfile 中用 credentials()注入绝不硬编码 │ ├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┤ │ 并发构建冲突 │ disableConcurrentBuilds()防止同分支并发部署 │ ├──────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┤ │ 构建产物清理 │dockerimage prune-f buildDiscarder 防止磁盘爆满 │ └──────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────┘