iOS 개발을 위한 CI 시스템: Intel에서 ARM으로의 전환

게시 됨: 2024-02-14

끊임없이 진화하는 기술 환경에서 기업은 관련성과 경쟁력을 유지하기 위해 변화의 바람에 적응해야 합니다. 기술계를 강타한 변화 중 하나는 Intel x86_64 아키텍처에서 iOS ARM 아키텍처로의 전환입니다. Apple의 획기적인 Apple M1 칩이 그 예시입니다. 이러한 맥락에서 iOS용 CI 시스템은 소프트웨어 개발 및 테스트 프로세스가 최신 기술 표준에 맞춰 효율적이고 최신 상태를 유지하도록 보장하면서 이러한 변화를 모색하는 기업에게 중요한 고려 사항이 되었습니다.

Apple은 거의 3년 전에 M1 칩을 발표했으며 그 이후로 회사가 ARM 아키텍처를 수용하고 결국 Intel 기반 소프트웨어에 대한 지원을 중단할 것이 분명해졌습니다. 아키텍처 간 호환성을 유지하기 위해 Apple은 독점 바이너리 변환 프레임워크인 Rosetta의 새 버전을 도입했습니다. 이 프레임워크는 과거 2006년 PowerPC에서 Intel로의 중요한 아키텍처 전환 과정에서 신뢰성이 입증되었습니다. 전환은 여전히 ​​진행 중이며 우리는 Xcode는 버전 14.3에서 Rosetta 지원을 잃습니다.

Miquido에서는 몇 년 전에 Intel에서 ARM으로 마이그레이션해야 할 필요성을 인식했습니다. 우리는 2021년 중반부터 준비를 시작했습니다. 여러 클라이언트, 앱, 프로젝트가 동시에 진행되는 소프트웨어 하우스로서 우리는 극복해야 할 몇 가지 과제에 직면했습니다. 이 문서는 귀하의 회사가 유사한 상황에 직면한 경우 방법을 안내할 수 있습니다. 설명된 상황과 솔루션은 iOS 개발 관점에서 설명되지만 다른 기술에도 적합한 통찰력을 찾을 수 있습니다. 전환 전략의 중요한 구성 요소는 iOS용 CI 시스템이 새로운 아키텍처에 완전히 최적화되었는지 확인하는 것과 관련이 있으며, 이러한 중요한 변화 속에서도 효율적인 작업 흐름과 고품질 출력을 유지하는 iOS용 CI 시스템입니다 .

문제: Intel에서 iOS ARM 아키텍처로 마이그레이션

Apple은 2020년에 M1 칩을 출시했습니다.

마이그레이션 프로세스는 두 가지 주요 분기로 나누어졌습니다.

1. 기존 Intel 기반 개발자 컴퓨터를 새로운 M1 Macbook으로 교체

이 과정은 비교적 간단할 것으로 예상되었습니다. 우리는 2년에 걸쳐 모든 개발자의 인텔 맥북을 점진적으로 교체한다는 정책을 세웠습니다. 현재 우리 직원의 95%가 ARM 기반 Macbook을 사용하고 있습니다.

하지만 이 과정에서 우리는 예상치 못한 난관에 부딪혔습니다. 2021년 중반, M1 Mac의 부족으로 인해 교체 프로세스가 지연되었습니다. 2021년 말까지 우리는 대기 중인 Macbook 약 200대 중 소수의 Macbook만 교체할 수 있었습니다. iOS가 아닌 엔지니어를 포함하여 회사의 모든 Intel Mac을 M1 Macbook으로 완전히 교체하는 데 약 2년이 걸릴 것으로 예상했습니다.

다행히 Apple은 새로운 M1 Pro 및 M2 칩을 출시했습니다. 결과적으로 우리는 Intel을 M1 Mac으로 교체하는 것에서 M1 Pro 및 M2 칩으로 교체하는 것으로 초점을 전환했습니다.

전환할 준비가 되지 않은 소프트웨어는 개발자의 좌절감을 야기했습니다.

새로운 M1 Macbook을 처음 받은 엔지니어들은 대부분의 소프트웨어가 Apple의 새로운 iOS ARM 아키텍처로 전환할 준비가 되어 있지 않았기 때문에 어려운 시간을 보냈습니다. 다른 많은 Rubygems에 의존하는 종속성 관리 도구인 Rubygems 및 Cocoapods와 같은 타사 도구가 가장 큰 영향을 받았습니다. 당시 이러한 도구 중 일부는 iOS ARM 아키텍처용으로 컴파일되지 않았기 때문에 대부분의 소프트웨어는 Rosetta를 사용하여 실행해야 했으며 이로 인해 성능 문제와 좌절이 발생했습니다.

그러나 소프트웨어 제작자는 이러한 문제가 발생하면 대부분을 해결하기 위해 노력했습니다. 획기적인 순간은 더 이상 Rosetta를 지원하지 않는 Xcode 14.3의 출시와 함께 찾아왔습니다. 이는 Apple이 Intel에서 iOS ARM 아키텍처로의 마이그레이션을 추진하고 있다는 모든 소프트웨어 개발자에게 분명한 신호였습니다. 이로 인해 이전에 Rosetta에 의존했던 대부분의 타사 소프트웨어 개발자는 소프트웨어를 ARM으로 마이그레이션해야 했습니다. 현재 Miquido에서 매일 사용되는 타사 소프트웨어의 99%는 Rosetta 없이 실행됩니다.

2. Miquido의 iOS용 CI 시스템 교체

Miquido에서 지속적인 통합 iOS 시스템을 교체하는 것은 단순히 시스템을 교체하는 것보다 더 복잡한 작업임이 입증되었습니다. 먼저 당시의 인프라를 살펴보십시오.

