Введение
Rust — это современный системный язык программирования, который сочетает в себе высокую производительность C/C++ с безопасностью памяти на уровне компиляции. Благодаря отсутствию сборщика мусора и низкому потреблению ресурсов, приложения на Rust идеально подходят для запуска в контейнерных средах. Kubernetes (K8s) — стандарт де-факто для оркестрации контейнеров. В этом руководстве мы на практике разберём, как собрать простое веб-приложение на Rust, упаковать его в Docker-образ и развернуть в кластере Kubernetes.
Статья рассчитана на разработчиков, которые уже знакомы с основами Rust и имеют базовое представление о Docker и Kubernetes. Мы не будем углубляться в теорию, а сосредоточимся на пошаговой инструкции с реальными примерами кода и команд.
1. Подготовка приложения на Rust
В качестве примера создадим минимальный HTTP-сервер с использованием популярного фреймворка actix-web. Он будет отвечать на GET-запросы и возвращать JSON-ответ.
Создайте новый проект:
cargo new rust-k8s-app
cd rust-k8s-app
Добавьте зависимости в Cargo.toml:
[dependencies]
actix-web = "4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Теперь напишем код сервера в src/main.rs:
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use serde::Serialize;
#[derive(Serialize)]
struct HealthResponse {
status: String,
message: String,
}
#[get("/health")]
async fn health_check() -> impl Responder {
HttpResponse::Ok().json(HealthResponse {
status: "ok".to_string(),
message: "Сервис работает".to_string(),
})
}
#[get("/")]
async fn index() -> impl Responder {
HttpResponse::Ok().json(serde_json::json!({
"service": "rust-k8s-app",
"version": env!("CARGO_PKG_VERSION")
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let host = std::env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string());
let port: u16 = std::env::var("PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse()
.expect("PORT must be a number");
println!("Сервер запущен на {}:{}", host, port);
HttpServer::new(|| {
App::new()
.service(health_check)
.service(index)
})
.bind((host.as_str(), port))?
.run()
.await
}
Обратите внимание: мы вынесли хост и порт в переменные окружения. Это стандартная практика для контейнеризованных приложений — конфигурация должна приходить извне.
Проверьте, что приложение собирается и запускается локально:
cargo build --release
./target/release/rust-k8s-app
Откройте браузер на http://localhost:8080/health — вы должны увидеть JSON-ответ.
2. Сборка Docker-образа
Для деплоя в Kubernetes нам нужно упаковать приложение в Docker-образ. Используем многоступенчатую сборку (multi-stage build), чтобы минимизировать размер финального образа.
Создайте файл Dockerfile в корне проекта:
# Этап сборки
FROM rust:1.75-slim-bookworm AS builder
WORKDIR /app
# Копируем только Cargo.toml для кэширования зависимостей
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release 2>/dev/null || true
# Теперь копируем реальный исходный код
COPY src ./src
RUN cargo build --release
# Финальный образ
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/rust-k8s-app /usr/local/bin/rust-k8s-app
EXPOSE 8080
CMD ["rust-k8s-app"]
Соберите образ:
docker build -t rust-k8s-app:latest .
Проверьте размер образа:
docker images | grep rust-k8s-app
Вы должны увидеть образ размером около 80-100 МБ — это отличный результат для production-приложения на Rust.
Запустите контейнер локально для проверки:
docker run -p 8080:8080 rust-k8s-app:latest
3. Написание манифестов Kubernetes
Теперь подготовим манифесты для деплоя в Kubernetes. Создайте директорию k8s и внутри неё файлы.
3.1. Deployment
Файл k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: rust-app
labels:
app: rust-app
spec:
replicas: 3
selector:
matchLabels:
app: rust-app
template:
metadata:
labels:
app: rust-app
spec:
containers:
- name: rust-app
image: rust-k8s-app:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
- name: HOST
value: "0.0.0.0"
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
Здесь мы задали 3 реплики для отказоустойчивости, указали лимиты ресурсов и настроили пробы (probes) для проверки здоровья приложения.
3.2. Service
Файл k8s/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: rust-app-service
spec:
selector:
app: rust-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP
Этот сервис будет балансировать трафик между подами на порту 80.
3.3. Ingress (опционально)
Если