前言
上一篇文章你学会了用别人的 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.sh
action.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.json
action.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.js
TypeScript 实现
// 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
-
在 Action 仓库 → Releases → Create a new release
-
选择 "Publish this Action to the GitHub Marketplace"
-
填写 Category(如 Continuous Integration)
-
发布
发布后,用户可以在 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 建立模板仓库。
六、本篇要点回顾
-
三种 Action 类型:Composite(YAML 组合,最简单)、JavaScript(自定义逻辑)、Docker(特定环境)
-
Composite Action 用
using: composite定义,通过inputs接收参数 -
JavaScript Action 必须用
@vercel/ncc打包成单文件 -
版本管理用语义化 Tag:
@v1自动跟随、@v1.2.3固定版本 -
Reusable Workflow 封装整个 Job,适合组织级 CI/CD 标准化
下一篇预告:进入 CI/CD 进阶篇,下一篇:《安全实践:密钥管理、镜像签名与供应链安全》。