Miquido의 CI 아키텍처. iOS 혁신을 위한 CI 시스템.

우리는 Gitlab 클라우드 인스턴스와 9개의 Intel 기반 Mac Mini를 여기에 연결했습니다. 이 머신은 작업 실행자 역할을 했고 Gitlab은 오케스트레이션을 담당했습니다. CI 작업이 대기열에 추가될 때마다 Gitlab은 이를 gitlab-ci.yml 파일에 지정된 프로젝트 요구 사항을 충족하는 사용 가능한 첫 번째 실행자에게 할당했습니다. Gitlab은 모든 빌드 명령, 변수, 경로 등을 포함하는 작업 스크립트를 생성합니다. 그런 다음 이 스크립트는 실행기로 이동되어 해당 머신에서 실행됩니다.

이 설정은 견고해 보일 수 있지만 Intel 프로세서의 열악한 지원으로 인해 가상화 문제에 직면했습니다. 결과적으로 우리는 Docker와 같은 가상화를 사용하지 않고 물리적 머신 자체에서 작업을 실행하기로 결정했습니다. Docker를 기반으로 효율적이고 안정적인 솔루션을 설정하려고 노력했지만 GPU 가속 부족과 같은 가상화 제한으로 인해 작업을 실행하는 데 실제 머신에서보다 두 배의 시간이 걸렸습니다. 이로 인해 오버헤드가 증가하고 대기열이 빠르게 채워졌습니다.

macOS SLA로 인해 동시에 두 개의 VM만 설정할 수 있었습니다. 따라서 우리는 물리적 실행기 풀을 확장하고 운영 체제에서 직접 Gitlab 작업을 실행하도록 설정하기로 결정했습니다. 그러나 이 접근 방식에는 몇 가지 단점도 있었습니다.

빌드 프로세스 및 러너 관리의 과제

  1. 빌드 디렉터리 샌드박스 외부에서 빌드를 격리하지 않습니다.

실행기는 물리적 시스템에서 모든 빌드를 실행합니다. 즉, 빌드가 빌드 디렉터리 샌드박스에서 격리되지 않습니다. 여기에는 장점과 단점이 있습니다. 한편으로는 대부분의 프로젝트가 동일한 타사 종속성 세트를 사용하므로 시스템 캐시를 사용하여 빌드 속도를 높일 수 있습니다.

반면에 한 프로젝트의 남은 부분이 다른 모든 프로젝트에 영향을 미칠 수 있으므로 캐시를 유지 관리할 수 없게 됩니다. Flutter 및 React Native 개발에 동일한 실행기가 사용되므로 이는 시스템 전체 캐시에 특히 중요합니다. 특히 React Native의 경우 NPM을 통해 캐시된 많은 종속성이 필요합니다.

  1. 잠재적인 시스템 도구 혼란.

두 작업 모두 sudo 권한으로 실행되지 않았지만 Ruby와 같은 일부 시스템 또는 사용자 도구에 액세스하는 것은 여전히 ​​가능했습니다. 이는 특히 macOS가 일부 레거시 Xcode 기능을 포함하여 일부 레거시 소프트웨어에 Ruby를 사용하기 때문에 이러한 도구 중 일부를 손상시킬 수 있는 잠재적인 위협이 되었습니다. Ruby의 시스템 버전은 당신이 엉망으로 만들고 싶은 것이 아닙니다.

그러나 rbenv를 도입하면 처리해야 할 또 다른 복잡성 계층이 생성됩니다. Rubygems는 Ruby 버전별로 설치되며 이러한 gem 중 일부에는 특정 Ruby 버전이 필요하다는 점을 기억하는 것이 중요합니다. 우리가 사용했던 거의 모든 타사 도구는 Ruby에 의존했으며 Cocoapods와 Fastlane이 주요 행위자였습니다.

  1. 서명 ID를 관리합니다.

다양한 클라이언트 개발 계정의 여러 서명 ID를 관리하는 것은 실행기의 시스템 키체인과 관련하여 어려울 수 있습니다. 서명 ID는 애플리케이션을 공동 설계할 수 있게 하여 잠재적인 위협에 취약하게 만드는 매우 민감한 데이터입니다.

보안을 보장하려면 프로젝트 전체에서 ID를 샌드박스 처리하고 보호해야 합니다. 그러나 macOS의 키체인 구현에 추가된 복잡성을 고려하면 이 프로세스는 악몽이 될 수 있습니다.

  1. 다중 프로젝트 환경의 과제.

모든 프로젝트가 동일한 도구, 특히 Xcode를 사용하여 생성된 것은 아닙니다. 일부 프로젝트, 특히 지원 단계의 프로젝트는 프로젝트 개발에 사용된 Xcode의 마지막 버전을 사용하여 유지되었습니다. 이는 해당 프로젝트에 작업이 필요한 경우 CI가 이를 구축할 수 있어야 함을 의미합니다. 결과적으로 러너는 동시에 여러 버전의 Xcode를 지원해야 했고, 이로 인해 특정 작업에 사용할 수 있는 러너 수가 효과적으로 줄어들었습니다.

5. 추가적인 노력이 필요합니다.

소프트웨어 설치 등 러너 전반에 걸쳐 이루어진 모든 변경 사항은 모든 러너에서 동시에 수행되어야 합니다. 이를 위한 자동화 도구가 있었지만 자동화 스크립트를 유지하려면 추가 노력이 필요했습니다.

다양한 고객 요구에 맞는 맞춤형 인프라 솔루션

