A/B(원활한) 시스템 업데이트

원활한 업데이트라고도 불리는 A/B 시스템 업데이트는 OTA(무선) 업데이트가 진행되는 동안 작동 가능한 부팅 시스템이 디스크에서 유지될 수 있게 해줍니다. 이 접근 방식을 사용하면 업데이트 이후에 기기가 비활성화될 확률이 감소합니다. 즉, 수리 및 보증 센터에서 교체하거나 다시 플래시해야 하는 기기의 수가 감소합니다. ChromeOS와 같은 다른 상용 등급 운영체제 역시 A/B 업데이트를 성공적으로 사용하고 있습니다.

A/B 시스템 업데이트 및 작동 방식에 관한 자세한 내용은 파티션 선택(슬롯)을 참고하세요.

A/B 시스템 업데이트는 다음과 같은 이점을 제공합니다.

  • 시스템이 실행되는 동안 사용자를 방해하지 않고도 OTA 업데이트가 진행될 수 있습니다. 사용자는 OTA 도중 계속해서 기기를 사용할 수 있으며, 업데이트 도중의 유일한 다운타임은 기기가 업데이트된 디스크 파티션으로 재부팅되는 순간뿐입니다.
  • 업데이트 이후에는 재부팅 시간이 일반 재부팅 시간보다 오래 걸리지 않습니다.
  • 플래시 불량 등으로 인해 OTA 적용에 실패하더라도 사용자에게 미치는 영향은 없습니다. 사용자는 계속해서 기존 OS를 실행하고, 클라이언트는 자유롭게 업데이트를 재시도할 수 있습니다.
  • OTA 업데이트가 적용되었지만 부팅에는 실패한 경우에는 기기가 기존 파티션으로 재부팅되고 가용성을 유지합니다. 클라이언트는 자유롭게 업데이트를 재시도할 수 있습니다.
  • I/O 오류를 비롯한 모든 오류는 설정된 미사용 파티션에만 영향을 미치며 재시도 가능합니다. I/O 로드가 사용자 환경 저하를 피할 수 있도록 의도적으로 낮게 설정되었기 때문에 이러한 오류가 발생할 확률도 감소합니다.
  • 업데이트는 A/B 기기로 스트리밍할 수 있습니다. 따라서 설치 전에 패키지를 다운로드할 필요가 없습니다. 스트리밍 덕분에 사용자에게는 /data 또는 /cache에 업데이트 패키지를 저장할 여유 공간이 충분하지 않아도 됩니다.
  • 캐시 파티션이 더 이상 OTA 업데이트 패키지를 저장하는 데 사용되지 않으므로 캐시 파티션이 향후 업데이트를 저장할 수 있을 정도로 큰지 확인할 필요도 없습니다.
  • dm-verity는 기기가 손상되지 않은 이미지를 부팅하도록 보장합니다. OTA 불량 또는 dm-verity 문제로 인해 기기가 부팅되지 않는 경우에는 기기를 기존 이미지로 재부팅할 수 있습니다. Android 자체 검사 부팅에는 A/B 업데이트가 요구되지 않습니다.

A/B 시스템 업데이트 정보

A/B 업데이트를 받으려면 클라이언트와 시스템을 모두 변경해야 합니다. 그러나 OTA 패키지 서버는 변경하지 않아야 합니다. 업데이트 패키지가 여전히 HTTPS를 통해 제공됩니다. Google의 OTA 인프라를 사용하는 기기의 경우 시스템 변경사항이 모두 AOSP에 있으며 클라이언트 코드는 Google Play 서비스에서 제공합니다. Google의 OTA 인프라를 사용하지 않는 OEM은 AOSP 시스템 코드를 재사용할 수 있지만 자체 클라이언트를 제공해야 합니다.

