🥳 200만 유저의 친구 ‘이루다’ 기술로 AI 캐릭터를 자유롭게 만들어보세요 ‘핑퐁 스튜디오’ 보러가기

Tech

React Native 앱의 배포 트랙 관리하기

CodePush와 adhoc으로 React Native 앱 '너티'의 배포 트랙 관리하기

장동훈 | 2022년 10월 12일 | #Engineering

지난 글에서는 ‘너티’ 앱을 플랫폼별로 빌드해서 배포하는 과정을 자동화하는 방법에 대해서 알아보았습니다. 추가로 ‘너티’는 React Native로 개발이 되었기 때문에 크로스 플랫폼 지원이 가능하고, 앱 전체를 빌드해서 배포하는 바이너리 배포뿐만 아니라 CodePush 배포가 가능하다고 말씀드렸습니다.

이번 글에서는 ‘너티’를 React Native로 만들기로 결정한 가장 큰 이유인 CodePush 배포와, 운영 환경과 테스트 환경 등의 다양한 배포 트랙을 관리하는 방법을 설명해보겠습니다.

CodePush 배포

CodePush는 Microsoft의 App Center 클라우드 서비스에서 제공하는 기능 중 하나로, React Native 개발자가 모바일 앱 업데이트를 사용자의 디바이스에 직접 배포할 수 있게 합니다. 즉, 플랫폼 스토어의 검수과정을 생략할 수 있어 사용자에게 빠르게 업데이트를 제공할 수 있습니다.

React Native의 아키텍쳐

어떻게 사용자의 디바이스에 앱 업데이트를 직접 배포할 수 있는지 알기 위해서는 React Native의 아키텍쳐를 간단하게 살펴볼 필요가 있습니다. React Native로 만들어진 앱은 기본적으로 아래 그림에 따라 렌더링되고 각 모바일 플랫폼에 따라 마운트됩니다. 여기서 중요한 것은 무엇을 렌더링할지를 결정하는 영역은 Javascript로 이루어진 React의 영역이라는 것입니다. 그렇기 때문에 새로운 기능을 구현할 때 플랫폼에 의존성이 있는 기능이 아니라면 React Native의 렌더링 시스템인 Fabric에게 새로운 React Element를 그리도록 명령하는 React 코드를 작성하기만 하면 됩니다.

react-native-architecture.png

그리고 Javascript는 컴파일이 필요 없이 바로 실행 가능하기 때문에 이미 앱에 내장되어 있는 런타임이 실행할 JS 번들을 마치 웹페이지를 새로고침하는 것처럼 새로 받아오면 새로운 기능이 포함된 페이지를 렌더링할 수 있는 것입니다.

CodePush는 개발자가 빌드한 JS 번들 파일을 App Center에 올려, 사용자가 앱을 실행할때 필요하다면 JS 번들 파일을 교체해주는 기능을 제공합니다.

CodePush 설정

CodePush를 설정하기 전에 가장 먼저 확인해봐야하는 것은 지원하는 React Native 버전입니다. App Center 클라우드 서비스는 Microsoft가 관리하지만, JS 번들을 다운로드해서 교체해주는 React Native Client SDK는 react-native-code-push Github Repository에서 오픈 소스로 관리되고 있습니다. 해당 Repository에는 지속적으로 업데이트되고 있는 React Native의 버전을 지원하기 위해 다양한 PR과 이슈가 올라오고 있습니다. 그렇기 때문에 단순히 README에 적어 놓은 지원 버전만을 확인하지 말고 Supporting for RN 0.70.0처럼 최신 RN 버전 지원과 관련된 이슈가 있는지 찾아보고 적용 가능하다고 알려진 최신 RN 버전을 직접 테스트해보는 것을 추천드립니다.

지원하는 최신 React Native 버전을 찾고 README의 Getting Started를 참고해 설정을 완료하고 나면 App Center에 플랫폼별로 App을 생성해야합니다. App을 생성했다면 다음 몇가지 옵션을 고려해봐야합니다.

첫번째는 Deployment 트랙입니다. App Center는 앱을 단계별로 관리할 수 있도록 Deployment 트랙 기능을 제공합니다. 프로덕션을 위한 Deployment 트랙에 릴리즈를 하면 프로덕션에 반영이 되도록 하고, 내부 테스트를 위한 Deployment 트랙에 릴리즈를 하면 내부 테스터만 해당 업데이트를 반영할 수 있도록 할 수 있습니다.

Deployment 트랙을 생성하면 그에 따른 Deployment Key가 생성됩니다. 아래와 같이 CLI 명령어를 통해 특정 Deployment 트랙으로 새로운 JS 번들을 릴리즈 한 후, 해당 Deployment Key로 JS 번들을 sync 하면 해당 트랙에 올라가있는 번들로 앱을 업데이트할 수 있습니다.

