Model Migration
본 문서는 Juju 4.0 코드베이스 분석과 공식 문서를 기반으로 AI가 정리했습니다.
Overview
Model Migration은 워크로드 모델을 하나의 controller에서 다른 controller로 이전하는 기능이다. 애플리케이션 중단 없이 머신, 유닛, 관계 등 모든 구성을 그대로 복제한다.
주요 용도:
- 메이저/마이너 버전 업그레이드 (2.9 → 3.x, 3.x → 4.0): in-place 업그레이드가 불가 Juju의 메이저/마이너 버전 간에는 DB 스키마가 크게 달라 기존 controller를 직접 업그레이드할 수 없다. 새 controller를 세우고 모델을 옮기는 것이 공식 경로이다. 하므로 새 controller를 부트스트랩한 후 모델을 마이그레이션
- 부하 분산: 하나의 controller에 모델이 너무 많을 때 일부를 다른 controller로 이전
업그레이드 시 Migration 활용
공식 문서에 따르면, 메이저/마이너 버전 업그레이드(예: 2.9 → 3.6)는 다음 4단계를 거친다:
1. 클라이언트 업그레이드
$ snap refresh juju --channel=3.6/stable
2. 새 controller 부트스트랩
$ juju bootstrap <cloud> newcontroller
3. 기존 controller 설정 복제
$ juju create-backup -m oldcontroller:controller
$ ./juju-restore --copy-controller <backup-path>
4. 모델 마이그레이션 + 에이전트 업그레이드
$ juju switch oldcontroller
$ juju migrate mymodel newcontroller
$ juju switch newcontroller
$ juju upgrade-model --agent-version=<new-version>
참고: controller 모델 자체는 마이그레이션할 수 없다. 워크로드 모델만 대상이다.
CLI: juju migrate
파일: cmd/juju/commands/migrate.go
juju migrate <model-name> <target-controller-name>
명령은 비동기로 동작한다. 마이그레이션을 개시하고 즉시 반환되며, 진행 상황은 juju status와 로그로 확인한다.
내부 흐름
migrateCommand.Run()
-> getMigrationSpec() // 타겟 controller 정보 구성
- UUID, API endpoints, CA cert
- 인증 정보 (password 또는 macaroons)
-> checkMigrationFeasibility() // 사전 검증
- 소스 모델의 모든 사용자가 타겟에 존재하는지 확인
- 외부 사용자가 있으면 identity provider URL 일치 확인
-> api.InitiateMigration() // 소스 controller에 마이그레이션 개시 요청
사전 조건
- 소스와 타겟 controller 모두 로컬 Juju 클라이언트에 등록되어 있어야 함
- 같은 cloud 환경에 위치해야 함
- 타겟 controller에 동일 이름의 모델이 없어야 함
- 모델에 접근하는 모든 사용자가 타겟 controller에도 설정되어 있어야 함
- 타겟 controller의 에이전트 버전이 소스보다 같거나 높아야 함 (하위 버전으로 마이그레이션 불가)
- 모델 스키마 버전 소스와 타겟의 description 스키마 버전이 호환되어야 한다. 예: 4.0의 v11 모델을 3.6의 v10 타겟으로 마이그레이션하면 import 단계에서 실패한다. 이 호환되어야 함
Migration Phases (상태 머신)
파일: core/migration/phase.go
마이그레이션은 엄격한 상태 머신으로 관리된다:
flowchart LR
Start(( )) --> QUIESCE
QUIESCE --> IMPORT
IMPORT --> PROCESSRELATIONS
PROCESSRELATIONS --> VALIDATION
VALIDATION --> SUCCESS
SUCCESS -. "Point of No Return" .-> LOGTRANSFER
LOGTRANSFER --> REAP
REAP --> DONE
DONE --> End(( ))
REAP --> REAPFAILED
QUIESCE -.-> ABORT
IMPORT -.-> ABORT
PROCESSRELATIONS -.-> ABORT
VALIDATION -.-> ABORT
ABORT --> ABORTDONE
style ABORT fill:#f66,color:#fff
style ABORTDONE fill:#f66,color:#fff
style REAPFAILED fill:#f66,color:#fff
style DONE fill:#6b6,color:#fff
style SUCCESS fill:#fa0,color:#fff
각 페이즈의 역할:
| Phase | 역할 | 설명 |
|---|---|---|
| QUIESCE 모델 내 모든 워커를 Fortress 패턴으로 중지시키고, 에이전트들이 안정 상태를 보고할 때까지 대기하는 단계. | 모델 안정화 | Fortress lockdown으로 모델 워커 중지, 에이전트들이 안정 상태 보고 |
| IMPORT | 데이터 전송 | 모델 데이터를 YAML로 직렬화하여 타겟 controller에 업로드 |
| PROCESSRELATIONS | 관계 처리 | cross-model relation 서로 다른 controller에 있는 모델 간의 관계. 마이그레이션 시 관계의 controller 참조를 새 controller로 업데이트해야 한다. 을 새 controller에 맞게 업데이트 |
| VALIDATION | 검증 | 에이전트들이 타겟 controller에 연결 가능한지 검증, 머신 일관성 확인 |
| SUCCESS | 확정 | 타겟에서 모델 활성화. 되돌릴 수 없는 지점 |
| LOGTRANSFER | 로그 이전 | 소스의 모델 디버그 로그를 타겟으로 스트리밍 |
| REAP | 소스 정리 | 소스 controller에서 모델 제거 |
| DONE | 완료 | 마이그레이션 성공 |
핵심: SUCCESS 이전까지는 어느 단계에서든 ABORT → ABORTDONE으로 롤백 가능하다. SUCCESS 이후에는 되돌릴 수 없다.
워커 구조: Master와 Minion
워커 등록과 기동
마이그레이션 관련 워커는 Juju의 dependency engine Juju 내부의 워커 관리 프레임워크. 워커 간 의존성을 선언하고, 의존성이 충족되면 자동으로 워커를 시작/중지한다. 에 항상 등록되어 있지만, 평소에는 대기 상태다.
파일: cmd/jujud-controller/agent/model/manifolds.go (master), cmd/jujud/agent/machine/manifolds.go (minion)
| 워커 | 등록 위치 | 실행 조건 | 의존성 |
|---|---|---|---|
| migration-master | controller의 model manifolds | 모델이 Alive/Dying + 마이그레이션 개시 시 | agent, api-caller, domain-services, migration-fortress |
| migration-minion | 각 machine agent manifolds | 마이그레이션 개시 시 | agent, api-caller, migration-fortress |
| migration-inactive-flag 마이그레이션 활성 상태를 나타내는 플래그. true이면 마이그레이션 비활성(평상시), false이면 활성. 다른 워커들이 이 플래그를 보고 자동 중지/시작된다. | model/machine manifolds | 항상 | api-caller |
| migration-fortress | model/machine manifolds | 업그레이드 완료 후 | - |
기동 메커니즘의 핵심은 **migration-inactive-flag**와 **migration-fortress**다:
flowchart TB
subgraph Normal["평상시 (마이그레이션 비활성)"]
Flag1["migration-inactive-flag = true"]
Flag1 --> Workers1["ifNotMigrating 워커들 정상 실행"]
Flag1 --> Wait["migration-master/minion 대기"]
end
Init["InitiateMigration() API 호출"] --> Flag2
subgraph Active["마이그레이션 개시 시"]
Flag2["migration-inactive-flag = false"]
Flag2 --> Workers2["ifNotMigrating 워커들 자동 중지"]
Flag2 --> MasterStart["migration-master 활성화"]
Flag2 --> MinionStart["migration-minion 활성화"]
end
ifNotMigrating은 다른 워커들을 감싸는 housing이다:
var ifNotMigrating = engine.Housing{
Flags: []string{migrationInactiveFlagName}, // flag가 set일 때만 실행
Occupy: migrationFortressName, // fortress를 점유해야 실행
}.Decorate
이 패턴 덕분에 마이그레이션이 시작되면 charm hook, 리더십 갱신 등 일반 워커가 자동으로 중지되고, master/minion이 안전하게 작업할 수 있다.
워커 간 협력
마이그레이션은 두 종류의 워커가 협력하여 진행한다:
flowchart LR
subgraph Source["Source Controller"]
Master["migrationmaster worker"]
Minion["migrationminion worker"]
end
subgraph Target["Target Controller"]
TargetFacade["migrationtarget facade"]
end
Master -- "Export" --> TargetFacade
Master -- "Upload" --> TargetFacade
TargetFacade -- "Check" --> Master
Minion <-- "Report" --> Master
Migration Master
파일: internal/worker/migrationmaster/worker.go
소스 controller에서 실행되며, 전체 마이그레이션을 오케스트레이션한다.
주요 흐름:
run()
-> waitForActiveMigration() // 마이그레이션 시작 대기
-> Guard.Lockdown() // Fortress 잠금 (모델 워커 중지)
-> phase별 핸들러 실행:
doQUIESCE()
-> Prechecks() // 소스/타겟 사전 검증
-> waitForMinions() // 모든 에이전트의 안정 보고 대기
-> Prechecks() // 안정화 후 재검증
doIMPORT()
-> transferModel()
-> Facade.Export() // 모델을 YAML bytes로 직렬화
-> targetClient.Import() // 타겟에 업로드
-> UploadBinaries() // charm, tools, resources 업로드
doPROCESSRELATIONS()
-> Facade.ProcessRelations() // cross-model relation 처리
doVALIDATION()
-> waitForMinions() // 에이전트들의 타겟 연결 검증 보고 대기
-> targetClient.CheckMachines() // 머신 일관성 확인
-> activateModel() // 타겟에서 모델 활성화
doSUCCESS()
-> waitForMinions() // 모든 에이전트의 성공 보고 대기
-> transferResources() // 클라우드 리소스 소유권 이전
doLOGTRANSFER()
-> transferLogs() // 30초마다 진행 보고하며 로그 스트리밍
doREAP()
-> Facade.Reap() // 소스에서 모델 제거
Migration Minion
파일: internal/worker/migrationminion/worker.go
각 machine/unit 에이전트에서 실행되며, master에게 상태를 보고한다.
Phase별 동작:
QUIESCE:
-> Guard.Lockdown() // 로컬 워커 중지
-> report(success) // master에게 안정화 완료 보고
VALIDATION:
-> dialNewController() // 타겟 controller에 연결 시도
-> APIOpen(targetAddrs) // WebSocket 연결
-> RedirectError 처리 // HA 환경에서 최대 5회 리다이렉트 추적
-> ValidateMigration() // 에이전트별 검증 (컨테이너 상태 등)
-> report(success/failure) // 결과 보고
SUCCESS:
-> robustReport(success) // API 연결이 끊겨도 재시도하며 보고
-> updateAgentConfigForTargetController()
-> Agent.ChangeConfig()
-> SetAPIHostPorts() // 타겟 controller 주소로 교체
-> SetCACert() // 타겟 CA 인증서로 교체
robustReport SUCCESS 단계에서는 소스 controller가 모델을 해제하는 중이라 API 연결이 불안정할 수 있다. 이 함수는 연결 재시도 로직을 포함하여 보고 완료를 보장한다. : SUCCESS 단계에서는 소스 controller와의 연결이 불안정할 수 있다.
robustReport()는 연결이 끊겨도 재시도하여 보고를 완료한다.
Minion Report 수집
Master는 waitForMinions()로 모든 에이전트의 보고를 수집한다:
- Fail-fast 정책 (VALIDATION): 첫 번째 실패 즉시 ABORT
- Wait-all 정책 (SUCCESS): 모든 에이전트 보고 또는 타임아웃(15분)까지 대기
- 미보고 에이전트가 있으면 unknown으로 집계
Fortress 마이그레이션 중 모델의 상태 변경을 방지하기 위해 모든 워커를 잠그는 패턴. Guard.Lockdown()으로 잠그고, Guard.Unlock()으로 해제한다. 중세 요새처럼 외부 활동을 차단하는 개념. 패턴
마이그레이션 중 모델 안정성을 보장하기 위해 Fortress(요새) 패턴을 사용한다:
flowchart LR
Q[QUIESCE] -->|Guard.Lockdown| Lock["Fortress 잠금"]
Lock --> S[SUCCESS]
S -->|Guard.Unlock| Unlock["Fortress 해제"]
Fortress가 잠기면:
- charm hook 실행이 중단된다
- 리더십 lease 갱신이 중단된다 (lease가 만료될 수 있음)
- 새로운 유닛 배포가 차단된다
이것이 Leadership Management 글에서 언급한 **“model migration 시 LeadershipGuarantee가 60초”**인 이유와 연결된다.
데이터 Export/Import
Export (소스 → 직렬화)
파일: domain/export/state/model/export.go
모델의 모든 데이터를 description/v11 Juju 모델의 직렬화 스키마 버전. 모델 구조가 변경될 때마다 버전이 올라가며, import 시 호환성 검증에 사용된다. 패키지로 YAML 직렬화한다:
Facade.Export()
-> State.Export()
-> 각 도메인 테이블에서 데이터 조회 (sqlair)
-> description.Model 객체로 변환
-> YAML bytes로 직렬화
SerializedModel {
Bytes []byte // YAML 직렬화된 모델 전체
Charms []string // 사용 중인 charm URL들
Tools []SerializedModelTools // 에이전트 tools 버전
Resources []SerializedModelResource // 앱 리소스
}
Import (직렬화 → 타겟)
파일: internal/migration/migration.go
타겟 controller에서 YAML을 역직렬화하고, 도메인별 import 작업을 순차 실행한다:
ModelImporter.ImportModel()
-> YAML bytes → description.Model 역직렬화
-> Coordinator 생성
-> migrations.ImportOperations() 등록
-> Coordinator.Perform() 순차 실행:
├── Application import
├── Machine import
├── Unit import
├── Relation import
├── Lease import (리더십 보존)
├── Storage import
└── ... (기타 도메인)
각 도메인은 domain/*/modelmigration/ 디렉터리에 자체 import 로직을 가지고 있다.
리더십 보존
파일: domain/lease/modelmigration/import.go
마이그레이션 중 Fortress lockdown으로 리더십 갱신이 중단되므로, import 시 기존 리더를 복원해야 한다:
const LeadershipGuarantee = time.Minute // 1분 보장
importOperation.Execute()
-> 각 application 순회
-> 리더가 지정된 경우:
lease key: "{ModelUUID}/{AppName}/leadership"
holder: 기존 리더 유닛 이름
duration: 1분
-> service.ClaimLease() // 타겟 controller에서 lease 재클레임
-> 리더가 없는 경우:
-> skip (lease worker가 나중에 새 리더 선출)
에이전트 재연결
마이그레이션의 최종 목표는 모든 에이전트가 새 controller에 연결되는 것이다.
VALIDATION 단계: 연결 가능성 검증
dialNewController()
-> 현재 에이전트의 API 정보 가져오기 (username, password)
-> 주소를 타겟 controller로 교체
-> CA cert를 타겟 것으로 교체
-> dialWithRedirect()
-> APIOpen()으로 WebSocket 연결
-> RedirectError 발생 시:
-> 새 주소/CA cert로 업데이트
-> 재시도 (최대 5회, 루프 방지)
-> ValidateMigration() // 에이전트별 검증
-> 타겟 controller 정보 저장
SUCCESS 단계: 설정 영구 변경
updateAgentConfigForTargetController()
-> Agent.ChangeConfig()
-> conf.SetAPIHostPorts(targetAddrs) // 새 controller 주소
-> conf.SetCACert(targetCACert) // 새 CA 인증서
-> agent.conf 파일에 기록 // 디스크에 영속화
에이전트가 재시작되면:
- 업데이트된 agent.conf 각 Juju 에이전트의 설정 파일(/var/lib/juju/agents/unit-xxx/agent.conf). controller 주소, CA 인증서, 인증 정보 등을 저장한다. 에서 새 controller 주소를 읽음
- 새 controller에 WebSocket 연결
- HA 환경에서 리다이렉트되면 추적
- 정상 워커 재개 (charm 실행, hook 처리 등)
API Facade 구성
마이그레이션에는 3개의 facade가 관여한다:
| Facade | 위치 | 호출자 | 역할 |
|---|---|---|---|
| migrationmaster | apiserver/facades/controller/migrationmaster/ | master worker (소스) | Export, SetPhase, MinionReports, StreamModelLog 등 |
| migrationminion | apiserver/facades/agent/migrationminion/ | minion worker (에이전트) | Watch, Report |
| migrationtarget | apiserver/facades/controller/migrationtarget/ | master worker → 타겟 | Import, Activate, CheckMachines, AdoptResources 등 |
핵심 포인트
- 메이저/마이너 업그레이드의 공식 경로: 새 controller 부트스트랩 후
juju migrate로 모델 이전 - 엄격한 상태 머신: 7개 정상 페이즈 + ABORT 경로, SUCCESS가 되돌릴 수 없는 지점
- Master-Minion 협력: master가 오케스트레이션, minion이 각 에이전트에서 참여/보고
- Fortress 패턴: 마이그레이션 중 모델 워커를 잠금하여 안정성 보장
- 도메인별 Import: 각 도메인이 자체 import 로직을 가지며 Coordinator가 순차 실행
- 리더십 보존: import 시 기존 리더에게 1분 lease를 재클레임 (Leadership Management 글 참조)
- 에이전트 재연결: SUCCESS 단계에서 agent.conf를 교체하여 새 controller로 자동 전환
- 롤백 가능: SUCCESS 이전까지 ABORT로 원상 복구, 실패 시 자동 롤백
관련 문서
- Leadership Management — Fortress lockdown 중 lease 갱신 중단과 리더십 보존 메커니즘
- Juju Status — 마이그레이션 진행 상황을
juju status로 모니터링하는 방법 - Change Stream — migration-inactive-flag 변경이 워커들에게 전파되는 경로