In this comprehensive guide, we’ll walk through the process of developing a high-performance API using Rust and the Warp framework, containerizing it with Docker, and automating its deployment to Google Cloud Run using GitLab CI/CD. This setup provides a scalable, efficient, and automated deployment pipeline for your Rust applications.

1. The Rust API (Domain-Driven Design)

Our API adopts a Domain-Driven Design (DDD) approach, ensuring a clean separation of concerns and maintainable code. For brevity, we’ll focus on the core main.rs and the project’s directory structure.

Project Structure:

The organized file tree facilitates development, with domain for business logic, infra for infrastructure concerns like handlers, and use_case for application-specific orchestrations.

├── Cargo.toml
├── Dockerfile
├── REAME.md
├── src
│   ├── domain
│   │   ├── entities
│   │   └── use_case
│   ├── infra
│   │   └── handler
│   ├── lib.rs
│   ├── main.rs
│   └── use_case
│       └── get_health
└── target
    ├── CACHEDIR.TAG
    └── debug
        ├── build
        ├── deps
        ├── examples
        └── incremental

main.rs Overview:

The main.rs sets up a basic health endpoint. We use tokio::main for asynchronous execution and warp::Filter to define our route.

use test_api::infra::handler::health::get_health_handler;
use tokio;
use warp::Filter;

#[tokio::main]
async fn main() {
    // Define the route for /health
    let get_health_route = warp::path("health")
        .and(warp::get())
        .and_then(get_health_handler);

    // Start the warp server, listening on all interfaces on port 8080
    warp::serve(get_health_route)
        .run(([0, 0, 0, 0], 8080))
        .await;
}

A crucial detail is .run(([0, 0, 0, 0], 8080)). This configuration allows the Docker container to be accessed externally on port 8080, which is essential for deployments to cloud platforms like Google Cloud Run.

2. Dockerizing the Rust API

To ensure our application is portable and deployable across various environments, we’ll containerize it using a Dockerfile. We employ a multi-stage build to create an optimized and smaller final image.

# Stage 1: Builder with Rust + OpenSSL
FROM rust:latest AS builder

# Install necessary development packages for OpenSSL
RUN apt-get update && apt-get install -y pkg-config libssl-dev

WORKDIR /usr/src/test_api

# Copy Cargo files and source code
COPY Cargo.toml ./
COPY src ./src

# Generate lockfile and build release binaries
RUN cargo generate-lockfile
RUN cargo build --release

# Stage 2: Final image with glibc compatibility
FROM debian:bookworm-slim

# Install OpenSSL and minimal runtime dependencies
RUN apt-get update && apt-get install -y libssl3 ca-certificates && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy the compiled binary from the builder stage
COPY --from=builder /usr/src/test_api/target/release/test_api .

# Set environment variable and expose port
ENV PORT=8080
EXPOSE 8080

# Define the command to run the application
CMD ["./test_api"]

The first stage compiles our Rust application. The second stage then copies only the compiled binary into a minimal Debian image, significantly reducing the final image size. We expose port 8080 and define the command to run our API.

3. Automated Deployment with GitLab CI/CD to Google Cloud Run

Automating our deployment process is key to continuous delivery. This GitLab CI/CD configuration will build our Docker image, push it to Google Artifact Registry, and deploy it to Google Cloud Run.

stages:
    - deploy_cloud_run