자체 클라이언트를 제공하는 OEM의 경우 클라이언트가 다음을 실행해야 합니다.

  • 언제 업데이트를 실시할지 결정해야 합니다. A/B 업데이트는 백그라운드에서 이루어지므로 더 이상 사용자가 시작하지 않습니다. 사용자를 방해하지 않으려면 기기가 유휴 유지관리 모드(예: 밤새)이거나 Wi-Fi 모드일 때로 업데이트를 예약하는 것이 좋습니다. 하지만 클라이언트는 원하는 모든 휴리스틱을 사용할 수 있습니다.
  • OTA 패키지 서버를 확인하여 업데이트가 사용 가능한지 파악해야 합니다. 이는 기존 클라이언트 코드와 거의 흡사하지만 기기가 A/B를 지원한다는 내용을 시사해야 한다는 점이 다릅니다. Google의 클라이언트에도 사용자가 최신 업데이트를 확인할 수 있게 해주는 지금 확인 버튼이 있습니다.
  • 사용 가능한 경우 업데이트 패키지의 HTTPS URL로 update_engine을 호출합니다. update_engine은 업데이트 패키지를 스트리밍할 때 현재 사용되지 않는 파티션의 원시 블록을 업데이트합니다.
  • update_engine 결과 코드에 따라 설치 성공 여부를 서버에 보고합니다. 업데이트가 정상적으로 적용되면 update_engine은 부트로더에 다음 재부팅 시 새 OS로 부팅하라고 지시합니다. 새 OS가 부팅에 실패하면 부트로더가 기존 OS로 대체하므로 클라이언트에서 작업이 필요 없습니다. 업데이트가 실패하면 클라이언트는 상세 오류 코드에 따라 다시 시도할지, 또는 언제 시도할지를 결정합니다. 예를 들어 좋은 클라이언트는 부분('diff') OTA 패키지 실패를 인식하고 대신 전체 OTA 패키지를 시도합니다.

클라이언트는 선택적으로 다음을 실행할 수 있습니다.

  • 사용자에게 알림을 표시하여 재부팅을 요청합니다. 사용자가 정기적으로 업데이트하도록 장려하는 정책을 구현하고 싶은 경우에는 이 알림을 클라이언트에 추가할 수 있습니다. 클라이언트가 사용자에게 메시지를 표시하지 않으면 사용자는 어차피 다음번에 재부팅할 때 업데이트를 받게 됩니다. Google 클라이언트에는 업데이트당 구성 가능한 지연 시간이 있습니다.
  • 새 OS 버전으로 부팅했는지, 아니면 이렇게 해야 하지만 기존 OS 버전으로 대체하는지를 사용자에게 알리는 알림을 표시합니다. 일반적으로 Google의 클라이언트는 둘 다 실행하지 않습니다.

시스템 측에서는 A/B 시스템 업데이트가 다음에 영향을 미칩니다.

  • 파티션 선택(슬롯), update_engine 데몬, 부트로더 상호작용(아래 설명 참고)
  • 빌드 프로세스 및 OTA 업데이트 패키지 생성(A/B 업데이트 구현에 설명됨)

파티션 선택(슬롯)

A/B 시스템 업데이트는 슬롯(보통 A 슬롯 및 B 슬롯)이라 불리는 두 개의 파티션 집합을 사용합니다. 시스템이 현재 슬롯에서 실행되지만 미사용 슬롯의 파티션은 정상 작업 도중 실행 중인 시스템에 의해 액세스되지 않습니다. 이 접근 방식을 사용하면 미사용 슬롯을 대체 슬롯으로 유지하여 업데이트의 결함을 막아줍니다. 업데이트 도중이나 업데이트 직후에 오류가 발생하면 시스템은 기존 슬롯으로 롤백하여 시스템의 정상 작동을 계속해서 유지할 수 있습니다. 이 목표를 달성하려면 현재 슬롯에 의해 사용되는 어떠한 파티션도 OTA 업데이트의 일부로 업데이트되면 안 됩니다(하나의 사본만 있는 파티션 포함).

