안녕하세요. 브랜디 iOS 파트의 고세림입니다. 이 문서에서 저는 브랜디 iOS 파트에서 앱을 배포하는 방법을 소개하고자 합니다. 제목과 같이 저희는 슬랙을 사용해 iOS 앱을 배포하는데요, 왜 이 방식을 선택하게 되었을까요?

본격적으로 소개하기 전에, 저희 파트의 앱 배포 방법 변천사를 먼저 보고 가시죠.


Xcode를 사용한 배포는 전통적이지만, 불편합니다.

직접 빌드 넘버를 올리고, 아카이빙합니다. 아카이빙을 하는 동안에는 다른 작업을 할 수 없죠. 여러 과정을 직접 진행해야 하기 때문에 모니터링을 해야 한다는 아쉬움이 있었습니다.


그래서 우리는 배포 자동화 툴 Fastlane을 도입하기로 합니다.

  • fastlane release branch:release/release trigger:#deliver_debug project:brandi

다음과 같은 형태로 명령어 한 줄만 입력하면 원하는 옵션에 맞게 앱 배포가 이루어집니다. 스크립트를 잘 작성하면 빌드 넘버도 직접 올리지 않아도 됩니다.


그러나 Fastlane도 완벽하지는 않았습니다.

아카이빙하는 동안에는 Xcode 사용이 어렵다는 아쉬움이 여전히 존재합니다. 간혹 팀원 간 배포 환경이 달라서 문제가 될 수도 있고요. 관련 명령어를 모두 파악해야 하고, 명령어를 잘못 입력할 가능성도 있습니다. 처음 앱 배포 명령어를 입력하게 되는 주니어 개발자는 왠지 모르게 덜덜 떨게 됩니다.. 제 이야기입니다.

Fastlane, 매우 편리한 배포 자동화 툴이지만 여전히 아쉬움이 있어서 더 나은 방법을 모색합니다. 저희 파트에서는 배포용 머신을 따로 사용하기로 하였습니다. 배포 중 로컬 머신에서 Xcode 사용이 어렵다는 아쉬움과 다른 환경에서 배포가 될 수 있는 문제를 방지할 수 있죠!

하지만… 아직 모든 문제가 해결된 것은 아닙니다. 명령어를 직접 입력해야 한다는 아쉬움. 아직 해결되지 않았습니다. 또한 배포 시 머신에 원격으로 접속해야 한다는 점, 여러 팀원이 접속하기 때문에 환경이 수시로 바뀔 수 있다는 점이 새로운 아쉬움으로 급부상합니다.


주니어 개발자도, 집에 가는 버스에서도 배포할 수 있도록. Slack 커맨드

그래서 iOS 파트에서는 Slack 커맨드를 사용해 배포하기로 합니다! 바로 배포 머신을 Slack과 네트워킹하기 위한 서버로 사용하는 것입니다.

팀원들이 직접 배포 머신에 접속하지 않아도 되니 기존의 아쉬운 점도 해결될 뿐더러, 슬랙 콘솔에서 선택한 옵션에 따라 fastlane 명령어가 구성되기 때문에 명령어를 잘못 입력할 가능성도 떨어지게 됩니다. 이제 배포를 할 때의 부담도 현저히 줄어들게 됩니다. 노트북이나 데스크탑이 아닌 스마트폰으로도 배포를 할 수 있고요.

그래서 슬랙으로 어떻게 앱을 배포하는지, 지금부터 결과물과 함께 소개하겠습니다.


Slack 커맨드로 앱 배포하기

athena

먼저 슬랙에서 /배포 커맨드를 호출하면 콘솔 창이 나타납니다.

브랜디는 브랜디, 하이버, 마미 세 개의 프로덕트를 관리하고 있으니 배포를 원하는 프로덕트를 먼저 선택합니다. 프로덕트를 선택하면 배포를 원하는 브랜치와 환경, 그리고 배포 타겟을 선택할 수 있습니다. 이렇게 모든 옵션 선택이 완료되면 배포 버튼이 노출됩니다.

