Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish images to ghcr.io #3

Merged
merged 2 commits into from
Oct 12, 2023
Merged
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
5 changes: 4 additions & 1 deletion .github/workflows/flowzone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ jobs:
with:
docker_runs_on: >
{
"linux/amd64": ["self-hosted","distro:jammy","X64"]
"linux/amd64": ["self-hosted","X64"],
"linux/arm64": ["self-hosted","ARM64"]
}
docker_images: >
ghcr.io/balena-io-experimental/ctr-jailer
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ Append a build stage to your containers and run them as microVMs with Firecracke

[Firecracker](https://firecracker-microvm.github.io/) is an open source virtualization technology that is purpose-built for creating and managing secure, multi-tenant container and function-based services that provide serverless operational models. Firecracker runs workloads in lightweight virtual machines, called microVMs, which combine the security and isolation properties provided by hardware virtualization technology with the speed and flexibility of containers.

## Benefits

- Privileged containers can be run in an isolated virtual environment
- Container root filesystem is truly ephemeral and [recreated on each restart](#filesystem)
- Container networks are segmented with one TAP/TUN interface per VM
- Allows for runtime secrets by deleting environment files after use
- Ideal for services exposed to the public, without risking the host OS

## Caveats

- The guest container OS must follow [some guidelines](#guest-container)
- Environment variables need to be read from a file, and [are not exported by default](#environment-variables)
- Only one persistent volume is supported right now, and [it needs to be mounted](filesystem)
- Ports can not be exposed without custom iptables rules (TBD)

## Requirements

### Kernel Modules
Expand Down Expand Up @@ -49,9 +64,9 @@ Add the following lines to the end of your existing Dockerfile for publishing.
# The rest of your docker instructions up here AS my-rootfs

# Include firecracker wrapper and scripts
FROM ghcr.io/balena-io/ctr-jailer AS runtime
FROM ghcr.io/balena-io-experimental/ctr-jailer

# Copy the root file system from your container final stage
# Copy the root file system from your existing final stage
COPY --from=my-rootfs / /usr/src/app/rootfs

# Provide your desired command to exec after init.
Expand Down Expand Up @@ -119,7 +134,9 @@ Resources like virtual CPUs and Memory can be overprovisioned and adjusted via t

The default is the maximum available on the host.

### Persistent Storage
The [jailer](https://github.com/firecracker-microvm/firecracker/blob/main/docs/jailer.md) also allows for resource slicing, but that implementation is TBD.

### Filesystem

The root filesystem is recreated on every run, so anything written to the root partition will not persist restarts and
is considered ephemeral similar to container layers.
Expand Down
7 changes: 7 additions & 0 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
target "default" {
platforms = [
"linux/amd64",
"linux/arm64"
]
target = "jailer"
}
23 changes: 17 additions & 6 deletions docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "2.4"

services:
sut:
image: docker:stable
image: alpine:3.18
environment:
DOCKER_HOST: unix:///var/run/docker.sock
COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-ctr-jailer}
Expand All @@ -19,16 +19,27 @@ services:
- /bin/sh
- -c
- |
set -e
apk add --no-cache docker-compose
set -ex
apk add --no-cache docker-cli-compose

count=0
while [ "$$(docker-compose logs | grep "touch /mnt/data/healthy" | wc -l)" -lt 3 ]; do
while true; do

if [ $$count -gt 10 ]; then
echo "Timed out waiting for 3 passed healthchecks"
echo "Timed out waiting for healthchecks to pass"
exit 1
fi
sleep 5

count=$$(($$count + 1))

sleep 3

docker compose logs --no-color alpine-test | grep "touch /mnt/data/healthy" || continue
docker compose logs --no-color debian-test | grep "touch /mnt/data/healthy" || continue
docker compose logs --no-color ubuntu-test | grep "touch /mnt/data/healthy" || continue

break

done

alpine-test:
Expand Down
52 changes: 42 additions & 10 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,32 @@

set -eu

# Accepts a suffix to specify the size in bytes (b), kilobytes (k),
# megabytes (m), gigabytes (g), terabytes (t), petabytes (p), or exabytes (e)
align_to_block_size() {
local _size="${1}"

# Get the numeric part and the suffix of ROOTFS_SIZE
numeric="${1//[bkmgtpeBKMGTPE]/}"
suffix="${1//[0-9]/}"

# Convert provided size to bytes
case ${suffix,,} in
e) bytes=$((numeric*1024*1024*1024*1024*1024*1024)) ;;
p) bytes=$((numeric*1024*1024*1024*1024*1024)) ;;
t) bytes=$((numeric*1024*1024*1024*1024)) ;;
g) bytes=$((numeric*1024*1024*1024)) ;;
m) bytes=$((numeric*1024*1024)) ;;
k) bytes=$((numeric*1024)) ;;
b) bytes=$((numeric)) ;;
*) echo "Invalid suffix in ${1}"; exit 1 ;;
esac