각 슬롯에는 슬롯에 기기를 부팅할 수 있는 올바른 시스템이 포함되어 있는지 언급하는 bootable 속성이 있습니다. 현재 슬롯은 시스템이 실행 중일 때 부팅 가능하지만 다른 슬롯에는 시스템의 기존(여전히 올바른) 버전, 새 버전과 잘못된 데이터가 있을 수 있습니다. 현재 슬롯이 무엇이든, 한 개의 슬롯은 활성 슬롯(다음 부팅 시 부트로더가 부팅되는 슬롯) 또는 선호되는 슬롯입니다.

각 슬롯에는 사용자 공간에 의해 설정된 successful 속성이 있습니다. 이는 슬롯이 부팅 가능한 경우에만 관련성을 지닙니다. successful 슬롯은 자체 부팅, 실행 및 업데이트가 가능해야 합니다. 부팅 가능한 슬롯이 successful로 표시되지 않은 경우(슬롯에서 여러 차례의 부팅이 시도된 후) 이 슬롯은 부트로더에 의해 부팅 불가로 표시되어야 합니다. 여기에는 활성 슬롯을 다른 부팅 가능한 슬롯으로 변경하는 작업도 포함됩니다(보통 새 활성 슬롯으로 부팅 시도 직전에 실행되고 있는 슬롯으로). 인터페이스의 구체적인 세부정보는 boot_control.h에 정의되어 있습니다.

엔진 데몬 업데이트

A/B 시스템 업데이트는 update_engine이라는 백그라운드 데몬을 사용하여 업데이트된 새 버전으로 부팅할 시스템을 준비합니다. 이 데몬은 다음과 같은 작업을 실행할 수 있습니다.

  • OTA 패키지의 지침에 따라 현재 슬롯 A/B 파티션에서 읽고 모든 데이터를 미사용 슬롯 A/B 파티션에 씁니다.
  • 사전 정의된 워크플로에서 boot_control 인터페이스를 호출합니다.
  • OTA 패키지의 지침에 따라 모든 미사용 슬롯 파티션을 작성한 후 파티션에서 설치 후 프로그램을 실행합니다. 자세한 내용은 설치 후를 참고하세요.

update_engine 데몬은 부팅 프로세스 자체에 관여하지 않으므로 현재 슬롯의 SELinux 정책과 기능에 의해 업데이트 중에 할 수 있는 작업이 제한됩니다. 이러한 정책과 기능은 시스템이 새 버전으로 부팅될 때까지 업데이트할 수 없습니다. 견고한 시스템을 유지하려면 업데이트 프로세스로 인해 파티션 테이블, 현재 슬롯의 파티션 콘텐츠 또는 초기화로 완전 삭제할 수 없는 A/B 외 파티션의 콘텐츠가 수정되면 안 됩니다.

엔진 소스 업데이트

update_engine 소스는 system/update_engine에 있습니다. A/B OTA dexopt 파일은 installd와 패키지 관리자 간에 분할됩니다.

실제 적용된 예는 /device/google/marlin/device-common.mk를 참고하세요.

엔진 로그 업데이트

Android 8.x 이하 버전에서는 update_engine 로그를 logcat 및 버그 신고서에서 찾을 수 있습니다. 파일 시스템에서 update_engine 로그를 사용할 수 있으려면 다음 변경사항을 빌드에 패치합니다.

이러한 변경사항으로 가장 최근의 update_engine 로그 사본이 /data/misc/update_engine_log/update_engine.YEAR-TIME에 저장됩니다. 현재 로그 외에도 가장 최근 로그 5개가 /data/misc/update_engine_log/ 아래에 저장됩니다. 로그 그룹 ID를 보유한 사용자는 파일 시스템 로그에 액세스할 수 있습니다.

부트로더 상호작용

