- 新增 Go、Node.js、Rust 服务的 Dockerfile 模板 - 新增 Rust 快速参考指南 - 新增 Rust 后端工作流模板 - 优化 create-runner.md,明确 host 网络模式为缓存必需条件 - 更新 gitea skill 主文档
1124 lines
35 KiB
Markdown
1124 lines
35 KiB
Markdown
# Rust 后端服务 Workflow 模板
|
||
|
||
适用于 Rust 后端 API 服务、微服务、CLI 工具的 CI/CD workflow。
|
||
|
||
## 适用场景
|
||
|
||
- Rust HTTP API 服务(Axum、Actix-web、Rocket 等)
|
||
- 异步后端服务(Tokio、async-std)
|
||
- MQTT/IoT 设备服务
|
||
- CLI 工具
|
||
- 需要构建 Docker 镜像的 Rust 项目
|
||
|
||
## 环境要求
|
||
|
||
| 依赖 | Runner 要求 |
|
||
|------|------------|
|
||
| Rust 1.75+ | 通过脚本自动安装 |
|
||
| Docker | Runner 主机已安装 |
|
||
| Zig(可选) | 用于交叉编译,脚本自动安装 |
|
||
|
||
## 特性说明
|
||
|
||
### 交叉编译支持
|
||
|
||
- **ARM64 主机 → x86_64 目标**:使用 Zig + cargo-zigbuild
|
||
- **musl 静态链接**:生成独立可执行文件,适配 Alpine 容器
|
||
- **缓存优化**:支持 Rust 依赖缓存,加速构建
|
||
|
||
### 镜像加速
|
||
|
||
使用 rsproxy.cn 镜像加速:
|
||
- rustup 安装源
|
||
- crates.io 索引
|
||
- cargo 依赖下载
|
||
|
||
### 构建优化
|
||
|
||
```yaml
|
||
env:
|
||
CARGO_INCREMENTAL: 0 # 禁用增量编译(CI 环境)
|
||
CARGO_NET_RETRY: 10 # 网络重试 10 次
|
||
CARGO_TERM_COLOR: always # 保持彩色输出
|
||
RUST_BACKTRACE: short # 简短的错误栈
|
||
```
|
||
|
||
## Workflow 骨架模板
|
||
|
||
### 完整版本(推荐用于生产项目)
|
||
|
||
参考实际项目 [api-hub](https://github.com/example/api-hub) 的最佳实践,包含完整的测试、构建、健康检查和发布流程。
|
||
|
||
```yaml
|
||
name: Rust Backend CI/CD
|
||
|
||
on:
|
||
push:
|
||
branches: [main, master]
|
||
pull_request:
|
||
branches: [main, master]
|
||
|
||
# 并发控制:同一分支/标签的重复构建会取消旧的运行
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
env:
|
||
# Rust CI 构建优化
|
||
CARGO_INCREMENTAL: 0
|
||
CARGO_NET_RETRY: 10
|
||
CARGO_TERM_COLOR: always
|
||
RUST_BACKTRACE: short
|
||
# 使用中国 Rust 镜像加速
|
||
RUSTUP_DIST_SERVER: https://rsproxy.cn
|
||
RUSTUP_UPDATE_ROOT: https://rsproxy.cn/rustup
|
||
|
||
jobs:
|
||
test:
|
||
name: 测试
|
||
runs-on: ubuntu-latest # 修改为你的 Runner 标签
|
||
env:
|
||
RUNNER_TOOL_CACHE: /toolcache
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: 配置 Cargo 镜像
|
||
run: |
|
||
mkdir -p ~/.cargo
|
||
cat >> ~/.cargo/config.toml << 'EOF'
|
||
[source.crates-io]
|
||
replace-with = 'rsproxy-sparse'
|
||
[source.rsproxy-sparse]
|
||
registry = "sparse+https://rsproxy.cn/index/"
|
||
[registries.rsproxy]
|
||
index = "https://rsproxy.cn/crates.io-index"
|
||
[net]
|
||
git-fetch-with-cli = true
|
||
EOF
|
||
|
||
- name: 安装 Rust
|
||
run: |
|
||
curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y --default-toolchain stable --component rustfmt,clippy
|
||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||
|
||
# rust-cache 需要 act_runner 正确配置缓存服务器
|
||
# 如果出现 "getCacheEntry failed: connect ETIMEDOUT" 错误,
|
||
# 需要在服务器端修改 act_runner 的 config.yaml 中的 cache 配置
|
||
- name: 缓存 Rust 依赖
|
||
uses: https://github.com/Swatinem/rust-cache@v2
|
||
continue-on-error: true
|
||
with:
|
||
cache-targets: true
|
||
cache-on-failure: true
|
||
cache-all-crates: true
|
||
|
||
- name: 检查代码格式
|
||
run: cargo fmt -- --check
|
||
|
||
- name: 运行 Clippy 检查
|
||
run: cargo clippy -- -D warnings
|
||
|
||
- name: 运行测试
|
||
run: cargo test --verbose
|
||
|
||
- name: 构建
|
||
run: cargo build --release --verbose
|
||
|
||
build-docker:
|
||
name: 构建 Docker 镜像
|
||
needs: test
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
registry: ${{ steps.vars.outputs.registry }}
|
||
image_repo: ${{ steps.vars.outputs.image_repo }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: 设置变量
|
||
id: vars
|
||
run: |
|
||
registry=$(echo "${{ github.server_url }}" | cut -d '/' -f 3)
|
||
echo "registry=${registry}" >> $GITHUB_OUTPUT
|
||
echo "REGISTRY=${registry}" >> $GITHUB_ENV
|
||
echo "image_repo=${{ github.event.repository.name }}" >> $GITHUB_OUTPUT
|
||
|
||
- name: 设置 Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
|
||
- name: 登录 Gitea 容器仓库
|
||
uses: docker/login-action@v3
|
||
with:
|
||
registry: ${{ steps.vars.outputs.registry }}
|
||
username: ${{ github.actor }}
|
||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||
|
||
- name: 提取元数据
|
||
id: meta
|
||
uses: docker/metadata-action@v5
|
||
with:
|
||
images: ${{ env.REGISTRY }}/${{ github.repository }}
|
||
tags: |
|
||
type=ref,event=branch
|
||
type=semver,pattern={{version}}
|
||
type=semver,pattern={{major}}.{{minor}}
|
||
type=sha,prefix=,suffix=,format=short
|
||
|
||
- name: 构建并推送镜像
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
context: .
|
||
push: true
|
||
tags: ${{ steps.meta.outputs.tags }}
|
||
labels: ${{ steps.meta.outputs.labels }}
|
||
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}:buildcache
|
||
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}:buildcache,mode=max
|
||
|
||
health-check:
|
||
name: 健康检查
|
||
needs: build-docker
|
||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: 设置变量
|
||
id: vars
|
||
run: |
|
||
registry=$(echo "${{ github.server_url }}" | cut -d '/' -f 3)
|
||
echo "REGISTRY=${registry}" >> $GITHUB_ENV
|
||
|
||
- name: 登录 Gitea 容器仓库
|
||
uses: docker/login-action@v3
|
||
with:
|
||
registry: ${{ env.REGISTRY }}
|
||
username: ${{ github.actor }}
|
||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||
|
||
- name: 运行健康检查测试
|
||
run: |
|
||
# 启动服务
|
||
docker run -d --name ${{ github.event.repository.name }}-test -p 8080:8080 ${{ env.REGISTRY }}/${{ github.repository }}:main
|
||
|
||
# 等待服务启动
|
||
sleep 5
|
||
|
||
# 健康检查 - 根据实际端点修改
|
||
echo "检查健康端点..."
|
||
curl -f http://localhost:8080/health || exit 1
|
||
|
||
# 就绪检查 - 根据实际端点修改
|
||
echo "检查就绪端点..."
|
||
curl -f http://localhost:8080/ready || exit 1
|
||
|
||
# 存活检查 - 根据实际端点修改
|
||
echo "检查存活端点..."
|
||
curl -f http://localhost:8080/live || exit 1
|
||
|
||
echo "所有健康检查通过!"
|
||
|
||
# 清理
|
||
docker stop ${{ github.event.repository.name }}-test
|
||
docker rm ${{ github.event.repository.name }}-test
|
||
|
||
release:
|
||
name: 创建发布
|
||
needs: test
|
||
if: startsWith(github.ref, 'refs/tags/v')
|
||
runs-on: ubuntu-latest
|
||
env:
|
||
RUNNER_TOOL_CACHE: /toolcache
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: 配置 Cargo 镜像
|
||
run: |
|
||
mkdir -p ~/.cargo
|
||
cat >> ~/.cargo/config.toml << 'EOF'
|
||
[source.crates-io]
|
||
replace-with = 'rsproxy-sparse'
|
||
[source.rsproxy-sparse]
|
||
registry = "sparse+https://rsproxy.cn/index/"
|
||
[registries.rsproxy]
|
||
index = "https://rsproxy.cn/crates.io-index"
|
||
[net]
|
||
git-fetch-with-cli = true
|
||
EOF
|
||
|
||
- name: 安装 Rust
|
||
run: |
|
||
curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y --default-toolchain stable --target x86_64-unknown-linux-gnu,x86_64-unknown-linux-musl
|
||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||
|
||
- name: 缓存 Rust 依赖
|
||
uses: https://github.com/Swatinem/rust-cache@v2
|
||
continue-on-error: true
|
||
with:
|
||
cache-targets: true
|
||
cache-on-failure: true
|
||
cache-all-crates: true
|
||
|
||
- name: 构建 Release 二进制文件
|
||
run: |
|
||
SERVICE_NAME="${{ github.event.repository.name }}"
|
||
|
||
# Linux GNU 版本
|
||
cargo build --release --target x86_64-unknown-linux-gnu
|
||
strip target/x86_64-unknown-linux-gnu/release/${SERVICE_NAME}
|
||
tar czf ${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz \
|
||
-C target/x86_64-unknown-linux-gnu/release ${SERVICE_NAME}
|
||
|
||
# Linux MUSL 版本 (静态链接)
|
||
cargo build --release --target x86_64-unknown-linux-musl
|
||
strip target/x86_64-unknown-linux-musl/release/${SERVICE_NAME}
|
||
tar czf ${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz \
|
||
-C target/x86_64-unknown-linux-musl/release ${SERVICE_NAME}
|
||
|
||
- name: 生成 Changelog
|
||
id: changelog
|
||
run: |
|
||
SERVICE_NAME="${{ github.event.repository.name }}"
|
||
echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT
|
||
if [ -f CHANGELOG.md ]; then
|
||
awk '/^## \[${{ github.ref_name }}\]/{flag=1;next} /^## \[/{flag=0} flag' CHANGELOG.md || echo "查看完整变更日志:CHANGELOG.md"
|
||
else
|
||
echo "## ${{ github.ref_name }}"
|
||
echo ""
|
||
echo "### 变更内容"
|
||
echo "- 发布版本 ${{ github.ref_name }}"
|
||
echo ""
|
||
echo "### 下载"
|
||
echo "- \`${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz\`: 标准 Linux 版本"
|
||
echo "- \`${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz\`: 静态链接版本(推荐用于容器)"
|
||
fi
|
||
EOF
|
||
|
||
- name: 创建 Release
|
||
uses: softprops/action-gh-release@v1
|
||
with:
|
||
name: ${{ github.event.repository.name }} ${{ github.ref_name }}
|
||
body: ${{ steps.changelog.outputs.CHANGELOG }}
|
||
files: |
|
||
${{ github.event.repository.name }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz
|
||
${{ github.event.repository.name }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz
|
||
draft: false
|
||
prerelease: ${{ contains(github.ref_name, '-') }}
|
||
|
||
notify:
|
||
name: 发送通知
|
||
needs: [test, build-docker, release]
|
||
if: always()
|
||
runs-on: ubuntu-latest
|
||
continue-on-error: true
|
||
steps:
|
||
- name: 发送构建通知
|
||
run: |
|
||
TEST_STATUS="${{ needs.test.result }}"
|
||
BUILD_STATUS="${{ needs.build-docker.result }}"
|
||
RELEASE_STATUS="${{ needs.release.result }}"
|
||
|
||
if [[ "$TEST_STATUS" == "success" && ("$BUILD_STATUS" == "success" || "$BUILD_STATUS" == "skipped") && ("$RELEASE_STATUS" == "success" || "$RELEASE_STATUS" == "skipped") ]]; then
|
||
STATUS="成功"
|
||
else
|
||
STATUS="失败"
|
||
fi
|
||
|
||
curl -X POST ${{ vars.WEBHOOK_URL }} \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"repository": "${{ github.repository }}",
|
||
"ref": "${{ github.ref }}",
|
||
"commit": "${{ github.sha }}",
|
||
"status": "'"$STATUS"'",
|
||
"workflow": "${{ github.workflow }}",
|
||
"actor": "${{ github.actor }}",
|
||
"test_result": "'"$TEST_STATUS"'",
|
||
"build_result": "'"$BUILD_STATUS"'",
|
||
"release_result": "'"$RELEASE_STATUS"'"
|
||
}' || true
|
||
```
|
||
|
||
### 多服务版本(单仓库多 Rust 服务)
|
||
|
||
如果你的单仓库包含多个 Rust 服务,使用以下模板:
|
||
|
||
```yaml
|
||
name: Rust Multi-Service CI/CD
|
||
|
||
on:
|
||
push:
|
||
paths:
|
||
- 'your-service/**' # 修改为实际目录
|
||
- '.gitea/workflows/your-service.yml'
|
||
tags:
|
||
- 'your-service-*' # 修改为实际 tag 前缀
|
||
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
env:
|
||
SERVICE_PREFIX: your-service # 修改为实际服务名
|
||
SERVICE_DIR: your-service # 修改为实际目录名
|
||
# Rust CI 构建优化
|
||
CARGO_INCREMENTAL: 0
|
||
CARGO_NET_RETRY: 10
|
||
CARGO_TERM_COLOR: always
|
||
RUST_BACKTRACE: short
|
||
# 使用中国 Rust 镜像加速
|
||
RUSTUP_DIST_SERVER: https://rsproxy.cn
|
||
RUSTUP_UPDATE_ROOT: https://rsproxy.cn/rustup
|
||
|
||
jobs:
|
||
test:
|
||
name: 测试
|
||
runs-on: ubuntu-latest
|
||
env:
|
||
RUNNER_TOOL_CACHE: /toolcache
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: 配置 Cargo 镜像
|
||
run: |
|
||
mkdir -p ~/.cargo
|
||
cat >> ~/.cargo/config.toml << 'EOF'
|
||
[source.crates-io]
|
||
replace-with = 'rsproxy-sparse'
|
||
[source.rsproxy-sparse]
|
||
registry = "sparse+https://rsproxy.cn/index/"
|
||
[registries.rsproxy]
|
||
index = "https://rsproxy.cn/crates.io-index"
|
||
[net]
|
||
git-fetch-with-cli = true
|
||
EOF
|
||
|
||
- name: 安装 Rust
|
||
run: |
|
||
curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y --default-toolchain stable --component rustfmt,clippy
|
||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||
|
||
- name: 缓存 Rust 依赖
|
||
uses: https://github.com/Swatinem/rust-cache@v2
|
||
continue-on-error: true
|
||
with:
|
||
workspaces: ${{ env.SERVICE_DIR }} -> target
|
||
cache-targets: true
|
||
cache-on-failure: true
|
||
cache-all-crates: true
|
||
|
||
- name: 检查代码格式
|
||
working-directory: ${{ env.SERVICE_DIR }}
|
||
run: cargo fmt -- --check
|
||
|
||
- name: 运行 Clippy 检查
|
||
working-directory: ${{ env.SERVICE_DIR }}
|
||
run: cargo clippy -- -D warnings
|
||
|
||
- name: 运行测试
|
||
working-directory: ${{ env.SERVICE_DIR }}
|
||
run: cargo test --verbose
|
||
|
||
- name: 构建
|
||
working-directory: ${{ env.SERVICE_DIR }}
|
||
run: cargo build --release --verbose
|
||
|
||
build-docker:
|
||
name: 构建 Docker 镜像
|
||
needs: test
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
registry: ${{ steps.vars.outputs.registry }}
|
||
image_repo: ${{ steps.vars.outputs.image_repo }}
|
||
git_tag: ${{ steps.vars.outputs.git_tag }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
- name: 设置变量
|
||
id: vars
|
||
run: |
|
||
registry=$(echo "${{ github.server_url }}" | cut -d '/' -f 3)
|
||
git_tag=$(git describe --tags --abbrev=0 --always)
|
||
echo "registry=${registry}" >> $GITHUB_OUTPUT
|
||
echo "REGISTRY=${registry}" >> $GITHUB_ENV
|
||
echo "image_repo=${{ github.event.repository.name }}" >> $GITHUB_OUTPUT
|
||
echo "git_tag=${git_tag}" >> $GITHUB_OUTPUT
|
||
|
||
- name: 设置 Docker Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
|
||
- name: 登录 Gitea 容器仓库
|
||
uses: docker/login-action@v3
|
||
with:
|
||
registry: ${{ env.registry }}
|
||
username: ${{ github.actor }}
|
||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||
|
||
- name: 构建并推送镜像
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
context: ./${{ env.SERVICE_DIR }}
|
||
file: ./${{ env.SERVICE_DIR }}/Dockerfile
|
||
push: true
|
||
tags: |
|
||
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.SERVICE_PREFIX }}-latest
|
||
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.git_tag }}
|
||
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:buildcache-${{ env.SERVICE_PREFIX }}
|
||
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:buildcache-${{ env.SERVICE_PREFIX }},mode=max
|
||
|
||
health-check:
|
||
name: 健康检查
|
||
needs: build-docker
|
||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: 设置变量
|
||
run: |
|
||
registry=$(echo "${{ github.server_url }}" | cut -d '/' -f 3)
|
||
echo "REGISTRY=${registry}" >> $GITHUB_ENV
|
||
|
||
- name: 登录 Gitea 容器仓库
|
||
uses: docker/login-action@v3
|
||
with:
|
||
registry: ${{ env.REGISTRY }}
|
||
username: ${{ github.actor }}
|
||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||
|
||
- name: 运行健康检查测试
|
||
run: |
|
||
# 启动服务 - 根据实际端口和端点修改
|
||
docker run -d --name ${{ env.SERVICE_PREFIX }}-test -p 8080:8080 ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.SERVICE_PREFIX }}-latest
|
||
|
||
sleep 5
|
||
|
||
curl -f http://localhost:8080/health || exit 1
|
||
curl -f http://localhost:8080/ready || exit 1
|
||
curl -f http://localhost:8080/live || exit 1
|
||
|
||
echo "健康检查通过!"
|
||
|
||
docker stop ${{ env.SERVICE_PREFIX }}-test
|
||
docker rm ${{ env.SERVICE_PREFIX }}-test
|
||
|
||
release:
|
||
name: 创建发布
|
||
needs: test
|
||
if: startsWith(github.ref, 'refs/tags/${{ env.SERVICE_PREFIX }}-')
|
||
runs-on: ubuntu-latest
|
||
env:
|
||
RUNNER_TOOL_CACHE: /toolcache
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: 配置 Cargo 镜像
|
||
run: |
|
||
mkdir -p ~/.cargo
|
||
cat >> ~/.cargo/config.toml << 'EOF'
|
||
[source.crates-io]
|
||
replace-with = 'rsproxy-sparse'
|
||
[source.rsproxy-sparse]
|
||
registry = "sparse+https://rsproxy.cn/index/"
|
||
[registries.rsproxy]
|
||
index = "https://rsproxy.cn/crates.io-index"
|
||
[net]
|
||
git-fetch-with-cli = true
|
||
EOF
|
||
|
||
- name: 安装 Rust
|
||
run: |
|
||
curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh -s -- -y --default-toolchain stable --target x86_64-unknown-linux-gnu,x86_64-unknown-linux-musl
|
||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||
|
||
- name: 缓存 Rust 依赖
|
||
uses: https://github.com/Swatinem/rust-cache@v2
|
||
continue-on-error: true
|
||
with:
|
||
workspaces: ${{ env.SERVICE_DIR }} -> target
|
||
cache-targets: true
|
||
cache-on-failure: true
|
||
cache-all-crates: true
|
||
|
||
- name: 构建 Release 二进制文件
|
||
run: |
|
||
SERVICE_NAME="${{ github.event.repository.name }}"
|
||
|
||
# Linux GNU 版本
|
||
cargo build --release --target x86_64-unknown-linux-gnu --manifest-path ${{ env.SERVICE_DIR }}/Cargo.toml
|
||
strip target/x86_64-unknown-linux-gnu/release/${SERVICE_NAME}
|
||
tar czf ${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz \
|
||
-C target/x86_64-unknown-linux-gnu/release ${SERVICE_NAME}
|
||
|
||
# Linux MUSL 版本
|
||
cargo build --release --target x86_64-unknown-linux-musl --manifest-path ${{ env.SERVICE_DIR }}/Cargo.toml
|
||
strip target/x86_64-unknown-linux-musl/release/${SERVICE_NAME}
|
||
tar czf ${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz \
|
||
-C target/x86_64-unknown-linux-musl/release ${SERVICE_NAME}
|
||
|
||
- name: 生成 Changelog
|
||
id: changelog
|
||
run: |
|
||
SERVICE_NAME="${{ github.event.repository.name }}"
|
||
|
||
# 从 tag 中提取版本号
|
||
version="${{ github.ref_name }}"
|
||
version="${version#${{ env.SERVICE_PREFIX }}-}"
|
||
|
||
echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT
|
||
if [ -f ${{ env.SERVICE_DIR }}/CHANGELOG.md ]; then
|
||
awk "/^## \\[${version}\\]/{flag=1;next} /^## \\[/{flag=0} flag" ${{ env.SERVICE_DIR }}/CHANGELOG.md || echo "查看完整变更日志"
|
||
else
|
||
echo "## ${{ github.ref_name }}"
|
||
echo ""
|
||
echo "### 下载"
|
||
echo "- \`${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz\`: 标准 Linux 版本"
|
||
echo "- \`${SERVICE_NAME}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz\`: 静态链接版本"
|
||
fi
|
||
EOF
|
||
|
||
- name: 创建 Release
|
||
uses: softprops/action-gh-release@v1
|
||
with:
|
||
name: ${{ github.event.repository.name }} ${{ github.ref_name }}
|
||
body: ${{ steps.changelog.outputs.CHANGELOG }}
|
||
files: |
|
||
${{ github.event.repository.name }}-${{ github.ref_name }}-x86_64-linux-gnu.tar.gz
|
||
${{ github.event.repository.name }}-${{ github.ref_name }}-x86_64-linux-musl.tar.gz
|
||
draft: false
|
||
prerelease: ${{ contains(github.ref_name, '-') }}
|
||
|
||
notify:
|
||
name: 发送通知
|
||
needs: [test, build-docker, release]
|
||
if: always()
|
||
runs-on: ubuntu-latest
|
||
continue-on-error: true
|
||
steps:
|
||
- name: 发送构建通知
|
||
run: |
|
||
TEST_STATUS="${{ needs.test.result }}"
|
||
BUILD_STATUS="${{ needs.build-docker.result }}"
|
||
RELEASE_STATUS="${{ needs.release.result }}"
|
||
|
||
if [[ "$TEST_STATUS" == "success" && ("$BUILD_STATUS" == "success" || "$BUILD_STATUS" == "skipped") && ("$RELEASE_STATUS" == "success" || "$RELEASE_STATUS" == "skipped") ]]; then
|
||
STATUS="成功"
|
||
else
|
||
STATUS="失败"
|
||
fi
|
||
|
||
curl -X POST ${{ vars.WEBHOOK_URL }} \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"repository": "${{ github.repository }}",
|
||
"ref": "${{ github.ref }}",
|
||
"commit": "${{ github.sha }}",
|
||
"status": "'"$STATUS"'",
|
||
"workflow": "${{ github.workflow }}",
|
||
"actor": "${{ github.actor }}",
|
||
"test_result": "'"$TEST_STATUS"'",
|
||
"build_result": "'"$BUILD_STATUS"'",
|
||
"release_result": "'"$RELEASE_STATUS"'"
|
||
}' || true
|
||
```
|
||
|
||
## Dockerfile 模板
|
||
|
||
### Dockerfile(推荐用于 CI/CD)
|
||
|
||
使用多阶段构建,优化镜像大小和构建缓存:
|
||
|
||
```dockerfile
|
||
# 构建阶段
|
||
FROM rust:1.84-slim AS builder
|
||
|
||
WORKDIR /app
|
||
|
||
# 安装构建依赖
|
||
RUN apt-get update && apt-get install -y \
|
||
pkg-config \
|
||
libssl-dev \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
|
||
# 先复制 Cargo 文件以缓存依赖
|
||
COPY Cargo.toml Cargo.lock ./
|
||
|
||
# 创建虚拟 main.rs 来缓存依赖编译
|
||
RUN mkdir src && echo 'fn main() {}' > src/main.rs
|
||
RUN cargo build --release && rm -rf src
|
||
|
||
# 复制源代码
|
||
COPY src ./src
|
||
|
||
# 构建应用(使用 touch 确保重新编译)
|
||
RUN touch src/main.rs && cargo build --release
|
||
|
||
# 运行阶段 - 使用最小镜像
|
||
FROM debian:bookworm-slim
|
||
|
||
WORKDIR /app
|
||
|
||
# 安装运行依赖
|
||
RUN apt-get update && apt-get install -y \
|
||
ca-certificates \
|
||
curl \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
|
||
# 复制二进制文件(根据实际项目名称修改)
|
||
COPY --from=builder /app/target/release/your-app-name /app/your-app-name
|
||
|
||
# 创建日志目录(如果有日志输出)
|
||
RUN mkdir -p /app/logs
|
||
|
||
# 暴露端口(根据实际服务修改)
|
||
EXPOSE 8080
|
||
|
||
# 健康检查(根据实际端点修改)
|
||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||
CMD curl -f http://localhost:8080/health || exit 1
|
||
|
||
# 运行应用
|
||
CMD ["/app/your-app-name"]
|
||
```
|
||
|
||
### Dockerfile.musl(静态链接版本)
|
||
|
||
使用 musl 进行静态链接,生成独立可执行文件:
|
||
|
||
```dockerfile
|
||
# 构建阶段
|
||
FROM rust:1.84-alpine AS builder
|
||
|
||
# 安装构建依赖
|
||
RUN apk add --no-cache musl-dev openssl-dev pkgconfig
|
||
|
||
# 配置 Cargo 镜像加速(可选)
|
||
RUN mkdir -p /root/.cargo && \
|
||
echo '[source.crates-io]' > /root/.cargo/config.toml && \
|
||
echo 'replace-with = "rsproxy-sparse"' >> /root/.cargo/config.toml && \
|
||
echo '[source.rsproxy-sparse]' >> /root/.cargo/config.toml && \
|
||
echo 'registry = "sparse+https://rsproxy.cn/index/"' >> /root/.cargo/config.toml
|
||
|
||
WORKDIR /build
|
||
|
||
# 先复制 Cargo 文件以缓存依赖
|
||
COPY Cargo.toml Cargo.lock ./
|
||
|
||
# 创建虚拟 main.rs 来缓存依赖编译
|
||
RUN mkdir src && echo 'fn main() {}' > src/main.rs
|
||
RUN cargo build --release --target x86_64-unknown-linux-musl && rm -rf src
|
||
|
||
# 复制源代码并构建
|
||
COPY src ./src
|
||
RUN touch src/main.rs && cargo build --release --target x86_64-unknown-linux-musl
|
||
|
||
# 运行阶段 - 使用 scratch 或 alpine
|
||
FROM alpine:3.21
|
||
|
||
RUN apk add --no-cache ca-certificates tzdata curl
|
||
|
||
WORKDIR /app
|
||
|
||
# 从构建阶段复制二进制文件
|
||
COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/your-app-name .
|
||
|
||
ENV TZ=Asia/Shanghai
|
||
ENV RUST_LOG=info
|
||
|
||
EXPOSE 8080
|
||
|
||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||
CMD curl -f http://localhost:8080/health || exit 1
|
||
|
||
CMD ["./your-app-name"]
|
||
```
|
||
|
||
### Dockerfile.ci(使用 CI 预编译二进制)
|
||
|
||
适用于 CI 中已经构建好二进制文件的场景:
|
||
|
||
```dockerfile
|
||
FROM debian:bookworm-slim
|
||
|
||
# 安装运行时依赖
|
||
RUN apt-get update && apt-get install -y \
|
||
ca-certificates \
|
||
curl \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
|
||
WORKDIR /app
|
||
|
||
# 复制 CI 构建好的二进制文件
|
||
COPY your-app-name .
|
||
|
||
# 创建日志目录
|
||
RUN mkdir -p /app/logs
|
||
|
||
ENV RUST_LOG=info
|
||
|
||
EXPOSE 8080
|
||
|
||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||
CMD curl -f http://localhost:8080/health || exit 1
|
||
|
||
CMD ["./your-app-name"]
|
||
```
|
||
|
||
## 模板选择指南
|
||
|
||
### 单服务仓库 vs 多服务仓库
|
||
|
||
| 场景 | 推荐模板 | 说明 |
|
||
|------|---------|------|
|
||
| 单 Rust 项目 | **完整版本** | 简单直接,基于分支触发 |
|
||
| 单仓库多服务 | **多服务版本** | 按路径和 tag 前缀触发,避免无关构建 |
|
||
| 微服务架构 | **多服务版本** | 每个服务独立 workflow |
|
||
|
||
### Workflow 阶段说明
|
||
|
||
```
|
||
┌─────────┐ ┌──────────────┐ ┌─────────────┐
|
||
│ Test │────▶│ Build Docker │────▶│ Health Check│
|
||
│(fmt, │ │ │ │ │
|
||
│ clippy, │ └──────────────┘ └─────────────┘
|
||
│ test) │
|
||
└─────────┘
|
||
│
|
||
▼
|
||
┌─────────┐ ┌─────────┐
|
||
│ Release │────▶│ Notify │
|
||
│(tag v*) │ │(always) │
|
||
└─────────┘ └─────────┘
|
||
```
|
||
|
||
| 阶段 | 触发条件 | 说明 |
|
||
|------|---------|------|
|
||
| **Test** | 每次 push/PR | 代码格式、Clippy 检查、单元测试、编译 |
|
||
| **Build Docker** | Test 成功后 | 构建并推送 Docker 镜像到仓库 |
|
||
| **Health Check** | main 分支 push | 启动容器并验证健康端点 |
|
||
| **Release** | Tag 以 `v` 开头 | 构建双平台二进制并创建 Release |
|
||
| **Notify** | 始终执行 | 发送构建结果通知 |
|
||
|
||
## 自定义配置说明
|
||
|
||
### 必须修改的变量
|
||
|
||
#### 完整版本(单服务)
|
||
|
||
| 变量 | 位置 | 说明 | 示例 |
|
||
|------|------|------|------|
|
||
| `runs-on` | `jobs.*.runs-on` | Runner 标签 | `ubuntu-latest` |
|
||
| 健康检查端点 | `health-check` job | 根据实际服务修改 | `/health`, `/ready`, `/live` |
|
||
| 服务端口号 | `health-check` job | Docker 端口映射 | `8080:8080` |
|
||
|
||
#### 多服务版本
|
||
|
||
| 变量 | 位置 | 说明 | 示例 |
|
||
|------|------|------|------|
|
||
| `SERVICE_PREFIX` | `env` | 服务前缀,用于 tag 匹配 | `device-rs` |
|
||
| `SERVICE_DIR` | `env` | 项目目录名 | `device-rs` |
|
||
| `runs-on` | `jobs.*.runs-on` | Runner 标签 | `ubuntu-latest` |
|
||
| 健康检查端点 | `health-check` job | 根据实际服务修改 | `/health`, `/ready`, `/live` |
|
||
|
||
### Secrets 配置
|
||
|
||
在 Gitea 仓库的 **Settings → Secrets** 中添加:
|
||
|
||
| Secret | 说明 | 必需 |
|
||
|--------|------|------|
|
||
| `REGISTRY_TOKEN` | 容器仓库推送 Token | 是 |
|
||
| `RELEASE_TOKEN` | Release 创建 Token | 可选(默认使用 GITHUB_TOKEN) |
|
||
|
||
### Variables 配置
|
||
|
||
在 Gitea 仓库的 **Settings → Variables** 中添加:
|
||
|
||
| Variable | 说明 | 必需 |
|
||
|----------|------|------|
|
||
| `WEBHOOK_URL` | 构建通知 Webhook URL | 可选 |
|
||
|
||
### 可选修改的变量
|
||
|
||
| 变量 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `CARGO_INCREMENTAL` | `0` | CI 环境禁用增量编译 |
|
||
| `CARGO_NET_RETRY` | `10` | 网络重试次数 |
|
||
| `RUST_BACKTRACE` | `short` | 错误栈长度 |
|
||
|
||
### 健康检查端点配置
|
||
|
||
模板默认检查以下端点,请根据你的服务实际端点修改:
|
||
|
||
```yaml
|
||
# 在 health-check job 中修改
|
||
- name: 运行健康检查测试
|
||
run: |
|
||
# 修改端口映射
|
||
docker run -d --name app-test -p 你的端口:容器端口 ...
|
||
|
||
# 修改检查端点
|
||
curl -f http://localhost:你的端口/health || exit 1
|
||
curl -f http://localhost:你的端口/ready || exit 1
|
||
curl -f http://localhost:你的端口/live || exit 1
|
||
```
|
||
|
||
推荐的端点实现(Axum 示例):
|
||
|
||
```rust
|
||
pub fn health_routes() -> Router {
|
||
Router::new()
|
||
.route("/health", get(health)) // 健康检查
|
||
.route("/ready", get(ready)) // 就绪检查
|
||
.route("/live", get(live)) // 存活检查
|
||
}
|
||
|
||
async fn health() -> Json<serde_json::Value> {
|
||
Json(json!({"status": "healthy"}))
|
||
}
|
||
|
||
async fn ready() -> Json<serde_json::Value> {
|
||
// 检查依赖服务(数据库、缓存等)
|
||
Json(json!({"status": "ready"}))
|
||
}
|
||
|
||
async fn live() -> &'static str {
|
||
"OK" // 简单存活检查
|
||
}
|
||
```
|
||
|
||
## build.rs 示例
|
||
|
||
如果需要在编译时注入版本号,创建 `build.rs`:
|
||
|
||
```rust
|
||
/// 构建脚本 - 将环境变量传递给编译过程
|
||
///
|
||
/// 在 CI 构建时,GIT_TAG 环境变量会被设置为版本号(如 "0.6.3"),
|
||
/// 该脚本将其传递给 rustc,使 `option_env!("GIT_TAG")` 能在编译时获取版本。
|
||
fn main() {
|
||
// 传递 GIT_TAG 环境变量到编译时
|
||
if let Ok(git_tag) = std::env::var("GIT_TAG") {
|
||
println!("cargo:rustc-env=GIT_TAG={}", git_tag);
|
||
}
|
||
|
||
// 重新运行条件:GIT_TAG 环境变量变化时重新编译
|
||
println!("cargo:rerun-if-env-changed=GIT_TAG");
|
||
}
|
||
```
|
||
|
||
在代码中使用:
|
||
|
||
```rust
|
||
fn main() {
|
||
let version = option_env!("GIT_TAG").unwrap_or("dev");
|
||
println!("Version: {}", version);
|
||
}
|
||
```
|
||
|
||
## 性能优化建议
|
||
|
||
### 1. CI 环境优化
|
||
|
||
```yaml
|
||
env:
|
||
# 禁用增量编译(CI 环境不需要)
|
||
CARGO_INCREMENTAL: 0
|
||
# 增加网络重试次数
|
||
CARGO_NET_RETRY: 10
|
||
# 保持彩色输出
|
||
CARGO_TERM_COLOR: always
|
||
# 简短错误栈
|
||
RUST_BACKTRACE: short
|
||
```
|
||
|
||
### 2. 缓存策略
|
||
|
||
```yaml
|
||
# 使用 rust-cache action(推荐)
|
||
- name: Cache Rust dependencies
|
||
uses: https://github.com/Swatinem/rust-cache@v2
|
||
continue-on-error: true # 缓存失败不中断构建
|
||
with:
|
||
cache-targets: true
|
||
cache-on-failure: true
|
||
cache-all-crates: true
|
||
```
|
||
|
||
### 3. 并行构建
|
||
|
||
```yaml
|
||
- name: Build
|
||
run: |
|
||
export CARGO_BUILD_JOBS="$(nproc)"
|
||
cargo build --release
|
||
```
|
||
|
||
### 4. Docker 构建缓存
|
||
|
||
```yaml
|
||
- name: 构建并推送镜像
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}:buildcache
|
||
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ github.repository }}:buildcache,mode=max
|
||
```
|
||
|
||
### 5. 依赖优化
|
||
|
||
```toml
|
||
# Cargo.toml - 优化 Release 构建
|
||
[profile.release]
|
||
opt-level = 3 # 最高优化级别
|
||
lto = true # 链接时优化
|
||
codegen-units = 1 # 单个代码生成单元(更好的优化)
|
||
strip = true # 去除符号表(减小体积)
|
||
panic = 'abort' # 减小二进制大小
|
||
```
|
||
|
||
## 健康检查端点实现
|
||
|
||
使用 Axum 实现健康检查(参考 device-rs):
|
||
|
||
```rust
|
||
use axum::{Router, routing::get, Json};
|
||
use serde_json::json;
|
||
|
||
pub fn health_routes() -> Router {
|
||
Router::new()
|
||
.route("/healthz", get(healthz)) // 存活探针
|
||
.route("/readyz", get(readyz)) // 就绪探针
|
||
.route("/health", get(health)) // 详细状态
|
||
.route("/metrics", get(metrics)) // Prometheus metrics
|
||
}
|
||
|
||
// 简单存活检查
|
||
async fn healthz() -> &'static str {
|
||
"OK"
|
||
}
|
||
|
||
// 就绪检查(检查依赖服务)
|
||
async fn readyz() -> Json<serde_json::Value> {
|
||
// 检查数据库、Redis、MQTT 等
|
||
Json(json!({"status": "ready"}))
|
||
}
|
||
|
||
// 详细健康状态
|
||
async fn health() -> Json<serde_json::Value> {
|
||
Json(json!({
|
||
"status": "healthy",
|
||
"version": env!("CARGO_PKG_VERSION"),
|
||
"uptime": "1h 23m"
|
||
}))
|
||
}
|
||
|
||
// Prometheus metrics
|
||
async fn metrics() -> String {
|
||
// 导出 metrics
|
||
"# HELP requests_total Total requests\n".to_string()
|
||
}
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
### Q1: rust-cache 报错 "getCacheEntry failed"
|
||
|
||
**症状**: 缓存步骤报错 `getCacheEntry failed: connect ETIMEDOUT`
|
||
|
||
**解决**: 在 act_runner 的 `config.yaml` 中配置缓存服务器:
|
||
|
||
```yaml
|
||
cache:
|
||
enabled: true
|
||
dir: "/cache"
|
||
host: "127.0.0.1" # 替换为你的缓存服务器地址
|
||
port: 9000
|
||
```
|
||
|
||
或在 workflow 中设置 `continue-on-error: true` 跳过缓存失败。
|
||
|
||
### Q2: 健康检查失败
|
||
|
||
**症状**: Health Check job 失败,提示 `curl: (7) Failed to connect`
|
||
|
||
**解决**:
|
||
1. 检查服务端口是否正确映射
|
||
2. 确认服务启动时间,可能需要增加 `sleep` 时间
|
||
3. 验证端点路径是否正确
|
||
|
||
```yaml
|
||
- name: 运行健康检查测试
|
||
run: |
|
||
docker run -d --name app-test -p 8080:8080 ${{ env.IMAGE }}
|
||
sleep 10 # 增加等待时间
|
||
curl -f http://localhost:8080/health || exit 1
|
||
```
|
||
|
||
### Q3: Docker 推送失败(401 Unauthorized)
|
||
|
||
**症状**: `denied: unauthorized to access repository`
|
||
|
||
**解决**:
|
||
1. 确认 `REGISTRY_TOKEN` 已正确配置
|
||
2. 检查 Token 权限(需要 `package:write`)
|
||
3. 验证 registry URL 是否正确
|
||
|
||
### Q4: 并发构建取消
|
||
|
||
**症状**: 新推送取消了正在运行的构建
|
||
|
||
**解决**: 这是预期行为。`concurrency` 配置会自动取消同一分支的旧构建:
|
||
|
||
```yaml
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
```
|
||
|
||
如需保留所有构建,可以移除或修改此配置。
|
||
|
||
### Q5: 多服务仓库触发混乱
|
||
|
||
**症状**: 修改服务 A 但服务 B 也触发了构建
|
||
|
||
**解决**: 确保 `paths` 配置正确:
|
||
|
||
```yaml
|
||
on:
|
||
push:
|
||
paths:
|
||
- 'service-a/**' # 仅监听服务 A 的变更
|
||
- '.gitea/workflows/service-a.yml'
|
||
tags:
|
||
- 'service-a-*' # 仅匹配服务 A 的 tag
|
||
```
|
||
|
||
### Q6: musl 编译链接错误
|
||
|
||
```bash
|
||
# 安装 musl 工具链
|
||
rustup target add x86_64-unknown-linux-musl
|
||
|
||
# 或在 CI 中自动安装
|
||
curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | \
|
||
sh -s -- -y --default-toolchain stable --target x86_64-unknown-linux-musl
|
||
```
|
||
|
||
## 参考项目
|
||
|
||
- [api-hub](https://github.com/example/api-hub) - Rust API 网关服务,MQTT5 + HTTP 健康检查
|
||
- [device-rs](https://git.voson.top/tianchu/bms/src/branch/main/device-rs) - Rust IoT 设备服务
|
||
|
||
## 模板更新日志
|
||
|
||
### 2026-01-30
|
||
|
||
- **优化 Workflow 结构**:分离 test、build-docker、health-check、release、notify 阶段
|
||
- **新增健康检查阶段**:自动验证 Docker 镜像的健康端点
|
||
- **新增并发控制**:避免重复构建浪费资源
|
||
- **新增单服务/多服务双模板**:适应不同项目结构
|
||
- **优化 Dockerfile**:基于实际项目改进多阶段构建
|
||
- **完善文档**:添加详细的配置说明和常见问题
|
||
|
||
## 相关文档
|
||
|
||
- [Gitea Actions Documentation](https://docs.gitea.com/usage/actions/quickstart)
|
||
- [Rust CI 最佳实践](https://doc.rust-lang.org/cargo/guide/continuous-integration.html)
|
||
- [cargo-zigbuild](https://github.com/rust-cross/cargo-zigbuild)
|
||
- [rust-cache](https://github.com/Swatinem/rust-cache)
|