마지막으로 배포 버튼을 선택하면 선택한 옵션에 맞게 앱이 배포됩니다.

athena

프로세스 완료 시에는 슬랙 메시지로 업로드 성공, 실패 여부를 확인할 수 있습니다. 바로 이렇게요! 🚀

athena


그래서 Slack 커맨드를 어떻게 사용했을까요?

초기에 말씀드렸듯, 저희는 파트 내에서 사용 중인 배포용 머신을 슬랙과 네트워킹하기 위한 서버로도 사용하고 있습니다.

athena

사용자가 배포 커맨드를 입력하는 이벤트가 발생하면 배포 머신에서는 사용자에게 보여줄 콘솔 View를 생성해 슬랙 서버로 전송합니다. 슬랙 서버에서는 이 콘솔 창을 사용자에게 보여줍니다.

어떤 프로덕트를 선택하는지에 따라 배포 브랜치 드롭다운에 들어갈 내용이 달라지는데요. 프로덕트 선택 이벤트가 발생하면 배포 머신에서는 해당 프로덕트 레포지토리로 이동한 후 선택한 프로덕트의 원격 브랜치 목록을 배열로 만들어서 드롭다운 메뉴를 구성하고, 다시 이벤트를 슬랙 서버로 전송합니다. 이렇게 이벤트가 발생할 때마다 뷰를 업데이트하여 다시 슬랙 서버로 전송해 주어야 합니다.

사용자가 모든 옵션 설정을 완료하고 배포 버튼을 선택하면 본격적으로 fastlane을 통한 앱 배포가 진행됩니다.

선택한 옵션 값을 가지고 fastlane 명령어를 구성하기 때문에 오타가 발생하지 않겠죠?

def execution(prod_name, branch, trigger_str, triggered_user):
    path_name = "{}-ios".format(prod_name)
    message = "cd ~/Documents/repository/{}" \
              "\ngit switch {}" \
              "\ngit pull --no-edit" \
              "\ngit submodule update --remote --merge" \
              "\nfastlane release branch:{} trigger:{} project:{} --env {} --verbose".format(
                 path_name, branch, branch, trigger_str, prod_name, prod_name)
    subprocess.Popen(message, shell=True)
    success_report("*{}*, Fastlane `{}` 프로세스 트리거됨 - by `@{}`".format(prod_name.upper(), trigger_str, triggered_user), 200)
    grep_fastlane_process_existence()


서버에서는 Fastlane으로 앱을 배포합니다.

Fastlane은 명령어 한 줄만 입력하면 원하는 방식으로 앱을 배포할 수 있는 배포 자동화 툴입니다.

Fastlane에서는 ipa 파일 추출, 앱스토어 업로드를 비롯한 다양한 API를 제공하고 있어서 명령어 입력만으로 배포 프로세스를 자동으로 진행되게끔 스크립트를 작성할 수 있습니다.


1. Fastfile

Fastlane 설치를 완료하면 레포지토리에서 Fastfile 이라는 파일을 확인할 수 있습니다. 이 파일에서 배포 스크립트를 작성할 수 있습니다.

lane :release do |options|

.. do something .. 

end

앱 배포 시 실행하는 release lane 을 정의하였습니다. fastlane release 명령어를 입력하면 release lane이 실행됩니다.

ipa 파일 추출만 실행하는 lane, fastlane 프로세스를 멈추는 lane 등 원하는 동작을 수행하는 lane을 정의할 수 있습니다.

lane :export_ipa do |options|

.. do something .. 

end

lane 실행 시 필요한 옵션을 주입할 수도 있습니다.

  • 옵션 없이 release lane 실행
    • fastlane release
  • branch 옵션을 추가하여 release lane 실행
    • fastlane release branch:release/release
  • branch, trigger, project 옵션 추가하여 실행
    • fastlane release branch:release/release trigger:#deliver_debug project:brandi

