---
id: e62d78ef5850c982dbddc9d3ff9134ae
author: Lemon Mint
title: 'gvx: 단일 비특권 프로세스에서 Linux 컨테이너, Docker, Kubernetes 실행'
description: gvx에 대한 기술 개요로, gVisor 사용자 공간 커널을 기반으로 구축된 보안 강화형 크로스 플랫폼 컨테이너 런타임이자 Go SDK입니다. 이를 통해 단일 비특권 프로세스 내에서 Linux 컨테이너, Docker-in-Docker, 그리고 다중 노드 고가용성(HA) Kubernetes를 안전하게 실행할 수 있으며, Windows, macOS, Linux 전반에서 동작합니다.
language: ko
date: 2026-11-06T12:02:56.008516536Z
path: /blog/posts/gvx-cross-platform-linux-containers-z48916d01
go_package: z.gfire.dev/gvx
go_repourl: https://github.com/gfire-sigs/gvx.git
category: lowlevel
---

## 서론: 루트 권한이나 호스트 데몬 없이 보안이 강화된 컨테이너

개발 머신, CI 러너, 또는 멀티테넌트 클라우드 노드 내부에서 Linux 컨테이너, `docker build`, Kubernetes를 실행하려면 대개 불편한 보안상의 절충이 필요합니다:

- **Linux**에서는 표준 OCI 런타임(`runc`, `crun`, `runsc`)이 `root` 권한, 호스트의 `/var/run/docker.sock` 노출, `--privileged`(`CAP_SYS_ADMIN`), 또는 호스트 커널 공격 표면을 확장하는 사용자 네임스페이스(`unshare(CLONE_NEWUSER)`) 중 하나를 요구합니다.
- **Windows**와 **macOS**에서는 컨테이너 엔진(`Docker Desktop`, `Podman Machine`, `Lima`, `OrbStack`)이 WSL2, Hyper-V 또는 `Virtualization.framework` 내부에서 완전한 Linux 가상 머신 커널을 부팅하고, 해당 VM 내부에서 지속적으로 실행되는 백그라운드 데몬을 구동합니다.