appcenter codepush release-react -a <ownerName>/<appName> -d <deploymentTrackName>

두번째는 앱에서 업데이트 여부를 체크하는 주기새로운 JS 번들을 설치하는 시점입니다.

checkFrequency installMode
ON_APP_START ON_NEXT_RESTART
ON_APP_RESUME ON_NEXT_RESUME
MANUAL ON_NEXT_SUSPEND
  IMMEDIATE

위의 표에 나와 있는 옵션으로 업데이트 여부를 언제 확인할 것인지 새로운 JS 번들 설치를 언제 진행할 것인지를 설정할 수 있습니다.

테스트 환경 구축

CodePush 배포에서 Deployment 트랙을 이해하면서 눈치채셨겠지만, React Native 앱에서의 테스트 환경 구축의 핵심은 적절한 Deployment Key를 통해 JS 번들을 Sync 하는 것입니다. 적절하게 Deployment Key를 사용하여 테스트 트랙의 앱을 Sync 하는 방법은 크게 두 가지입니다.

  1. 사내의 특정 유저에게만 테스트 단계의 Deployment 트랙과 연결되는 Deployment Key로 앱을 Sync할 수 있는 버튼을 클릭할 수 있게 한다.
  2. 바이너리 빌드를 할때 테스트 트랙에 따라 다른 Configuration을 주입해서 이를 통해 ENV로 주입된 Deployment Key로만 앱을 Sync 하도록 한다.

‘너티’의 경우는 테스트 트랙 별로 서버와 DB가 달라져서 테스트 유저 여부를 저장하는 별도의 DB를 관리하기가 어렵고, 플랫폼 의존성이 있는 업데이트 사항을 쉽게 테스트하기 위해서 2번 방법을 통해 테스트 트랙을 관리하기로 결정했습니다. 하지만, 2번 방법은 테스트 트랙 별로 다양한 Configuration과 그에 따른 다양한 종류의 바이너리 빌드 결과물(apk, ipa)을 관리해야 합니다. React Native 앱의 Configuration을 관리하는 것은 이 블로그 글을 참고했습니다. 안드로이드의 경우는 flavor를 통해 관리하고, iOS의 경우는 별도의 Configuration을 생성하면 됩니다.

바이너리 빌드 결과물을 제대로 관리하는 것은 좀 더 어렵습니다. 일반적인 경우는 플랫폼 별로 제공해주는 테스트 트랙 관리 기능으로 앱의 빌드 결과물을 관리합니다. 하지만, ‘너티’의 경우는 테스트 Config로 빌드되어 실제 유저들에게 배포될 일이 없는 결과물을 스토어에 올리고 그에 따라 앱의 버전 코드가 변경되는 일련의 과정이 부적절하다고 생각했습니다. 그렇기 때문에 테스트 트랙을 위해 빌드된 apkipa 파일을 스토어에 업로드하는 것이 아닌 별도의 스토리지(S3)에 업로드하고 이를 다운 받을 수 있는 링크를 만들었습니다.

자동으로 슬랙에 다운로드 링크를 전송하도록 했습니다.

안드로이드의 경우는 apk 파일을 다운 받기만 하면 설치가 가능하지만, iOS의 경우는 웹 링크를 통해 다운받은 ipa 파일로 앱을 설치하기 위해서는 몇 가지 작업을 해야합니다. iOS에서 웹 링크를 통해 새로운 앱을 설치할 수 있게 하는 것이 adhoc 배포입니다.

adhoc 배포

지난 글에서 iOS는 앱 서명을 위해 Provisioning Profile을 관리할 필요가 있다고 언급했고, 이를 관리하기 위해 match 를 사용한다고 했습니다. 그리고 Provisioning Profile에는 어떤 디바이스에 어떤 형태로 배포를 하는 것을 허용할지에 대한 정보도 같이 담겨있습니다. 그래서 다음과 같은 절차를 통해 adhoc 배포를 위한 새로운 프로필과 그에 대한 인증서를 발급받아야 합니다.

  1. 테스트 디바이스의 UDID를 알아냅니다. (디바이스를 Mac에 연결하면 Finder를 통해 알 수 있습니다.)
  2. 애플 개발자 사이트의 디바이스 관리 페이지에서 UDID로 디바이스를 등록합니다.
  3. 프로필 관리 페이지에서 Adhoc 배포를 위한 Provisioning Profile을 디바이스를 포함해서 생성합니다.
  4. fastlane match adhoc 명령어를 통해 Provisioning Profile에 대한 인증서를 발급받습니다.