Miquido는 다양한 요구 사항을 가진 여러 클라이언트와 협력하는 소프트웨어 하우스입니다. 우리는 각 고객의 특정 요구 사항을 충족하도록 서비스를 맞춤화합니다. 우리는 소규모 기업이나 스타트업을 위한 코드베이스와 필수 인프라를 호스팅하는 경우가 많습니다. 유지 관리에 필요한 리소스나 지식이 부족할 수 있기 때문입니다.

기업 고객은 일반적으로 프로젝트를 호스팅하기 위한 자체 인프라를 보유하고 있습니다. 그러나 일부는 그렇게 할 능력이 없거나 업계 규정에 따라 인프라를 사용해야 하는 의무가 있습니다. 또한 Xcode Cloud 또는 Codemagic과 같은 타사 SaaS 서비스를 사용하지 않는 것을 선호합니다. 대신 기존 아키텍처에 맞는 솔루션을 원합니다.

이러한 클라이언트를 수용하기 위해 우리는 인프라에서 프로젝트를 호스팅하거나 인프라에 동일한 지속적 통합 iOS 구성을 설정하는 경우가 많습니다. 그러나 신원 서명과 같은 민감한 정보 및 파일을 다룰 때는 각별한 주의를 기울입니다.

효율적인 빌드 관리를 위해 Fastlane 활용

여기서 Fastlane은 편리한 도구로 제공됩니다. 이는 프로세스를 간소화하고 여러 클라이언트 간에 프로세스를 분리하는 데 도움이 되는 작업이라는 다양한 모듈로 구성됩니다. 일치 라고 하는 이러한 작업 중 하나는 개발 및 프로덕션 서명 ID는 물론 프로비저닝 프로필을 유지하는 데 도움이 됩니다. 또한 OS 수준에서 작동하여 빌드 시간 동안 해당 ID를 별도의 키체인으로 분리하고 빌드 후에 정리를 수행합니다. 이는 모든 빌드를 물리적 시스템에서 실행하기 때문에 더욱 유용합니다.

Fastlane: 개발 자동화 도구
이미지 크레디트: Fastlane

우리는 처음에 특정한 이유로 Fastlane을 선택했지만, 우리에게 유용할 수 있는 추가 기능이 있다는 것을 알게 되었습니다.

  1. Testflight에 업로드 빌드

과거에는 AppStoreConnect API가 개발자에게 공개적으로 제공되지 않았습니다. 즉, Testflight에 빌드를 업로드하는 유일한 방법은 Xcode를 통하거나 Fastlane을 사용하는 것뿐이었습니다. Fastlane은 본질적으로 ASC API를 스크랩하여 이를 파일럿 이라는 작업으로 전환한 도구였습니다. 그러나 이 방법은 다음 Xcode 업데이트에서 종종 중단되었습니다. 개발자가 명령줄을 사용하여 Testflight에 빌드를 업로드하려는 경우 Fastlane이 최고의 옵션이었습니다.

  1. Xcode 버전 간 간편한 전환

단일 시스템에 둘 이상의 Xcode 인스턴스가 있으므로 빌드에 사용할 Xcode를 선택해야 했습니다. 불행하게도 Apple은 Xcode 버전 간 전환을 불편하게 만들었습니다. 전환하려면 'xcode-select'를 사용해야 하며, 추가로 sudo 권한이 필요합니다. Fastlane에서도 이 내용을 다룹니다.

  1. 개발자를 위한 추가 유틸리티

Fastlane은 버전 관리 및 빌드 결과를 웹후크에 제출하는 기능을 포함하여 다른 많은 유용한 유틸리티를 제공합니다.

Fastlane의 단점

Fastlane을 우리 프로젝트에 적용하는 것은 건전하고 견고했기 때문에 우리는 그 방향으로 나아갔습니다. 우리는 그것을 몇 년 동안 성공적으로 사용했습니다. 그러나 지난 몇 년 동안 우리는 몇 가지 문제를 확인했습니다.

  1. Fastlane을 사용하려면 Ruby 지식이 필요합니다.

Fastlane은 Ruby로 작성된 도구이므로 효과적으로 사용하려면 Ruby에 대한 좋은 지식이 필요합니다. Fastlane 구성이나 도구 자체에 버그가 있는 경우 irb 또는 pry를 사용하여 버그를 디버깅하는 것은 상당히 어려울 수 있습니다.

  1. 수많은 보석에 대한 의존성.

Fastlane 자체는 약 70개의 보석에 의존합니다. 시스템 Ruby를 깨뜨릴 위험을 완화하기 위해 프로젝트에서는 로컬 번들러 gem을 사용하고 있었습니다. 이러한 보석을 모두 가져오는 데 많은 시간이 소요되었습니다.

  1. 시스템 Ruby 및 rubygems 문제.

결과적으로 앞서 언급한 시스템 Ruby 및 rubygems의 모든 문제도 여기에 적용 가능합니다.

  1. Flutter 프로젝트를 위한 중복성.

Flutter 프로젝트에서는 iOS 프로젝트와의 호환성을 유지하고 러너의 키체인을 보호하기 위해 강제로 fastlane match를 사용해야 했습니다. Flutter에는 자체 빌드 시스템이 내장되어 있고 앞서 언급한 오버헤드는 서명 ID 및 프로비저닝 프로필을 관리하는 데에만 도입되었기 때문에 이는 터무니없이 불필요했습니다.

이러한 문제의 대부분은 도중에 해결되었지만 보다 강력하고 안정적인 솔루션이 필요했습니다.

아이디어: 새롭고 더욱 강력한 iOS용 지속적 통합 도구 적용