**[gvx](https://github.com/gfire-sigs/gvx)** (`rungv` CLI 및 `z.gfire.dev/gvx/pkg/gvx` Go SDK)는 **보안이 강화되고 권한이 필요 없는 대안**을 제공합니다. Google의 **gVisor** 사용자 공간 Linux 커널(`Sentry`)과 사용자 공간 네트워크 스택(`netstack`)을 상류(upstream) 변경 사항을 추적할 수 있는 포크(fork)로 구축된 `gvx`는, Windows, macOS, Linux에서 수정되지 않은 `linux/amd64` 및 `linux/arm64` ELF64 바이너리와 OCI 컨테이너를 **단일하고, 권한이 없으며, 스스로 잠금 처리된 호스트 프로세스** 내부에서 직접 실행합니다.

> **보안 보장 (3중 심층 방어)**: `gvx`는 호스트 OS 보안 경계를 결코 약화시키지 않으며, 커널 드라이버를 설치하지 않고, 호스트에서 `root`, `sudo`, `--privileged`, 또는 `CLONE_NEWUSER`를 요구하지 않습니다. 단 하나의 게스트 명령어도 실행하기 전에, `gvx`는 Windows Restricted Tokens 및 Job Objects, macOS Seatbelt(`deny default`), 또는 Linux `PR_SET_NO_NEW_PRIVS` + BPF `seccomp`를 사용하여 자체 호스트 프로세스를 잠급니다. 모든 게스트 시스템 호출, 가상 `UID 0` 권한, 마운트(`VFS2`), 네트워크 테이블(`netstack`)은 메모리 안전성이 보장된 Go `Sentry` 커널 내부에 엄격하게 제한됩니다.

| 런타임                     | 호스트 OS                                             |        콜드 스타트 | 메모리 사용량    | 관리자 또는 `sudo` 필요 | 백그라운드 VM 데몬 | 직접 OCI 이미지 실행 |
| :------------------------- | :---------------------------------------------------- | ----------------: | ---------------: | :---------------------- | :----------------- | :------------------- |
| **`gvx` (`whpx` / `hvf`)** | Windows (`amd64`, `arm64`) & macOS (`arm64`)          |          `~20 ms` |        `~30 MiB` | 아니오                  | 아니오             | 예                   |
| **`gvx` (`systrap`)**      | Windows (`amd64`, `arm64`) & Linux (`amd64`, `arm64`) |    `~25~65 ms` |        `~31 MiB` | 아니오                  | 아니오             | 예                   |
| **WSL2 / Docker Desktop**  | Windows 및 macOS                                      |  `800~2500 ms` |      `> 512 MiB` | 예 (초기 설정)          | 예                 | 데몬 필요            |
| **QEMU (전체 시스템)**     | Windows 및 macOS                                      | `1500~4000 ms` |      `> 256 MiB` | 아니오                  | 아니오             | 디스크 이미지 필요   |

Windows나 macOS에서는 어떠한 Linux VM 커널도 부팅되지 않습니다. 호스트에서는 백그라운드 데몬이 실행되지 않습니다. 호스트 OS에서는 `root`, `sudo`, 사용자 네임스페이스(`CLONE_NEWUSER`), `mount(2)`, `TUN`/`TAP` 드라이버, 또는 `CAP_NET_ADMIN` 권한이 필요하지 않습니다. 이 권한이 없는 프로세스 내부에서 `gvx`는 **Docker-in-Docker(`docker:dind`)**, `docker build`, `docker compose`, 그리고 **다중 노드 고가용성 Kubernetes(내장 `etcd` 및 `Calico` CNI를 사용하는 `k3s`)**를 실행합니다.

---

## 컨테이너 수명 주기와 3중 호스트 잠금

모든 `gvx` 샌드박스는 하나의 권한이 없는 호스트 프로세스 내부에서 실행되며, 세 단계의 수명 주기를 따릅니다. 필요한 모든 호스트 파일 디스크립터와 소켓을 사전에 열고, `pkg/hostlockdown`을 통해 호스트 OS 프로세스를 잠그며, 프로세스 내부의 `Sentry` 실행 루프에 진입합니다.

![gvx 컨테이너 수명 주기 및 호스트 자체 잠금](/assets/images/gvx/gvx-lifecycle-lockdown.png)

첫 번째 게스트 명령어로 제어를 넘기기 전에, `gvx`는 캐시된 `.erofs` 이미지 레이어 디스크립터, `.ext4` 볼륨 파일 디스크립터, `/workspace` `*os.Root` 핸들, 그리고 호스트 네트워크 소켓을 사전에 열고, 그다음 `hostlockdown.Apply()`를 호출합니다:

1. **Windows NT (`lockdown_windows.go`)**: `SE_PRIVILEGE_REMOVED`와 함께 `AdjustTokenPrivileges`를 호출하여 제자리에서 권한을 제거하고, Low 무결성 수준(`S-1-16-4096`)의 `DISABLE_MAX_PRIVILEGE` 제한된 위임(impersonation) 토큰을 바인딩하며, `JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 1`로 설정된 익명 Job Object에 프로세스를 할당하여 모든 자식 프로세스 생성을 차단하고, `SetProcessMitigationPolicy`를 통해 `DisallowWin32kSystemCalls = 1`을 활성화합니다.
2. **macOS XNU (`lockdown_darwin.go`)**: `RLIMIT_NPROC = 0`을 설정하고, `(version 1)(deny default)`로 시작하여 `process-fork`, `process-exec*`, 그리고 모든 새로운 호스트 파일 열기를 차단하는 Seatbelt 프로파일(`sandbox_init_with_parameters`)을 적용합니다.
3. **Linux (`lockdown_linux.go` + `pkg/gvx/syscallfilter_linux.go`)**: `PR_SET_NO_NEW_PRIVS`를 설정하고, `SECCOMP_FILTER_FLAG_TSYNC`를 통해 모든 스레드에 Sentry BPF `seccomp` 허용 목록을 설치하며, `execve`, `execveat`, `clone`, `openat`, `socket`에 대해 명시적인 `EPERM` 규칙을 적용합니다.

---

## 플랫폼 간 시스템 호출 가로채기와 4 KB 페이징 (`pkg/sentry/platform`)

Linux ELF64 바이너리는 `mmap(MAP_FIXED)` 세그먼트 매핑을 위해 4 KB(`0x1000`) 메모리 페이지 단위를 전제합니다. 그러나 Windows `VirtualAlloc`과 `MapViewOfFile3`은 64 KB(`0x10000`) 정렬을 강제하며, Apple Silicon macOS(`darwin/arm64`)는 16 KB(`0x4000`) 호스트 페이지를 사용합니다. 더 나아가, 각 OS는 Linux VM을 부팅하지 않고 Linux `SYSCALL`(`amd64`) 및 `SVC #0`(`arm64`) 명령어를 가로채기 위해 서로 다른 메커니즘을 필요로 합니다.

`gvx`는 `pkg/sentry/platform.Context`를 구현하는 네 가지 실행 백엔드를 통해 이 두 가지 문제를 모두 해결합니다:

![gvx 플랫폼 시스템 호출 가로채기 및 4KB 페이징 메커니즘](/assets/images/gvx/gvx-platform-syscalls.png)

### 1. Windows 하드웨어 엔진: `platform/whpx` (`WinHvPlatform.dll`)

`windows/amd64`와 `windows/arm64`에서 `platform/whpx`는 게스트 OS 커널을 부팅하지 않고 `WHvCreatePartition`을 통해 경량 하이퍼바이저 파티션을 생성합니다:

- **64 KB-4 KB 페이징 디커플러**: `whpx`는 `pgalloc.MemoryFile`의 64 MiB 아레나마다 `WHvMapGpaRange`를 통해 파티션에 한 번 등록합니다. 이후의 `MapFile` 및 `Unmap` 호출은 파티션 내부의 게스트 페이지 테이블만 수정하여, Windows의 64 KB 할당 단위에도 불구하고 정확한 4 KB `R/W/X` 권한을 강제합니다.
- **`CommPage`를 통한 API 없는 레지스터 교환**:
  - `amd64`에서 게스트 Ring-3 `SYSCALL`은 파티션 내부의 Ring-0 `MSR_LSTAR` 스텁(`stubgen_x86.go`)으로 진입합니다. 이 스텁은 고속 경로 시스템 호출 GPR(`RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP`)을 공유 4 KB `CommPage`에 기록하고, `HLT`를 실행하여 `WHvRunVirtualProcessor`에서 빠져나옵니다.
  - `arm64`에서 게스트 EL0 `SVC #0`은 EL1 벡터(`stubgen_a64.go`)로 진입하고, `X0..X8, SP_EL0, ELR_EL1, PSTATE`를 `CommPage`에 기록한 다음, 백업되지 않은 GPA 0에서 로드하여 Sentry로 돌아가는 메모리 종료를 트리거합니다.

### 2. macOS 하드웨어 엔진: `platform/hvf` (`Hypervisor.framework`)

Apple Silicon(`darwin/arm64`)에서 `platform/hvf`는 `Hypervisor.framework` vCPU(`hv_vm_create`, `hv_vcpu_run`) 내부에서 컨테이너 스레드를 실행합니다:

- **16 KB 위의 2단계 4 KB MMU 변환**: 2단계(`hv_vm_map`)는 1 GiB 메모리 파일 청크를 16 KB 호스트 페이지 단위로 중간 물리 주소(IPA) 공간에 한 번 매핑하며, 권한은 `HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC`입니다. vCPU 내부에서 EL1 `bluepill` 스텁(`bluepill_arm64.go`)은 `TTBR0_EL1`에서 `TCR_EL1.TG0 = 0b00`(4 KB granule)을 설정하고, 정확한 `R-X` 또는 `RW-`(`UXN=1`) 권한을 가진 4 KB 1단계 페이지 테이블 항목을 채웁니다. ARM64 MMU는 1단계와 2단계 권한의 교집합을 강제하므로, 하드웨어는 정확한 4 KB 경계에서 권한 오류를 트랩합니다.
- **시스템 호출 트랩**: 게스트 EL0 `SVC #0`은 EL1 `VBAR_EL1`로 트랩하고, 레지스터를 공유 4 KB `CommPage`에 기록한 다음, `HVC #0`을 실행하여 `hv_vcpu_run`에서 Go Sentry 스레드로 복귀합니다.

### 3. Windows 사용자 모드 대체 수단: `platform/systrap` (`winsystrap`)

하드웨어 가상화를 사용할 수 없는 경우(예: 중첩된 클라우드 VM 내부), `winsystrap`은 `windows/amd64`와 `windows/arm64`에서 게스트 코드를 사용자 모드로 실행합니다:

- **하드웨어 `CR3` / `TTBR0_EL1` 격리**: 게스트 스레드는 생성된 1 KB 실행 파일(`stubimage_xplat.go`)로부터 부팅된 격리된 스텁 하위 프로세스 내부에서 실행되며, Low 무결성 토큰과 Job Object에 의해 제한됩니다.
- **제자리 바이너리 패칭 및 `winSysmsg` IPC**: `amd64`에서 패처(`patcher_amd64_xplat.go`)는 2바이트 `syscall`(`0f 05`) 명령어를 5바이트 `jmp rel32` 점프(5바이트 공간을 사용할 수 없는 경우 `CC 90` 브레이크포인트 트랩)로 교체하여 고속 경로 아일랜드로 연결합니다. `arm64`에서는 `svc #0`이 아일랜드 슬롯으로의 `B` 분기로 패치됩니다. 아일랜드는 레지스터를 공유 `winSysmsg` 페이지에 기록하고, `WaitOnAddress` 및 `WakeByAddressSingle`을 통해 Sentry와 동기화합니다. 패치되지 않은 트랩과 페이지 폴트는 Windows Debug Object(`WaitForDebugEventEx`)에서 정지합니다.

### 4. Linux 사용자 모드 엔진: `platform/systrap`

`linux/amd64`와 `linux/arm64`에서 `systrap`은 게스트 스레드(`sysmsg`)를 실행합니다. 이 스레드는 `SECCOMP_RET_TRAP`(`SIGSYS`)을 통해 게스트 시스템 호출을 트랩하고, `syscall` 지점을 `SYS_FUTEX`로 동기화되는 공유 메모리 트램펄린(`sysmsg`)으로 바이너리 패치합니다. 한편, 스텁 `syscallThread`(`syscall_thread.go`)는 `SECCOMP_RET_USER_NOTIF`(`_RUN_SECCOMP_LOOP`)를 통해 주소 공간 연산(`mmap`, `munmap`, `clone`)을 실행합니다. `gvx`가 단일 `SECCOMP_FILTER_FLAG_NEW_LISTENER` 슬롯이 `EBUSY`를 반환하는 외부 `gVisor`(`runsc`) 샌드박스 내부에서 실행되면, `gvx`는 자동으로 `syscallThread`를 공유 메모리 futex 루프(`_RUN_SYSCALL_LOOP`)로 전환합니다. 이에 대한 자세한 내용은 [이 시리즈의 3부](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)에서 다룹니다.

---

## `gvx`가 게스트 Linux 시스템 호출을 가상 서브시스템과 호스트 핸들에 매핑하는 방식

플랫폼 엔진이 게스트 `SYSCALL`(`amd64`) 또는 `SVC #0`(`arm64`) 명령어를 트랩하고 게스트 레지스터를 4 KB 공유 페이지(`CommPage`, `sysmsg`, 또는 `winSysmsg`)에 기록하면, 제어권은 Go `Sentry` 태스크 고루틴(`pkg/sentry/kernel/task_syscall.go`)으로 돌아갑니다. Linux 시스템 호출을 기저의 호스트 OS 커널로 전달하는 대신, `Task.doSyscall()`은 원래 레지스터 상태(`SyscallSaveOrig()`)를 저장하고, 게스트 내 BPF `seccomp` 필터와 `ptrace` 정지를 평가한 뒤, `linux.AMD64` 또는 `linux.ARM64`(`pkg/sentry/syscalls/linux/linux64.go`, 약 370개의 Linux 시스템 호출 포괄)를 통해 시스템 호출 번호를 네 가지 내부 도메인으로 디스패치합니다:

![gvx가 게스트 Linux 시스템 호출을 서브시스템과 호스트 OS에 매핑하는 방식](/assets/images/gvx/gvx-syscall-mapping.png)

| 시스템 호출 도메인 | 대표적인 게스트 Linux 시스템 호출 | 내부 `gvx` 서브시스템 (`pkg/sentry`) | 호스트 OS 경계 상호작용 (`hostlockdown` 이후) |
| :--- | :--- | :--- | :--- |
| **A. 프로세스, 네임스페이스, 마운트 및 동기화** | `clone`, `execve`, `unshare`, `setns`, `mount`, `umount2`, `pivot_root`, `setuid`, `capset`, `futex`, `epoll_wait`, `pipe2` | `kernel.Task`, 가상 자격 증명, futex 테이블, 그리고 메모리 내 `VFS2`(`procfs`, `sysfs`, `cgroupfs` v2, `tmpfs`, `overlayfs`) | **호스트 시스템 호출 없음** (100% 메모리 내 Go 상태이며, 호스트 OS에는 완전히 보이지 않음) |
| **B. 가상 메모리 (`MM`)** | `mmap`, `munmap`, `mremap`, `mprotect`, `brk`, `madvise`, `memfd_create` | `mm.MemoryManager` (4 KB VMA 트리) 및 `pgalloc.MemoryFile` | `WHvMapGpaRange`, `hv_vm_map` + `TTBR0_EL1`, 또는 스텁 `mmap`/`mprotect`를 통한 양방향 4 KB 페이지 테이블 업데이트 |
| **C. 파일 및 스토리지 (`VFS2`)** | `openat`, `pread64`, `pwrite64`, `getdents64`, `statx`, `renameat2`, `setxattr`, `mknod`, `inotify_add_watch` | `vfs.VirtualFilesystem` 마운트 테이블 라우터(`fsimpl/erofs`, `fsimpl/ext4lite`, `fsimpl/hostfs` v2) | 고정된 1개의 `.erofs` FD 또는 1개의 `.ext4` FD에 대한 양방향 `pread`/`pwrite`, 또는 `*os.Root`에 대한 범위 지정 상대 경로 탐색 |
| **D. 네트워킹 및 Netfilter** | `socket`, `bind`, `listen`, `connect`, `sendmsg`, `recvmsg`, `netlink`, `setsockopt(IPT_SO_SET_REPLACE)` | 사용자 공간 `pkg/tcpip`(`lo`, `eth0`, `docker0`, `veth`, `vxlan`, `wireguard`, `ipset`, `iptables-legacy`) | 샌드박스 내부 트래픽은 **호스트 시스템 호출을 사용하지 않으며**, 외부 트래픽은 `net.Dial`/`net.Listen`(`-net=nat`) 또는 1개의 UDP 소켓(`-net=wg`)으로 매핑됨 |

핸들러가 `(rval, ctrl, err)`를 반환하면, `Task.doSyscallInvoke()`는 모든 오류를 음수 Linux `errno`로 변환하고(`-ExtractErrno(err, sysno)`), 반환 값을 공유 레지스터 페이지(`amd64`에서는 `RAX`, `arm64`에서는 `X0`)에 기록한 다음 게스트 실행을 재개합니다.

---

## 스토리지(`VFS2`)와 이중 트랙 사용자 공간 네트워킹(`netstack`)

모든 게스트 파일 및 네트워크 작업은 세 개의 `VFS2` 파일 시스템과 두 개의 `pkg/netbackend` 트랙을 통해 Go `Sentry` 내부에서 처리됩니다:

![gvx VFS2 스토리지 엔진 및 이중 트랙 Netstack](/assets/images/gvx/gvx-storage-netstack.png)

| 스토리지 엔진          | `gvx`에서의 마운트 역할                           | 기반 호스트 리소스                     | 실행 시 호스트 `openat` / `NtCreateFile` 호출 | Windows Defender (`WdFilter.sys`) 파일별 검사 |
| :--------------------- | :----------------------------------------------- | :------------------------------------- | --------------------------------------------: | --------------------------------------------: |
| **`fsimpl/erofs`**     | 읽기 전용 OCI 이미지 하위 레이어                 | 레이어당 단일 `<sha256>.erofs` 파일    |                                           `0` |                                           `0` |
| **`fsimpl/ext4lite`**  | 명명된 볼륨 (`-v name:/path`)                    | 볼륨당 단일 `<name>.ext4` 파일         |                                           `0` |                                           `0` |
| **`fsimpl/hostfs` v2** | 호스트 워크스페이스 (`-workspace`, `-v /host:/guest`) | 고정된 `*os.Root` 호스트 디렉터리     |                       캐시되지 않은 열기당 `O(depth)` |                          호스트 파일 닫기당 `1` |

### `ext4lite`가 Docker, 데이터베이스, 패키지 관리자에게 중요한 이유

수만 개의 컨테이너 파일을 Windows NTFS 또는 macOS APFS에 직접 추출하거나 기록하면, 안티바이러스 미니필터 검사(`WdFilter.sys`), 대소문자 구분 충돌, 그리고 POSIX 확장 속성 누락이 발생합니다.

`pkg/sentry/fsimpl/ext4lite`는 사전에 열린 하나의 호스트 파일(Windows에서는 `%LOCALAPPDATA%\gvx\cache\volumes\<name>.ext4`, macOS에서는 `~/Library/Caches/gvx/volumes/<name>.ext4`, Linux에서는 `~/.cache/gvx/volumes/<name>.ext4`) 내부에 순수 Go `ext4` 파일 시스템을 구현하며, 다음 기능을 제공합니다:

- **4 KiB 블록 및 16 MiB 동적 블록 그룹(`meta_bg`)**: 희소 파일 할당 지연 없이 동적으로 증가하며(Linux에서는 `fallocate`, macOS에서는 `fcntl(F_PREALLOCATE)`, Windows에서는 `FileAllocationInfo`), 정상 언마운트 시 사용되지 않는 후미 그룹을 압축합니다.
- **JBD2 순서 모드 저널(inode 8)**: CRC-32 체크섬과 revoke 블록을 포함한 원자적 트랜잭션 커밋, 그리고 `fsckLite` 복구를 제공합니다.
- **`overlayfs` 상위 레이어 지원**: `trusted.overlay.*` 및 `security.capability` 확장 속성, 문자 장치 whiteout(`0:0`), `d_type` 디렉터리 항목 보고, 그리고 `RENAME_EXCHANGE`를 구현합니다.

---

## 실제 워크로드: Docker 엔진, Kubernetes(`k3s` + `Calico`), Go SDK

### `rungv` 설치: SLSA 인증 사전 빌드 릴리스 또는 `go install`

`linux/amd64`, `linux/arm64`, `darwin/arm64`, `windows/amd64`, `windows/arm64`용 사전 빌드된 `rungv` 아카이브를 [GitHub Releases](https://github.com/gfire-sigs/gvx/releases)에서 다운로드할 수 있습니다(각 아카이브는 Sigstore 출처 번들과 `SHA256SUMS`를 포함하는 SLSA 릴리스 파이프라인으로 빌드됨). 또는 `go install`을 사용하여 소스에서 직접 설치할 수 있습니다:

```sh
# Option A: 사전 빌드된 릴리스를 다운로드하고 SLSA 빌드 출처 검증
gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
  --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
  --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
shasum -a 256 -c SHA256SUMS --ignore-missing
tar -xzf rungv-v0.2.0-linux-amd64.tar.gz

# Option B: Go를 통해 직접 설치 (Linux / Windows)
CGO_ENABLED=0 go install z.gfire.dev/gvx/cmd/rungv@latest
```

> macOS(`darwin/arm64`)에서 사전 빌드된 릴리스 아카이브는 이미 `com.apple.security.hypervisor` 권한으로 서명되어 있습니다(`xattr -d com.apple.quarantine rungv`는 브라우저 격리 속성을 제거합니다). macOS에서 `go install` 또는 `go build`로 로컬에서 빌드하는 경우, `codesign --sign - --entitlements entitlements.plist --force $(which rungv)`로 바이너리에 서명하십시오.

### 1. 권한이 없는 Docker 엔진 실행 (`docker:dind`)

[`docs/docker-engine.md`](https://github.com/gfire-sigs/gvx/blob/main/docs/docker-engine.md)에 따라, Windows, macOS, Linux에서 `rungv` 내부에 수정되지 않은 `docker:dind` 컨테이너를 실행하고 호스트의 `docker` CLI로 제어할 수 있습니다:

```sh
# 1. 전달된 Unix 소켓을 위한 호스트 디렉터리 생성
mkdir -p ~/.gvx

# 2. ext4lite 볼륨과 함께 gvx 샌드박스 내부에서 docker:dind 시작
rungv run \
  -p unix:~/.gvx/docker.sock:2375 \
  -p 127.0.0.1:9000-9019:9000-9019 \
  -v gvx-docker:/var/lib/docker \
  -e DOCKER_TLS_CERTDIR= \
  docker:dind --tls=false
```

두 번째 터미널에서 호스트의 `docker` CLI가 샌드박스 소켓을 가리키도록 설정합니다:

```sh
docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
docker context use gvx

docker info --format '{{.Driver}} {{.OperatingSystem}}'
# 출력: overlayfs Alpine Linux ... (컨테이너화됨)

docker build -t myapp ./app
docker compose up -d
```

### 2. 권한이 없는 샌드박스에서 Kubernetes(`k3s` + `Calico`) 실행

`gvx`는 `/dev/kmsg`, `cgroup` v2(`memory.max` OOM 킬러 및 `cpu.max` 대역폭 스로틀링), `/proc/diskstats`, `wireguard` 및 `vxlan` netlink 장치, `ipset`, 그리고 `iptables-legacy`(`addrtype`, `conntrack`, `statistic`, `recent`)를 구현하므로, 수정되지 않은 `rancher/k3s` 이미지는 단일 `rungv` 프로세스 내부에서 `Ready` 상태에 도달합니다:

```sh
mkdir -p ~/.gvx/k3s

rungv run \
  -e IPTABLES_MODE=legacy \
  -p 127.0.0.1:6443:6443 \
  -p 127.0.0.1:30000-30019:30000-30019 \
  -v k3s:/var/lib/rancher \
  --mount type=bind,src=$HOME/.gvx/k3s,dst=/output \
  rancher/k3s:v1.31.4-k3s1 server \
  --disable=traefik,servicelb \
  --write-kubeconfig=/output/kubeconfig.yaml \
  --write-kubeconfig-mode=644
```

여러 `gvx` 노드를 WireGuard(`flannel-backend=wireguard-native` 또는 `wg0` 메시)를 통해 연결하여 내장 `etcd` 고가용성을 구성하거나, **Calico**(`calico-vxlan.yaml`과 `FELIX_IPTABLESBACKEND=Legacy`)를 실행하여 Kubernetes `NetworkPolicy`를 사용자 공간 내에서 완전히 적용할 수도 있습니다.

### 3. `z.gfire.dev/gvx/pkg/gvx`를 통한 Go 내 Linux 컨테이너 임베딩

Go 애플리케이션은 외부 컨테이너 데몬 없이 Linux 컨테이너를 직접 임베드할 수 있습니다:

```go
package main

import (
	"context"
	"fmt"
	"os"

	"z.gfire.dev/gvx/pkg/gvx"
)

func main() {
	ctx := context.Background()
	sb, err := gvx.New(ctx, gvx.Config{
		Platform:      gvx.PlatformAuto,
		NetworkMode:   gvx.NetModeNAT,
		OCIImage:      "alpine:3.20",
		WorkspacePath: "./repo",
		Argv:          []string{"/usr/bin/git", "status"},
		Stdout:        os.Stdout,
		Stderr:        os.Stderr,
	})
	if err != nil {
		fmt.Fprintf(os.Stderr, "init sandbox: %v\n", err)
		os.Exit(1)
	}
	defer sb.Close()

	exitCode, err := sb.Run(ctx)
	if err != nil {
		fmt.Fprintf(os.Stderr, "run sandbox: %v\n", err)
		os.Exit(1)
	}
	os.Exit(exitCode)
}
```

---

## 이 시리즈의 추가 읽을거리

1. **[gvx: 하나의 권한 없는 프로세스에서 Linux 컨테이너, Docker, Kubernetes 실행](/blog/posts/gvx-cross-platform-linux-containers-z48916d01)** (본 게시물)
2. **[root 또는 사용자 네임스페이스 없이 Docker, docker build, docker compose 실행](/blog/posts/run-docker-without-root-or-userns-gvx-z48916d02)**: `sudo`, `unshare(CLONE_NEWUSER)`, `mount(2)`, `/dev/fuse`가 차단된 잠긴 샌드박스에서 `docker:dind`를 부트스트랩하는 방법.
3. **[gvx를 사용하여 gVisor 및 중첩 샌드박스 내부에서 Docker-in-Docker 실행](/blog/posts/docker-in-docker-inside-gvisor-with-gvx-z48916d03)**: `apt-get install docker.io`와 중첩된 `runsc`가 다섯 가지 커널 장벽으로 인해 `gVisor` 내부에서 실패하는 이유, 그리고 `gvx`가 공유 메모리 futex 루프를 사용하여 중첩된 `docker:dind`를 실행하는 방법.
4. **[gvx를 사용한 AI 에이전트 RL 롤아웃을 위한 빠른 격리 Docker 환경](/blog/posts/fast-docker-environments-for-agent-rl-rollouts-z48916d04)**: 수백 개의 병렬 권한 없는 `docker:dind` 인스턴스와 100밀리초 copy-on-write `.ext4` 볼륨 리셋으로 `SWE-bench` 및 에이전트 강화학습 롤아웃을 확장하는 방법.
