Skip to content

Rust in the Cloud Cloud Native

Serverless và Containers — Tận dụng tối đa performance của Rust trong môi trường Cloud

Tại sao Rust cho Cloud?

┌─────────────────────────────────────────────────────────────────────┐
│                    CLOUD PERFORMANCE COMPARISON                      │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Metric          │  Java/Node.js    │  Go           │  Rust        │
│  ────────────────┼──────────────────┼───────────────┼─────────────  │
│  Cold Start      │  2-5 seconds     │  200-500ms    │  50-100ms    │
│  Memory Usage    │  256-512MB       │  50-100MB     │  10-30MB     │
│  Binary Size     │  50-200MB        │  10-20MB      │  2-8MB       │
│  Throughput      │  Baseline        │  5-10x        │  10-20x      │
│                                                                     │
│  Chi phí Lambda  │  $$$$$           │  $$           │  $           │
│  (per 1M req)    │                  │               │              │
└─────────────────────────────────────────────────────────────────────┘

1. AWS Lambda với Rust

Setup với cargo-lambda

bash
# Install cargo-lambda
cargo install cargo-lambda

# Tạo project mới
cargo lambda new my-lambda-function
cd my-lambda-function

Handler cơ bản

rust
// src/main.rs
use lambda_runtime::{service_fn, LambdaEvent, Error};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Request {
    name: String,
}

#[derive(Serialize)]
struct Response {
    message: String,
}

async fn handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
    let name = &event.payload.name;
    
    Ok(Response {
        message: format!("Hello, {}!", name),
    })
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    lambda_runtime::run(service_fn(handler)).await
}

Cargo.toml

toml
[package]
name = "my-lambda-function"
version = "0.1.0"
edition = "2021"

[dependencies]
lambda_runtime = "0.8"
tokio = { version = "1", features = ["macros"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Optimize for size
[profile.release]
opt-level = "z"      # Optimize for size
lto = true           # Link-time optimization
codegen-units = 1    # Better optimization
panic = "abort"      # Smaller binary
strip = true         # Strip symbols

Build và Deploy

bash
# Build cho Amazon Linux 2 (ARM64 - Graviton)
cargo lambda build --release --arm64

# Hoặc x86_64
cargo lambda build --release

# Deploy trực tiếp
cargo lambda deploy --iam-role arn:aws:iam::ACCOUNT:role/LambdaRole

# Test locally
cargo lambda watch
cargo lambda invoke --data-ascii '{"name": "World"}'

Tối ưu Cold Start

rust
// 1. Lazy initialization với OnceCell
use std::sync::OnceLock;
use aws_sdk_dynamodb::Client;

static DB_CLIENT: OnceLock<Client> = OnceLock::new();

fn get_db_client() -> &'static Client {
    DB_CLIENT.get_or_init(|| {
        let config = aws_config::load_from_env().await;
        Client::new(&config)
    })
}

// 2. Warm up connections trong init phase
// Lambda runtime tự động gọi init trước handler đầu tiên

2. Multi-Stage Docker với musl (Alpine)

Goal: Tạo static binary < 10MB có thể chạy trên bất kỳ Linux container nào.

Dockerfile tối ưu

dockerfile
# Stage 1: Build
FROM rust:1.75-alpine AS builder

# Install build dependencies
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static pkgconfig

WORKDIR /app

# Cache dependencies
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {}" > src/main.rs
RUN cargo build --release && rm -rf src

# Build actual app
COPY src ./src
RUN touch src/main.rs  # Force rebuild
RUN cargo build --release --target x86_64-unknown-linux-musl

# Strip binary
RUN strip /app/target/x86_64-unknown-linux-musl/release/myapp

# Stage 2: Runtime (scratch or distroless)
FROM scratch

# Copy CA certificates for HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# Copy binary
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/myapp /myapp

# Run as non-root
USER 1000:1000

ENTRYPOINT ["/myapp"]

Kết quả

bash
$ docker images myapp
REPOSITORY   TAG       SIZE
myapp        latest    5.2MB   # vs 200MB+ cho Java/Node

$ docker run myapp
# Starts in ~10ms vs seconds for JVM

Cross-compile từ macOS/Windows

bash
# Install musl target
rustup target add x86_64-unknown-linux-musl

# Install cross-compilation toolchain
# macOS:
brew install FiloSottile/musl-cross/musl-cross

# Build
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-gcc \
cargo build --release --target x86_64-unknown-linux-musl

3. OpenSSL Static Linking

Vấn đề: OpenSSL thường là dynamic library → container cần có lib.

Option A: rustls (Pure Rust TLS)

toml
# Cargo.toml - Dùng rustls thay OpenSSL
[dependencies]
reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"] }

Option B: OpenSSL Static

toml
# Cargo.toml
[dependencies]
openssl = { version = "0.10", features = ["vendored"] }
dockerfile
# Dockerfile - Alpine với static OpenSSL
RUN apk add --no-cache openssl-libs-static

4. Docker Compose cho Development

yaml
# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
      - RUST_LOG=info
    depends_on:
      - db
      - redis

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    
volumes:
  postgres_data:

5. Health Checks & Kubernetes Ready

rust
// Axum health check endpoint
use axum::{Router, Json, routing::get};
use serde::Serialize;

#[derive(Serialize)]
struct HealthResponse {
    status: &'static str,
    version: &'static str,
}

async fn health() -> Json<HealthResponse> {
    Json(HealthResponse {
        status: "healthy",
        version: env!("CARGO_PKG_VERSION"),
    })
}

async fn ready() -> &'static str {
    // Check DB connection, etc.
    "ready"
}

fn app() -> Router {
    Router::new()
        .route("/health", get(health))   // Liveness probe
        .route("/ready", get(ready))     // Readiness probe
        // ... other routes
}

Kubernetes Deployment

yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rust-app
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: app
          image: myrepo/rust-app:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              memory: "32Mi"    # Rust cần rất ít RAM
              cpu: "50m"
            limits:
              memory: "64Mi"
              cpu: "200m"
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 1  # Rust khởi động nhanh
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 1
            periodSeconds: 5

Bảng Tóm tắt

ScenarioBest Practice
AWS Lambdacargo-lambda, ARM64 Graviton, cold start < 100ms
DockerMulti-stage build, musl, scratch/distroless base
TLSrustls (pure Rust) hoặc vendored OpenSSL
Binary Sizeopt-level = "z", LTO, strip, panic = "abort"
Kubernetes32MB memory limit, 1s initialDelaySeconds