Search
Duplicate
🛠️

나의 iOS CI환경 구축 노하우

Created
2021/10/24
Tags
Programming
CI
CI(Continuous Integration) 환경이 중요하다는 것은 이제 많은 사람들이 알고 있습니다. 하지만 막상 CI 환경을 만드는 것은 어렵기도 하지만 굉장히 많은 시간을 투자해야 하는 일입니다. 그래서 적지 않은 분들이 CI도입을 망설이거나, 도전했다가 포기하는 경우가 많은 것 같습니다. 제가 시행착오를 통해 알게 된 사소한 노하우 몇 가지를 공유해, 그 분들에게 용기를 드리거나, 또는 몇 시간의 작업시간이라도 아껴드릴 수 있다면 대단히 기쁠 것 같습니다.

CI 구축은 언제나 생각보다 어렵다는 것을 받아들여야

앱스토어에서 앱을 리뷰 받을 때 그런 경험 없으신가요? 앱스토어에서 리젝을 먹여서, 문제 되는 부분을 수정해서 다시 제출했는데, 이번에는 또 다른 이유로 리젝을 먹이고, 그래서 그것을 다시 수정해서 제출했는데 또 다른 이유로 리젝을 먹는.. 그런 경험 말이죠. 사실 리뷰어가 앱을 전체적으로 다 검수하고 문제가 되는 부분을 한 번에 정리해서 알려주고, 그 부분들을 한 번에 싹 고쳐서 제출한다면, 한 번의 핑퐁으로 앱스토어에 앱을 올릴 수 있었을 것입니다. 하지만 언제나 앱스토어 리뷰어는 문제가 되는 부분을 하나라도 발견하면 그 즉시 검수를 멈추고, 리젝을 먹이고, 그 리젝에 대한 응답이 오기 전까지 절대로 검수를 재개하지 않죠. 그 결과 우리는 길고, 불확실하며, 매우 짜증나는 핑퐁을 짧으면 며칠 길게는 몇 주에 걸쳐 진행하게도 되는 것입니다.
CI를 구축하는 것은 그런 면에서 앱스토어 리뷰와 매우 닮았습니다. 내 컴퓨터에서는 분명 잘 돌아갔던 명령어들이 CI에서 말썽을 일으키는 일은 흔합니다. 그래서 그것을 고치면, 바로 짜잔 하고 CI가 완성되지를 않습니다. 그 명령어는 넘어가더라도, 또 그 다음 부분에서 얼마든지 불만을 제기 할 수 있는 것이 CI입니다. CI의 에러메시지들은 결코 한 번에 출력되지 않고, 그럴 수도 없습니다.
CI작업에 시간이 오래 걸리는 이유 중 하나는 "이 에러만 해결하면 이제 정말로 다 잘 될 것 같은" 느낌에 속기 아주 쉽기 때문입니다. 그것은 정말 느낌일 뿐입니다. 그 에러만 해결한다고, 방금 전까지 실패하던 빌드가 이번에는 잘 될 것이라는 보장은 전혀 없습니다. CI 작업자는 이 사실을 겸허하게 받아들여야 합니다. 최초로 CI 환경을 구축하는 일에는 아무리 적게 잡아도 최소 1~2주의 시간을 온전히 투자해야 한다고 생각합니다.

에러 메시지를 어떻게든 빨리 만날 수 있는 환경을 만들어야

CI 환경 구축에 오랜 시간이 걸리는 가장 중요한 이유는, 에러 메시지를 만나서, 그 부분에 해당하는 수정을 했을 때, 그 수정이 효과가 있는지의 여부를 알기까지 아주 오랜 시간이 걸릴 수 있기 때문입니다. 왜냐하면 결국 "iOS앱을 빌드"하는 과정이 모든 CI 파이프라인에서 필요한데, 일반적으로 iOS앱의 빌드는 아주 오래 걸리기 때문입니다.
따라서 어떻게 해서든 "더 빨리 에러메시지를 더 많이 만날 수 있는 환경을 구축하는 것, 그리고 거기에 시간과 노력을 쏟는 것"이 핵심입니다. 이런 환경을 만드는 몇 가지 노하우들이 있습니다.

1. 내 컴퓨터에서 쓸 수 있는 CLI 도구를 만들자

