Fuwari Banner
지니제스트Tech Archive
Windows7분 소요

WSL2 디스크 용량 부족 해결

ggeniezst

WSL2와 Docker 사용 중 C 드라이브 용량을 잠식하는 ext4.vhdx 파일의 동적 확장 원인을 분석하고, diskpart 수동 압축부터 최신 sparse VHD 및 autoMemoryReclaim 자동 축소 설정까지 완벽 정리합니다.

Sponsored

Windows 11 또는 10 환경에서 WSL2(Windows Subsystem for Linux 2)와 Docker Desktop을 운용하다 보면 어느 순간 C 드라이브 잔여 공간이 수십 GB 단위로 증발하는 현상을 겪게 됩니다. 원인을 추적해 보면 AppData 경로에 위치한 가상 디스크 이미지 파일인 ext4.vhdx가 50GB에서 100GB 이상으로 비대해진 상태를 확인하게 됩니다.

급한 마음에 리눅스 터미널에 접속하여 대용량 로그나 사용하지 않는 Docker 이미지를 삭제하고 df -h로 기가바이트 단위의 여유 공간을 확보하더라도, Windows 파일 탐색기에서 확인하는 ext4.vhdx의 물리적 파일 크기는 단 1바이트도 줄어들지 않습니다. 리눅스 디스크 삭제 파일 핸들 정리법에서 다루었던 프로세스 언링크 문제와 달리, 이는 하이퍼바이저와 가상 디스크 포맷의 블록 할당 메커니즘에서 기인합니다.

WSL2의 가상 디스크 크기가 줄어들지 않는 구조적 이유를 짚어보고, diskpart를 통해 즉각 물리 디스크 공간을 회수하는 오프라인 압축 절차와 최신 WSL 2.0 엔진의 Sparse VHD 기능을 통한 자동 용량 회수 설정법을 단계별로 정리합니다.


VHDX 동적 할당 구조와 디스크 미회수 원인

WSL2는 Hyper-V 가상화 기술을 기반으로 경량 유틸리티 가상 머신(VM) 위에서 독립된 리눅스 커널을 구동합니다. 이때 게스트 리눅스 배포판의 루트 파일시스템 전체는 Windows 호스트 상의 단일 가상 하드 디스크 파일인 ext4.vhdx 내부에 저장됩니다.

동적 확장 디스크의 단방향 할당 메커니즘

Hyper-V의 VHDX 포맷은 초기 생성 시 최소 크기(수백 MB)로 시작하여 데이터가 추가될 때마다 최대 지정 용량(기본 1TB)까지 호스트 디스크의 물리 블록을 요청하는 동적 확장(Dynamically Expanding) 방식을 취합니다.

문제는 블록 할당이 단방향으로만 동작한다는 점입니다. 게스트 리눅스 OS 내부에서 rm -rf 명령으로 파일을 삭제하면 ext4 파일시스템 내부의 아이노드(inode)와 데이터 블록 비트맵만 여유(Free) 상태로 마킹됩니다. 호스트 운영체제인 Windows와 가상화 레이어는 게스트 OS 내부의 특정 블록이 비워졌다는 사실을 실시간으로 통지받지 못하므로, 이미 호스트 물리 디스크로부터 획득한 VHDX 파일의 크기를 자동으로 줄여주지 않습니다.

비교 항목 전통적 동적 확장 VHDX Sparse VHD (최신 WSL2)
블록 할당 방식 게스트 쓰기 요청 시 물리 블록 영구 추가 가상 주소 공간에 씬 프로비저닝(Thin Provisioning)
게스트 파일 삭제 시 ext4 메타데이터만 갱신 (물리 크기 고정) TRIM/UNMAP 신호를 통해 호스트에 블록 반환
호스트 용량 회수 절차 WSL 전체 종료 후 diskpart 오프라인 수동 축소 파일시스템 레벨에서 온라인 실시간 자동 회수
요구 운영체제 환경 WSL2 초기 버전부터 Windows 10/11 공통 지원 Windows 11 22H2 및 WSL v2.0.0 이상 필수
I/O 성능 영향 블록 확장 완료 후 안정적인 순차 쓰기 유지 동적 홀 펀칭(Hole Punching) 처리 시 미세한 I/O 소요