deploy_cloud_run:
    stage: deploy_cloud_run
    tags: [temp] # Ensure you have appropriate GitLab Runner tags
    environment:
        name: dev
    rules:
        - if: '$CI_COMMIT_BRANCH == "dev" && $CI_PIPELINE_SOURCE == "push"'
          changes:
              - **/*
          when: always
        - when: never # Do not run if above condition is not met
    before_script:
        - mkdir -p "$CI_PROJECT_DIR/.tmp/google-cloud-sdk"
        - curl -sSL "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-438.0.0-linux-x86_64.tar.gz" | tar -xz -C "$CI_PROJECT_DIR/.tmp/google-cloud-sdk" --strip-components=1
        - export PATH="$CI_PROJECT_DIR/.tmp/google-cloud-sdk/bin:$PATH"
        - echo "$SERVICE_ACCOUNT_JSON" | base64 -d > gcloud-key.json
        - gcloud auth activate-service-account --key-file=gcloud-key.json
        - export GCP_PROJECT_ID="your-gcp-project-id" # Replace with your GCP Project ID
        - gcloud config set project "$GCP_PROJECT_ID"
        - gcloud auth configure-docker us-central1-docker.pkg.dev --quiet # Configure Docker to push to Artifact Registry
    script:
        - |
            IMAGE_TAG="$(git rev-parse --short HEAD)"
            IMAGE_URI="us-central1-docker.pkg.dev/your-repo/api/test-api:${IMAGE_TAG}" # Replace 'your-repo'

            docker build \
              -t "$IMAGE_URI" \
              -f test_api/Dockerfile \
              test_api

            docker push "$IMAGE_URI"

            gcloud run deploy api-dev \
              --image "$IMAGE_URI" \
              --platform managed \
              --region us-central1 \
              --allow-unauthenticated \
              --project "$GCP_PROJECT_ID" \
              --set-env-vars "ENVIRONMENT=staging"
    after_script:
        - rm -f gcloud-key.json # Clean up service account key

Key CI/CD Points:

  • rules: The pipeline triggers on pushes to the dev branch when there are changes.
  • before_script: This section downloads and sets up the Google Cloud SDK, authenticates using a service account key (stored as a GitLab CI/CD secret SERVICE_ACCOUNT_JSON), and configures Docker for pushing images to Google Artifact Registry.
  • script:
    • A unique IMAGE_TAG is generated from the Git commit hash.
    • The Docker image is built and tagged with the Artifact Registry URI.
    • The image is pushed to Artifact Registry.
    • gcloud run deploy is used to deploy the containerized application to Google Cloud Run, specifying the image, platform, region, and allowing unauthenticated access. An environment variable ENVIRONMENT is also set.
  • after_script: The service account key file is removed for security.

Required IAM Permissions for Service Account:

The service account used for deployment must have the following roles on your Google Cloud Project:

  • roles/run.admin (Cloud Run Admin)
  • roles/viewer (Project Viewer)
  • roles/iam.serviceAccountUser (Service Account User)

These can be granted using gcloud commands:

gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
  --member="serviceAccount:[email protected]" \
  --role="roles/run.admin"

gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
  --member="serviceAccount:[email protected]" \
  --role="roles/viewer"

gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
  --member="serviceAccount:[email protected]" \
  --role="roles/iam.serviceAccountUser"

Remember to replace YOUR_PROJECT_ID and [email protected] with your actual project ID and service account email.

4. Verifying the Deployment

After a successful GitLab CI/CD pipeline run, you should see output similar to this in your job logs:

Service [api-dev] revision [your-api-00000-pez] has been deployed and is serving 100 percent of traffic.
Service URL: https://your-api-hash-uc.a.run.app

You can then test your deployed API’s health endpoint using curl:

curl https://your-api-hash-uc.a.run.app/health
{"health_status":{"status":"healthy","message":"API is working fine"}}%

If you receive the {"health_status":{"status":"healthy","message":"API is working fine"}} response, your Rust API has been successfully deployed and is running on Google Cloud Run!

Conclusion

This guide has demonstrated a complete workflow for building a Rust API, containerizing it with Docker, and establishing an automated deployment pipeline to Google Cloud Run using GitLab CI/CD. This robust setup enables efficient development and reliable, scalable deployments.

For the full source code of the API, you can refer to the GitHub repository: https://github.com/burgossrodrigo/test_api

We hope this detailed explanation helps you in your Rust and cloud deployment endeavors!

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.
You need to agree with the terms to proceed