iOS 파트에서는 release lane에 branch, trigger, project 옵션을 추가해 사용하고 있습니다.

  • branch : 배포하기를 원하는 브랜치명을 입력받습니다.
  • trigger : 테스트용 앱 배포, 상용 앱 배포, 또는 Firebase 업로드 중 원하는 배포 방식을 선택할 수 있도록 트리거 메시지를 정의해 두었습니다. 트리거 메시지마다 다른 방식으로 배포가 진행되어야 합니다.

      $TRIGGER_DELIVER = "#deliver" : 상용  배포 
      $TRIGGER_FIREBASE = "#export_firebase" : firebase 업로드 
      $TRIGGER_DELIVER_DEBUG = "#deliver_debug" : 테스트용  배포 
    
  • project : 배포하기를 원하는 프로젝트를 입력받습니다. 브랜디, 하이버, 마미 각 프로젝트에 해당하는 변수들을 세팅합니다.

options[:key] 로 주입한 옵션 값을 사용할 수 있습니다. ex. options[:branch]


2. Appfile

Appfile은 앱 배포에 필요한 데이터를 저장하고 있으며, 기본값은 다음과 같습니다.

Fastfile에서 lane 실행 시 Appfile에서 필요한 값을 가져다 사용합니다.

저희는 app_identifier, apple_id 외에 itc_team_id, team_name, team_id 값을 저장하도록 하였습니다.

app_identifier "{bundle app identifier}" # The bundle identifier of your app
apple_id "{email address}"  # Your Apple email address

# You can uncomment the lines below and add your own
# team selection in case you're in multiple teams
# team_name ""
# team_id "

# To select a team for App Store Connect use
# itc_team_name ""
# itc_team_id ""


3. 본격적으로 앱 배포하기

앱 배포에 필요한 데이터, 옵션을 설정하였으니 release lane에 정의된 앱 배포 프로세스를 소개하겠습니다. fastlane에서 다양한 api를 제공하고 있어서 입맛에 맞게 배포 프로세스를 구성할 수 있습니다.

  1. release lane이 실행되면 먼저 옵션으로 받은 project 값을 가지고 해당 프로젝트에 맞는 변수 (ex. 프로젝트 이름, identifier, firebase app distribution, firebase invitation link) 들을 세팅합니다.

    각 프로젝트별 .env 파일의 데이터를 사용하고, project 옵션을 사용하여 추가로 필요한 값을 세팅할 수 있으니 하나의 파일로 세 프로젝트 배포 프로세스를 정의할 수 있게 됩니다.

  2. 옵션으로 받은 branch 값을 가지고 현재 해당 브랜치로 체크아웃되어 있는지 확인하고, version number를 확인합니다.
    • ensure_git_branch
    • get_version_number
  3. 현재 테스트플라이트에 올라온 빌드 넘버 값을 가져와서 프로젝트의 빌드 넘버를 증가시켜줍니다.
    • latest_testflight_build_number
    • increment_build_number
  4. 프로젝트를 아카이빙하여 ipa 파일을 추출하고, 앱스토어에 업로드합니다.
    • gym
    • upload_to_app_store
gym(
  configuration: "Release",
  workspace: $WORKSPACE_FILE_NAME,
  scheme: $PROJECT_NAME,
  export_method: "app-store",
  xcargs: "-allowProvisioningUpdates"
)

upload_to_app_store(
  skip_binary_upload: false,
  skip_screenshots: true,
  skip_metadata: true,
  skip_app_version_update: true
)

5. 3번 과정에서 빌드 넘버를 증가시켰으니 변경사항을 커밋 후 푸시하면 배포가 완료됩니다! 👏🏻👏🏻

  • git_commit
  • push_to_git_remote

추가로, fastlane에서 슬랙 메시지 전송 api 또한 제공하고 있어서 배포 프로세스 시작, 성공, 실패 등과 같은 알림을 슬랙 메시지로 전송할 수 있습니다.

  • slack