좋은 소식은 Apple이 칩 아키텍처에 대한 완전한 통제권을 얻었고 macOS를 위한 새로운 가상화 프레임워크를 개발했다는 ​​것입니다. 이 프레임워크를 통해 사용자는 빠르게 시작하고 네이티브와 유사한 성능을 특징으로 하는 Linux 또는 macOS 가상 머신을 생성, 구성 및 실행할 수 있습니다. 실제로는 네이티브와 유사한 것을 의미합니다.

그것은 유망해 보였고 iOS용 새로운 지속적 통합 도구의 초석이 될 수 있었습니다. 그러나 이는 완전한 솔루션의 일부에 불과했습니다. VM 관리 도구가 있으면 Gitlab 실행기와 함께 해당 프레임워크를 사용할 수 있는 도구도 필요했습니다.

그렇게 되면 가상화 성능 저하와 관련된 대부분의 문제는 쓸모 없게 될 것입니다. 또한 Fastlane으로 해결하려고 했던 대부분의 문제를 자동으로 해결할 수 있습니다.

주문형 서명 ID 관리를 위한 맞춤형 솔루션 개발

마지막으로 해결해야 할 문제가 하나 있었습니다. 바로 ID 관리에 서명하는 것이었습니다. 우리는 Fastlane이 우리의 필요에 비해 과도해 보였기 때문에 이를 사용하고 싶지 않았습니다. 대신 우리는 우리의 요구 사항에 더 잘 맞는 솔루션을 찾고 있었습니다. 우리의 요구 사항은 간단했습니다. ID 관리 프로세스는 키체인에 사전 설치된 ID 없이 빌드 시간에만 주문형으로 수행되어야 하며 실행되는 모든 시스템과 호환되어야 했습니다.

배포 문제와 안정적인 AppstoreConnect API 부족은 Apple이 사용자와 ASC 간의 통신을 허용하는 'altool'을 출시하면서 더 이상 쓸모가 없게 되었습니다.

그래서 우리는 아이디어가 있었고 이 세 가지 측면을 함께 연결할 방법을 찾아야 했습니다.

  1. Apple의 가상화 프레임워크를 활용하는 방법을 찾습니다.
  2. Gitlab 러너와 함께 작동하도록 만듭니다.
  3. 여러 프로젝트 및 실행자 전반에 걸쳐 서명 ID 관리를 위한 솔루션을 찾습니다.

해결책: 우리의 접근 방식 엿보기(도구 포함)

우리는 앞서 언급한 모든 문제를 해결하기 위한 솔루션을 찾기 시작했습니다.

  1. Apple의 가상화 프레임워크를 활용합니다.

첫 번째 장애물에 대해 우리는 매우 빠르게 해결책을 찾았습니다. Cirrus Labs의 타트 도구를 발견했습니다. 처음부터 우리는 이것이 우리의 선택이 될 것이라는 것을 알았습니다.

Cirrus Lab에서 제공하는 타트 도구를 사용하면 다음과 같은 가장 큰 이점을 얻을 수 있습니다.

  • 원시 .ipsw 이미지에서 VM을 생성할 수 있습니다.
  • Cirrus Labs GitHub 페이지에서 사용할 수 있는 사전 포장된 템플릿(Brew 또는 Xcode와 같은 일부 유틸리티 도구가 설치되어 있음)을 사용하여 VM을 생성할 수 있습니다.
  • Tart 도구는 동적 이미지 구축 지원을 위해 패커를 활용합니다.
  • Tart 도구는 Linux 및 MacOS 이미지를 모두 지원합니다.
  • 이 도구는 실제로 디스크 공간을 예약하지 않고도 파일을 복제할 수 있는 APFS 파일 시스템의 뛰어난 기능을 활용합니다. 이렇게 하면 원본 이미지 크기의 3배에 해당하는 디스크 공간을 할당할 필요가 없습니다. 원본 이미지를 위한 충분한 디스크 공간만 필요한 반면, 복제본은 원본 이미지와 차이가 나는 공간만 차지합니다. 이는 특히 macOS 이미지가 상당히 큰 경향이 있기 때문에 매우 유용합니다.

예를 들어 Xcode 및 기타 유틸리티가 설치된 운영 macOS Ventura 이미지에는 최소 60GB의 디스크 공간이 필요합니다. 일반적인 상황에서는 이미지 하나와 해당 복제본 두 개가 최대 180GB의 디스크 공간을 차지하게 되는데, 이는 상당한 양입니다. 둘 이상의 원본 이미지를 갖고 싶거나 단일 VM에 여러 Xcode 버전을 설치하려고 할 수 있으므로 이는 시작에 불과하며 이로 인해 크기가 더욱 커집니다.

  • 이 도구를 사용하면 원본 및 복제된 VM에 대한 IP 주소 관리가 가능하므로 VM에 대한 SSH 액세스가 가능합니다.
  • 호스트 시스템과 VM 간에 디렉토리를 교차 마운트하는 기능.
  • 이 도구는 사용자 친화적이며 매우 간단한 CLI를 가지고 있습니다.

VM 관리에 활용하는 측면에서 이 도구에는 부족한 것이 거의 없습니다. 한 가지를 제외하고는 거의 아무것도 없습니다. 즉석에서 이미지를 구축하는 패커 플러그인은 유망하지만 시간이 너무 많이 걸리기 때문에 사용하지 않기로 결정했습니다.

우리는 타르트를 먹어봤는데 환상적으로 효과가 있었어요. 성능은 네이티브 수준이었고 관리도 쉬웠습니다.

타르트를 성공적으로 통합하여 인상적인 결과를 얻은 다음에는 다른 과제를 해결하는 데 중점을 두었습니다.

  1. Tart를 Gitlab 러너와 결합하는 방법을 찾습니다.