boot_control HAL은 부트로더에 어디에서 부팅하는지 지시하기 위해 update_engine(또는 다른 데몬)에서 사용합니다. 일반적인 시나리오 예시와 관련 상태는 다음과 같습니다.

  • 정상 사례: 시스템이 현재 슬롯(A 또는 B 슬롯)에서 실행 중입니다. 아직 적용된 업데이트는 없습니다. 시스템의 현재 슬롯은 부팅 가능하고 정상적인 활성 슬롯입니다.
  • 업데이트 진행 중: 시스템이 B 슬롯에서 실행되고 있으므로 B 슬롯이 부팅 가능한 정상적인 활성 슬롯입니다. A 슬롯의 콘텐츠가 업데이트되고는 있지만 아직 완료되지 않았으므로 A 슬롯이 부팅 불가로 표시되었습니다. 이 상태에서 재부팅하면 B 슬롯에서 계속 부팅됩니다.
  • 업데이트 적용됨, 재부팅 대기 중: 시스템이 B 슬롯에서 실행되고 있습니다. B 슬롯은 부팅 가능하고 정상적이지만 A 슬롯이 활성으로 표시되었습니다(따라서 부팅 가능으로 표시됨). A 슬롯이 아직 정상으로 표시되지 않았으며, 부트로더에 의해 A 슬롯에서의 부팅이 몇 차례 시도되어야 합니다.
  • 시스템이 새 업데이트로 재부팅됨: 시스템이 처음으로 A 슬롯에서 실행되고 있습니다. B 슬롯은 계속해서 부팅 가능하고 정상적인 반면 A 슬롯은 부팅만 가능합니다. 여전히 활성 상태이지만 정상은 아닙니다. 사용자 공간 데몬 update_verifier는 일부 확인이 완료되면 A 슬롯을 정상으로 표시해야 합니다.

스트리밍 업데이트 지원

사용자 기기에는 /data에 업데이트 패키지를 다운로드할 공간이 충분하지 않을 때도 있습니다. OEM이나 사용자 모두 /cache 파티션의 공간을 낭비하고 싶어하지 않는 만큼 일부 사용자는 업데이트를 생략하기도 합니다. 이는 기기에 업데이트 패키지를 저장할 곳이 없기 때문입니다. 이 문제를 해결하기 위해 Android 8.0에서는 블록을 /data에 저장할 필요 없이 블록을 다운로드하는 동시에 B 파티션에 직접 쓰는 A/B 업데이트 스트리밍 지원을 추가했습니다. A/B 업데이트를 스트리밍할 때는 임시 공간이 거의 필요 없으며 약 100KiB의 메타데이터를 저장할 공간만 있으면 됩니다.

Android 7.1에 스트리밍 업데이트를 사용 설정하려면 다음 패치를 선택하세요.

이러한 패치는 Google 모바일 서비스(GMS) 또는 기타 어떠한 업데이트 클라이언트를 사용하든 Android 7.1 이상에서 A/B 업데이트 스트리밍을 지원하는 데 필요합니다.

A/B 업데이트 수명

업데이트 프로세스는 OTA 패키지(코드에서 페이로드라 불림)의 다운로드가 준비되면 시작됩니다. 기기의 정책은 배터리 수준, 사용자 활동, 충전 상태나 기타 정책에 따라 페이로드 다운로드 및 적용을 지연시킬 수 있습니다. 또한 업데이트가 백그라운드에서 실행되기 때문에 사용자가 업데이트가 진행 중인지 모를 수도 있습니다. 따라서 업데이트 프로세스가 정책, 예상치 못한 재부팅이나 사용자 작업으로 인해 언제든지 중단될 수 있습니다.

OTA 패키지 자체의 메타데이터가 업데이트를 스트리밍할 수 있음을 나타내며, 같은 패키지를 비 스트리밍 설치에 사용할 수도 있습니다(선택사항). 서버는 클라이언트에 메타데이터를 사용하여 스트리밍 중이라고 알릴 수 있으므로 클라이언트는 OTA를 update_engine에 올바르게 전달합니다. 자체 서버와 클라이언트가 있는 기기 제조업체는 업데이트가 스트리밍 중인 것을 서버에서 확인하도록 또는 모든 업데이트가 스트리밍 중이라고 가정하도록 하여 그리고 클라이언트에서 스트리밍을 위한 update_engine를 올바르게 호출하도록 하여 스트리밍 업데이트를 사용 설정할 수 있습니다. 제조업체는 패키지가 스트리밍 변형에 속한다는 사실을 활용하여 플래그를 클라이언트에 전송해 프레임워크 측에 스트리밍으로 전달을 트리거할 수 있습니다.