현재 어떤 배포가 진행되고 있는지, 배포가 성공적으로 완료되었는지 배포 상태를 팀원들이 슬랙으로 확인할 수 있으니 매우 편리합니다!


멀티 프로덕트 배포는 어떻게 대응하였을까요?

1. 환경변수 사용하기

브랜디는 브랜디, 하이버, 마미 세 프로젝트를 관리하므로 앱 배포에 필요한 데이터도 프로젝트마다 다릅니다. 프로젝트별로 각각 Appfile을 작성하고, 배포 시 각 프로젝트 디렉토리에 접근하여 실행하는 방법도 있으나, 프로젝트 관련 데이터 외에 배포 프로세스는 동일하기 때문에 이를 한 파일에서 관리하고자 합니다.

어떻게 프로젝트별로 다른 값을 가지도록 할까요? 바로 환경변수를 사용하는 것입니다.

다음과 같이 fastlane/ 디렉토리 내에 .env 파일을 생성하고, Appfile에서 환경변수를 사용하도록 합니다.

athena
// .env.brandi 
APP_IDENTIFIER = ""
APPLE_ID = ""
TEAM_NAME = ""
TEAM_ID = ""
ITC_TEAM_ID = ""

// Appfile 
app_identifier(ENV['APP_IDENTIFIER']) # The bundle identifier of your app
apple_id(ENV['APPLE_ID']) # Your Apple email address

itc_team_id(ENV['ITC_TEAM_ID']) # App Store Connect Team ID
team_name(ENV['TEAM_NAME'])
team_id(ENV['TEAM_ID']) # Developer Portal Team ID
  • fastlane release branch:release/release trigger:#deliver_debug project:brandi —env brandi

release lane 실행 시 —env {} 를 추가하여 .env 파일의 값을 가져와 사용할 수 있도록 합니다.

이렇게 프로젝트 관련 데이터를 .env 파일에 저장하고 Appfile에서 환경변수를 사용하도록 하면 하나의 Appfile에서 여러 프로젝트의 데이터를 사용할 수 있습니다.


2. Submodule

지금까지 옵션, 환경변수를 이용해 동일한 내용의 Fastfile, Appfile로 세 가지 프로덕트 배포에 대응하는 방법에 대해 소개하였습니다.

이렇게 각 프로젝트 디렉토리에는 동일한 내용의 Fastlane 레포지토리가 있으므로 이를 서브모듈화 할 수 있습니다. Fastlane 레포지토리를 서브모듈로 만들고, 각 프로젝트에서 fastlane 서브모듈을 추가하면 배포 프로세스 변경이 필요할 때 fastlane 서브모듈만 수정하여 각 프로젝트에 반영시킬 수 있습니다.

추후 새로운 앱을 출시하게 되어도 배포 관련 세팅이 아주 간단해지겠죠?

  1. .env 파일 추가
  2. 새 프로젝트에 fastlane 서브모듈 추가

이렇게 두 가지 작업만 추가로 해 주면 기존과 동일하게 배포 프로세스를 진행할 수 있습니다! ☺️


마무리

지금까지 브랜디 iOS 파트에서 슬랙을 통해 앱을 배포하는 방법, 그리고 멀티 프로덕트 배포에 대응하는 방법에 대해서 소개하였습니다.

Fastlane Docs를 참고하면 다양한 api를 확인할 수 있으니 현재 개발 중인 프로젝트, 팀 내 개발 프로세스에 맞게 배포 스크립트를 작성할 수 있습니다.

Slack과 Fastlane을 사용해 업무 중 배포에 사용하는 시간을 줄여보시고, 멀티 프로덕트를 관리하는 팀에서는 환경변수와 서브모듈을 활용해 편리하게 배포 프로세스를 적용해 보세요 !


참고자료


고세림 | APPS실 MA팀
브랜디, 오직 예쁜 옷만