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-mastercontroller의 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-fortressmodel/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 파일에 기록  // 디스크에 영속화

에이전트가 재시작되면:

  1. 업데이트된 agent.conf 각 Juju 에이전트의 설정 파일(/var/lib/juju/agents/unit-xxx/agent.conf). controller 주소, CA 인증서, 인증 정보 등을 저장한다. 에서 새 controller 주소를 읽음
  2. 새 controller에 WebSocket 연결
  3. HA 환경에서 리다이렉트되면 추적
  4. 정상 워커 재개 (charm 실행, hook 처리 등)

API Facade 구성

마이그레이션에는 3개의 facade가 관여한다:

Facade위치호출자역할
migrationmasterapiserver/facades/controller/migrationmaster/master worker (소스)Export, SetPhase, MinionReports, StreamModelLog 등
migrationminionapiserver/facades/agent/migrationminion/minion worker (에이전트)Watch, Report
migrationtargetapiserver/facades/controller/migrationtarget/master worker → 타겟Import, Activate, CheckMachines, AdoptResources 등

핵심 포인트

  1. 메이저/마이너 업그레이드의 공식 경로: 새 controller 부트스트랩 후 juju migrate로 모델 이전
  2. 엄격한 상태 머신: 7개 정상 페이즈 + ABORT 경로, SUCCESS가 되돌릴 수 없는 지점
  3. Master-Minion 협력: master가 오케스트레이션, minion이 각 에이전트에서 참여/보고
  4. Fortress 패턴: 마이그레이션 중 모델 워커를 잠금하여 안정성 보장
  5. 도메인별 Import: 각 도메인이 자체 import 로직을 가지며 Coordinator가 순차 실행
  6. 리더십 보존: import 시 기존 리더에게 1분 lease를 재클레임 (Leadership Management 글 참조)
  7. 에이전트 재연결: SUCCESS 단계에서 agent.conf를 교체하여 새 controller로 자동 전환
  8. 롤백 가능: SUCCESS 이전까지 ABORT로 원상 복구, 실패 시 자동 롤백

관련 문서

  • Leadership Management — Fortress lockdown 중 lease 갱신 중단과 리더십 보존 메커니즘
  • Juju Status — 마이그레이션 진행 상황을 juju status로 모니터링하는 방법
  • Change Stream — migration-inactive-flag 변경이 워커들에게 전파되는 경로