前言
上一篇文章你学会了用别人的 Action,本篇教你写自己的 Action——把重复的步骤封装成可复用的组件,发布到 Marketplace 让全世界的项目都能用。这是 GitHub Actions 生态的核心能力。
一、Action 的三种类型
| 类型 | 实现方式 | 适用场景 | 复杂度 |
|------|---------|---------|--------|
| Composite Action | YAML 组合 | 组合多个步骤为一个 Action | 低 |
| JavaScript Action | TypeScript/JS | 需要自定义逻辑和 UI 交互 | 中 |
| Docker Action | Dockerfile | 需要特定运行环境 | 中 |
**培训要点**:90% 的场景用 Composite Action 就够了——不需要写代码,纯 YAML 组合。先学这种。
二、Composite Action 开发
创建 Action 仓库
my-deploy-action/ ├── action.yml # Action 定义文件 ├── README.md └── scripts/ └── deploy.shaction.yml 核心配置
# action.yml name: 'Deploy to Kubernetes' description: 'Deploy a Docker image to Kubernetes with health check and auto-rollback' author: 'Your Name' # 定义输入参数 inputs: image: description: 'Docker image to deploy' required: true namespace: description: 'Kubernetes namespace' required: false default: 'default' deployment-name: description: 'Kubernetes Deployment name' required: true timeout: description: 'Rollout timeout in seconds' required: false default: '180' # 定义输出 outputs: status: description: 'Deployment status (success/failed)' value: ${{ steps.deploy.outputs.status }} runs: using: composite steps: # 第一步:部署 - name: Deploy id: deploy shell: bash env: IMAGE: ${{ inputs.image }} NAMESPACE: ${{ inputs.namespace }} DEPLOYMENT: ${{ inputs.deployment-name }} TIMEOUT: ${{ inputs.timeout }} KUBECONFIG: ${{ env.KUBECONFIG }} run: | # 更新镜像 kubectl set image deployment/${DEPLOYMENT} \ app=${IMAGE} -n ${NAMESPACE} # 等待滚动更新完成 if kubectl rollout status deployment/${DEPLOYMENT} \ -n ${NAMESPACE} --timeout=${TIMEOUT}s; then echo "status=success" >> $GITHUB_OUTPUT else echo "status=failed" >> $GITHUB_OUTPUT # 自动回滚 kubectl rollout undo deployment/${DEPLOYMENT} -n ${NAMESPACE} exit 1 fi # 第二步:健康检查 - name: Health check if: ${{ steps.deploy.outputs.status == 'success' }} shell: bash env: NAMESPACE: ${{ inputs.namespace }} DEPLOYMENT: ${{ inputs.deployment-name }} run: | for i in $(seq 1 12); do READY=$(kubectl get deployment ${DEPLOYMENT} -n ${NAMESPACE} \ -o jsonpath='{.status.readyReplicas}') DESIRED=$(kubectl get deployment ${DEPLOYMENT} -n ${NAMESPACE} \ -o jsonpath='{.status.replicas}') if [ "$READY" = "$DESIRED" ]; then echo "All pods ready ($READY/$DESIRED)" exit 0 fi sleep 10 done echo "Health check failed" exit 1使用自己的 Action
# 在其他项目的 Workflow 中使用 jobs: deploy: runs-on: [self-hosted, k8s] steps: - uses: actions/checkout@v4 # 使用本地 Action(同一仓库内) - uses: ./.github/actions/deploy with: image: ghcr.io/myorg/myapp:latest namespace: production deployment-name: myapp # 使用其他仓库的 Action - uses: myorg/my-deploy-action@v1 with: image: ghcr.io/myorg/myapp:${{ github.sha }} namespace: production deployment-name: myapp timeout: '300'三、JavaScript Action 开发
项目结构
js-action/ ├── action.yml ├── package.json ├── tsconfig.json ├── src/ │ └── main.ts ├── dist/ │ └── index.js # 编译后的 JS └── .eslintrc.jsonaction.yml
name: 'PR Comment Bot' description: 'Add a comment to a PR with build status and test results' inputs: pr-number: description: 'PR number' required: true status: description: 'Build status (success/failure)' required: true report-path: description: 'Path to test report' required: false runs: using: node20 main: dist/index.jsTypeScript 实现
// src/main.ts import * as core from '@actions/core'; import * as github from '@actions/github'; import * as fs from 'fs'; async function run(): Promise<void> { try { const prNumber = core.getInput('pr-number'); const status = core.getInput('status'); const reportPath = core.getInput('report-path'); const token = core.getInput('github-token') || process.env.GITHUB_TOKEN; const octokit = github.getOctokit(token); // 读取测试报告 let reportSummary = 'No report available'; if (reportPath && fs.existsSync(reportPath)) { const report = fs.readFileSync(reportPath, 'utf-8'); const parsed = JSON.parse(report); reportSummary = `Tests: ${parsed.total}, Passed: ${parsed.passed}, Failed: ${parsed.failed}`; } // 构建评论内容 const statusEmoji = status === 'success' ? '✅' : '❌'; const body = [ `## ${statusEmoji} Build ${status}`, ``, `**Report:** ${reportSummary}`, ``, `**Commit:** ${github.context.sha.substring(0, 7)}`, ].join('\n'); // 发表评论到 PR await octokit.rest.issues.createComment({ ...github.context.repo, issue_number: parseInt(prNumber), body: body, }); core.setOutput('comment-url', `PR #${prNumber}`); } catch (error) { core.setFailed((error as Error).message); } } run();package.json
{ "name": "pr-comment-bot", "version": "1.0.0", "main": "dist/index.js", "scripts": { "build": "ncc build src/main.ts -o dist --source-map", "lint": "eslint src/**/*.ts" }, "dependencies": { "@actions/core": "^1.10.0", "@actions/github": "^6.0.0" }, "devDependencies": { "@vercel/ncc": "^0.38.0", "typescript": "^5.3.0" } }编译
npm install npm run build # 用 ncc 打包成单文件**踩坑提示**:JavaScript Action 必须用 `@vercel/ncc` 打包成单文件,不能直接引用 node_modules。因为 GitHub Actions 运行时只下载你的仓库,不会自动 npm install。
四、Action 版本管理
Tag 策略
# 语义化版本 Tag v1 → 指向最新的 1.x.x 版本(自动跟随 minor/patch 更新) v1.0 → 指向最新的 1.0.x 版本 v1.0.0 → 固定版本 # 使用时: - uses: myorg/my-action@v1 # 自动获取最新 1.x.x - uses: myorg/my-action@v1.2 # 自动获取最新 1.2.x - uses: myorg/my-action@v1.2.3 # 固定版本 - uses: myorg/my-action@main # 跟随主分支(不推荐生产用)发布到 GitHub Marketplace
1. 在 Action 仓库 → Releases → Create a new release
2. 选择 "Publish this Action to the GitHub Marketplace"
3. 填写 Category(如 Continuous Integration)
4. 发布
发布后,用户可以在 GitHub Marketplace 搜索到你的 Action:
# 用户使用 - uses: your-name/my-deploy-action@v1 with: image: myapp:latest版本发布的最佳实践
# .github/workflows/release.yml name: Release on: push: tags: ['v*'] jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # 自动创建/更新 major version tag (v1) - name: Update major tag uses: actions/publish-action@v0.3 with: source-tag: ${{ github.ref_name }} # 如 v1.2.3五、Reusable Workflow(可复用工作流)
什么是 Reusable Workflow
Composite Action 封装的是步骤,Reusable Workflow 封装的是整个 Job——可以跨仓库复用完整的 CI/CD 流程。
定义 Reusable Workflow
# .github/workflows/reusable-build.yml name: Reusable Build on: workflow_call: inputs: java-version: type: string required: false default: '17' run-tests: type: boolean required: false default: true secrets: sonar-token: required: false jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: java-version: ${{ inputs.java-version }} distribution: temurin cache: maven - run: mvn clean package -DskipTests - run: mvn test if: ${{ inputs.run-tests }} - uses: actions/upload-artifact@v4 with: name: app-jar path: target/*.jar调用 Reusable Workflow
# .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: # 调用可复用工作流 build: uses: myorg/ci-templates/.github/workflows/reusable-build.yml@v1 with: java-version: '21' run-tests: true secrets: sonar-token: ${{ secrets.SONAR_TOKEN }} # 调用后继续自己的步骤 deploy: needs: build runs-on: [self-hosted] steps: - uses: actions/download-artifact@v4 - run: ./deploy.sh组织级模板仓库
创建一个专门的 CI 模板仓库:
ci-templates/ ├── .github/workflows/ │ ├── reusable-build-java.yml # Java 构建模板 │ ├── reusable-build-go.yml # Go 构建模板 │ ├── reusable-build-node.yml # Node 构建模板 │ ├── reusable-deploy-k8s.yml # K8s 部署模板 │ └── reusable-security-scan.yml # 安全扫描模板 └── README.md所有项目只需几行调用:
# 项目的 .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: build: uses: myorg/ci-templates/.github/workflows/reusable-build-java.yml@v1 with: java-version: '17' deploy: needs: build if: github.ref == 'refs/heads/main' uses: myorg/ci-templates/.github/workflows/reusable-deploy-k8s.yml@v1 with: namespace: production secrets: kubeconfig: ${{ secrets.KUBECONFIG_PROD }}**培训要点**:Reusable Workflow 比 Composite Action 更强大——它可以定义完整的 Job(含 runs-on、services、environment),而 Composite Action 只是 Step 级别的复用。对于组织级的 CI/CD 标准化,用 Reusable Workflow 建立模板仓库。
六、本篇要点回顾
1. 三种 Action 类型:Composite(YAML 组合,最简单)、JavaScript(自定义逻辑)、Docker(特定环境)
2. Composite Action 用using: composite定义,通过inputs接收参数
3. JavaScript Action 必须用@vercel/ncc打包成单文件
4. 版本管理用语义化 Tag:@v1自动跟随、@v1.2.3固定版本
5. Reusable Workflow 封装整个 Job,适合组织级 CI/CD 标准化
下一篇预告:进入 CI/CD 进阶篇,下一篇:《安全实践:密钥管理、镜像签名与供应链安全》。