Kuinka tehdä minimaalinen Docker-kontti, jossa on vain Rust-ohjelma
Minimaalisessa Docker-kontissa ei ole muuta kuin yksi staattisesti linkitetty ohjelma.
Staattisessa ohjelmassa on kaikki mahdolliset kirjastot otettu mukaan samaan suoritettavaan tiedostoon.
Scratch-image
Kun käytämme pohjana Docker-konttia Scratch
, imagen koko muu tiedostojärjestelmä on tyhjä.
Itse asiassa Docker toteuttaa tämän imagen sisäisesti. Vanhemmissa Dockereissa FROM Scratch
loi oikeasti tyhjän tiedostolayerin, mutta uusimmissa ei luoda erillistä tiedostolayeriä. [5]
Rust-ohjelmamme tulee olemaan tiedostojärjestelmän ainoa tiedosto.
Esimerkkiohjelma
Esimerkkiohjelma käy pyytämässä httpbin.org -palvelusta kysyjän eli meidän ulkoisen IP-osoitteemme merkkijonona, esim. "122.123.124.125". [1]
Haussa käytetään webbikutsun tekevää reqwest
-pakettia ja JSON
-vastauksen aukaisevaa Serde
-pakettia.
use serde::Deserialize;
use std::error::Error;
#[derive(Deserialize, Debug)]
struct ApiRes {
origin: String,
}
fn main() -> Result<(), Box<dyn Error>> {
let res = reqwest::blocking::get("http://httpbin.org/ip")?.json::<ApiRes>()?;
println!("{}", res.origin);
Ok(())
}
Cargo.toml
Projektin kuvauksessa joudutaan asettamaan muutama valinnainen ominaisuus.
Serden ominaisuus "derive" on valinnainen. Sen ja reqwest-craten "json"-ominasuuden avulla web-kutsun paluu-JSON luetaan suoraan kentän "origin" arvoksi.
Web-kutsun tekevän reqwest-paketin oletus on asynkroninen toiminta. Yksinkertaisesti vastausta odottamaan jäävää blocking-ominaisuutta täytyy erikseen pyytää.
[package]
name = "minimal-docker"
version = "0.1.0"
edition = "2018"
[dependencies]
serde = { version = "1.0.130", features = ["derive"] }
reqwest = { version = "0.11.6", default-features = false, features = ["json", "blocking"] }
Käännös
Staattisessa käännöksessä kaikki kirjastot halutaan ohjelman sisään.
Yleensä Linuxissa käännettävät Rust-ohjelmat olettavat koneesta löytyvän dynaamisesti ladattava glibc
-kirjasto. glibc
soveltuu kuitenkin huonosti staattiseen käännökseen. Vaikka glibc
olisi käännetty staattisesti, se haluaa silti ladata muutaman alikirjaston dynaamisesti. [3]
Helpointa on käyttää pienempää MUSL
-kirjastoa ja kääntää se staattisesti ohjelmaan.
Kun valitsemme käännöksen target-arkkitehtuuriksi
x86_64-unknown-linux-musl
saamme käyttöön MUSL
-kirjaston ja staattisen käännöksen.
Tämä käännöstargetin kääntäjät ja kirjastot saadaan käyttöön rustup
-komennolla
rustup target add x86_64-unknown-linux-musl
Lisäksi saatetaan tarvita paketit (Debian/Ubuntu) musl-tools
ja musl-dev
, mutta esimerkissämme näitä ei tarvita.
Imagen luonti
Dockerfile
on seuraavan näköinen (Yksinkertaistettu lähteen [1] esimerkistä.)
#
# Käännösimage
#
FROM rust:latest AS builder
RUN rustup target add x86_64-unknown-linux-musl
ENV USER=minimal-docker
ENV UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
WORKDIR /minimal-docker
COPY ./ .
RUN cargo build --target x86_64-unknown-linux-musl --release
#
# Lopullinen image
#
FROM scratch
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
WORKDIR /minimal-docker
COPY --from=builder minimal-docker/target/x86_64-unknown-linux-musl/release/minimal-docker ./
USER minimal-docker:minimal-docker
CMD ["/minimal-docker/minimal-docker"]
Tämä ajetaan hakemistossa, jossa on Cargo.toml
ja src/main.rs
docker build .
(Vihje: jos olet jo kääntänyt ohjelman paikallisesti, poista hakemisto /target ennen docker buildia. Mahdollisesti unohtunut /target kopioidaan tarpeettomasti käännösimageen. Sen koko on puolisen gigatavua ja kopio voi kestää minuutin verran.)
Yhteenveto
Tuloksena oleva Docker-image on kooltaan 8,14 MB.
Tämä siis sisältää perustavaran, mutta ainoa tiedostojärjestelmän tiedosto on staattisesti linkitetty ohjelma minimal-docker
.
(Ja /etc/passwd ja /etc/group, joiden avulla voimme ajaa ohjelman ei-root-käyttäjänä.)
Windowsissa kontin ajo kestää noin viisi sekuntia.
Jos keksit tavan tehdä vielä pienempiä kontteja, kerro ideasi esa@learners.fi.
Ratkaisun lähdekoodi (kolme tiedostoa) on ladattavissa
https://blog.learners.fi/minimal-docker.zip
Lähteitä
[1] Sylvain Kerkour, How to create small Docker images for Rust, https://kerkour.com/rust-small-docker-image/
[2] Pyry Kontio, Container for building Rust crates for MUSL target https://gitlab.com/rust_musl_docker/image
[3] Miksi glibc ei toimi staattisesti linkitettynä https://stackoverflow.com/questions/57476533/why-is-statically-linking-glibc-discouraged
[4] James Walker, How to Create Your Own Docker Base Images From “Scratch” https://www.howtogeek.com/devops/how-to-create-your-own-docker-base-images-from-scratch/
[5] Scracth-imagen muuttuminen sisäisesti toteutetuksi https://github.com/moby/moby/pull/8827
Päivitys 2024: Vaihdettu James Walkerin linkki toimivaksi.