무턱대고 CI 환경을 설치하거나, CI 도구들을 구매하기 이전에, 내가 원하는 파이프라인을 최대한 그런 도구들 없이 만들어봐야 합니다. 예컨대 "누구나 쓸 수 있는 배포 도구"를 만들기 전에, "내 노트북에서 커맨드 하나 실행시켜서 바로 배포 할 수 있는 환경"을 만드는 것을 선행 목표로 잡아야 합니다. 그리고 CI 도구는 이 때 내가 만든 커맨드라인 도구를 트리거 하는 역할만 맡긴다고 생각해야 합니다. 이런 마음가짐으로 CI 환경을 만들면 여러 이점이 있습니다.
똑같이 iOS앱을 빌드하더라도, 내 컴퓨터에서는 증분빌드를 쓸 수 있기 때문에 더 빨리 빌드되고, 에러가 발생해도 디버거를 연결하기도 쉽습니다. 문제 파악, 문제 대응의 레벨이 훨씬 쉽기 때문에 더 빨리 만들 수 있습니다.
설령 나중에 여러 이유로 CI 환경 구축이 엎어지더라도, 최소한 "내가 더 편하게 배포 할 수 있는 환경"이라는 산출물이 남습니다.
나중에 다른 CI 서비스로 이전을 할 때, 고려 할 것이 적어집니다. 예컨대 Jenkins에서만 쓰는 특정한 도구를 활용해 CI 환경을 구축했다면, 그 도구가 없는 다른 CI 서비스로의 이전은 고려하기 어려울 수 있습니다. 하지만 그런 의존성이 없다면, 언제든 내가 원하는 CI 서비스로 갈아탈 수 있습니다.

2. 가장 작은 앱으로 CI를 길들여보자

단순한 얘기입니다. 더 작은 앱은 더 빨리 빌드되고, 에러메시지도 더 빨리 받을 수 있습니다. 가장 작은 앱으로 가장 단순한 CI 파이프라인을 구축하면서 경험을 쌓으면, 이런 에러메시지를 읽고 대처하는 노하우를 빨리 쌓을 수 있습니다. 이런 노하우를 쌓을 시간에, 그냥 CI 환경을 바로 구축하는 것이 낫겠다는 생각이 드시나요? 하지만 "CI구축은 언제나 생각보다 어렵다"는 것을 잊지 마세요.

3. 내게 필요한, CI 서비스에서 제공하는 환경변수들을 "출력만" 해보자.

내 컴퓨터에서 잘 돌아가는 CI 파이프라인이, 왜 CI가 실제로 돌아가는 다른 컴퓨터에서는 잘 돌아가지 않을까요? 다양한 요소가 있을 수 있지만, 아마 가장 중요한 요인은, git으로 관리되지 않으면서도 빌드에 중요한 역할을 하는 다양한 환경변수들일 것입니다.
예컨대 저는 $BUILD_URL이라는 환경변수에 언제나 특정 값이 들어있을 것이라 기대하고 파이프라인을 만들었습니다. 하지만 환경변수를 선언하는 방법이 틀렸거나, 혹은 그 환경변수에 제가 기대하지 않은 값이 들어있을 때, 제 CI 파이프라인을 실패하거나 제 기능을 못하게 될 것입니다. 이런 식으로 발생하는 문제가 경험상 CI 구축에서 만나는 오류의 5~60%는 되는 것 같습니다.
이런 환경변수는 CI 서비스들이 제공하는 도구들을 활용해야 하기 때문에 로컬에서 테스트하기 어렵습니다. 그런데 이 환경변수들이 잘 적용되어 빌드가 잘 되는지 확인하는 것은 시간이 오래걸리고 어렵지만, 그 환경변수들 출력하기만 해서 내가 기대한 값들이 잘 들어 있는지 확인하는 것은 아주 적은 시간 투자만으로 할 수 있는 일입니다.
내게 필요한 환경변수가 무엇인지 먼저 리스트업 해보세요. 그리고 그 환경변수들만 CI 환경에서 출력해보세요. 쉽고 빠르게 할 수 있는 일을 먼저 끝내놓으면, 그 다음에 해야 할 일들을 더 빠르게 높은 확신을 가지고 진행할 수 있습니다.

4. 스텝별로 테스트하기

