키즈노트 CI/CD 파이프라인 진화기(3)
키즈노트 CI/CD 파이프라인 진화기: 없던 것을 만들고, 만든 것을 통합하기까지
Part 3 — Mobile로의 확장: iOS와 Android Pipeline 구축
본 글은 당시 작업 기억을 바탕으로 재구성한 내용입니다. 빌드 및 배포 구성의 전체적인 흐름과 맥락은 사실에 기반하나, 일부 세부 구현 내용은 실제와 다를 수 있습니다.
1. 왜 Backend보다 Mobile을 먼저 했는가
Frontend 파이프라인 작업이 어느 정도 마무리될 무렵, 사실 Mobile 쪽 작업은 이미 시작되고 있었다.
엄밀히 말하면 순차적이 아니라 투트랙이었다. Frontend 파이프라인 변환이 진행되는 동안, 함께 일하던 DevOps 동료가 iOS 빌드 및 배포 환경 구축을 병행하고 있었다. 모바일 관련 학과 출신이었던 동료가 주축이 되어 iOS와 Android 파이프라인 스크립트 작성을 이끌었고, 나는 Mac Mini 세팅, 파트장들과의 협의 미팅, 이슈 대응 등을 함께 했다.
Mobile을 Backend보다 먼저 진행한 데는 이유가 있었다.
인프라부터 없었다. Frontend는 Jenkins Freestyle이라도 있었고, Backend는 fabfile이라도 있었다. 하지만 iOS와 Android는 Jenkins 연동 자체가 없었다. 빌드도, 배포도 완전히 수동이었다. 파트장이나 파트원들이 직접 노트북을 열어 Xcode로 빌드하고 Apple 계정으로 배포하는 방식이 전부였다.
환경 구성이 시급했다. iOS는 macOS가 필수인 특성상 카카오 IDC 인프라에서 꾸릴 수 없었다. 빌드 및 배포 이슈가 생길 때마다 IDC에 직접 가서 대응해야 한다는 건 현실적으로 불가능했다. 그래서 사내망에 Mac Mini를 별도로 세팅하고, 그 위에 Jenkins Agent를 구성하는 방향으로 결정했다.
2. iOS Pipeline 구축
Mac Mini를 Jenkins Agent로
iOS 파이프라인 구축의 시작은 파이프라인 스크립트 작성이 아니었다. 먼저 빌드가 가능한 환경을 만드는 것부터 시작해야 했다.
당시 카카오 IDC 환경에서는 macOS 기반 빌드 머신을 운영하기 어려웠기 때문에, 사내망에 Mac Mini를 별도로 구축하는 방향으로 결정했다.
Mac Mini는 Jenkins Agent로 동작하도록 구성했다. iOS 빌드는 Keychain에 저장된 인증서와 Provisioning Profile에 의존하기 때문에 Jenkins 실행 계정과 Keychain 접근 권한을 맞추는 작업이 필요했다. 초기에는 Keychain Unlock 이슈로 빌드가 실패하는 경우가 잦았고, 이를 해결하기 위해 파이프라인 시작 시 Keychain을 명시적으로 Unlock하도록 구성했다.
Frontend나 Backend는 기존 인프라를 활용할 수 있었지만, Mobile은 빌드 인프라 자체가 존재하지 않았다. 파이프라인을 구축하기 전에 먼저 모바일 빌드 환경을 만드는 작업이 필요했다.
구축 이후에는 키즈노트, 클래스노트, 패밀리노트 각각의 배포용으로 총 3대의 Mac Mini를 모바일 빌드 및 배포 머신으로 운영하게 되었다.
구축 이후에도 운영 이슈는 계속 발생했다. 라이브러리 버전 호환성 문제, 프로비저닝 관련 이슈, Apple 개발자 계정 상태 변경 등 다양한 원인으로 빌드가 실패했다.
특히 iOS는 코드 변경이 없더라도 인증서, 프로비저닝 프로파일, 개발자 계정 상태에 따라 빌드 결과가 달라질 수 있었다. 파이프라인 자체보다도 빌드 환경을 안정적으로 유지하는 것이 더 어려운 경우가 많았다.
초기 구축 단계에서는 운영 경험이 부족했던 만큼 이슈 원인을 파악하는 데 많은 시간이 필요했다.
빌드 및 배포 플로우
iOS 파이프라인은 다음 순서로 구성했다.
1
2
Checkout → Provisioning → Version Management → Dependency Install
→ Build & Archive → Katalon Test (조건부) → Deploy → README 업데이트
각 단계가 단순해 보이지만, iOS 특유의 환경 제약 때문에 내부 로직은 상당히 복잡했다. sh 명령어가 유독 많았던 것도 이 때문이었다.
Provisioning 단계에서는 Mac Mini 로컬 Keychain을 잠금 해제하고, .mobileprovision 파일을 설치한다. 인증서가 없으면 즉시 파이프라인을 중단하도록 했다.
Version Management 단계는 iOS 파트장의 요청이 특히 디테일했던 부분이었다. 앱 버전, 빌드 번호, 라이브러리 버전까지 별도 파일(versions.yml)로 관리했고, 파이프라인 스크립트가 이 파일을 읽고 업데이트하는 로직이 포함되어 있었다.
PlistBuddy—Info.plist의 버전 값 직접 수정agvtool— 모든 타겟 빌드 번호 일괄 동기화versions.yml— 앱 버전, 빌드 번호, CocoaPods 버전 등 고정
Katalon Test 단계는 조건부였다. 파라미터로 RUN_KATALON 값을 선택했을 때만 실행되도록 했다. QA팀과 연계하여 빌드 → 테스트 → 배포 플로우를 자동화하려는 시도였다.
Deploy 단계에서는 Firebase App Distribution 또는 TestFlight 중 선택하여 배포했다.
README 업데이트는 배포 완료 후 iOS 레포지토리의 README.md에 버전 정보를 자동으로 기록하도록 했다.
로깅 구조
iOS 파이프라인에서 특별히 신경 쓴 부분이 로그 파일 기록이었다.
프로비저닝부터 배포 완료까지의 전 과정을 pipeline-result.txt 파일로 남기도록 했다. tee -a 패턴으로 콘솔 로그와 파일 양쪽에 동시에 기록했고, Jenkins 대시보드 Artifacts 섹션에 파일로 노출시켰다. 이슈가 발생했을 때 콘솔 로그를 전부 뒤지지 않고도 이 파일 하나로 원인을 빠르게 파악할 수 있었다.
1
2
3
4
build/
├── pipeline-result.txt ← 전체 흐름 요약
├── xcodebuild.log ← 빌드 상세 로그
└── altool.log ← TestFlight 업로드 로그
로그를 별도 파일로 남긴 이유는 모바일 빌드 실패 원인이 다양했기 때문이다.
Jenkins 콘솔 로그만으로는 원인 파악에 시간이 오래 걸리는 경우가 많았고, 빌드 과정별 로그를 분리해 저장함으로써 장애 분석 시간을 줄일 수 있었다.
3. Android Pipeline 구축
Android는 iOS와 목적은 같았지만 구성은 훨씬 단순했다.
macOS 종속성이 없어서 빌드 머신 제약이 없었고, 서명 방식도 Keystore 파일 기반이라 iOS의 Keychain + 프로비저닝 조합보다 훨씬 단순했다. Gradle이 의존성 관리와 빌드를 대부분 처리해줬기 때문에 파이프라인 스크립트가 iOS 대비 간결했다.
Android 파트장은 버전 관리에 크게 관여하지 않았다. iOS 파트장이 버전 하나하나를 파이프라인으로 제어하길 원했던 것과 달리, Android는 Gradle이 처리하도록 두는 방식을 택했다. 그래서 versions.yml이나 PlistBuddy 같은 별도 버전 관리 로직이 필요 없었다.
1
Checkout → Keystore Setup → Build → Katalon Test (조건부) → Deploy → README 업데이트
빌드 결과물도 환경에 따라 달랐다.
dev/staging→ APKprod→ AAB (Google Play Store 필수 포맷)
Katalon 연동과 Firebase 배포는 iOS와 동일한 구조였다. 다만 Android에서는 adb devices로 연결 기기를 확인하고 adb install로 APK를 설치하는 단계가 추가됐다.
Mobile Pipeline이 어려웠던 이유
Frontend나 Backend 파이프라인은 대부분 Linux 환경에서 동작했다.
하지만 Mobile은 플랫폼 자체가 달랐다.
iOS는 macOS, Xcode, Apple 인증서, Provisioning Profile 등의 제약이 있었고, Android는 Gradle과 Keystore 기반의 별도 빌드 체계를 가지고 있었다.
같은 “모바일 배포”라는 목표를 가지고 있었지만 빌드 환경과 운영 방식은 전혀 달랐다.
결국 하나의 공통 스크립트를 만드는 것보다 플랫폼 특성을 존중하면서도 사용자 경험은 통일하는 방향을 선택하게 되었다.
4. iOS vs Android — 같은 목적, 다른 구현
두 플랫폼을 하나로 통합하려는 시도를 하지 않았다. 처음부터 “통합”이 아닌 “추상화” 방향으로 갔다.
iOS와 Android는 빌드 도구도, 서명 방식도, 배포 대상도 다 달랐다. Xcode와 Gradle을 하나의 스크립트로 묶으려 하면 오히려 복잡도만 올라가고 유지보수가 어려워진다. 같은 Katalon을 쓰고, 같은 Firebase에 배포하지만 내부 구현은 완전히 달랐다.
대신 인터페이스는 통일했다. 파라미터 구조, Slack 알림 포맷, README 업데이트 방식은 두 플랫폼이 동일하게 가져갔다. 이 경험이 이후 Shared Library를 설계할 때 “공통 인터페이스, 내부는 플랫폼별 분리”라는 방향의 기반이 되었다.
5. Katalon, 그리고 철수
Katalon 연동은 QA팀과 함께 빌드 → 테스트 → 배포 자동화 플로우를 만들어보려는 시도였다.
하지만 결과적으로 철수했다. 투자 대비 성능이 기대에 미치지 못했고, 수동 테스트로 돌아가는 것으로 결정됐다. Katalon 관련 스크립트는 파이프라인에서 걷어냈다.
결국 사내망에 구축했던 Mac Mini 3대가 iOS와 Android 빌드 및 배포 머신으로 확정됐다. 이후에는 Android가 카카오 IDC와 사내망 두 곳에 인프라가 분산되어 비용이 이중으로 나오는 문제가 생겼고, CTO에게 사내망으로 일원화하는 방향을 건의해 정리했다.
6. 팀 간 협업
막막함에서 시작한 구축
모바일 빌드 및 배포에 대해 처음에는 둘 다 문외한이었다. DevOps 동료가 모바일 관련 전공이었다고는 해도, iOS 빌드 환경을 Jenkins 파이프라인으로 자동화한다는 건 전혀 다른 영역이었다.
그래서 iOS, Android 파트장의 도움을 많이 받았다. 파이프라인을 만들려면 개발자들이 실제로 어떤 과정을 거쳐 빌드하는지 알아야 했다. 직접 리서치하고, 테스트해보고, 파트장에게 질문하며 검증해가는 과정을 반복했다.
iOS 파트장과의 협업
iOS 파트장은 요청이 상당히 디테일했다. 파라미터 옵션 하나하나, Slack 알림 메시지 포맷까지 꼼꼼하게 피드백을 줬다. 처음에는 그 디테일한 요청이 부담스럽기도 했지만, 그 요청 하나하나를 반영해가면서 스크립트가 고도화됐다.
초기에는 요구사항을 이해하는 데 시간이 많이 들었다.
파라미터 구조, 버전 관리 정책, 배포 프로세스를 함께 검토하며 파이프라인에 반영해 나갔다.
그 과정에서 요구사항이 점차 정리되었고, 스크립트 역시 반복적으로 개선되었다. 그 과정을 거치며 요구사항을 조율하는 방식이 정착되었고, 이후에는 신규 기능이나 개선 사항을 논의할 때도 비슷한 방식으로 협업을 이어갈 수 있었다.
Android 파트장과의 협업
Android 파트장은 iOS 파트장과 달리 버전 관리나 세부 설정에 크게 관여하지 않았다. 방향을 제시하면 믿고 맡기는 스타일이었다. 덕분에 Android 파이프라인은 비교적 빠르게 완성됐다.
이 경험이 남긴 것
Frontend 파이프라인 작업이 “파이프라인 스크립트를 어떻게 만드는가”의 기준점이 되었다면, Mobile 파이프라인 작업은 “어떻게 팀과 협업하며 신뢰를 쌓는가” 를 몸으로 배운 시간이었다.
Mobile 파이프라인 구축 과정에서 배운 것은 기술만으로 자동화가 완성되지 않는다는 점이었다.
실제 빌드와 배포 과정을 이해하기 위해서는 개발팀과 지속적으로 소통해야 했고, 파이프라인 역시 팀의 운영 방식에 맞춰 반복적으로 개선되어야 했다.
이 경험은 이후 Backend 파이프라인 구축과 Shared Library 설계 과정에도 큰 영향을 주었다. 팀이 믿어줘야 파이프라인이 돌아갔고, 그 신뢰는 밤새운 이슈 대응과 디테일한 요청을 끝까지 반영하는 과정에서 만들어졌다. DevOps 엔지니어로서의 태도와 마음가짐을 배울 수 있었던 시간이었다.
7. 운영하면서 배운 것들
동료가 퇴사하고 나서 모바일 파이프라인 운영은 혼자 맡게 됐다. 구축 단계와 운영 단계는 전혀 다른 이야기였다.
운영 과정에서 마주한 이슈들
iOS 관련 이슈는 생각보다 훨씬 잦았다. 라이브러리 버전 호환성 문제, 프로비저닝 인증서 만료, Apple 개발자 계정 약관 미동의로 인한 빌드 실패 등 매번 예상치 못한 곳에서 터졌다.
당시는 AI 도구가 지금처럼 상용화되지 않았던 시절이었다. 구글 검색과 Apple 개발자 커뮤니티를 뒤지고 또 뒤졌다. 모바일 배포가 안 된다는 건 서비스 출시 자체가 막히는 상황이었으니, 악착같이 찾아야 했다. 그래도 모르겠으면 iOS 파트장이나 팀원에게 직접 물었다. 자존심보다 해결이 먼저였다.
트러블슈팅 문서화의 시작
이렇게 밤샘 대응을 몇 번 반복하고 나서 생각이 바뀌었다. 같은 이슈로 또 밤을 새울 수는 없었다.
그때부터 이슈가 해결될 때마다 트러블슈팅 문서를 작성하기 시작했다. 증상, 원인, 해결 방법을 정리해두는 방식이었다. 처음엔 나 자신을 위한 기록이었지만, 쌓이다 보니 다음에 같은 이슈가 생겼을 때 문서 하나로 빠르게 대응할 수 있는 자산이 됐다.
구축 과정도 문서화했다. 이전 동료의 성향이 강하게 묻어있는 구성이었기 때문에, 다음 사람이 와도 운영할 수 있도록 설명을 남겨두는 것이 필요했다. 내가 처음 맡았을 때 겪었던 막막함을 다음 사람은 겪지 않았으면 했다.
팀 간 소통의 변화
예상치 못한 변화가 생겼다. 모바일 배포 구성과 플로우가 얼마나 복잡한지를 다른 팀들이 체감하기 시작한 것이다.
이슈 대응 과정을 공유하고, 구축 배경과 운영 방식을 문서로 남기면서 “이 사람이 왜 이렇게 구성했는지”가 보이기 시작했다. 그것이 팀 간 소통의 물꼬를 트는 계기가 됐다. 기술 문서가 신뢰를 만드는 도구가 될 수 있다는 걸 그때 처음 체감했다.
구축 결과
결과적으로, Mobile 파이프라인 구축 이후 iOS와 Android는 기존의 수동 빌드 및 배포 방식에서 Jenkins 기반 자동화 배포 체계로 전환되었다.
- Mac Mini 기반 iOS 빌드 인프라 구축
- Jenkins를 통한 모바일 빌드 및 배포 자동화
- Firebase / TestFlight 배포 절차 표준화
- Slack 기반 배포 알림 체계 적용
- 트러블슈팅 문서 및 운영 문서 축적
기술적으로는 모바일 CI/CD 환경을 처음부터 구축한 경험이었고, 운영 측면에서는 문서화와 표준화의 중요성을 체감한 프로젝트였다.
무엇보다 익숙하지 않은 플랫폼이라도 구조를 이해하고 자동화 가능한 형태로 만들 수 있다는 자신감을 얻은 계기가 되었다.
다음 글에서는 전체 파이프라인 작업의 마지막 퍼즐, Backend Pipeline 구축 과정을 다룬다. fabfile 또는 아예 CI/CD가 없던 상태에서 6개 서비스를 어떻게 하나의 파이프라인 구조로 담아냈는지를 이야기할 것이다.
본 시리즈는 총 6부로 구성됩니다.
| 파트 | 제목 |
|---|---|
| Part 1 | 시작 전야: 플랫폼마다 달랐던 배포 풍경 |
| Part 2 | 첫 번째 삽: Frontend Pipeline 만들기 |
| Part 3 | Mobile로의 확장: iOS와 Android Pipeline 구축 ← 현재 글 |
| Part 4 | 마지막 퍼즐: Backend Pipeline 구축 |
| Part 5 | 중복을 코드로: Shared Library와 Git 기반 전환 |
| Part 6 | 회고: 없던 것을 만들고 통합하기까지 |