热门角色不仅是灵感来源,更是你的效率助手。通过精挑细选的角色提示词,你可以快速生成高质量内容、提升创作灵感,并找到最契合你需求的解决方案。让创作更轻松,让价值更直接!
我们根据不同用户需求,持续更新角色库,让你总能找到合适的灵感入口。
基于指定CI/CD工具生成基础脚本,覆盖自动化测试与部署流程,并结合最佳实践提供优化建议,帮助开发团队提升效率、增强流程可维护性和项目交付质量。
下面给出一个可直接落地的 .github/workflows/ci.yml 示例,满足你的 monorepo 要求,并在后文总结如何用 GitHub Actions 自动化与最佳实践拆分为可复用工作流。
文件:.github/workflows/ci.yml 内容: name: CI (monorepo api + worker)
on: push: branches: - main - 'feature/' paths: - 'services/' - '.github/' - 'docker-compose.yml' pull_request: branches: - main - 'feature/' paths: - 'services/' - '.github/' - 'docker-compose.yml'
concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
permissions: contents: read packages: write security-events: write
env: REGISTRY: ghcr.io ORG: ${{ github.repository_owner }} SHORT_SHA: ${{ github.sha }} RETENTION_DAYS: 14
jobs: changes: name: Detect changed dirs runs-on: ubuntu-latest outputs: api: ${{ steps.filter.outputs.api }} worker: ${{ steps.filter.outputs.worker }} steps: - name: Checkout uses: actions/checkout@v4 - name: Paths filter id: filter uses: dorny/paths-filter@v3 with: filters: | api: - 'services/api/' worker: - 'services/worker/'
build-test: name: Build + Lint + Unit Test needs: changes if: ${{ needs.changes.outputs.api == 'true' || needs.changes.outputs.worker == 'true' }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - service: api lang: node runtime: '18' workdir: services/api image: ${{ env.REGISTRY }}/${{ env.ORG }}/api - service: worker lang: python runtime: '3.11' workdir: services/worker image: ${{ env.REGISTRY }}/${{ env.ORG }}/worker # 针对每个矩阵元素按目录变更过滤 if: ${{ (matrix.service == 'api' && needs.changes.outputs.api == 'true') || (matrix.service == 'worker' && needs.changes.outputs.worker == 'true') }} steps: - name: Checkout uses: actions/checkout@v4
- name: Setup Node
if: ${{ matrix.lang == 'node' }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.runtime }}
cache: 'npm'
cache-dependency-path: ${{ matrix.workdir }}/package-lock.json
- name: Setup Python
if: ${{ matrix.lang == 'python' }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.runtime }}
cache: 'pip'
cache-dependency-path: |
${{ matrix.workdir }}/requirements.lock
${{ matrix.workdir }}/pyproject.toml
- name: Install deps (api)
if: ${{ matrix.lang == 'node' }}
working-directory: ${{ matrix.workdir }}
run: npm ci
- name: Install deps (worker)
if: ${{ matrix.lang == 'python' }}
working-directory: ${{ matrix.workdir }}
run: |
python -m pip install --upgrade pip
if [ -f requirements.lock ]; then pip install -r requirements.lock; else pip install -r requirements.txt; fi
- name: Lint (ESLint)
if: ${{ matrix.lang == 'node' }}
working-directory: ${{ matrix.workdir }}
run: |
npx eslint . --max-warnings=0
- name: Lint (Flake8)
if: ${{ matrix.lang == 'python' }}
working-directory: ${{ matrix.workdir }}
run: |
pip install flake8
flake8 .
- name: Unit Test (Jest)
if: ${{ matrix.lang == 'node' }}
working-directory: ${{ matrix.workdir }}
env:
JEST_JUNIT_OUTPUT: test-results/junit.xml
run: |
mkdir -p test-results
npm test -- --ci --reporters=default --reporters=jest-junit --coverage
ls -la
- name: Unit Test (Pytest)
if: ${{ matrix.lang == 'python' }}
working-directory: ${{ matrix.workdir }}
run: |
pip install pytest pytest-cov
mkdir -p test-results
pytest -q --junitxml=test-results/junit.xml --cov=. --cov-report=xml
- name: Upload unit-test artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.service }}-unit-artifacts-${{ env.SHORT_SHA }}
path: |
${{ matrix.workdir }}/test-results/junit.xml
${{ matrix.workdir }}/coverage*
${{ matrix.workdir }}/coverage.xml
retention-days: ${{ env.RETENTION_DAYS }}
if-no-files-found: warn
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ matrix.image }}
tags: |
type=sha,format=short
type=ref,event=tag
labels: |
org.opencontainers.image.source=${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
- name: Build & Push image
uses: docker/build-push-action@v6
with:
context: ${{ matrix.workdir }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Trivy scan (image)
uses: aquasecurity/trivy-action@0.20.0
with:
image-ref: ${{ matrix.image }}:${{ steps.meta.outputs.version || 'sha-'+github.sha }}
format: 'sarif'
output: trivy-${{ matrix.service }}.sarif
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
- name: Upload Trivy SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-${{ matrix.service }}.sarif
- name: Job summary
shell: bash
working-directory: ${{ matrix.workdir }}
run: |
echo "Service: ${{ matrix.service }}" >> $GITHUB_STEP_SUMMARY
if [ -f coverage/coverage-summary.json ]; then
jq -r '"Jest line coverage: " + (.total.lines.pct|tostring) + "%"' coverage/coverage-summary.json >> $GITHUB_STEP_SUMMARY || true
elif [ -f coverage.xml ]; then
awk -F'"' '/line-rate/{printf "Pytest line coverage: %.2f%%\n", $2*100}' coverage.xml >> $GITHUB_STEP_SUMMARY || true
fi
integration: name: Integration tests via docker-compose needs: build-test if: ${{ needs.changes.outputs.api == 'true' || needs.changes.outputs.worker == 'true' }} runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull images by short SHA
run: |
SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
docker pull ${{ env.REGISTRY }}/${{ env.ORG }}/api:sha-$SHORT || docker pull ${{ env.REGISTRY }}/${{ env.ORG }}/api:$SHORT || true
docker pull ${{ env.REGISTRY }}/${{ env.ORG }}/worker:sha-$SHORT || docker pull ${{ env.REGISTRY }}/${{ env.ORG }}/worker:$SHORT || true
- name: Compose up
run: |
SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
cat > docker-compose.integration.yml <<'EOF'
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: example
ports: ["5432:5432"]
api:
image: ${{ env.REGISTRY }}/${{ env.ORG }}/api:sha-${SHORT}
environment:
DATABASE_URL: postgres://postgres:example@db:5432/postgres
depends_on: [db]
ports: ["8080:8080"]
worker:
image: ${{ env.REGISTRY }}/${{ env.ORG }}/worker:sha-${SHORT}
environment:
QUEUE_URL: amqp://guest:guest@rabbitmq:5672
DATABASE_URL: postgres://postgres:example@db:5432/postgres
depends_on: [db]
EOF
docker compose -f docker-compose.integration.yml up -d
sleep 15
docker compose ps
- name: Contract + E2E smoke
run: |
mkdir -p integration-artifacts/logs integration-artifacts/screenshots
# 合同测试示例:OpenAPI 合规
curl -sSf http://localhost:8080/healthz | tee integration-artifacts/logs/healthz.log
curl -sSf http://localhost:8080/openapi.json -o integration-artifacts/openapi.json
# 简单冒烟:关键端点
set -e
curl -sSf -X POST http://localhost:8080/v1/items -H 'Content-Type: application/json' -d '{"name":"demo"}' | tee integration-artifacts/logs/create-item.json
curl -sSf http://localhost:8080/v1/items | tee integration-artifacts/logs/list-items.json
# 截图占位(若有前端可用 playwright/puppeteer)
echo "placeholder" > integration-artifacts/screenshots/smoke.txt
- name: Compose logs and teardown
if: always()
run: |
docker compose -f docker-compose.integration.yml logs --no-color > integration-artifacts/logs/compose.log || true
docker compose -f docker-compose.integration.yml down -v || true
- name: Upload integration artifacts
uses: actions/upload-artifact@v4
with:
name: integration-${{ env.SHORT_SHA }}
path: integration-artifacts
retention-days: ${{ env.RETENTION_DAYS }}
if-no-files-found: warn
- name: Summary
run: |
echo "Integration tests completed. Artifacts: integration-${{ env.SHORT_SHA }}" >> $GITHUB_STEP_SUMMARY
deploy-staging: name: Deploy to Staging (Helm) needs: integration if: ${{ github.ref == 'refs/heads/main' }} runs-on: ubuntu-latest environment: name: staging url: https://staging.example.com concurrency: group: deploy-staging-${{ github.ref }} cancel-in-progress: true steps: - name: Checkout uses: actions/checkout@v4 # 可替换为 OIDC + 云提供商登录(见最佳实践说明) - name: Install Helm run: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - name: Helm upgrade --install env: KUBE_CONFIG: ${{ secrets.STAGING_KUBE_CONFIG }} run: | SHORT=$(echo "${{ github.sha }}" | cut -c1-7) export KUBECONFIG=$(mktemp) printf "%s" "$KUBE_CONFIG" > "$KUBECONFIG" helm upgrade --install api charts/api -n ${{ secrets.STAGING_NAMESPACE }} -f charts/api/values.staging.yaml --set image.tag=sha-$SHORT --wait --timeout 5m helm upgrade --install worker charts/worker -n ${{ secrets.STAGING_NAMESPACE }} -f charts/worker/values.staging.yaml --set image.tag=sha-$SHORT --wait --timeout 5m - name: Health probes run: | curl -sf https://staging.example.com/healthz || (echo "Health check failed" && exit 1) - name: Notify if: always() run: | if [ -n "${{ secrets.SLACK_WEBHOOK_URL }}" ]; then curl -X POST -H 'Content-type: application/json' --data "{"text":"Staging deploy finished: ${{ github.sha }}"}" ${{ secrets.SLACK_WEBHOOK_URL }} fi
deploy-production: name: Progressive deploy to Production (25% -> 50% -> 100%) needs: integration # 可根据发布策略触发:tag 或手工;此处示例为 main 上手工批准 if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }} runs-on: ubuntu-latest environment: name: production url: https://prod.example.com concurrency: group: deploy-production-lock cancel-in-progress: false timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v4 - name: Install Helm run: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - name: Prepare Kubeconfig env: KUBE_CONFIG: ${{ secrets.PROD_KUBE_CONFIG }} run: | export KUBECONFIG=$(mktemp) printf "%s" "$KUBE_CONFIG" > "$KUBECONFIG"
- name: Phase 1 (25%)
run: |
SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
helm upgrade --install api charts/api -n ${{ secrets.PROD_NAMESPACE }} -f charts/api/values.prod.yaml --set image.tag=sha-$SHORT --set rollout.percent=25 --wait --timeout 10m
helm upgrade --install worker charts/worker -n ${{ secrets.PROD_NAMESPACE }} -f charts/worker/values.prod.yaml --set image.tag=sha-$SHORT --set rollout.percent=25 --wait --timeout 10m
curl -sf https://prod.example.com/healthz
- name: Phase 2 (50%)
run: |
helm upgrade api charts/api -n ${{ secrets.PROD_NAMESPACE }} --set rollout.percent=50 --wait --timeout 10m
helm upgrade worker charts/worker -n ${{ secrets.PROD_NAMESPACE }} --set rollout.percent=50 --wait --timeout 10m
curl -sf https://prod.example.com/healthz
- name: Phase 3 (100%)
run: |
helm upgrade api charts/api -n ${{ secrets.PROD_NAMESPACE }} --set rollout.percent=100 --wait --timeout 10m
helm upgrade worker charts/worker -n ${{ secrets.PROD_NAMESPACE }} --set rollout.percent=100 --wait --timeout 10m
curl -sf https://prod.example.com/healthz
- name: Compliance checks
run: |
./scripts/compliance.sh || (echo "Compliance failed" && exit 1)
- name: Rollback on failure
if: failure()
run: |
helm rollback api -n ${{ secrets.PROD_NAMESPACE }} || true
helm rollback worker -n ${{ secrets.PROD_NAMESPACE }} || true
- name: Notify
if: always()
run: |
if [ -n "${{ secrets.SLACK_WEBHOOK_URL }}" ]; then
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"Prod deploy result (${ { success() && 'SUCCESS' || 'FAIL' } }): ${{ github.sha }}\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
fi
说明与最佳实践建议:
自动化过程
工作流拆分与复用(推荐)
安全与权限
性能与可靠性
如需继续优化,我建议:
下面提供一个可直接使用的 .gitlab-ci.yml 示例,覆盖请求的阶段与功能,并在后文说明如何用 GitLab CI 自动化与优化该流程的最佳实践。
workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - src//* - helm//* - if: $CI_PIPELINE_SOURCE == "push" changes: - src//* - helm//* - if: $CI_PIPELINE_SOURCE == "tag" - when: never
stages:
include:
default: image: golang:1.22 before_script: - go version cache: key: ${CI_COMMIT_REF_SLUG} paths: - .cache/gomod - .cache/gobuild policy: pull-push variables: REGISTRY: $CI_REGISTRY_IMAGE APP_NAME: go-service HELM_RELEASE: go-service K8S_CONTEXT: "" # 通过CI变量注入 GOMODCACHE: ${CI_PROJECT_DIR}/.cache/gomod GOCACHE: ${CI_PROJECT_DIR}/.cache/gobuild REVIEW_BASE_DOMAIN: dev.example.com # 评审环境域名 # COSIGN相关(在CI变量中以Protected+Masked方式配置) COSIGN_KEY_REF: "k8s://cosign-system/signing-key"
.prepare: rules: - changes: - src//* - helm//* - go.mod - go.sum - if: '$CI_PIPELINE_SOURCE == "tag"' - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
prepare: stage: prepare script: - if [ -n "$CI_COMMIT_TAG" ]; then echo "APP_VERSION=$CI_COMMIT_TAG" > variables.env; else echo "APP_VERSION=${CI_COMMIT_SHORT_SHA}" > variables.env; fi - go env -w GOMODCACHE=$GOMODCACHE - go env -w GOCACHE=$GOCACHE - go mod download artifacts: reports: dotenv: variables.env paths: - .cache/gomod retry: 2 rules: - !reference [.prepare, rules]
lint: image: golangci/golangci-lint:v1.60 stage: lint needs: ["prepare"] script: - golangci-lint run ./... --timeout 5m --out-format code-climate -o gl-code-quality-report.json artifacts: reports: codequality: gl-code-quality-report.json paths: - gl-code-quality-report.json retry: 2 rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - src//* - helm//* - if: $CI_COMMIT_BRANCH == "main" changes: - src//* - helm//* - if: $CI_PIPELINE_SOURCE == "tag"
test: stage: test needs: ["prepare"] script: - go install gotest.tools/gotestsum@latest - go install github.com/boumenot/gocover-cobertura@latest - gotestsum --format standard-verbose --junitfile junit.xml -- -covermode=atomic -coverprofile=coverage.out ./... - gocover-cobertura < coverage.out > cobertura.xml artifacts: reports: junit: junit.xml coverage_report: coverage_format: cobertura path: cobertura.xml paths: - coverage.out retry: 2 rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - src//* - helm//* - if: $CI_COMMIT_BRANCH == "main" changes: - src//* - helm//* - if: $CI_PIPELINE_SOURCE == "tag"
build_binary: stage: build needs: ["test"] script: - mkdir -p dist - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "-s -w -X main.version=$APP_VERSION" -o dist/$APP_NAME ./cmd/$APP_NAME artifacts: paths: - dist/$APP_NAME expire_in: 1 week retry: 2 rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - src//* - helm//* - if: $CI_COMMIT_BRANCH == "main" changes: - src//* - helm//* - if: $CI_PIPELINE_SOURCE == "tag"
build_image: stage: build image: gcr.io/kaniko-project/executor:latest needs: ["test"] variables: DOCKER_CONFIG: /kaniko/.docker script: - mkdir -p $DOCKER_CONFIG - printf '{ "auths": { "%s": { "username": "%s", "password": "%s" } } }' "$CI_REGISTRY" "$CI_REGISTRY_USER" "$CI_REGISTRY_PASSWORD" > $DOCKER_CONFIG/config.json - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile Dockerfile --destination $REGISTRY:$APP_VERSION --build-arg APP_VERSION=$APP_VERSION --cache=true --cache-repo $REGISTRY/cache --snapshotMode=redo retry: 2 rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - src//* - helm//* - if: $CI_COMMIT_BRANCH == "main" changes: - src//* - helm//* - if: $CI_PIPELINE_SOURCE == "tag"
sast_custom: stage: scan extends: sast rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^(Draft|WIP)/i - if: $CI_COMMIT_BRANCH == "main" - if: $CI_PIPELINE_SOURCE == "tag"
container_scan: stage: scan extends: container_scanning needs: ["build_image"] variables: CS_IMAGE: "$REGISTRY:$APP_VERSION" rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^(Draft|WIP)/i - if: $CI_COMMIT_BRANCH == "main" - if: $CI_PIPELINE_SOURCE == "tag"
sbom_and_sign: stage: scan image: alpine:3.19 needs: ["build_image"] script: - apk add --no-cache curl bash - curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin - curl -sSfL https://raw.githubusercontent.com/sigstore/cosign/main/install.sh | sh -s -- -b /usr/local/bin - syft $REGISTRY:$APP_VERSION -o spdx-json > sbom-spdx.json - if [ "$CI_PIPELINE_SOURCE" = "tag" ] || [ "$CI_COMMIT_BRANCH" = "main" ]; then cosign login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD; cosign sign --key $COSIGN_KEY_REF $REGISTRY:$APP_VERSION; else echo "Skip cosign signing for non-main/non-tag pipelines"; fi artifacts: paths: - sbom-spdx.json rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^(Draft|WIP)/i - if: $CI_COMMIT_BRANCH == "main" - if: $CI_PIPELINE_SOURCE == "tag"
deploy_dev: stage: deploy_dev image: dtzar/helm-kubectl:3.13.2 needs: ["build_image"] resource_group: "${HELM_RELEASE}-dev" environment: name: review/$CI_COMMIT_REF_SLUG url: https://$CI_COMMIT_REF_SLUG.$REVIEW_BASE_DOMAIN auto_stop_in: 1 day on_stop: stop_review script: - mkdir -p ~/.kube - echo "$KUBE_CONFIG" | base64 -d > ~/.kube/config - kubectl config use-context "$K8S_CONTEXT" - helm plugin install https://github.com/databus23/helm-diff || true - helm repo update - helm diff upgrade "$HELM_RELEASE-$CI_COMMIT_REF_SLUG" ./helm -n dev -f helm/values/base.yaml -f helm/values/dev.yaml --set image.repository=$REGISTRY --set image.tag=$APP_VERSION || true - helm upgrade --install "$HELM_RELEASE-$CI_COMMIT_REF_SLUG" ./helm -n dev -f helm/values/base.yaml -f helm/values/dev.yaml --set image.repository=$REGISTRY --set image.tag=$APP_VERSION --set ingress.host="$CI_COMMIT_REF_SLUG.$REVIEW_BASE_DOMAIN" - kubectl rollout status deploy/$HELM_RELEASE-$CI_COMMIT_REF_SLUG -n dev --timeout=180s retry: 2 rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" changes: - src//* - helm//* when: on_success
stop_review: stage: deploy_dev image: dtzar/helm-kubectl:3.13.2 resource_group: "${HELM_RELEASE}-dev" environment: name: review/$CI_COMMIT_REF_SLUG action: stop script: - echo "$KUBE_CONFIG" | base64 -d > ~/.kube/config - kubectl config use-context "$K8S_CONTEXT" - helm uninstall "$HELM_RELEASE-$CI_COMMIT_REF_SLUG" -n dev || true rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" when: manual allow_failure: true
deploy_stage: stage: deploy_stage image: dtzar/helm-kubectl:3.13.2 needs: ["build_image", "sast_custom", "container_scan", "sbom_and_sign"] resource_group: "${HELM_RELEASE}-stage" environment: name: staging url: https://stage.example.com script: - echo "$KUBE_CONFIG" | base64 -d > ~/.kube/config - kubectl config use-context "$K8S_CONTEXT" - helm upgrade --install "$HELM_RELEASE" ./helm -n stage -f helm/values/base.yaml -f helm/values/stage.yaml --set image.repository=$REGISTRY --set image.tag=$APP_VERSION --set canary.enabled=true --set canary.weight=20 - sleep 10 - curl -fsS "https://stage.example.com/healthz" || (echo "Smoke test failed, rolling back"; helm rollback "$HELM_RELEASE" 1 -n stage; exit 1) when: manual rules: - if: $CI_COMMIT_BRANCH == "main" when: manual
如何用GitLab CI自动化该过程与最佳实践说明
说明
以下为一个可直接落地的 Jenkins Declarative Pipeline(Jenkinsfile)示例,覆盖你提出的各项流水线需求与最佳实践点。请按需替换其中的占位符(如仓库地址、凭据ID、镜像仓库等)。
Jenkinsfile(Declarative)
pipeline { agent { docker { image 'maven:3.9-jdk-17' // 缓存 Maven 本地仓库 + Docker Socket 以便构建/推送镜像 args '-v $HOME/.m2:/root/.m2 -v /var/run/docker.sock:/var/run/docker.sock' reuseNode true } }
options { skipDefaultCheckout(true) timestamps() disableConcurrentBuilds() ansiColor('xterm') buildDiscarder(logRotator(numToKeepStr: '20')) }
parameters { string(name: 'BRANCH', defaultValue: 'main', description: '要构建的Git分支或Tag') choice(name: 'TARGET_ENV', choices: ['staging', 'prod'], description: '目标环境(将进行分段灰度/发布)') }
// 可用 GitHub Webhook 或 SCM 轮询(按需选用,二者可并存) triggers { githubPush() pollSCM('H/10 * * * *') }
environment { // 仓库、镜像与通用变量(按需替换) GIT_URL = 'git@github.com:your-org/your-repo.git' GIT_CRED_ID = 'git-ssh-cred-id' DOCKER_REGISTRY = 'registry.example.com' IMAGE_REPO = 'registry.example.com/your-team/your-app' // Maven 本地仓库缓存目录(已通过 -v $HOME/.m2:/root/.m2 映射) MAVEN_OPTS = '-Dmaven.repo.local=/root/.m2/repository' MVN_COMMON = '-B -U -Dmaven.repo.local=/root/.m2/repository' }
stages { stage('Checkout') { options { timeout(time: 5, unit: 'MINUTES') } steps { deleteDir() // 拉取指定分支 git branch: params.BRANCH, credentialsId: env.GIT_CRED_ID, url: env.GIT_URL
script {
env.SHORT_SHA = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
// 从 pom 获取语义版本
env.VERSION = sh(script: "mvn -q -DforceStdout help:evaluate -Dexpression=project.version", returnStdout: true).trim()
env.IMAGE_TAG_SEMVER = "v${env.VERSION}"
env.IMAGE_TAG_SHA = "v${env.VERSION}-${env.SHORT_SHA}"
currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.IMAGE_TAG_SHA}"
currentBuild.description = "branch=${params.BRANCH}, sha=${env.SHORT_SHA}, env=${params.TARGET_ENV}"
}
}
}
stage('Dependencies') {
options { timeout(time: 10, unit: 'MINUTES') }
steps {
retry(2) {
sh "mvn ${env.MVN_COMMON} dependency:resolve"
}
}
}
stage('Lint & Static') {
options { timeout(time: 10, unit: 'MINUTES') }
steps {
// 只生成报告,不强制失败,把阈值交给 Jenkins Warnings NG 与质量门控
sh "mvn ${env.MVN_COMMON} checkstyle:checkstyle spotbugs:spotbugs"
// 归档报告
archiveArtifacts artifacts: '**/target/site/**, **/target/spotbugsXml.xml, **/target/checkstyle-result.xml', allowEmptyArchive: true
// 静态分析质量门控(需要 Warnings Next Generation 插件)
recordIssues(
enabledForFailure: true,
qualityGates: [
[threshold: 1, type: 'TOTAL', unstable: true] // 任意问题将标记为不稳定,可按需收紧
],
tools: [
checkStyle(pattern: '**/target/checkstyle-result.xml'),
spotBugs(pattern: '**/target/spotbugsXml.xml')
]
)
}
}
stage('UnitTest') {
options { timeout(time: 15, unit: 'MINUTES') }
steps {
sh "mvn ${env.MVN_COMMON} test jacoco:report"
junit allowEmptyResults: false, testResults: '**/target/surefire-reports/*.xml'
// 覆盖率质量门控(需要 Jacoco 插件)
jacoco(
changeBuildStatus: true,
minimumLineCoverage: '0.80', // 80%行覆盖率作为门槛,按需调整
execPattern: '**/target/jacoco.exec'
)
}
}
stage('Build Artifact') {
options { timeout(time: 10, unit: 'MINUTES') }
steps {
sh "mvn ${env.MVN_COMMON} -DskipTests package"
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
stash name: 'app-jar', includes: 'target/*.jar'
}
}
stage('Docker Build & Push + Trivy') {
options { timeout(time: 20, unit: 'MINUTES') }
steps {
unstash 'app-jar'
withCredentials([usernamePassword(credentialsId: 'docker-registry-cred', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh '''
echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin ${DOCKER_REGISTRY}
docker build -t ${IMAGE_REPO}:${IMAGE_TAG_SHA} -t ${IMAGE_REPO}:${IMAGE_TAG_SEMVER} .
docker push ${IMAGE_REPO}:${IMAGE_TAG_SHA}
docker push ${IMAGE_REPO}:${IMAGE_TAG_SEMVER}
'''
}
// Trivy 扫描高危/严重漏洞,命中则退出失败
sh '''
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:0.52.3 image \
--timeout 10m --severity HIGH,CRITICAL --exit-code 1 \
${IMAGE_REPO}:${IMAGE_TAG_SHA}
'''
}
}
stage('Parallel Tests') {
failFast true
parallel {
stage('Contract Tests') {
options { timeout(time: 15, unit: 'MINUTES') }
steps {
retry(2) {
sh "mvn ${env.MVN_COMMON} -Pcontract-test verify -DskipITs=false"
}
junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/*.xml'
}
}
stage('API Smoke') {
options { timeout(time: 10, unit: 'MINUTES') }
steps {
retry(2) {
// 示例:可替换为 Postman/Newman 或 curl 脚本
sh "mvn ${env.MVN_COMMON} -Papi-smoke test -Dtest=*Smoke*"
}
junit allowEmptyResults: true, testResults: '**/target/surefire-reports/*Smoke*.xml'
}
}
}
}
stage('Deploy Staging (Canary 10% -> 50%)') {
// staging 与 prod 都先走staging灰度
options { timeout(time: 30, unit: 'MINUTES') }
steps {
script {
// Kubeconfig 文件类型凭据
withCredentials([file(credentialsId: 'kubeconfig-staging', variable: 'KUBECONFIG')]) {
try {
// 10% 灰度
sh """
docker run --rm -v "\${KUBECONFIG}":/kube/config -e KUBECONFIG=/kube/config \
-v "\$PWD":/work -w /work alpine/helm:3.15.2 upgrade --install your-app charts/your-app \
--namespace staging --create-namespace --wait --timeout 5m \
--set image.repository=${IMAGE_REPO} \
--set image.tag=${IMAGE_TAG_SHA} \
--set canary.enabled=true \
--set canary.weight=10
"""
// 就绪与健康探针门控
sh '''
docker run --rm -v "${KUBECONFIG}":/kube/config -e KUBECONFIG=/kube/config bitnami/kubectl:1.29 \
rollout status deploy/your-app-canary -n staging --timeout=180s
'''
// 升级到 50%
sh """
docker run --rm -v "\${KUBECONFIG}":/kube/config -e KUBECONFIG=/kube/config \
-v "\$PWD":/work -w /work alpine/helm:3.15.2 upgrade your-app charts/your-app \
--namespace staging --wait --timeout 5m \
--set image.repository=${IMAGE_REPO} \
--set image.tag=${IMAGE_TAG_SHA} \
--set canary.enabled=true \
--set canary.weight=50
"""
sh '''
docker run --rm -v "${KUBECONFIG}":/kube/config -e KUBECONFIG=/kube/config bitnami/kubectl:1.29 \
rollout status deploy/your-app-canary -n staging --timeout=180s
'''
// 记录变更(示例:为部署打注解)
sh '''
docker run --rm -v "${KUBECONFIG}":/kube/config -e KUBECONFIG=/kube/config bitnami/kubectl:1.29 \
annotate deploy/your-app-canary -n staging \
release.jenkins/build="${BUILD_TAG}" \
release.image="${IMAGE_REPO}:${IMAGE_TAG_SHA}" --overwrite
'''
} catch (err) {
// 自动回滚
sh '''
docker run --rm -v "${KUBECONFIG}":/kube/config -e KUBECONFIG=/kube/config \
-v "$PWD":/work -w /work alpine/helm:3.15.2 rollback your-app 0 --namespace staging
'''
error "Staging 灰度失败,已回滚: ${err}"
}
}
}
}
}
stage('Promote Prod (Manual, 100%)') {
when { expression { params.TARGET_ENV == 'prod' } }
options { timeout(time: 45, unit: 'MINUTES') }
steps {
// 审批门禁(可限定审批人角色)
input message: "确认将版本 ${env.IMAGE_TAG_SHA} 发布至生产并切流量到100%?", ok: 'Promote', submitter: 'ops,devlead'
script {
withCredentials([file(credentialsId: 'kubeconfig-prod', variable: 'KUBECONFIG')]) {
// 切换到100% 或关闭 canary(具体取决于你的 Helm Chart 设计)
sh """
docker run --rm -v "\${KUBECONFIG}":/kube/config -e KUBECONFIG=/kube/config \
-v "\$PWD":/work -w /work alpine/helm:3.15.2 upgrade --install your-app charts/your-app \
--namespace prod --create-namespace --wait --timeout 10m \
--set image.repository=${IMAGE_REPO} \
--set image.tag=${IMAGE_TAG_SHA} \
--set canary.enabled=true \
--set canary.weight=100
"""
sh '''
docker run --rm -v "${KUBECONFIG}":/kube/config -e KUBECONFIG=/kube/config bitnami/kubectl:1.29 \
rollout status deploy/your-app-canary -n prod --timeout=300s
'''
}
}
}
}
}
post {
always {
// 统一清理
cleanWs()
}
success {
script {
// 可选:打 Git Tag(需要对应 push 权限)
// withCredentials([sshUserPrivateKey(credentialsId: env.GIT_CRED_ID, keyFileVariable: 'SSH_KEY')]) { ... }
// 记录版本与发布说明(示例:输出到描述与归档文件)
sh '''
printf "Release: %s\nImage: %s:%s\nCommit: %s\n"
"${IMAGE_TAG_SEMVER}" "${IMAGE_REPO}" "${IMAGE_TAG_SHA}" "${SHORT_SHA}" > release-notes.txt || true
'''
archiveArtifacts artifacts: 'release-notes.txt', allowEmptyArchive: true
}
// 成功通知(按需替换为企业IM/邮箱)
// slackSend channel: '#deploy', color: 'good', message: "SUCCESS ${JOB_NAME} #${BUILD_NUMBER} ${IMAGE_TAG_SHA}"
}
failure {
// 失败通知(按需替换)
// slackSend channel: '#deploy', color: 'danger', message: "FAILED ${JOB_NAME} #${BUILD_NUMBER}"
echo "Pipeline failed. Please check above logs."
}
}
}
说明:如何用 Jenkins 自动化该过程与最佳实践
Declarative Pipeline 与共享库
动态容器代理保证可重复环境
凭据与环境变量作用域化
稳定性:超时与重试
构件流转与可追溯性
CI 与 CD 作业分流
质量阈值作为合入门槛
渐进式灰度与健康门控
post 统一清理与通知
可行的优化建议
提速与稳定
扩展质量与安全
更强的环境一致性
可观测性与变更管理
流水线结构化
此 Jenkinsfile 可作为基础模板,结合 Jenkins 插件与共享库进一步工程化,即可实现从代码、质量、制品、镜像安全到灰度/发布的端到端自动化。
帮助用户快速构建和优化CI/CD(持续集成与持续交付)流程,提供高效可操作的指导,降低技术门槛,提升开发效率和交付质量。
通过提示词快速生成基础CI/CD脚本,解决新手对流程不熟悉的问题,提升代码交付效率。
完善和优化当前的自动化流水线,提升系统稳定性和部署速度。
为团队设计标准化的CI/CD解决方案,实现快速落地并推广至整个团队。
将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。
把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。
在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。
半价获取高级提示词-优惠即将到期