첫 번째 문제를 해결한 후 우리는 Tart를 Gitlab 러너와 결합하는 방법에 대한 질문에 직면했습니다.

Gitlab 실행기가 실제로 수행하는 작업을 설명하는 것부터 시작하겠습니다.

Gitlab 작업 위임의 단순화된 다이어그램. iOS용 CI 시스템

실행기 호스트에서 VM으로 작업을 할당하는 것과 관련된 추가 퍼즐을 다이어그램에 포함해야 했습니다. GitLab 작업은 중요한 변수, PATH 항목 및 명령을 보유하는 쉘 스크립트입니다.

우리의 목표는 이 스크립트를 VM으로 전송하고 실행하는 것이었습니다.

그러나 이 작업은 우리가 처음에 생각했던 것보다 더 어려운 것으로 판명되었습니다.

주자

Docker 또는 SSH와 같은 표준 Gitlab 실행기 실행기는 설정이 간단하고 구성이 거의 또는 전혀 필요하지 않습니다. 그러나 구성에 대한 더 큰 제어가 필요했기 때문에 GitLab에서 제공하는 사용자 정의 실행기를 탐색하게 되었습니다.

사용자 정의 실행기는 각 실행 단계(준비, 실행, 정리)가 셸 스크립트 형식으로 설명되므로 비표준 구성을 위한 훌륭한 옵션입니다. 누락된 유일한 것은 필요한 작업을 수행하고 실행기 구성 스크립트에서 실행할 수 있는 명령줄 도구였습니다.

현재 이를 정확하게 수행하는 몇 가지 도구(예: CirrusLabs Gitlab tart executor)가 있습니다. 이 도구는 당시 우리가 찾던 도구였습니다. 그러나 아직 존재하지 않았고, 연구를 수행한 후에도 작업을 수행하는 데 도움이 될 수 있는 도구를 찾지 못했습니다.

자체 솔루션 작성

완벽한 솔루션을 찾을 수 없었기 때문에 우리가 직접 솔루션을 작성했습니다. 결국 우리는 엔지니어입니다! 아이디어가 탄탄해 보였고, 필요한 도구도 모두 갖춰져 있어서 개발을 진행했습니다.

우리는 Swift와 Apple에서 제공하는 몇 가지 오픈 소스 라이브러리를 사용하기로 결정했습니다. Swift Argument Parser는 명령줄 실행을 처리하고 Swift NIO는 VM과의 SSH 연결을 처리합니다. 우리는 개발을 시작했고 며칠 만에 MQVMRunner로 발전한 도구의 첫 번째 작업 프로토타입을 얻었습니다.

iOS CI 인프라: MQVMRunner

높은 수준에서 이 도구는 다음과 같이 작동합니다.

  1. (준비단계)
    1. gitlab-ci.yml에 제공된 변수(이미지 이름 및 추가 변수)를 읽어보세요.
    2. 요청된 VM 기반을 선택하세요.
    3. 요청된 VM 베이스를 복제합니다.
    4. 교차 마운트 디렉터리를 설정하고 Gitlab 작업 스크립트를 복사하여 필요한 권한을 설정합니다.
    5. 클론을 실행하고 SSH 연결을 확인합니다.
    6. 필요한 경우 필요한 종속성(예: Xcode 버전)을 설정합니다.
  2. (실행 단계)
    1. SSH를 통해 준비된 VM 복제본의 교차 마운트 디렉터리에서 스크립트를 실행하는 Gitlab 작업을 실행합니다.
  3. (클린업 단계)
    1. 복제된 이미지를 삭제합니다.

개발의 과제

개발 과정에서 우리는 몇 가지 문제에 직면했고, 그로 인해 우리가 원했던 것만큼 원활하게 진행되지 않았습니다.

  1. IP 주소 관리.

IP 주소 관리는 주의해서 처리해야 하는 중요한 작업입니다. 프로토타입에서는 SSH 처리가 직접적이고 하드코딩된 SSH 셸 명령을 사용하여 구현되었습니다. 그러나 비대화형 쉘의 경우 키 인증을 권장합니다. 또한 중단을 방지하려면 Known_hosts 파일에 호스트를 추가하는 것이 좋습니다. 그럼에도 불구하고 가상 머신 IP 주소의 동적 관리로 인해 특정 IP에 대한 항목이 두 배로 늘어나 오류가 발생할 가능성이 있습니다. 따라서 이러한 문제를 방지하려면 특정 작업에 대해 Known_hosts를 동적으로 할당해야 합니다.

  1. 순수한 Swift 솔루션.

그런 점과 Swift 코드에 하드코딩된 셸 명령이 그다지 우아하지 않다는 점을 고려하면 전용 Swift 라이브러리를 사용하는 것이 좋겠다고 생각하여 Swift NIO를 사용하기로 결정했습니다. 우리는 몇 가지 문제를 해결했지만 동시에 몇 가지 새로운 문제를 도입했습니다. 예를 들어 때때로 stdout에 저장된 로그는 명령 실행이 완료되어 SSH 채널이 종료된 *후에* 전송되었습니다. 추가 작업에서 해당 출력이 실행되면 무작위로 실패했습니다.

  1. Xcode 버전 선택.

Packer 플러그인은 시간 소모로 인해 동적 이미지 구축을 위한 옵션이 아니었기 때문에 여러 Xcode 버전이 사전 설치된 단일 VM 기반을 사용하기로 결정했습니다. 우리는 개발자가 gitlab-ci.yml에서 필요한 Xcode 버전을 지정할 수 있는 방법을 찾아야 했으며 모든 프로젝트에서 사용할 수 있는 사용자 정의 변수를 마련했습니다. 그런 다음 MQVMRunner는 복제된 VM에서 `xcode-select`를 실행하여 해당 Xcode 버전을 설정합니다.

