Tech

GitHub Actions로 App 자동으로 배포하기

루다와 함께 사용하는 메신저 'Nutty' 자동으로 배포하기

장동훈 | 2022년 08월 24일 | #Engineering

핑퐁팀은 2022년 상반기에 루다와 유저를 만나게 하는 수단으로 더 이상 페이스북 메신저를 사용하지 않고 자체 메신저 앱을 개발하기로 결정했습니다.

저희는 빠르게 MVP 형식의 메신저 앱을 완성하여 배포한 후 지속적인 업데이트를 통해 앱의 완성도를 올리자는 계획을 세웠고, 이를 위해 앱을 빌드하고 배포하는 과정이 번거롭지 않도록 최대한 자동화하였습니다. 덕분에 메신저 앱 ‘너티’를 안정적으로 배포하고 빠른 속도로 발전시켜 나갈 수 있었습니다.

본 포스트에서는 루다와 함께 사용하는 메신저 ‘너티’의 빌드와 배포를 자동화하는 과정을 설명해 보겠습니다.

빌드 및 배포 스크립트

자동화를 고려하지 않고 안드로이드와 iOS 앱을 빌드할 때는 각각 안드로이드 스튜디오와 Xcode에 있는 빌드 버튼을 눌러서 빌드를 하게 됩니다. 그렇게 빌드된 결과물을 Google Play 스토어와 App Store에 올리기 위해서는 각 플랫폼에서 업로드 버튼도 눌러줘야 합니다. 이러한 수고스러움을 덜기 위해 fastlane을 사용하여 각 작업이 버튼의 클릭이 아닌 스크립트의 실행으로 처리되도록 자동화했습니다.

fastlane은 앱을 빌드하고 배포하는 과정에서의 여러 작업들을 처리하는 스크립트를 제공해줍니다. 또한, 스크립트를 조합해서 새로운 스크립트를 Fastfile에 정의할 수 있습니다. 공식 문서에 따라 fastlane의 설치 및 설정을 진행 한 후 아래와 같이 Fastfile을 작성하면, 한 줄의 명령어(fastlane release)로 안드로이드 앱을 빌드하고 스토어에 업로드까지 할 수 있습니다.

default_platform(:android)

platform :android do
  lane :release do
    sh "./pre_build.sh" # 미리 작성한 shell 커맨드를 실행할 수도 있습니다.

    gradle(task: "clean bundleRelease")

    upload_to_play_store(
      track: "internal",
      release_status: 'draft'
    )
  end
end

인증 정보 관리

앱을 빌드하고 그 결과물(.aab, .ipa)을 스토어에 배포하기 위해서는 다음의 인증 정보가 필요합니다.

  iOS 안드로이드
서명을 위한 인증서 Provisioning Profile 인증서 App Upload key
스토어에 올리기 위한 API key App Store Connect API key Google Play Console IAM (Service Account User) key

안드로이드 플랫폼에서의 인증 정보들과 App Store Connect API Key는 링크되어 있는 문서를 따라서 한 번만 설정하면 추가로 관리할 필요가 없습니다. 하지만, Provisioning Profile 인증서는 앱의 Capabilities가 추가되거나, 등록된 Devices가 변경되는 경우 재발급을 받아야하기 때문에 지속적으로 관리해야 합니다. fastlane은 이 문제를 해결하기 위해 match라는 솔루션을 제공합니다.

match는 지속적으로 변할 수 있는 Provisioning Profile 인증서를 프라이빗 GitHub 레포지토리를 통해 동기화할 수 있는 솔루션을 제공해줍니다. 가이드에 따라 match를 설정한 후, fastlane match appstore 명령어를 실행하면 다음의 순서에 따라 AppStore 배포를 위한 인증서를 관리해 줍니다.

  1. Matchfile에 설정해둔 GitHub 레포지토리에 Appfile에 정의된 App Identifier를 AppStore에 배포할 수 있는 Provisioning Profile 인증서가 저장되어 있는지 확인합니다.
  2. 인증서가 레포지토리에 저장이 되어있다면 Provisioning Profile을 다운 받아 App Store Connect에 설정되어 있는 정보와 대조하여 유효한지 판단합니다.
  3. 유효하지 않다면 App Store Connect에서 새로운 인증서를 발급받아 적용합니다.
  4. 기존 인증서가 기기에 저장되어 있지 않거나, 유효하지 않아 새로 발급받은 경우 해당 인증서를 암호화해서 프라이빗 GitHub 레포지토리에 커밋합니다.