이렇게 발급받은 인증서로 서명한 ipa 파일을 배포하는 방법은 Apple 플랫폼 배포 문서의 “웹 사이트를 사용하여 앱 배포하기” 챕터를 참고하세요. 간단히 요약하자면, 다음과 같습니다.

  1. 앱의 정보와 ipa 파일 경로를 담고 있는 manifest.plist 파일을 만듭니다.
  2. 해당 manifest 파일을 다운 받을 수 있는 링크를 만듭니다.

그리고 이 과정을 자동화하기 위해 다음과 같이 bundle-identifier 별로 manifest 파일을 작성하고 이를 변환하는 js 스크립트를 작성했습니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>items</key>
  <array>
    <dict>
      <key>assets</key>
      <array>
        <dict>
          <key>kind</key>
          <string>software-package</string>
          <key>url</key>
          <string>###RECENT_PATH###/NuttyAlpha.ipa</string>
        </dict>
      </array>
      <key>metadata</key>
      <dict>
        <key>bundle-identifier</key>
        <string>com.scatterlab.messenger.alpha</string>
        <key>kind</key>
        <string>software</string>
        <key>title</key>
        <string>NuttyAlpha</string>
      </dict>
    </dict>
  </array>
</dict>
</plist>
const exportManifestFiles = async (date) => {
  const manifest = fs
    .readFileSync(path.resolve(FOLDER_PATH, `manifest-${DEPLOYMENT}.plist`))
    .toString();

  const result = manifest.replace(
    '###RECENT_PATH###',
    `${STORAGE_URL}/ios/${date}`
  );

  await fs.mkdirSync(path.resolve(FOLDER_PATH, date));

  fs.writeFileSync(
    path.resolve(FOLDER_PATH, date, `manifest-${DEPLOYMENT}.plist`),
    result,
    { recursive: true }
  );
};

const getDownloadLink = (date) => {
  if (PLATFORM === 'android') {
    return `${STORAGE_URL}/android/${date}/app-${DEPLOYMENT}-release.apk`;
  }
  return `itms-services://0.0.0.0?action=download-manifest&url=${STORAGE_URL}/ios/${date}/manifest-${DEPLOYMENT}.plist`;
};

adhoc 배포를 위한 작업까지 끝내면 getDownloadLink()가 반환한 링크를 통해 다음과 같이 테스트 트랙별로 앱을 설치할 수 있습니다.

Alpha와 Canary는 테스트 트랙 이름입니다.

Release Tag

이렇게 CodePush 배포와 내부 테스트 트랙 배포까지 할 수 있게 되면 어떤 배포(CodePush, Binary)를 어느 플랫폼(Android, iOS)의 어떤 트랙(prod, alpha, canary)에 할지에 따라 총 12가지 종류의 릴리즈를 할 수 있게 됩니다. 저희는 GitHub Actions Runner가 12가지의 배포 방식 중 어떤 배포를 해야되는지 구분할 수 있도록 {배포 종류}.{플랫폼}.{배포 트랙}.{버전 코드} 형태의 태그를 사용했습니다.

예를 들어 binary.ios.alpha.1.1.0 태그와 함께 릴리즈를 생성하면 알파 Configuration을 주입시킨 ipa 파일을 adhoc 배포용으로 빌드하고 업로드한 후, 이를 다운받기 위한 링크가 슬랙 채널에 전송됩니다. 이 링크를 통해 설치한 NuttyAlpha를 실행하면 알파 Configuration을 통해 주입된 Deployment Key를 이용하여 해당하는 iOS 앱의 알파 트랙에 CodePush 배포가 되어있는지 확인합니다. 그 후에 QA를 통해 발견된 버그를 고치고 codepush.ios.alpha.v10 태그로 새로 릴리즈하면 CodePush를 iOS 앱의 알파 Deployment 트랙에 배포하게됩니다. 배포가 마무리되면 아까 설치했던 NuttyAlpha가 새로운 CodePush가 배포된 것을 알고 JS 번들을 업데이트하게 됩니다.

마치며

지난 글에 이어서 이번에는 React Native로 만든 ‘너티’의 배포 트랙을 관리하는 방법에 대해서 알아보았습니다. 크로스 플랫폼 앱을 만들고 싶다면, React Native를 이용해 CodePush 배포 이점도 챙기고, 사내 테스트 환경도 제대로 구축해서 다양한 플랫폼에 안정적으로 릴리즈되는 앱을 만드시길 추천합니다.

스캐터랩 핑퐁팀에는 유저에게 안정적인 서비스를 지속적으로 제공하기 위해 끊임없이 고민하는 뛰어난 개발자들이 많이 있습니다. 저희와 함께 재밌는 일을 해보고 싶으시다면 채용 공고를 참고해주세요!

스캐터랩이 직접 전해주는
AI에 관한 소식을 받아보세요

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