Files
opencode/skill/gitea/workflow-templates/rust-backend.md
voson 425ca5b5fd feat(gitea): 添加 Dockerfile 模板和 Rust 支持,优化 runner 网络配置说明
- 新增 Go、Node.js、Rust 服务的 Dockerfile 模板
- 新增 Rust 快速参考指南
- 新增 Rust 后端工作流模板
- 优化 create-runner.md,明确 host 网络模式为缓存必需条件
- 更新 gitea skill 主文档
2026-01-30 10:12:09 +08:00

1124 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)