Storage Provisioning
본 문서는 Juju 4.0 코드베이스 분석을 기반으로 AI가 정리했습니다.
Overview
Juju Storage는 클라우드 프로바이더의 스토리지를 추상화하여 charm에 일관된 인터페이스를 제공하는 시스템이다. AWS EBS, GCE Persistent Disk, LXD ZFS 등 다양한 백엔드를 Storage Pool로 추상화하고, charm은 “filesystem 100GB”처럼 선언적으로 요구한다.
flowchart TB
subgraph Charm["Charm Metadata"]
REQ["storage:\n pgdata:\n type: filesystem\n minimum-size: 50G"]
end
subgraph Juju["Juju Storage Layer"]
SI["StorageInstance"]
VOL["Volume"]
FS["Filesystem"]
SI --> VOL
VOL --> FS
end
subgraph Cloud["Cloud Provider"]
EBS["AWS EBS"]
PD["GCE PD"]
LXD["LXD ZFS"]
end
REQ --> SI
FS --> EBS
FS --> PD
FS --> LXD
Storage 유형
Volume vs Filesystem
| Volume | Filesystem | |
|---|---|---|
| 추상화 | 원시 블록 디바이스 | 마운트 가능한 파일시스템 |
| 예시 | EBS volume, Persistent Disk | ext4, XFS on volume |
| charm 선언 | type: block | type: filesystem |
| 용도 | DB 엔진이 직접 디바이스 관리 | 일반적인 파일 저장 |
합성 (Composition)
filesystem을 요청했지만 프로바이더가 block만 지원하면, volume 위에 filesystem을 자동 생성한다:
flowchart LR
SI["StorageInstance\n(filesystem 요청)"] --> VOL["Volume\n(model 스코프)"]
VOL --> FS["Filesystem\n(machine 스코프)"]
FS --> MP["/var/lib/postgresql\n(mount point)"]
합성 규칙 프로바이더가 filesystem을 직접 지원하면 volume 없이 바로 filesystem을 생성한다. volume만 지원하면 volume을 먼저 생성하고 그 위에 filesystem을 올린다. 이 결정은 StorageInstanceComposition으로 관리된다. 은 프로바이더 능력에 따라 자동으로 결정된다.
Charm 메타데이터 선언
# charm metadata.yaml
storage:
pgdata:
type: filesystem
description: "PostgreSQL data directory"
minimum-size: 50G
location: /var/lib/postgresql
shared: false
read-only: false
count-min: 1 # 최소 1개 필수
count-max: 1 # 최대 1개
properties:
- transient # 일시적 (영속 불필요)
| 필드 | 설명 |
|---|---|
type | block 또는 filesystem |
minimum-size | 최소 크기 |
location | filesystem 마운트 포인트 |
shared | 유닛 간 공유 여부 |
count-min/max | 인스턴스 수 범위 (-1 = 무제한) |
properties | transient 등 추가 속성 |
Storage Pool
Storage Pool 클라우드 프로바이더의 스토리지 유형과 설정을 이름으로 추상화한 것. 사용자가 커스텀 풀을 생성하거나 프로바이더 기본 풀을 사용할 수 있다. 은 프로바이더별 스토리지 설정을 캡슐화한다:
# 기본 풀 목록
juju storage-pools list
# ebs (aws), azure (azure), kubernetes (k8s), lxd (lxd) 등
# 커스텀 풀 생성
juju storage-pool create fast-ebs ebs volume-type=io1 iops=3000
| 기본 풀 | 프로바이더 | 설명 |
|---|---|---|
ebs | AWS | EBS 볼륨 |
azure | Azure | Managed Disk |
gce | GCE | Persistent Disk |
kubernetes | K8s | PVC (Persistent Volume Claim) |
lxd | LXD | ZFS/Btrfs 스토리지 |
tmpfs | 모든 IAAS | 메모리 기반 임시 스토리지 |
rootfs | 모든 IAAS | 루트 파일시스템 |
배포 시 스토리지 지정
# 기본 (charm의 minimum-size와 기본 풀 사용)
juju deploy postgresql
# 풀과 크기 지정
juju deploy postgresql --storage pgdata=ebs,100G
# 커스텀 풀, 여러 인스턴스
juju deploy postgresql --storage pgdata=fast-ebs,2,100G
# 풀 수 크기
Storage Directive 형식
<pool>,<count>,<size>
모두 선택적이다. 생략하면:
- pool: 모델의
default-block-source또는default-filesystem-source - count: 1
- size: charm의
minimum-size
Provisioning 스코프
두 가지 스코프
| 스코프 | 프로비저닝 주체 | 예시 |
|---|---|---|
| Model 스코프 controller의 model provisioner가 관리. 클라우드 API를 통해 볼륨을 생성하며, 머신과 독립적으로 존재할 수 있다. | Controller (모델 provisioner) | EBS volume, GCE PD |
| Machine 스코프 각 머신의 machine agent가 관리. 머신 로컬 리소스를 사용하며, 머신이 제거되면 함께 사라진다. | Machine agent | tmpfs, loop device, LXD |
합성 시 스코프 분리
filesystem 요청 + block-only 프로바이더:
StorageInstance
├── Volume → Model 스코프 (EBS volume, 클라우드 API로 생성)
└── Filesystem → Machine 스코프 (machine agent가 mkfs + mount)
같은 StorageInstance 안에서 volume과 filesystem이 서로 다른 스코프로 프로비저닝될 수 있다.
영속성 (Persistence)
| Persistent | Ephemeral | |
|---|---|---|
| 유닛 제거 시 | 유지됨 | 삭제됨 |
| 재연결 가능 | juju attach-storage로 다른 유닛에 연결 | 불가 |
| 스코프 | Model 스코프 → 자동 persistent | Machine 스코프 → 자동 ephemeral |
| 예시 | EBS volume | tmpfs, loop device |
Persistent Storage 재연결
# 유닛에서 분리
juju detach-storage pgdata/0
# 다른 유닛에 연결
juju attach-storage postgresql/1 pgdata/0
persistent storage는 유닛이 제거되어도 데이터가 보존되므로, 새 유닛에 기존 데이터를 그대로 연결할 수 있다.
Provisioner Worker
파일: internal/worker/storageprovisioner/
두 종류의 provisioner가 실행된다:
flowchart TB
subgraph Controller["Controller"]
MP["Model Provisioner\n(model 스코프)"]
end
subgraph Machine["Machine Agent"]
MaP["Machine Provisioner\n(machine 스코프)"]
end
subgraph Cloud["Cloud Provider"]
API["클라우드 API\n(EBS, PD 등)"]
end
subgraph Local["Machine Local"]
FS["mkfs, mount\ntmpfs, loop"]
end
MP -->|"CreateVolumes()"| API
MP -->|"AttachVolumes()"| API
MaP -->|"CreateFilesystems()"| Local
MaP -->|"AttachFilesystems()"| Local
Watcher 기반 이벤트 처리
Model Provisioner:
WatchVolumes(model) → 새 volume 감지 → CreateVolumes()
WatchVolumeAttachments(model) → attach 요청 감지 → AttachVolumes()
Machine Provisioner:
WatchFilesystems(machine) → 새 filesystem 감지 → CreateFilesystems()
WatchFilesystemAttachments(machine) → mount 요청 감지 → AttachFilesystems()
WatchVolumeAttachmentPlans() → iSCSI 초기화 감지
Provider 인터페이스
type Provider interface {
VolumeSource(config) (VolumeSource, error)
FilesystemSource(config) (FilesystemSource, error)
Supports(kind StorageKind) bool
Scope() Scope // Environ(model) or Machine
Dynamic() bool // 동적 생성 지원 여부
Releasable() bool // 삭제 없이 해제 가능 여부
DefaultPools() []*Config
}
| 프로바이더 | Volume | Filesystem | 스코프 | Dynamic |
|---|---|---|---|---|
| EBS | ✓ | - | Model | ✓ |
| Azure | ✓ | - | Model | ✓ |
| GCE | ✓ | - | Model | ✓ |
| K8s | - | ✓ | Model | ✓ |
| LXD | ✓ | ✓ | Machine | ✓ |
| tmpfs | - | ✓ | Machine | ✓ |
| rootfs | - | ✓ | Machine | - |
| loop | ✓ | - | Machine | ✓ |
Attachment 생명주기
flowchart LR
A["Alive\n(활성)"] -->|"detach 요청"| DY["Dying\n(분리 중)"]
DY -->|"유닛 해제"| DE["Dead\n(제거됨)"]
제거 케스케이드
1. RemoveStorage(storageInstance)
2. 모든 attachment → Dying
3. 유닛이 storage 해제
4. attachment → Dead, 삭제
5. storageInstance → Dead
6. Provisioner가 클라우드 리소스 삭제 (DestroyVolumes/DestroyFilesystems)
iSCSI iSCSI(Internet Small Computer Systems Interface)를 통해 네트워크 너머의 블록 디바이스를 로컬처럼 사용하는 기능. VolumeAttachmentPlan에 서버 주소, 타겟 이름, CHAP 인증 정보가 포함된다. 지원
VolumeAttachmentPlan으로 iSCSI 연결을 관리한다:
type VolumeAttachmentPlan struct {
Life Life
DeviceType VolumeDeviceType // Local 또는 iSCSI
DeviceAttributes map[string]string // 서버/타겟/CHAP 정보
}
Machine agent가 iSCSI initiator를 설정하여 원격 블록 디바이스를 로컬 디바이스로 매핑한다.
CLI 명령어
| 명령 | 설명 |
|---|---|
juju storage | 모델의 모든 스토리지 조회 |
juju add-storage <unit> <name>=<directive> | 기존 유닛에 스토리지 동적 추가 |
juju attach-storage <unit> <storage-id> | 기존 스토리지를 유닛에 연결 |
juju detach-storage <storage-id> | 스토리지 분리 (데이터 보존) |
juju remove-storage <storage-id> | 스토리지 삭제 |
juju storage-pools list | 풀 목록 조회 |
juju storage-pools create <name> <provider> [key=value...] | 커스텀 풀 생성 |
핵심 포인트
- Volume/Filesystem 이중 추상화: block은 Volume, mountable은 Filesystem으로 분리하고 필요 시 자동 합성
- Model/Machine 스코프 분리: 클라우드 리소스는 controller가, 로컬 리소스는 machine agent가 각각 프로비저닝
- Persistent vs Ephemeral: Model 스코프 스토리지는 자동 persistent, 유닛 제거 후 재연결 가능
- Provider 추상화: 단일 인터페이스로 EBS/Azure/GCE/K8s/LXD 등 다양한 백엔드 지원
- Storage Pool: 프로바이더별 설정을 이름으로 캡슐화하여 재사용
- 합성 규칙: filesystem 요청 + block-only 프로바이더 = volume(model) + filesystem(machine) 자동 조합
- 이벤트 기반: Watcher로 변경 감지 → 멱등적 프로비저닝 → 실패 시 재시도
관련 문서
- Charm Deployment —
--storage플래그로 배포 시 스토리지 지정하는 흐름 - Model Migration — 마이그레이션 시 Storage import가 수행되는 과정
- Change Stream — volume/filesystem 변경이 provisioner 워커에 전파되는 이벤트 인프라