Skip to content

Latest commit

 

History

History
185 lines (136 loc) · 5.78 KB

File metadata and controls

185 lines (136 loc) · 5.78 KB

Layer 1: Container Runtime

Why Use gVisor?

gVisor is a user-space kernel that implements a significant portion of the Linux kernel surface using Go. Unlike Docker which shares the host kernel, gVisor intercepts application system calls and acts as a protective barrier between the application and the host kernel.

Example - gVisor emulated kernel:

# gVisor reports an emulated kernel version (typically 4.4.x)
# NOT the host kernel version - this confirms isolation

$ docker run --runtime=runsc --rm alpine uname -r
4.4.0

$ uname -r  # On host (different!)
6.8.0-...

Note for Multipass/ARM64 users: gVisor binaries are architecture-specific. The install script auto-detects aarch64 vs x86_64.

Key Benefits:

  • User-space kernel (intercepts syscalls)
  • Prevents kernel exploits (CVE protection)
  • Strong isolation (better than Docker alone)
  • Compatible with standard containers

Comparing Container Runtimes

Different container runtimes offer different security and performance tradeoffs. For multi-tenant sandbox environments where security is paramount, gVisor provides the best balance of strong isolation with reasonable performance overhead.

Feature Docker (runc) gVisor (runsc) Kata Containers
Isolation Namespace User-space kernel Lightweight VM
Performance 5/5 stars 4/5 stars 3/5 stars
Security 3/5 stars 5/5 stars 5/5 stars
Overhead Low Medium High
Best For General use Multi-tenant SaaS Extreme isolation

Installation and Configuration

The following steps install gVisor on your host system. This needs to be done once per host before running sandboxed containers.

1. Install gVisor

# Install gVisor runtime (auto-detects architecture)
(
  set -e
  ARCH=$(uname -m)

  # Map architecture names
  if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
    GVISOR_ARCH="aarch64"
  else
    GVISOR_ARCH="x86_64"
  fi

  echo "Downloading gVisor for $GVISOR_ARCH..."
  URL=https://storage.googleapis.com/gvisor/releases/release/latest/${GVISOR_ARCH}
  wget ${URL}/runsc ${URL}/runsc.sha512
  sha512sum -c runsc.sha512
  rm -f *.sha512
  chmod a+rx runsc
  sudo mv runsc /usr/local/bin
)

Important: Using the wrong architecture binary (e.g., x86_64 on ARM64/M1 Macs) will fail with exec format error.

2. Configure Docker with gVisor

After installation, configure Docker to recognize gVisor as a valid runtime. This allows you to use --runtime=runsc with Docker commands.

File: /etc/docker/daemon.json

{
  "runtimes": {
    "runsc": {
      "path": "/usr/local/bin/runsc",
      "runtimeArgs": ["--platform=ptrace", "--network=none"]
    }
  },
  "default-runtime": "runsc"
}

Restart Docker:

After modifying the daemon configuration, restart Docker to apply the changes. The restart will temporarily stop running containers, so plan this during a maintenance window.

sudo systemctl restart docker

3. Configure Containerd with gVisor

For Kubernetes deployments using containerd as the CRI runtime, configure gVisor as a runtime class. This allows Pods to request gVisor isolation via runtimeClassName: gvisor.

File: /etc/containerd/config.toml

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
  runtime_type = "io.containerd.runsc.v1"

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc.options]
  TypeUrl = "io.containerd.runsc.v1.options"
  ConfigPath = "/etc/containerd/runsc.toml"

File: /etc/containerd/runsc.toml

[runsc_config]
  platform = "ptrace"
  network = "none"
  debug = false
  debug-log = "/var/log/runsc/%ID%/"
  strace = false
  file-access = "exclusive"
  overlay = false
  fsgofer-host-uds = true

  # Disable direct access
  vfs2 = true
  directfs = false

Security Flags

These flags control gVisor's runtime behavior. Choose based on your environment: ptrace for compatibility, KVM for maximum isolation.

--platform=ptrace         # Use ptrace for syscall interception (more compatible)
--platform=kvm            # Use KVM for better isolation (if available)
--network=none            # Disable default networking
--file-access=exclusive   # Exclusive file access (no shared mounts)
--overlay=false           # Disable overlay filesystem
--directfs=false          # Disable direct filesystem access

Verification

After installation, verify that gVisor is working correctly and actually filtering system calls. These tests confirm the security layer is active.

# Test gVisor installation
docker run --runtime=runsc --rm hello-world

# Check gVisor is running
docker run --runtime=runsc --rm alpine uname -r
# Should show: 4.4.0 (or similar gVisor emulated kernel)
# NOT the same as host kernel (use `uname -r` on host to compare)

# Verify syscall filtering
docker run --runtime=runsc --rm alpine cat /proc/kallsyms 2>&1
# Should fail or show limited entries (gVisor filters kernel access)

# Check kernel version differs from host (confirms gVisor isolation)
HOST_KERNEL=$(uname -r)
CONTAINER_KERNEL=$(docker run --runtime=runsc --rm alpine uname -r)
echo "Host: $HOST_KERNEL"
echo "Container: $CONTAINER_KERNEL"
# Should be DIFFERENT values

Common Mistakes to Avoid

These examples show the difference between insecure and secure configurations. Never use the default Docker runtime for untrusted code - it shares the host kernel and provides no real isolation.

Don't use default Docker runtime for multi-tenant workloads

# INSECURE - shares host kernel
docker run ubuntu bash

Do use gVisor for isolation

# SECURE - isolated user-space kernel
docker run --runtime=runsc ubuntu bash