따라서 가상 디스크의 물리적 크기를 호스트 스토리지에 환원하려면, 게스트 내부에서 미사용 블록에 TRIM 신호를 발생시키고 Windows에서 가상 디스크 내 빈 공간을 찾아내 파일 크기를 재정렬하는 명시적인 압축 작업이 반드시 수반되어야 합니다.


WSL2 내부 파일시스템 정리 및 TRIM 신호 전송

Windows 호스트에서 ext4.vhdx를 압축하기 전에, 먼저 리눅스 게스트 환경 내부의 임시 파일, 패키지 캐시, 그리고 Docker 컨테이너 찌꺼기를 말끔히 청소하여 압축 가능한 '빈 공간'을 최대로 확보해야 합니다.

캐시 및 잔여 패키지 제거

WSL2 터미널을 열고 패키지 매니저 캐시와 임시 파일들을 정리합니다:

BASH
# APT 패키지 캐시 및 불필요한 의존성 패키지 삭제
sudo apt-get clean
sudo apt-get autoremove -y

# systemd 저널 로그 중 3일 이상 경과한 오래된 기록 정리
sudo journalctl --vacuum-time=3d

# 사용자 홈 디렉터리 임시 캐시 확인 및 비우기
rm -rf ~/.cache/*

Docker Ollama 환경 구축 가이드 등 다양한 컨테이너를 빌드하고 테스트했다면 Docker 빌드 캐시와 중지된 레이어가 상당한 용량을 차지하고 있을 확률이 높습니다. 다음 명령으로 미사용 Docker 리소스를 일괄 제거합니다:

BASH
# 중지된 컨테이너, 미사용 네트워크, 댕글링 이미지 및 빌드 캐시 강제 삭제
docker system prune -a --volumes -f

fstrim 명령어로 미사용 블록 해제 알림

파일을 디스크에서 지운 후 ext4 파일시스템에 해당 블록이 비었음을 스토리지 계층에 명시적으로 알리기 위해 fstrim 명령어를 실행합니다:

BASH
# 마운트된 모든 활성 파일시스템의 미사용 블록에 TRIM 명령 전달
sudo fstrim -av

터미널 출력으로 /: xx GiB trimmed 형태의 메시지가 출력되면, 리눅스 커널이 가상 디스크 컨트롤러에 빈 블록의 범위를 성공적으로 통지한 것입니다.


diskpart 유틸리티를 활용한 오프라인 VHDX 수동 압축

게스트 내부 정리가 완료되었다면 Windows의 표준 디스크 파티션 관리 유틸리티인 diskpart를 사용하여 ext4.vhdx의 빈 블록을 잘라내는 작업을 수행합니다.

1단계: WSL 프로세스 완전 종료

가상 디스크가 사용 중인 상태에서는 압축 명령이 실행되지 않거나 파일 시스템이 손상될 수 있습니다. 관리자 권한의 PowerShell 터미널을 실행하고 실행 중인 모든 WSL 인스턴스를 강제 종료합니다:

POWERSHELL
# 실행 중인 모든 WSL2 배포판 종료
wsl --shutdown

# 상태가 'Stopped'인지 확인
wsl --list --verbose

Docker Desktop을 사용 중인 경우 시스템 트레이에서 Docker 아이콘을 우클릭하여 완전히 종료(Quit Docker Desktop)해야 파일 락(File Lock) 충돌을 방지할 수 있습니다.

2단계: 대상 ext4.vhdx 파일 경로 확인

일반적인 Ubuntu 배포판의 가상 디스크는 로컬 앱데이터 패키지 폴더 내부에 위치합니다:

POWERSHELL
# Ubuntu 배포판 VHDX 기본 경로
$vhdxPath = "$env:LOCALAPPDATA\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\ext4.vhdx"

# 만약 Docker Desktop WSL 엔진을 사용하는 경우
# $vhdxPath = "$env:LOCALAPPDATA\Docker\wsl\data\ext4.vhdx"

# 압축 전 파일 용량 확인 (GB 단위)
(Get-Item $vhdxPath).Length / 1GB

배포판의 고유 패키지 식별자가 기억나지 않는다면 PowerShell에서 Get-ChildItem -Path "$env:LOCALAPPDATA\Packages" -Filter "ext4.vhdx" -Recurse 명령을 통해 정확한 절대 경로를 탐색할 수 있습니다.

3단계: diskpart 실행 및 읽기 전용 압축 수행

PowerShell에서 diskpart를 입력하여 대화형 파티션 셸로 진입한 후 아래 명령을 순서대로 입력합니다:

CMD
rem 1. 가상 디스크 파일 선택 (본인의 실제 경로로 변경)
select vdisk file="C:\Users\username\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\ext4.vhdx"

rem 2. 디스크를 읽기 전용 모드로 호스트에 마운트
attach vdisk readonly

rem 3. 가상 디스크 압축 실행 (진행률 표시)
compact vdisk

rem 4. 가상 디스크 마운트 해제
detach vdisk

rem 5. diskpart 종료
exit

⚠️ 주의: attach vdisk 단계에서 반드시 readonly 플래그를 지정해야 합니다. 읽기 전용 옵션 없이 마운트하면 Windows 호스트가 ext4 파티션을 인식하지 못하고 '디스크를 포맷해야 합니다'라는 경고창을 띄우거나 파일시스템 메타데이터를 훼손할 위험이 있습니다.

DiskPart가 가상 디스크 파일을 압축했습니다. 메시지가 나타나면 작업이 완료된 것입니다.


WSL 2.0+ Sparse VHD 및 자동 메모리/디스크 회수 구성

Windows 11 22H2 이상의 환경과 최신 WSL 릴리스(v2.0.0 이상)를 사용 중이라면, 매번 번거롭게 diskpart를 실행할 필요 없이 Sparse VHD(스파스 가상 디스크) 설정과 autoMemoryReclaim 옵션을 통해 디스크 공간을 자동으로 회수할 수 있습니다.

기존 VHDX를 Sparse 모드로 변환

기본 생성된 VHDX는 일반 동적 디스크이므로, WSL CLI를 통해 해당 배포판의 가상 디스크 속성을 스파스 모드로 전환해주어야 합니다:

POWERSHELL
# 현재 WSL 버전 확인 (2.0.0 이상 필요)
wsl --version

# 실행 중인 WSL 종료
wsl --shutdown

# 특정 배포판(예: Ubuntu)의 VHDX를 Sparse 모드로 활성화
wsl --manage Ubuntu --set-sparse true

이 명령어는 기존 VHDX 파일을 읽어 빈 공간을 호스트 파일시스템 상에서 즉시 천공(Hole Punching) 처리하여 실제 디스크 점유 용량을 슬림하게 줄여줍니다.

.wslconfig 파일에 자동 회수 정책 정의

사용자 홈 디렉터리(C:\Users\<사용자명>\.wslconfig)에 전역 WSL 설정 파일을 작성하거나 편집하여 리눅스 게스트에서 파일 삭제 시 호스트 공간이 실시간으로 반환되도록 구성합니다:

INI
# %USERPROFILE%\.wslconfig
[wsl2]
# WSL2 VM에 할당할 최대 메모리 제한
memory=8GB
# 사용 가능한 논리 프로세서 코어 수
processors=4

[experimental]
# 유휴 메모리 및 디스크 여유 공간 자동 회수 (dropcache 또는 gradual)
autoMemoryReclaim=dropcache
# 신규 생성되는 VHDX의 스파스 모드 기본 활성화
sparseVhd=true

설정을 저장한 후 PowerShell에서 wsl --shutdown을 실행하여 백그라운드 가상 머신 인스턴스를 재시동하면 새로운 설정이 즉시 반영됩니다.

💡 Tip: autoMemoryReclaim=dropcache 옵션을 활성화하면 WSL2 내부에서 대용량 패키지 빌드나 파일 복사 작업이 끝난 후 캐시된 페이지 메모리를 호스트로 신속하게 반환하며, sparseVhd=true와 연계되어 파일 삭제 시 fstrim이 주기적으로 자동 구동되어 디스크 공간이 백그라운드에서 실시간 압축됩니다.


트러블슈팅 FAQ 및 운영 시 고려사항

가상 디스크 압축 및 자동화 구성 과정에서 실무 개발자들이 빈번히 마주치는 오류와 예외 상황에 대한 해결책입니다.

Q1. compact vdisk 실행 시 '프로세스가 파일에 액세스할 수 없습니다' 오류가 발생합니다.

Windows 백그라운드 서비스나 WSL 관련 프로세스가 여전히 ext4.vhdx 파일을 점유하고 있을 때 발생합니다. 작업 관리자에서 vmmem 또는 vmmemWSL 프로세스가 살아있는지 확인하고, 관리자 권한 PowerShell에서 Get-Service LxssManager | Restart-Service를 실행하여 WSL 하위 시스템 서비스를 완전히 재시작한 후 재시도합니다.

Q2. Docker Desktop 환경에서는 어떤 VHDX 파일을 압축해야 하나요?

WSL2 백엔드를 사용하는 Docker Desktop은 두 개의 가상 머신 배포판을 생성합니다:

  • docker-desktop: 도커 엔진 구동용 런타임 배포판 (수백 MB 수준)
  • docker-desktop-data: 이미지 레이어, 컨테이너 볼륨, 로컬 스토리지가 저장되는 데이터 배포판

실제 수십 GB의 공간을 차지하는 주범은 docker-desktop-data입니다. 해당 파일은 일반적으로 %LOCALAPPDATA%\Docker\wsl\data\ext4.vhdx 경로에 존재하므로 이 파일을 대상으로 압축을 진행해야 합니다.

Q3. diskpart 압축을 완료했는데 파일 크기가 거의 줄어들지 않았습니다.

WSL 내부에서 실제 파일만 삭제하고 파일시스템 블록 해제(sudo fstrim -av)를 사전에 실행하지 않았거나, 리눅스 상에서 삭제한 파일의 디스크립터를 프로세스가 물고 있는 Unlinked Open File 상태일 수 있습니다. 게스트 내부에서 lsof | grep deleted로 잔류 핸들을 점검하고 fstrim을 실행한 뒤 다시 호스트에서 압축을 시도해야 합니다.

Q4. Windows 10 환경에서도 Sparse VHD 설정을 사용할 수 있나요?

sparseVhdautoMemoryReclaim 기능은 Windows 11 22H2 빌드 이상에 내장된 신규 Hyper-V 스토리지 가상화 API를 요구합니다. Windows 10 환경에서는 실험적 기능 플래그가 동작하지 않으므로, 정기적인 스크립트 기반의 diskpart 오프라인 압축 방식을 유지해야 합니다.


설정 적용 후 용량 회수 검증

모든 압축 절차와 설정을 마쳤다면 PowerShell을 통해 실제 물리 파일 크기가 얼마나 줄어들었는지 검증합니다:

POWERSHELL
# 가상 디스크의 현재 물리 크기 확인
$file = Get-Item "$env:LOCALAPPDATA\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\ext4.vhdx"
[Math]::Round($file.Length / 1GB, 2).ToString() + " GB"

압축 전 60GB~80GB에 달하던 파일 크기가 게스트 리눅스의 실제 데이터 점유량(예: 12GB) 수준으로 축소된 것을 확인할 수 있습니다.

WSL2 환경에서 C 드라이브 용량 부족은 개발 생산성을 저해하는 주된 원인이므로, 최신 Sparse VHD 구성을 도입하거나 월 1회 주기적인 오프라인 수동 압축 루틴을 정착시켜 디스크 공간을 선제적으로 관리하는 것을 권장합니다.

Sponsored