그리고 훨씬 더 많은 것

Mac Studio와 iOS 작업 흐름을 위한 프로젝트 마이그레이션 간소화 및 지속적인 통합

우리는 두 개의 새로운 Mac Studio에 이를 설정하고 프로젝트 마이그레이션을 시작했습니다. 우리는 개발자를 위한 마이그레이션 프로세스를 최대한 투명하게 만들고 싶었습니다. 완전히 원활하게 만들 수는 없었지만 결국 gitlab-ci.yml에서 몇 가지 작업만 수행하면 되는 지점에 도달했습니다.

  • 주자의 태그: Intel 대신 Mac Studio를 사용합니다.
  • 이미지 이름: 두 개 이상의 기본 VM이 필요한 경우 향후 호환성을 위해 도입된 선택적 매개변수입니다. 지금은 항상 기본적으로 우리가 보유한 단일 기본 VM을 사용합니다.
  • Xcode 버전: 선택적 매개변수; 제공되지 않은 경우 사용 가능한 최신 버전이 사용됩니다.

이 도구는 초기 피드백이 매우 좋았으므로 오픈 소스로 만들기로 결정했습니다. Gitlab Custom Runner와 필요한 모든 작업 및 변수를 설정하기 위한 설치 스크립트를 추가했습니다. 우리 도구를 사용하면 몇 분 안에 자신만의 GitLab 실행기를 설정할 수 있습니다. 필요한 유일한 것은 작업이 실행될 타르트 및 기본 VM뿐입니다.

iOS 구조의 최종 지속적 통합은 다음과 같습니다.

최종 CI 인프라: MQVMRunner

3. 효율적인 신원 관리를 위한 솔루션

우리는 고객의 서명 신원을 관리하기 위한 효율적인 솔루션을 찾기 위해 고군분투해 왔습니다. 신원 서명은 필요 이상으로 오래 동안 안전하지 않은 장소에 저장하면 안 되는 매우 기밀한 데이터이기 때문에 이는 특히 어려웠습니다.

또한 우리는 프로젝트 간 솔루션 없이 빌드 시간에만 이러한 ID를 로드하기를 원했습니다. 이는 앱(또는 빌드) 샌드박스 외부에서 ID에 액세스할 수 없다는 의미입니다. 우리는 이미 VM으로 전환하여 후자의 문제를 해결했습니다. 그러나 우리는 빌드 시간 동안에만 서명 ID를 VM에 저장하고 로드하는 방법을 찾아야 했습니다.

Fastlane 일치 문제

당시 우리는 여전히 암호화된 ID와 프로비저닝을 별도의 저장소에 저장하고, 빌드 프로세스 중에 이를 별도의 키체인 인스턴스에 로드하고, 빌드 후에 해당 인스턴스를 제거하는 Fastlane 일치를 사용하고 있었습니다.

이 접근 방식은 편리해 보이지만 몇 가지 문제가 있습니다.

  • 작동하려면 전체 Fastlane 설정이 필요합니다.

Fastlane은 Rubygem이며 첫 번째 장에 나열된 모든 문제가 여기에 적용됩니다.

  • 빌드 시 리포지토리 체크아웃.

우리는 설정 프로세스가 아닌 빌드 프로세스 중에 체크아웃된 별도의 저장소에 ID를 보관했습니다. 이는 우리가 개인 타사 종속성을 처리하는 방법과 유사하게 Gitlab뿐만 아니라 특정 실행자에 대해 ID 저장소에 대한 별도의 액세스를 설정해야 함을 의미했습니다.

  • 매치 외부에서는 관리가 어렵습니다.

ID 관리 또는 프로비저닝을 위해 Match를 활용하는 경우 수동 개입이 거의 필요하지 않습니다. 나중에 일치 항목이 계속 작동할 수 있도록 프로필을 수동으로 편집, 암호 해독 및 암호화하는 작업은 지루하고 시간이 많이 걸립니다. Fastlane을 사용하여 이 프로세스를 수행하면 일반적으로 애플리케이션 프로비저닝 설정이 완전히 지워지고 새 설정이 생성됩니다.

  • 디버깅하기가 조금 어렵습니다.

코드 서명 문제가 있는 경우 먼저 디코딩해야 하므로 방금 설치된 ID 및 프로비저닝 일치를 확인하기 어려울 수 있습니다.

  • 보안 문제.

제공된 자격 증명을 사용하여 액세스한 개발자 계정을 일치시켜 대신 변경합니다. Fastlane은 오픈 소스임에도 불구하고 일부 클라이언트는 보안 문제로 인해 이를 거부했습니다.

  • 마지막으로, Match를 제거하면 Fastlane을 완전히 제거하는 데 가장 큰 장애물이 제거됩니다.

우리의 초기 요구 사항은 다음과 같습니다.

  • 로드하려면 안전한 장소에서 가급적 일반 텍스트가 아닌 형식으로 ID에 서명하고 이를 키체인에 배치해야 합니다.
  • 해당 ID는 Xcode에서 액세스할 수 있어야 합니다.
  • 가급적이면 ID 비밀번호, 키체인 이름, 키체인 비밀번호 변수는 디버깅 목적으로 설정 가능해야 합니다.

Match에는 필요한 모든 것이 있었지만 Match를 사용하기 위해 Fastlane을 구현하는 것은 과도한 것처럼 보였습니다. 특히 자체 빌드 시스템을 갖춘 크로스 플랫폼 솔루션의 경우 더욱 그렇습니다. 우리는 Match와 비슷한 것을 원했지만 Ruby의 무거운 부담 없이 휴대가 가능했습니다.