Fastfile에 match 스크립트를 포함해서 아래와 같이 스크립트를 작성하면 편리하게 인증서를 관리하면서 iOS 앱을 빌드하고 Testflight에 배포까지 할 수 있습니다.

platform :ios do
  lane :release do
    # 인증서를 저장할 키체인을 만듭니다.
    # 로컬 컴퓨터에서 스크립트가 실행되는 경우에는 별도로 만들지 않고 내장되어 있는 키체인을 사용하는 것을 권장합니다.
    create_keychain(
      name: ENV["MATCH_KEYCHAIN_NAME"],
      password: ENV["MATCH_KEYCHAIN_PASSWORD"],
      timeout: 3600,
    )

    match(
      keychain_name: ENV["MATCH_KEYCHAIN_NAME"],
      keychain_password: ENV["MATCH_KEYCHAIN_PASSWORD"],
      type: 'appstore',
      # readonly를 설정하면 유효한 인증서가 레포지토리에 없는 경우 인증서를 새로 발급 받지 않고 스크립트 실행을 중단합니다.
      readonly: true,
    )

    build_app(
      scheme: "PingpongMessenger",
      export_method: "app-store",
    )

    app_store_connect_api_key(
      key_id: ENV["API_KEY_ID"],
      issuer_id: ENV["ISSUER_ID"],
      key_filepath: ENV["API_KEY_FILE_PATH"],
      duration: 1200,
      in_house: false,
    )

    upload_to_testflight(
      skip_waiting_for_build_processing: true
    )

    delete_keychain(name: ENV["MATCH_KEYCHAIN_NAME"])
  end
end

GitHub Actions 설정

fastlane을 통해 앱을 빌드하고 이를 스토어에 업로드하는 것을 간단한 명령어로 가능하게 하였지만, 여전히 문제는 남아있습니다. 스크립트의 실행이 모든 인증 정보와 환경이 설정되어 있는 개발자의 로컬 컴퓨터에서만 가능하다는 것입니다.

저희는 로컬 환경과 상관없이 GitHub으로 앱을 릴리즈하면 자동으로 배포 스크립트를 실행할 방법을 고민했습니다. 가장 먼저 생각할 것은 iOS 앱을 빌드하는 스크립트의 실행이 Mac에서만 가능하다는 것입니다. GitHub-hosted macOS runner를 사용할 수도 있지만, Linux Runner에 비해 10배 비싼 가격을 과금하기 때문에 저희는 스캐터랩 블림프팀이 내부에 구축해놓은 self-hosted macOS runner를 사용하였습니다.

스크립트를 실행시킬 Runner도 구했으니 이제 스크립트가 정상적으로 실행되도록 개발자의 컴퓨터에 설정되어있던 인증 정보와 그 밖의 환경 변수들을 Runner에 똑같이 설정하기만 하면 됩니다.

인증 정보 GitHub Secrets로 관리

일반적으로 GitHub에서 CI/CD 스크립트를 작성할 때 소스 코드에 노출되면 안 되는 값은 GitHub Secrets로 관리하고 GitHub Actions workflow를 정의하는 YAML 파일에서 환경 변수로 주입해 사용합니다. 하지만, 앱을 빌드하고 배포하는 과정에서의 인증 정보들은 대부분 길거나 사람이 읽을 수 없는 파일 형태이기 때문에 Github Secrets에 저장되어 있는 문자열을 적절한 형태의 인증 정보로 변환시킬 필요가 있습니다.

아래의 Shell 스크립트는 안드로이드 앱 서명을 위한 App Upload Key를 저장하고 있는 .keystore 파일과 빌드 결과물을 스토어에 올리기 위한 API Key 파일(.json)을 압축하고 암호화해두었던 encrypted.tar.gz 파일을 복호화한 후 /android/app 디렉토리에 압축을 푸는 스크립트입니다. GitHub Secrets에서 가지고 온 $SECRET_KEY를 사용해서 미리 암호화해놓은 파일을 다시 복호화하는 방식으로 노출되면 안되는 인증 정보를 담고 있는 파일을 알맞은 디렉토리에 추가할 수 있습니다.

