Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
409 changes: 409 additions & 0 deletions docs/guides/deploy-a-rust-app.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions examples/hello-rust/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Build artifacts produced locally; not committed.
/target/
.unikraft/
9 changes: 9 additions & 0 deletions examples/hello-rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "hello-rust"
version = "0.1.0"
edition = "2021"

[profile.release]
strip = true
opt-level = "z"
lto = true
45 changes: 45 additions & 0 deletions examples/hello-rust/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Multi-stage build for the static-PIE Rust runtime proof.
#
# Stage 1 builds against the x86_64-unknown-linux-musl target, which produces a
# fully static AND position-independent (PIE) ELF by default (Rust >= 1.46). The
# app IS the kraftcloud entrypoint, so there is no separate dynamic interpreter
# for the elfloader's PIE gate to reject at boot -- the exact trap the prior
# non-PIE bun image hit. A self-check FAILS the build unless the binary is an
# ET_DYN (PIE) statically-linked ELF with no program interpreter, so a bad shape
# can never be published.
#
# Stage 2 is a FROM scratch rootfs holding just the binary; `kraft cloud deploy`
# turns this rootfs + the base:latest runtime into a kraftcloud unikernel.
FROM rust:1.83-bookworm AS build
RUN apt-get update && apt-get install -y --no-install-recommends binutils file \
&& rm -rf /var/lib/apt/lists/*
RUN rustup target add x86_64-unknown-linux-musl
WORKDIR /src
COPY Cargo.toml ./
COPY src ./src
RUN cargo build --release --target x86_64-unknown-linux-musl
RUN cp target/x86_64-unknown-linux-musl/release/hello-rust /server

# Static-PIE self-check: fail the build unless the binary is ET_DYN (PIE) AND
# statically linked AND has no INTERP segment. A dynamically-linked binary or one
# with a program interpreter would fail to boot on the elfloader (like bun did).
RUN echo "=== readelf -h /server ===" && readelf -h /server && \
echo "=== file /server ===" && file /server && \
echo "=== readelf -l (INTERP check) ===" && readelf -l /server | grep -i interp || true
RUN readelf -h /server | grep -q 'Type:[[:space:]]*DYN' \
|| (echo "FAIL: binary is not ET_DYN (not PIE)"; exit 1)
# musl static-PIE binaries are reported by `file` as "static-pie linked"; a real
# dynamic binary says "dynamically linked". Accept the former, reject the latter.
RUN if file /server | grep -q 'dynamically linked'; then \
echo "FAIL: binary is dynamically linked"; exit 1; \
fi
RUN file /server | grep -qE 'static-pie linked|statically linked' \
|| (echo "FAIL: binary is not statically linked / static-pie"; exit 1)
RUN if readelf -l /server | grep -qi 'INTERP'; then \
echo "FAIL: binary has a program interpreter (INTERP segment)"; exit 1; \
fi
RUN echo "OK: /server is a static-PIE ELF (ET_DYN, static-pie linked, no INTERP)"

FROM scratch
COPY --from=build /server /server
ENTRYPOINT ["/server"]
20 changes: 20 additions & 0 deletions examples/hello-rust/Kraftfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Kraftfile for the static-PIE Rust runtime proof.
#
# A static-PIE Rust binary (x86_64-unknown-linux-musl), packaged FROM scratch, run
# on the base:latest runtime (app-elfloader). The app IS the binary -- there is no
# dynamic interpreter for the elfloader's PIE gate to reject (the trap the prior
# non-PIE bun image hit). `rootfs: ./Dockerfile` makes kraft build the rootfs from
# the multi-stage Dockerfile.
#
# Build/push (push-only, do not start):
# kraft cloud --metro <metro>/v1 --token <token> \
# --buildkit-host docker-container://buildkit \
# deploy --no-start -M 512 --name hello-rust \
# --runtime base:latest --rootfs ./Dockerfile .
spec: v0.6

runtime: base:latest

rootfs: ./Dockerfile

cmd: ["/server"]
40 changes: 40 additions & 0 deletions examples/hello-rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# hello-rust

A minimal Rust HTTP service packaged as a Unikraft unikernel and deployed on Datum
compute. It responds `Hello from Datum (Rust)` on `/` and `ok` on `/healthz`,
listening on `$PORT` (default `8080`).

This is the runnable companion to the step-by-step guide:
[Deploy a Rust Web Service on Datum Compute](../../docs/guides/deploy-a-rust-app.md).

## Files

- `src/main.rs` — the service (standard library only, no dependencies).
- `Cargo.toml` — package definition with a size-optimized release profile.
- `Dockerfile` — multi-stage build producing a fully static, position-independent
(`x86_64-unknown-linux-musl`) binary packaged `FROM scratch`. Includes a build-time
self-check that fails unless the binary is a static PIE, so a wrong-shaped binary
can never be published.
- `Kraftfile` — packages the rootfs on the `base:latest` runtime.
- `workload.yaml` — the Datum compute Workload manifest.

## Quick start

```sh
# 1. Build and publish the image (kraft builds + pushes; it does not run it).
kraft cloud --metro "$UKC_METRO" --token "$UKC_TOKEN" \
--buildkit-host docker-container://buildkit \
deploy --no-start -M 512 --name hello-rust \
--runtime base:latest --rootfs ./Dockerfile .

# 2. Deploy on Datum compute.
datumctl compute deploy -f workload.yaml -y

# 3. Verify.
datumctl compute instances --workload=hello-rust
curl https://<EXTERNAL-IP>/
```

See the [guide](../../docs/guides/deploy-a-rust-app.md) for prerequisites, why the binary
must be a static PIE (the `static-pie linked` vs `dynamically linked` distinction),
and troubleshooting.
82 changes: 82 additions & 0 deletions examples/hello-rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Minimal std-only HTTP service used to prove the compiled static-PIE Rust path
// on Datum compute's Unikraft app-elfloader runtime (base:latest).
//
// Dependency-light on purpose: only std::net so the runtime question is isolated
// from any async-runtime / FFI variables. Rust's x86_64-unknown-linux-musl target
// builds a fully static, position-independent (PIE) ELF -- exactly the shape the
// elfloader requires and the property the prior non-PIE bun image lacked.

use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};

fn main() {
let port = std::env::var("PORT").unwrap_or_else(|_| "8080".to_string());
let addr = format!("0.0.0.0:{port}");

let listener = TcpListener::bind(&addr).unwrap_or_else(|e| {
eprintln!("failed to bind {addr}: {e}");
std::process::exit(1);
});

println!("listening on :{port}");

for stream in listener.incoming() {
match stream {
Ok(stream) => {
// Handle connections serially; the workload is a liveness probe,
// not a load target, so a single-threaded accept loop is enough.
if let Err(e) = handle(stream) {
eprintln!("connection error: {e}");
}
}
Err(e) => eprintln!("accept error: {e}"),
}
}
}

fn handle(mut stream: TcpStream) -> std::io::Result<()> {
// Read until end of request headers (CRLFCRLF) or the buffer fills. We only
// need the request line to route, so we never consume a body.
let mut buf = [0u8; 4096];
let mut filled = 0usize;
loop {
if filled == buf.len() {
break;
}
let n = stream.read(&mut buf[filled..])?;
if n == 0 {
break;
}
filled += n;
if buf[..filled].windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
}

let req = String::from_utf8_lossy(&buf[..filled]);
let path = req
.lines()
.next()
.and_then(|line| line.split_whitespace().nth(1))
.unwrap_or("/");

let body = match path {
"/healthz" => "ok",
_ => "Hello from Datum (Rust)",
};

let response = format!(
"HTTP/1.1 200 OK\r\n\
Content-Type: text/plain; charset=utf-8\r\n\
Content-Length: {}\r\n\
Connection: close\r\n\
\r\n\
{}",
body.len(),
body
);

stream.write_all(response.as_bytes())?;
stream.flush()?;
Ok(())
}
33 changes: 33 additions & 0 deletions examples/hello-rust/workload.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
apiVersion: compute.datumapis.com/v1alpha
kind: Workload
metadata:
name: hello-rust
labels:
app: hello-rust
spec:
template:
metadata:
labels:
app: hello-rust
spec:
runtime:
resources:
instanceType: datumcloud/d1-standard-2
sandbox:
containers:
- name: app
image: index.unikraft.io/datum/hello-rust:latest
ports:
- name: http
port: 8080
protocol: TCP
networkInterfaces:
- network:
name: default
placements:
- name: default
cityCodes:
- DFW
scaleSettings:
minReplicas: 1
instanceManagementPolicy: OrderedReady
Loading