Почему ваш Rust-проект заслуживает настоящего CI/CD?
Представьте: вы пишете на Rust, компилятор — ваш лучший друг, borrow checker не даёт расслабиться, а код работает молниеносно. Но как только дело доходит до деплоя, магия заканчивается. Ручной запуск тестов, сборка на локальной машине, заливка бинарника на сервер через SCP — это путь в никуда. Особенно когда проект разрастается, а в команде появляется второй разработчик.
CI/CD (Continuous Integration / Continuous Deployment) — это не просто модное слово. Это система, которая берёт на себя рутину: прогнать тесты, проверить стиль кода, собрать релизную версию и отправить её туда, где она должна работать. Для Rust, с его акцентом на надёжность и производительность, автоматизация сборки — естественное продолжение философии языка.
В этой статье мы с нуля настроим CI/CD pipeline для Rust-приложения на GitHub Actions. Вы узнаете, как организовать тестирование, линтинг, сборку бинарников под разные платформы и деплой. Всё с реальными примерами кода, которые можно скопировать в свой проект.
Что мы будем автоматизировать?
Прежде чем писать код, давайте определимся с целями. Наш pipeline будет делать четыре вещи:
- Линтинг и форматирование — чтобы код соответствовал стандартам Rust и не содержал типичных ошибок.
- Тестирование — запуск всех unit-тестов и, если есть, интеграционных.
- Сборка бинарника — компиляция оптимизированной версии (release).
- Деплой — загрузка собранного артефакта на сервер или в релизы GitHub.
Всё это будет происходить автоматически при каждом пуше в ветку main или при создании pull request. Звучит как магия? На самом деле это просто YAML-файлы и пара команд Cargo.
Шаг 1: Подготовка проекта
Для примера возьмём простой веб-сервер на Actix-web. Но pipeline универсален: подойдёт для любого Rust-проекта, будь то CLI-утилита, библиотека или микросервис.
Создадим новый проект:
cargo new rust-ci-cd-demo --bin
cd rust-ci-cd-demo
Добавим зависимости в Cargo.toml:
[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
И простейший код в src/main.rs:
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello from Rust CI/CD!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(hello)
})
.bind(("0.0.0.0", 8080))?
.run()
.await
}
Добавим простой тест в src/main.rs (внизу файла):
#[cfg(test)]
mod tests {
use actix_web::{test, App, web};
use crate::hello;
#[actix_web::test]
async fn test_hello_endpoint() {
let app = test::init_service(App::new().service(hello)).await;
let req = test::TestRequest::get().uri("/").to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
}
Всё готово. Теперь самое интересное — настройка CI/CD.
Шаг 2: Создаём GitHub Actions workflow
GitHub Actions — это встроенный CI/CD-инструмент GitHub. Он работает на основе YAML-файлов, которые лежат в директории .github/workflows/. Создадим её:
mkdir -p .github/workflows
touch .github/workflows/ci.yml
Откроем ci.yml и начнём писать pipeline. Назовём его "Rust CI/CD Pipeline".
Базовый триггер и окружение
name: Rust CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
Мы настроили запуск при пуше в main и при создании PR. CARGO_TERM_COLOR просто делает вывод цветным — приятно глазу.
Шаг 2.1: Линтинг и форматирование
Rust-сообщество очень трепетно относится к стилю кода. cargo fmt и cargo clippy — обязательные инструменты. Добавим их в pipeline:
jobs:
quality:
name: Code Quality
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
components: clippy, rustfmt
- name: Check formatting
run: cargo fmt --check
- name: Run Clippy
run: cargo clippy -- -D warnings
Обратите внимание: мы используем action actions-rust-lang/setup-rust-toolchain — он умеет устанавливать не только сам Rust, но и дополнительные компоненты (clippy, rustfmt). Флаг -- -D warnings в clippy превращает все предупреждения в ошибки — жёстко, но эффективно.
Шаг 2.2: Тестирование
Добавим отдельную job для тестов. Зачем отдельно? Чтобы, если линтинг упал, тесты даже не запускались — экономим время и ресурсы.
test:
name: Run Tests
needs: quality
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- name: Run tests
run: cargo test --verbose
Ключевое слово needs создаёт зависимость: job test запустится только после успешного завершения quality. Если линтинг провален — тесты не пойдут.
Шаг 2.3: Сборка релизного бинарника
Когда тесты прошли, пора собрать оптимизированный бинарник. Для Rust это команда cargo build --release. Но мы пойдём дальше — добавим кеширование зависимостей. Это ускорит последующие сборки в разы.
build:
name: Build Release
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
target