gvx: 단일 비특권 프로세스에서 Linux 컨테이너, Docker, Kubernetes 실행

서론: 루트 권한이나 호스트 데몬 없이 보안이 강화된 컨테이너
개발 머신, 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 (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), 또는 LinuxPR_SET_NO_NEW_PRIVS+ BPFseccomp를 사용하여 자체 호스트 프로세스를 잠급니다. 모든 게스트 시스템 호출, 가상UID 0권한, 마운트(VFS2), 네트워크 테이블(netstack)은 메모리 안전성이 보장된 GoSentry커널 내부에 엄격하게 제한됩니다.
| 런타임 | 호스트 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는 캐시된 .erofs 이미지 레이어 디스크립터, .ext4 볼륨 파일 디스크립터, /workspace *os.Root 핸들, 그리고 호스트 네트워크 소켓을 사전에 열고, 그다음 hostlockdown.Apply()를 호출합니다:
- 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을 활성화합니다. - macOS XNU (
lockdown_darwin.go):RLIMIT_NPROC = 0을 설정하고,(version 1)(deny default)로 시작하여process-fork,process-exec*, 그리고 모든 새로운 호스트 파일 열기를 차단하는 Seatbelt 프로파일(sandbox_init_with_parameters)을 적용합니다. - Linux (
lockdown_linux.go+pkg/gvx/syscallfilter_linux.go):PR_SET_NO_NEW_PRIVS를 설정하고,SECCOMP_FILTER_FLAG_TSYNC를 통해 모든 스레드에 Sentry BPFseccomp허용 목록을 설치하며,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를 구현하는 네 가지 실행 백엔드를 통해 이 두 가지 문제를 모두 해결합니다:

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 KBR/W/X권한을 강제합니다. CommPage를 통한 API 없는 레지스터 교환:amd64에서 게스트 Ring-3SYSCALL은 파티션 내부의 Ring-0MSR_LSTAR스텁(stubgen_x86.go)으로 진입합니다. 이 스텁은 고속 경로 시스템 호출 GPR(RAX, RDI, RSI, RDX, R10, R8, R9, RIP, RSP)을 공유 4 KBCommPage에 기록하고,HLT를 실행하여WHvRunVirtualProcessor에서 빠져나옵니다.arm64에서 게스트 EL0SVC #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 내부에서 EL1bluepill스텁(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은 EL1VBAR_EL1로 트랩하고, 레지스터를 공유 4 KBCommPage에 기록한 다음,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에 의해 제한됩니다. - 제자리 바이너리 패칭 및
winSysmsgIPC: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부에서 다룹니다.
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 시스템 호출 포괄)를 통해 시스템 호출 번호를 네 가지 내부 도메인으로 디스패치합니다:

| 시스템 호출 도메인 | 대표적인 게스트 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에서의 마운트 역할 | 기반 호스트 리소스 | 실행 시 호스트 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에서 다운로드할 수 있습니다(각 아카이브는 Sigstore 출처 번들과 SHA256SUMS를 포함하는 SLSA 릴리스 파이프라인으로 빌드됨). 또는 go install을 사용하여 소스에서 직접 설치할 수 있습니다:
1# Option A: 사전 빌드된 릴리스를 다운로드하고 SLSA 빌드 출처 검증
2gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
3 --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
4 --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
5shasum -a 256 -c SHA256SUMS --ignore-missing
6tar -xzf rungv-v0.2.0-linux-amd64.tar.gz
7
8# Option B: Go를 통해 직접 설치 (Linux / Windows)
9CGO_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에 따라, Windows, macOS, Linux에서 rungv 내부에 수정되지 않은 docker:dind 컨테이너를 실행하고 호스트의 docker CLI로 제어할 수 있습니다:
1# 1. 전달된 Unix 소켓을 위한 호스트 디렉터리 생성
2mkdir -p ~/.gvx
3
4# 2. ext4lite 볼륨과 함께 gvx 샌드박스 내부에서 docker:dind 시작
5rungv run \
6 -p unix:~/.gvx/docker.sock:2375 \
7 -p 127.0.0.1:9000-9019:9000-9019 \
8 -v gvx-docker:/var/lib/docker \
9 -e DOCKER_TLS_CERTDIR= \
10 docker:dind --tls=false
두 번째 터미널에서 호스트의 docker CLI가 샌드박스 소켓을 가리키도록 설정합니다:
1docker context create gvx --docker host=unix://$HOME/.gvx/docker.sock
2docker context use gvx
3
4docker info --format '{{.Driver}} {{.OperatingSystem}}'
5# 출력: overlayfs Alpine Linux ... (컨테이너화됨)
6
7docker build -t myapp ./app
8docker 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 상태에 도달합니다:
1mkdir -p ~/.gvx/k3s
2
3rungv run \
4 -e IPTABLES_MODE=legacy \
5 -p 127.0.0.1:6443:6443 \
6 -p 127.0.0.1:30000-30019:30000-30019 \
7 -v k3s:/var/lib/rancher \
8 --mount type=bind,src=$HOME/.gvx/k3s,dst=/output \
9 rancher/k3s:v1.31.4-k3s1 server \
10 --disable=traefik,servicelb \
11 --write-kubeconfig=/output/kubeconfig.yaml \
12 --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 컨테이너를 직접 임베드할 수 있습니다:
1package main
2
3import (
4 "context"
5 "fmt"
6 "os"
7
8 "z.gfire.dev/gvx/pkg/gvx"
9)
10
11func main() {
12 ctx := context.Background()
13 sb, err := gvx.New(ctx, gvx.Config{
14 Platform: gvx.PlatformAuto,
15 NetworkMode: gvx.NetModeNAT,
16 OCIImage: "alpine:3.20",
17 WorkspacePath: "./repo",
18 Argv: []string{"/usr/bin/git", "status"},
19 Stdout: os.Stdout,
20 Stderr: os.Stderr,
21 })
22 if err != nil {
23 fmt.Fprintf(os.Stderr, "init sandbox: %v\n", err)
24 os.Exit(1)
25 }
26 defer sb.Close()
27
28 exitCode, err := sb.Run(ctx)
29 if err != nil {
30 fmt.Fprintf(os.Stderr, "run sandbox: %v\n", err)
31 os.Exit(1)
32 }
33 os.Exit(exitCode)
34}
이 시리즈의 추가 읽을거리
- gvx: 하나의 권한 없는 프로세스에서 Linux 컨테이너, Docker, Kubernetes 실행 (본 게시물)
- root 또는 사용자 네임스페이스 없이 Docker, docker build, docker compose 실행:
sudo,unshare(CLONE_NEWUSER),mount(2),/dev/fuse가 차단된 잠긴 샌드박스에서docker:dind를 부트스트랩하는 방법. - gvx를 사용하여 gVisor 및 중첩 샌드박스 내부에서 Docker-in-Docker 실행:
apt-get install docker.io와 중첩된runsc가 다섯 가지 커널 장벽으로 인해gVisor내부에서 실패하는 이유, 그리고gvx가 공유 메모리 futex 루프를 사용하여 중첩된docker:dind를 실행하는 방법. - gvx를 사용한 AI 에이전트 RL 롤아웃을 위한 빠른 격리 Docker 환경: 수백 개의 병렬 권한 없는
docker:dind인스턴스와 100밀리초 copy-on-write.ext4볼륨 리셋으로SWE-bench및 에이전트 강화학습 롤아웃을 확장하는 방법.