CI 구축의 큰 산 하나는 "빌드 스텝"에서 빌드를 성공시키는 것입니다. 그 다음 산은, 그렇게 만들어진 빌드를 "배포"하는 것일 겁니다. 그런데 "빌드"스텝이 성공해도, "배포" 스텝은 실패할 수도 있습니다. 이 때, "배포" 스텝의 실패 원인을 파악하려면 어떻게 해야 할까요?
CI 파이프라인을 구축 할 때 쉽게 빠질 수 있는 함정이, "배포" 스텝을 테스트하려면, 반드시 그 전에 "빌드" 스텝을 실행시켜야 한다는 생각입니다. 하지만 배포 스텝을 테스트 할 때는, 오직 배포 스텝만 테스트해야 합니다. 그것을 테스트하기 위해 매번 빌드스텝까지 실행시킨다면, 위에서 얘기한 것처럼, 에러메시지로부터 피드백을 받는데 매번 수십분의 시간을 소모하게 됩니다.
그런데 어떻게 앱을 빌드하지 않고 "배포"스텝을 테스트 할 수 있을까요? 생각보다 여러가지 방법이 있습니다. 예컨대 이런 방법이 있을 수 있죠.
빌드 스텝에서 만들어진 빌드를 공용 저장소의 별도 브랜치에 업로드
아까 만들었든 CI 파이프라인 스크립트에서 "빌드" 스텝을 주석처리
그 주석처리된 자리에 "이미 만들어진 빌드를 다운로드" 하는 스크립트를 추가
하는 방법이 있을 수 있습니다. 그럼 CI 파이프라인에서는 앱을 20분에 걸쳐 빌드하는 대신, 10초에 걸쳐 이미 빌드된 앱을 다운로드 한 다음, 바로 이어서 문제가 되는 "배포" 스텝을 실행 할 수 있게 되겠죠. 여기서 만약 또 문제가 발생한다고 해도, 그 문제를 고친 다음 잘 고쳐졌는지 확인하는데에는 이제 10초밖에 걸리지 않게 됩니다.
이것은 하나의 예시일 뿐입니다. 어떻게 해서든 "하나 하나의 스텝을 따로 따로 테스트 할 수 있는지"를 궁리하고 고민하고 그런 환경을 구현해야 합니다. 여기에 쏟는 시간이 아깝다는 생각이 든다면, 기억하세요. "CI 환경 구축은 언제나 생각보다 어렵다"는 것을.

5. fastlane보다 xcodebuild

iOS의 CI 하면 바로 생각나는 단짝친구. 바로 fastlane입니다. 저는 fastlane을 정말 좋아해요. 하지만 특히 CI 환경 구축이 처음인 분이라면, fastlane 사용을 권하고 싶지 않습니다.
fastlane은 많은 경우 xcodebuild라는 명령어의 wrapper역할을 합니다. 그래서 더 간단해 보이지만, 사실 우리가 마주해야 하는 복잡함을 제거해주지는 않습니다. 오히려 fastlane과 같이 복잡한 것을 간단한 것처럼 만들어주는 도구들은, 에러가 발생했을 때 우리에게 더 큰 당혹감을 주는 경우가 많습니다.
fastlane이 간결해 보이는 것은, xcodebuild등의 도구들에서는 언제나 명시적으로 입력해야 하는 값들 중 많은 것들을 "적절한 default값"을 넣어주기 때문입니다. 그런데 xcodebuild에서 어떤 옵션들이 필요하고, fastlane이 그 값들에 무엇을 언제 어떻게 default로 넣어주는지 제대로 이해하지 않으면, CI 파이프라인에서 만나게 되는 에러를 해석하고 대응하는 일이 아주 어려워집니다. 이게 fastlane이 만든 에러인지, xcodebuild가 만든 에러인지조차 헷갈릴 수 있죠. 그래서 어떤 키워드로 검색해야 할지 아주 헷갈리는 경우가 많이 생깁니다.
Xcode에서만 iOS개발을 주로 해왔다면, xcodebuild, xcrun, simctl, xcode-select 등의 CLI도구들이 많이 생소할 겁니다. 하지만 친근해 보이는 fastlane을 통해 이 도구들을 간접적으로 사용해보려 하기 전에, 이 도구들과 최대한 먼저 많이 친해지는 것을 권해드립니다. 분명 러닝커브가 있을 수 있지만, 전체적으로 봤을 때 이 도구들을 이해하고 사용 할 수 있느냐 없느냐는 CI 환경 구축에 걸리는 시간에 아주 많은 영향을 끼칠겁니다.
여기에 시간과 노력을 들이는 것이 아깝다는 생각이 든다면, 기억하세요. "CI 환경 구축은 언제나 생각보다 어렵다"는 것을.

마치며

CI 환경 구축은 언뜻 간단해보이면서도, 생각보다는 어렵고, 그러면서도 가치있는 일입니다. 한 번 제대로 구축을 해 놓은 다음에는, CI 없는 개발환경은 상상 할 수도 없을 겁니다. 이 글이 CI 구축에 첫 발을 내딛는 분들에게 조금이라도 도움이 되었으면 좋겠습니다.

관련 글