자체 솔루션 만들기

그래서 우리는 생각했습니다 – 그것을 직접 작성해보자! MQVMRunner를 사용하여 이 작업을 수행했으므로 여기서도 수행할 수 있습니다. 또한 Apple Security 프레임워크를 사용하면 필요한 많은 API를 무료로 얻을 수 있기 때문에 Swift를 선택했습니다.

물론 기대만큼 순조롭게 진행되지도 않았다.

  • 보안 프레임워크가 마련되어 있습니다.

가장 쉬운 전략은 Fastlane처럼 bash 명령을 호출하는 것이었습니다. 그러나 보안 프레임워크를 사용할 수 있게 되면 개발에 사용하는 것이 더 우아할 것이라고 생각했습니다.

  • 경험의 부족.

우리는 macOS용 보안 프레임워크에 대한 경험이 많지 않았으며 iOS에서 익숙했던 보안 프레임워크와 크게 다르다는 것이 밝혀졌습니다. 이는 우리가 macOS의 제한 사항을 인식하지 못했거나 iOS에서와 동일하게 작동한다고 가정한 많은 경우에 역효과를 냈습니다. 이러한 가정의 대부분은 틀렸습니다.

  • 끔찍한 문서.

Apple Security 프레임워크의 문서는 가볍게 말하면 소박합니다. 이는 OSX의 첫 번째 버전으로 거슬러 올라가는 매우 오래된 API이며 때로는 그 이후로 업데이트되지 않았다는 인상을 받았습니다. 큰 코드 덩어리는 문서화되어 있지 않지만 소스 코드를 읽으면서 어떻게 작동할지 예상했습니다. 다행스럽게도 이는 오픈 소스입니다.

  • 교체 없이 지원 중단됩니다.

이 프레임워크의 상당 부분은 더 이상 사용되지 않습니다. Apple은 일반적인 "macOS 스타일" 키체인(비밀번호로 액세스할 수 있는 여러 키체인)에서 벗어나 "iOS 스타일" 키체인(iCloud를 통해 동기화되는 단일 키체인)을 구현하려고 노력하고 있습니다. 따라서 그들은 2014년에 macOS Yosemite에서 이 API를 더 이상 사용하지 않았지만 지난 9년 동안 이를 대체할 API가 나오지 않았습니다. 따라서 현재 우리가 사용할 수 있는 유일한 API는 아직 새로운 API가 없기 때문에 더 이상 사용되지 않습니다.

우리는 서명 ID가 프로젝트별 Gitlab 변수에 base64로 인코딩된 문자열로 저장될 수 있다고 가정했습니다. 프로젝트 기반으로 안전하며 마스크된 변수로 설정된 경우 빌드 로그에서 일반 텍스트가 아닌 텍스트로 읽고 표시할 수 있습니다.

그래서 우리는 신원 데이터를 얻었습니다. 우리는 그것을 키체인에 넣기만 하면 되었습니다. 보안 API 사용하기 보안 프레임워크 문서를 검토하는 몇 번의 시도와 난투 끝에 우리는 나중에 MQSwiftSign이 되는 프로토타입을 준비했습니다.

macOS 보안 시스템을 배우지만 어려운 방법

우리는 도구를 개발하기 위해 macOS 키체인이 어떻게 작동하는지 깊이 이해해야 했습니다. 여기에는 키체인이 항목, 액세스 및 권한, 키체인 데이터 구조를 관리하는 방법을 조사하는 작업이 포함되었습니다. 예를 들어, 키체인은 운영 체제가 ACL 세트를 무시하는 유일한 macOS 파일이라는 것을 발견했습니다. 또한 특정 키체인 항목에 대한 ACL은 키체인 파일에 저장된 일반 텍스트 plist라는 사실도 알게 되었습니다. 우리는 그 과정에서 여러 가지 어려움에 직면했지만 많은 것을 배웠습니다.

우리가 직면한 중요한 과제 중 하나는 프롬프트였습니다. 우리 도구는 주로 CI iOS 시스템에서 실행되도록 설계되었으므로 비대화형이어야 했습니다. 사용자에게 CI의 암호를 확인하도록 요청할 수 없었습니다.

그러나 macOS 보안 시스템은 잘 설계되어 있어 명시적인 사용자 허가 없이 서명 ID를 포함한 기밀 정보를 편집하거나 읽는 것이 불가능합니다. 확인 없이 리소스에 액세스하려면 액세스하는 프로그램이 리소스의 액세스 제어 목록에 포함되어 있어야 합니다. 이는 시스템과 함께 제공되는 Apple 프로그램이라도 어떤 프로그램도 중단될 수 없다는 엄격한 요구 사항입니다. 프로그램에서 키체인 항목을 읽거나 편집해야 하는 경우 사용자는 키체인 비밀번호를 제공하여 잠금을 해제하고 선택적으로 항목의 ACL에 추가해야 합니다.

사용자 권한 문제 극복

그래서 우리는 암호 프롬프트를 사용하여 사용자에게 허가를 요청하지 않고 Xcode가 키체인에 의해 설정된 ID에 액세스할 수 있는 방법을 찾아야 했습니다. 이를 위해 항목의 액세스 제어 목록을 변경할 수 있지만 이를 위해서는 사용자 권한도 필요합니다. 물론 그렇습니다. 그렇지 않으면 ACL 보유의 전체 요점이 훼손될 수 있습니다. 우리는 이러한 보호 장치를 우회하려고 노력해 왔습니다. 'security set-key-partition-list' 명령과 동일한 효과를 얻으려고 노력했습니다.