block_size="$(stat -fc %s "${2}")"

echo "$((bytes/block_size*block_size))"
}

populate_rootfs() {
echo "Populating rootfs..."

Expand All @@ -15,11 +41,16 @@ populate_rootfs() {

mkdir -p "$(dirname "${_dst_rootfs}")"
rm -f "${_dst_rootfs}"
mkdir -p "${_rootfs_mnt}"

# truncate -s "$(align_to_block_size "${ROOTFS_SIZE}" "${_rootfs_mnt}")" "${_dst_rootfs}"
truncate -s "${ROOTFS_SIZE}" "${_dst_rootfs}"
mkfs.ext4 -q "${_dst_rootfs}"
mkdir -p "${_rootfs_mnt}"
mount "${_dst_rootfs}" "${_rootfs_mnt}"
mkfs.ext4 "${_dst_rootfs}"

# tune2fs -l "${_dst_rootfs}"
# tune2fs -O ^has_journal "${_dst_rootfs}"

mount -v -t ext4 -o defaults "${_dst_rootfs}" "${_rootfs_mnt}" || { dmesg | tail -5 ; exit 1 ; }

rsync -a "${_src_rootfs}"/ "${_rootfs_mnt}"/
for dir in dev proc run sys var; do mkdir -p "${_rootfs_mnt}/${dir}"; done
Expand Down Expand Up @@ -276,18 +307,19 @@ chroot_dir="${chroot_base}/firecracker/${id}/root"
echo "Creating jailer chroot..."
mkdir -p "${boot_jail}" "${chroot_dir}"/boot
mkdir -p "${data_jail}" "${chroot_dir}"/data

populate_rootfs "${rootfs_src}" "${boot_jail}"/rootfs.ext4
populate_datafs "${data_jail}"/datafs.ext4
setup_networking "${TAP_DEVICE}" "${TAP_IP}" "${HOST_IFACE}"
generate_config "${config_src}" "${boot_jail}"/config.json
create_logs_fifo "${boot_jail}"/logs.fifo /dev/stdout

# Bind mount /jail/boot and /jail/data to /boot and /data in the chroot.
# This way users can mount their own volumes to /jail/boot and /jail/data
# without needing to know the exact path of the chroot.
mount --bind "${boot_jail}" "${chroot_dir}"/boot
mount --bind "${data_jail}" "${chroot_dir}"/data

populate_rootfs "${rootfs_src}" "${chroot_dir}"/boot/rootfs.ext4
populate_datafs "${chroot_dir}"/data/datafs.ext4
setup_networking "${TAP_DEVICE}" "${TAP_IP}" "${HOST_IFACE}"
generate_config "${config_src}" "${chroot_dir}"/boot/config.json
create_logs_fifo "${chroot_dir}"/logs.fifo /dev/stdout

# /usr/local/bin/firecracker --help

echo "Starting firecracker via jailer..."
Expand All @@ -300,4 +332,4 @@ echo "Starting firecracker via jailer..."
-- \
--no-api \
--config-file /boot/config.json \
--log-path logs.fifo
--log-path /boot/logs.fifo
Loading