페이로드가 준비되면 업데이트 프로세스는 다음과 같이 진행됩니다.

단계 활동
1 현재 슬롯(또는 '소스 슬롯')이 markBootSuccessful()과 함께 정상으로 표시됩니다(이미 표시되지 않은 경우).
2 setSlotAsUnbootable() 함수를 호출하여 미사용 슬롯(또는 '타겟 슬롯')이 부팅 불가로 표시됩니다. 현재 슬롯은 부트로더가 조만간 잘못된 데이터를 보유하게 될 미사용 슬롯으로 돌아가지 않도록 항상 업데이트 초반에 정상으로 표시됩니다. 시스템이 업데이트 적용을 시작할 수 있는 시점에 도달하면 다른 주요 구성요소가 손상(예: 충돌 루프의 UI)된 경우에도 현재 슬롯이 정상으로 표시됩니다. 이는 새 소프트웨어를 푸시하여 이러한 문제를 해결할 수 있기 때문입니다.

업데이트 페이로드는 새 버전으로 업데이트하라는 지침을 포함하는 불투명 blob입니다. 업데이트 페이로드는 다음으로 구성됩니다.
  • 메타데이터. 업데이트 페이로드의 비교적 작은 부분인 메타데이터에는 타겟 슬롯의 새 버전을 생성하고 확인하기 위한 연산 목록이 포함됩니다. 예를 들어 연산은 특정 blob을 압축 해제하여 타겟 파티션의 특정 블록에 쓰거나 소스 파티션에서 내용을 읽고 바이너리 패치를 적용하고 타겟 파티션의 특정 블록에 내용을 쓸 수 있습니다.
  • 추가 데이터. 업데이트 페이로드에서 큰 부분을 차지하며, 연산과 관련된 추가 데이터가 압축된 blob 또는 이러한 예시의 바이너리 패치로 구성됩니다.
3 페이로드 메타데이터가 다운로드됩니다.
4 메타데이터에 순서대로 정의된 각 연산의 경우, 관련 데이터(있는 경우)가 메모리에 다운로드되고 연산이 적용되고 관련 메모리가 삭제됩니다.
5 전체 파티션을 다시 읽고 예상 해시에서 확인합니다.
6 설치 후 단계(있는 경우)가 실행됩니다. 단계 실행 도중에 오류가 발생하면 업데이트가 실패하고 다른 페이로드로 재시도될 가능성이 있습니다. 현재까지의 모든 단계가 성공하면 업데이트도 성공하고 마지막 단계가 실행됩니다.
7 setActiveBootSlot()을 호출하여 미사용 슬롯을 활성으로 표시합니다. 미사용 슬롯을 활성으로 표시한다고 해서 부팅이 마무리되지는 않습니다. 부트로더(또는 시스템 자체)는 정상 상태가 읽히지 않는 경우 활성 슬롯으로 다시 전환할 수 있습니다.
8 설치 후(아래에 설명됨)에는 기존 버전을 계속해서 실행하는 동시에 '새 업데이트' 버전의 프로그램을 실행해야 합니다. OTA 패키지에 정의된 경우 이 단계는 필수이며 프로그램이 종료 코드 0과 함께 반환되어야 합니다. 그렇지 않으면 업데이트가 실패합니다.
9 시스템이 새 슬롯으로 성공적으로 충분히 부팅되고 재부팅 후 검사를 완료하면 이제 현재 슬롯(이전의 '타겟 슬롯')이 markBootSuccessful()을 호출하여 정상으로 표시됩니다.

설치 후

설치 후 단계가 정의된 모든 파티션의 경우 update_engine이 새 파티션을 특정 위치에 마운트하고 마운트된 파티션을 기준으로 OTA에 지정된 프로그램을 실행합니다. 예를 들어 설치 후 프로그램이 시스템 파티션에서 usr/bin/postinstall로 정의된 경우 미사용 슬롯의 이 파티션은 고정된 위치(예: /postinstall_mount)에 마운트되고 /postinstall_mount/usr/bin/postinstall 명령어가 실행됩니다.