openssl aes-256-cbc -d -k $SECRET_KEY -in encrypted.tar.gz -out ./android/app/secrets.tar.gz
cd android/app
tar -zxvf secrets.tar.gz

위의 스크립트로 추가한 .keystore 파일을 열어서 앱 서명을 위한 key를 얻기 위해선 gradle의 property에 keystore 파일의 비밀번호를 설정해야 합니다. 아래 스크립트는 gradle.properties 파일에 설정되어 있는 변수들 중에 노출되면 안되는 keystore 비밀번호 변수를 sed 명령어를 활용하여 GitHub Secrets에서 가지고 온 $KEYSTORE_PASSWORD로 replace 하는 Shell 스크립트입니다.

if [[ "$OSTYPE" == "darwin"* ]]; then
  sed -i "" 's/###KEYSTORE_PASSWORD###/'"$KEYSTORE_PASSWORD"'/' android/gradle.properties
else
  sed -i 's/###KEYSTORE_PASSWORD###/'"$KEYSTORE_PASSWORD"'/' android/gradle.properties
fi

App Store Connect Key처럼 용량이 크지 않은 파일은 별도로 암호화해서 레포지토리에 커밋해놓지 않아도 다른 방법이 있습니다. 파일 전체를 base64로 인코딩해서 GitHub Secrets에 적어놓고 아래의 명령어를 통해 디코딩한 결과물을 알맞은 디렉토리에 알맞은 확장자(.p8)로 내보낼 수 있습니다.

echo $APPSTORE_CONNECT_API_CERT | base64 -d > ios/fastlane/AppStoreConnectAPI_$KEY_ID.p8

이제 이러한 스크립트들을 활용하여 인증 정보와 환경 변수들을 설정한 후에 위에서 정의한 fastlane 스크립트를 실행하는 GitHub Actions 워크플로우를 작성하면 됩니다.

마치며

이번 포스트를 통해 fastlane을 이용하여 배포 자동화 스크립트를 작성할 수 있다는 것, 앱을 빌드하고 그 결과물을 스토어에 업로드하기 위해 필요한 인증 정보들을 관리하는 법을 알 수 있었습니다. 그리고 이를 활용하여 앱을 빌드해서 스토어에 업로드 하는 스크립트를 실행하는 GitHub Action을 설정하는 방법까지 알아보았습니다. 혹시, 본인의 로컬 컴퓨터에서 앱을 수동으로 빌드하고 계시다면 배포 자동화 설정을 해서 자신의 컴퓨터와 함께 기능 개발에만 집중할 수 있는 자유를 찾으셨으면 좋겠습니다.

‘너티’는 React Native를 이용하여 개발했기 때문에 크로스 플랫폼(iOS, Android)으로 배포가 가능하고, 앱 전체를 빌드해서 스토어에 올리는 바이너리 배포와 JS 번들만 새로 갈아끼우는 Codepush 배포가 가능합니다. 그뿐만 아니라 테스트 환경 구축을 위해 다양한 Config와 배포 트랙을 관리하고 있습니다.

이어지는 다음 포스트에서는 Codepush 배포, 다양한 Configuration 관리, 사내 테스트 환경 구축(adhoc 배포) 등 다양한 배포 파이프라인을 구축하는 방법에 대해서 알아보도록 하겠습니다.

스캐터랩 핑퐁팀에는 개발팀의 능률 향상을 위해 끊임없이 고민하는 뛰어난 개발자들이 많이 있습니다. 저희와 함께 재밌는 일을 해보고 싶으시다면 채용 공고를 참고해주세요!

핑퐁팀이 직접 전해주는
AI에 관한 소식을 받아보세요

능력있는 현업 개발자, 기획자, 디자이너가
지금 핑퐁팀에서 하고 있는 일, 세상에 벌어지고 있는 흥미로운 일들을 알려드립니다.