프레임워크 문서를 자세히 살펴본 후에도 사용자에게 비밀번호를 제공하라는 메시지를 표시하지 않고 ACL을 편집할 수 있는 API를 찾지 못했습니다. 우리가 찾은 가장 가까운 것은 매번 UI 프롬프트를 트리거하는 `SecKeychainItemSetAccess`입니다. 그런 다음 우리는 또 다른 다이빙을 했지만 이번에는 소스 코드 자체인 최고의 문서를 살펴보았습니다. Apple은 이를 어떻게 구현했나요?

예상대로 그들은 비공개 API를 사용하고 있는 것으로 나타났습니다. 'SecKeychainItemSetAccessWithPassword'라는 메서드는 기본적으로 'SecKeychainItemSetAccess'와 동일한 작업을 수행하지만 사용자에게 비밀번호를 묻는 대신 비밀번호가 함수에 대한 인수로 제공됩니다. 물론, 비공개 API이기 때문에 문서에는 나와 있지 않지만 Apple에는 개인용 또는 기업용 앱을 만드는 것을 생각할 수 없는 것처럼 그러한 API에 대한 문서가 부족합니다. 이 도구는 내부 전용으로 만들어졌기 때문에 주저하지 않고 비공개 API를 사용했습니다. 해야 할 유일한 일은 C 메소드를 Swift에 연결하는 것이었습니다.

사용자 권한 문제 극복

따라서 프로토타입의 최종 작업 흐름은 다음과 같습니다.

  1. 자동 잠금이 꺼진 상태에서 임시 잠금 해제 키체인을 만듭니다.
  2. 환경 변수(Gitlab에서 전달)에서 base64로 인코딩된 서명 ID 데이터를 가져오고 디코딩합니다.
  3. 생성된 키체인으로 ID를 가져옵니다.
  4. Xcode 및 기타 도구가 공동 설계를 위해 이를 읽을 수 있도록 가져온 ID에 대한 적절한 액세스 옵션을 설정하십시오.

추가 업그레이드

프로토타입이 잘 작동했기 때문에 도구에 추가하고 싶은 몇 가지 추가 기능을 식별했습니다. 우리의 목표는 결국 fastlane을 교체하는 것이었습니다. 우리는 이미 `match` 액션을 구현했습니다. 그러나 fastlane은 아직 제공하지 못한 두 가지 중요한 기능, 즉 프로비저닝 프로필 설치와export.plist 생성을 제공했습니다.

프로비저닝 프로필 설치

프로비저닝 프로필 설치는 매우 간단합니다. 프로필 UUID를 추출하고 UUID를 파일 이름으로 사용하여 파일을 `~/Library/MobileDevice/Provisioning Profiles/`에 복사하는 것으로 나누어집니다. 그러면 Xcode에서 이를 제대로 볼 수 있습니다. 제공된 디렉토리를 반복하고 내부에서 발견되는 모든 .mobileprovision 파일에 대해 이를 수행하는 간단한 플러그인을 도구에 추가하는 것은 대단한 과학이 아닙니다.

내보내기.plist 생성

그러나 내보내기.plist 생성은 약간 더 까다롭습니다. 적절한 IPA 파일을 생성하기 위해 Xcode는 사용자에게 프로젝트 파일, 자격 부여 plist, 작업 공간 설정 등 다양한 소스에서 수집된 특정 정보가 포함된 plist 파일을 제공하도록 요구합니다. Xcode가 배포 마법사를 통해서만 해당 데이터를 수집할 수 있지만 그렇지 않은 이유는 다음과 같습니다. CLI를 통한 것은 나에게 알려지지 않았습니다. 그러나 우리는 Swift API를 사용하여 프로젝트/작업 공간 참조와 Xcode 프로젝트 파일 구축 방법에 대한 약간의 지식만 가지고 이를 수집해야 했습니다.

결과가 예상보다 좋았기 때문에 우리 도구에 또 다른 플러그인으로 추가하기로 결정했습니다. 우리는 또한 이를 더 많은 청중을 위한 오픈 소스 프로젝트로 출시했습니다. 현재 MQSwiftSign은 iOS 애플리케이션을 구축하고 배포하는 데 필요한 기본 fastlane 작업을 대체하여 성공적으로 사용할 수 있는 다목적 도구이며 Miquido의 모든 프로젝트에서 이를 사용합니다.

최종 생각: 성공

Intel에서 iOS ARM 아키텍처 로 전환하는 것은 어려운 작업이었습니다. 우리는 수많은 장애물에 직면했고 문서 부족으로 인해 도구 개발에 상당한 시간을 소비했습니다. 그러나 우리는 궁극적으로 강력한 시스템을 구축했습니다.

  • 9명이 아닌 2명의 주자를 관리해야 합니다.
  • 루비젬 형태의 엄청난 오버헤드 없이 완전히 우리가 제어할 수 있는 소프트웨어를 실행합니다. 우리는 빌드 구성에서 fastlane이나 타사 소프트웨어를 제거할 수 있었습니다.
  • macOS 시스템 보안 및 보안 프레임워크 자체, 실제 Xcode 프로젝트 구조 등과 같이 우리가 일반적으로 주의를 기울이지 않는 것들에 대한 많은 지식과 이해가 있습니다.

기꺼이 격려해 드리겠습니다. iOS 빌드용 GitLab Runner 설정에 어려움을 겪고 계신다면 MQVMRunner를 사용해 보세요. 단일 도구를 사용하여 앱을 빌드하고 배포하는 데 도움이 필요하고 Rubygems에 의존하고 싶지 않은 경우 MQSwiftSign을 사용해 보세요. 나에게도 효과가 있고 당신에게도 효과가 있을 수 있습니다!