설치 후 과정이 성공하려면 기존 커널이 다음을 실행할 수 있어야 합니다.

  • 새 파일 시스템 형식을 마운트. 파일 시스템 유형은 기존 커널에서 지원하지 않는 이상 변경할 수 없습니다. 여기에는 압축된 파일 시스템(SquashFS) 사용 시 압축 알고리즘이 사용되는 등의 세부적인 내용도 포함됩니다.
  • 새 파티션의 설치 후 프로그램 형식을 이해. ELF(Executable and Linkable Format) 바이너리를 사용 중인 경우 바이너리가 기존 커널과 호환되어야 합니다(예: 아키텍처가 32비트에서 64비트 빌드로 전환되었을 때 기존의 32비트 커널에서 실행되고 있는 새로운 64비트 프로그램). 로더(ld)가 다른 경로를 사용하거나 정적 바이너리를 빌드하도록 지시받지 않는 이상 라이브러리는 새 시스템 이미지가 아닌 기존 시스템 이미지에서 로드됩니다.

예를 들어 상단에 #! 마커가 있는 기존 시스템의 셸 바이너리에서 해석된 설치 후 프로그램으로 셸 스크립트를 사용한 다음 더 복잡한 바이너리 설치 후 프로그램을 실행하기 위해 새 환경에서 라이브러리 경로를 설정할 수 있습니다. 아니면 더 작은 전용 파티션에서 설치 후 단계를 실행하여 이전 버전과의 호환성 문제나 중간 업데이트 없이 업데이트하려는 기본 시스템 파티션에 파일 시스템 형식을 사용 설정할 수도 있습니다. 이렇게 하면 사용자가 공장 출고 시 이미지의 최신 버전으로 바로 업데이트할 수 있습니다.

새로운 설치 후 프로그램은 기존 시스템에서 정의된 SELinux 정책에 의해 제한됩니다. 따라서 설치 후 단계는 주어진 기기나 기타 최선의 작업(A/B 지원 펌웨어 또는 부트로더 업데이트, 새 버전을 위한 데이터베이스의 사본 준비 등)에 의도적으로 요구되는 작업을 실행하는 경우에 적합합니다. 예기치 않은 권한을 요구하는 재부팅 이전의 1회성 버그 수정에는 설치 후 단계가 적합하지 않습니다.

선택한 설치 후 프로그램은 postinstall SELinux 컨텍스트에서 실행됩니다. 새로 마운트된 파티션의 모든 파일은 새 시스템으로 재부팅된 후의 속성과 상관없이 postinstall_file로 태그가 지정됩니다. 새 시스템의 SELinux 속성을 변경해도 설치 후 단계는 영향을 받지 않습니다. 설치 후 프로그램에 추가 권한이 필요한 경우에는 설치 후 컨텍스트에 권한을 추가해야 합니다.

재부팅 후

재부팅 후에는 update_verifier가 dm-verity를 사용하여 무결성 검사를 트리거합니다. 이 검사는 자바 서비스에서 되돌릴 수 없는 변경사항을 야기하여 안전한 롤백을 막지 않도록 zygote 전에 시작됩니다. 이 프로세스 도중에 자체 검사 부팅이나 dm-verity에 의해 손상이 감지될 경우 부트로더와 커널에서도 재부팅을 트리거할 수 있습니다. 검사가 완료되면 update_verifier는 부팅을 정상으로 표시합니다.

update_verifier는 AOSP 코드를 사용할 때 A/B OTA 패키지에 포함되어 있는 /data/ota_package/care_map.txt에 나열된 블록만 읽습니다. GmsCore와 같은 자바 시스템 업데이트 클라이언트는 care_map.txt를 추출하고 기기를 재부팅하기 전에액세스 권한을 설정하며 시스템이 새 버전으로 성공적으로 부팅된 후 추출된 파일을 삭제합니다.