2025. 10. 21. 00:44ㆍiOS/자동화

CD(Continuous Deployment)
지속적 배포라는 뜻으로, 빌드, 아카이빙, 업로드를 수동으로 하지 않고, 스크립트를 이용하여 손쉽게 자동화하여 배포하는 뜻입니다.
이번 포스팅에서는 Fastlane과 GithubAction을 이용하여 Testflight 및 앱스토어 배포 자동화 경험을 이야기해보겠습니다.
Overview
- match를 이용한 인증서 및 Provisining 관리
- fastlane을 이용한 Testflight 배포 자동화
- GithubAction을 이용하여 앱스토어 배포 원격 자동화 + Tag Push를 이용한 버전 관리
인증서와 Provisioning 관리 - Fastlane match
팀 프로젝트에서 위젯이 추가되면서, 개인 인증서 만으로는 개발 및 테스트하기 한계가 왔고, 팀원 1명의 계정을 이용하기로 했습니다. provisioning 파일과 인증서를 파일로 공유하여 관리하였는데 번들id 변경, 디바이스 추가 또는 교체가 있을 때 마다 재발급하여 공유하는 것이 번거로웠습니다. 전에 진행했던 프로젝트에서 fastlane match를 이용하였었는데 이번에도 적용하여 인증서와 provisioning 관리에 대한 부담을 줄였습니다.
Testflight 배포 자동화
local에서 testflight 배포는 비교적 간단했습니다.
- match appstore 인증서 갱신
- API Key 세팅
- 빌드 버전 가져오기
- 빌드
- 업로드
빌드 및 업로드 실패
실패에는 크게 2가지 원인이 있었는데요. 첫번째로, 프로비저닝 매칭 문제 두번째로 버전 문제가 있었습니다.
프로비저닝이 1개 이상일 경우, 타겟이 제대로 찾지 못하는 문제가 발생하였습니다. 다음과 같이 명시적으로 지정하여 해결하였습니다.
build_app(
scheme: "iCo",
clean: true,
export_method: "app-store",
export_options: {
signingStyle: "manual",
provisioningProfiles: {
"com.est.ico" => "match AppStore com.est.ico",
"com.est.ico.widget" => "match AppStore com.est.ico.widget"
}
}
)
다음으로 버전 문제입니다. 테스트 플라이트에 올라가 있는 동일 버전의 빌드 넘버보다 작은 값이 올라갈 경우, 업로드 실패하였습니다.
다음과 같이 버전을 가져와서 해결하였습니다.
latest = latest_testflight_build_number(
app_identifier: "com.est.ico",
version: version
)
increment_build_number(
build_number: latest + 1
)
GithubAction's runner 환경에서 트러블 슈팅
로컬에서 쉽게 되어 workflow 적용을 금방될 줄 알았는데 생각보다 오래걸렸고 다음과 같은 고민도 해보게 되었습니다.appstore 업로드를 분리하게 되면서, testflight 업로드를 언제 해야할지 고민하게 되었습니다. 매 dev 코드가 합쳐질 때 마다 testflight 업로드한다면, 너무 빈번한 업로드가 될 거 같았습니다. 회의 결과 dev와 release(main) 사이에 다른 하나의 레이어를 추가해보기로 했습니다.
AppStoreConnect Key 관리
로컬에서는 파일 형태로 AppStoreConnect 인증서를 사용하고, path를 지정해주었었는데, runner에서는 파일 관리가 부담되었습니다. 다른 workflow를 참조해보니 key content를 문자열 형태로 사용하여, 파일 관리에 대한 부담을 줄였습니다. \n 문자열을 인식하지 못하여 base64로 인코딩하여 사용하였습니다.
키체인 관리
러너 환경에서는 매 빌드마다 클린한 환경이고, 키 관련 보안과 팝업 없이 컨트롤 하기 위하여 매번 키체인을 생성합니다. 이 키체인 생성을 로컬환겨에서 테스트하다가 기본 로그인 키체인이 변경되어 xcode에서 인증서를 인식하지 못하는 등의 문제가 발생하였습니다.
cli에서 키체인을 삭제하고 재부팅하여 해결하였습니다.
인증서 저장소 접근
workflow에서 다른 저장소 접근하는 것은 action@checkut을 사용하여 괜찮았지만 lane 스크립트에서 저장소 접근할 때는 까다로웠습니다. git url의 token@github.com 패턴을 사용하여 url에 토큰을 삽입하여 접근하였습니다.
하지만, 이 방법은 전역적으로 변경하여 로그가 찍힐 때 키가 노출될 위험이 있어, fastlane 공식 문서에서 추천하는 방법인 토큰을 base64로 인코딩하여 authorization header 삽입으로 변경하였습니다.
Workflow Step Working Directory
root가 xcodeproj에 위치하지 않아서 따로 지정해줘야했습니다.
- name: execute lane
working-directory: AIProject
run: fastlane testflight
env:
....
태그 트리거 및 버전 추출 및 버전 이슈
마케팅 버전 관련 업로드 이슈가 생겨서 마케팅 버전은 자동 증가를 하지 않고, 버전을 파라미터로 받게 변경하였습니다.
버전 태그를 push하면 해당 이벤트를 트리거로 appstore upload를 수행하도록 workflow를 작성하였습니다.
아래 구현과는 반대로 릴리즈가 배포되면 릴리즈 태그를 다는 방법 등 태그를 다양하게 자동화하는 방법이 존재한다는 것을 배웠습니다.
workflow 전체
name: Release
on:
push:
tags:
- "Release-*"
jobs:
release:
runs-on: macos-latest
steps:
# 레파지토리 체크인
- name: Checkout
uses: actions/checkout@v4
# 태그 번호 가져오기
- name: Extract version from tag
id: ver
run: echo "VERSION=${GITHUB_REF_NAME#Release-}" >> $GITHUB_OUTPUT
# 태그 번호 가져오기
- name: Print version
run: echo "Version is ${{ steps.ver.outputs.VERSION }}"
# XCConfig / GoogleService 등의 파일을 클론
- name: Clone Secret file
uses: actions/checkout@v4
with:
repository: ESTiOSAI/Secrets
path: temp/
token: ${{ secrets.SECRET_TOKEN }}
# 가져온 파일 이동
- name: Move config
run: |
mv temp/XCConfig/Secrets.xcconfig AIProject/iCo/App/Resource/
mv temp/GoogleServices/GoogleService-Info.plist AIProject/iCo/App/Resource/
- name: Configure git auth for match repo
run: git config --global url."https://${GIT_TOKEN}@github.com/".insteadOf "https://github.com/"
env:
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
- name: install fastlane
run: brew install fastlane
- name: execute lane
working-directory: AIProject
run: fastlane release version:${{ steps.ver.outputs.VERSION }}
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
APP_STORE_CONNECT_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_KEY_CONTENT }}
KEYCHAIN_NAME: ${{ secrets.KEYCHAIN_NAME }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
회고 및 Next Step
주말과 늦은 시간까지 투자해서 배포 자동화를 경험해본 뜻 깊은 시간이었습니다. 열정이 있는 팀원들이 있어서 즐겁게 해볼 수 있었고, 같이 개발하는 팀원이 중요하다는 것을 깨닫는 시간이었습니다. githubAction까지 경험해본 것은 처음이라 예상한 시간보다 오래걸렸지만, xcode build 부터 깊게 알아봤던 의미있는 시간이었습니다.
다음은 fastlane에 의존적인 부분을 덜어내고 github marketplace의 apple-action을 이용하여 작성해보겠습니다.
'iOS > 자동화' 카테고리의 다른 글
| [GithubAction] CI - feat.Caching (2) (0) | 2025.10.14 |
|---|---|
| [GithubAction] Foundation (1) (0) | 2025.10.13 |