와피 프로젝트의 기능 개발을 마무리하며, 드디어 앱을 배포할 시기가 다가왔다 !
처음 프로젝트를 시작할 때 CI 환경을 직접 구축하고, 관련된 블로그 글을 올린지 6개월이 지났다.
이번에는 Github Actions를 활용하여 APK를 빌드하고, APK Signing 그리고 Firebase App Distribution까지 자동으로 수행하는 프로세스를 구축하고자 한다.
아래에서는 자동 앱 배포 프로세스에 대한 방법과 구현하며 마주친 문제와 해결방법에 대해 소개한다.
(Github Actions와 CI 관련된 내용은 이전 내용을 참고하세요 !)
Android CI/CD : Github Action을 통해 CI/CD 구축하기
Android CI/CD : Github Action을 통해, Discord + Jira 연동하기
Android CD WorkFlow 설계하기
구현하고자 하는 자세한 워크플로우는 다음과 같다.
- Github에 저장된 Secrets 불러오기
- Android App 빌드하기
- Android APK 추출하기
- Android APK 서명하기
- Firebase 계정 인증하기
- Firebase App Distribution을 통해 배포하기
- 결과 Discord로 전송하기
1번과 2번 그리고 7번은 이전 게시물에서 확인할 수 있으므로 3번부터 6번까지 소개할 예정이다.
Github Actions를 구현하기 전, 계정과 프로젝트에 대한 인증을 거쳐야 한다.
해당 인증을 구현하기 위해, Firebase와 Google에서의 Id, Password, Key를 가져와야 한다.
아래에서 Id, Password, Key들을 Keys라고 묶어서 설명한다.
Android CD WorkFlow 준비하기
Keys가 필요한 actions들은 다음과 같다.
- Android APK 추출하기
- Key Alias
- key Password
- KeyStore Password
- Key (Encoded Base64)
- Firebase 계정 인증하기
- Firebase 서비스 계정 Key
- Firebase App Distribution을 통해 배포하기
- Firebase App Id
- Firebase App Distribution SDK Service Agent Key
그럼 이제 하나씩 찾아보도록 하자.
KeyAlias, Key Password, KeyStore Password는
Android Studio에서 처음 APK, AAB 생성 시 Signing Key를 함께 만들 수 있고, 생성할 때의 정보를 그대로 사용하면 된다.
APK, AAB Key가 같아도 무방한 것 같았다. (처음 AAB를 배포하려다 APK로 변경했는데 따로 문제가 발생하지 않았다.)
Base64 Encoded Key는
만들어진 Key의 내용을 Base64로 encode해서 가져온다.
맥에서는 터미널에서 다음과 같은 명령어를 통해 수행이 가능하다.
openssl base64 -in [원본 키 주소] -out [encode 키 주소]
이렇게 얻은 Key들을 Github Settings/Secrets and Variable/actions에 새로운 Repository Secrets을 등록한다.
- 필자는 다음과 같이 저장했다.
Firebase 서비스 계정 Key는
프로젝트의 Firebase 대쉬보드 설정에서 확인할 수 있다.
- Firebase 대쉬보드 -> 설정 -> 서비스 계정
- 새로운 비공개 Key를 생성한다. 해당 키가 Firebase 서비스 계정 Key가 된다.
다음으로 Firebase App id는
- 같은 Firebase 대쉬보드 -> 설정 -> 일반에서 확인할 수 있다.
- 아래의 앱 ID가 Firebase App Id가 된다.
위의 두 개의 Keys들은 다음과 같이 Github Secrets에 저장했다.
마지막으로 Firebase App Distribution SDK Service Agent Key는
Google Cloud Platform IAM에서 확인할 수 있다.
- IAM 및 관리자 -> 서비스 계정 -> 서비스 계정 만들기를 선택한다.
- 프로젝트 정보를 입력하고, Firebase 앱 배포 Admin SDK 서비스 에이전트를 선택하고 키 생성을 완료한다.
다시 서비스 계정 대쉬보드로 나와서, 만들어진 Key에서 작업, 점 세 개를 클릭하고 "키 관리"를 선택한다.
- 들어와서 새로운 Json Key를 생성한다.
해당 키가 Firebase App Distribution SDK Service Agent Key가 된다.
마찬가지로 Github Secrets에 다음과 같이 저장했다.
Android CD WorkFlow 구현하기
시간이 지나면 지날 수록 필자가 구현한 워크플로가 제대로 수행되지 않을 때가 있다.
그럴 때는 해당 action name을 검색하여, 직접 Github Repository를 확인하고, 그래도 해결할 수 없으면 issue 탭을 잘 찾아보길 바란다.
(필자도 issue 탭을 확인해서 해결한 경우가 많았다. 개발하신 분들이 귀찮아서, 이슈탭에 답변을 달아도 문서나 리드미에 반영 안했을 가능성이 있다.)
(Actions 하나씩 설명하고, 마지막에 전체 워크플로를 올려두었습니다.)
APK Build Actions
- 빌드의 경우 gradlew 명령어를 사용하면 된다.
- 디버그 모드, 릴리즈 모드, APK, AAB 총 4가지의 경우가 있는데,
AAB인 경우 시작 단어가 bundle, APK인 경우 assemble이다.
디버그 모드일 경우 마지막 단어가 Debug, 릴리즈 모드인 경우 Release이다.
APK Signing Actions
- 만들어진 APK에 서명을 하기위한 Action이다.
- 공식 Github Actions Repostiory를 그대로 사용하면, 빌드 오류가 발생한다.
원인은 가상 우분투 환경에서, 사이닝을 하는 빌드 도구를 찾을 수 없다고 한다.
필자도 다음과 같은 공식 Github Issue에서 같은 문제를 발견했고, 다른 개발자분이 해결한 방법 (미리 환경변수를 통해 빌드 도구 버전을 불러오는 방법)을 통해 해결할 수 있었다.
Firebase Authentication Actions
- Firebase에 접근하기 위해, 다음과 같은 actions를 통해 인증을 수행한다.
Firebase App Distribution Actions
- 다음은 Firebase App Distribution이다.
- 속성에 맞게 이전에 준비한 Keys를 입력하고, groups의 경우는 Firebase App Distribution에 등록한 테스터 그룹의 이름을 추가한다. (이메일도 가능하다고 한다.)
- file의 경우 이전 APK Signing에서 서명한 APK를 가져온다.
- 해당 Action의 경우도 공식 Github에서는 1.7.0 버전이 새로 릴리즈 되었음에도 불구하고, 아직까지 공식 문서나 리드미에는 1 버전이 기록되어 있었다. (그래서 그런지 다른 블로그 레퍼런스도 모두 1 버전을 사용하고 있었다. 속도 차이가 많이 난다고 하니, 1.7.0 버전을 선택하는 걸 추천한다.)
- AAB를 배포하는 경우, Firebase와 Google Play와 연동이 되어야 하며, 앱을 실제 출시해야 연동이 되는 것 같았다.
그래서 필자도, 릴리즈 App Distribution은 APK로 방향을 틀었다 ㅠ.ㅠ
전체 워크플로 (앱 빌드 + 디스코드 메세지 포함 코드블록)
name: Released-App-Distribution
on:
push:
branches:
- 'release'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 17
distribution: zulu
cache: gradle
- name: add google-services.json
run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > ./app/google-services.json
- name: add local.properties
run: |
echo api_key=\"${{ secrets.API_KEY }}\" >> ./local.properties
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Build release APK
run: ./gradlew assembleRelease
- name: Setup build tool version variable
shell: bash
run: |
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
echo Last build tool version is: $BUILD_TOOL_VERSION
- name: Sign APK
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.KEY_BASE_64_RELEASE }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
- name: Authenticate to Firebase
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
- name: Setup Firebase CLI
run: curl -sL https://firebase.tools | bash
- name: upload artifact to Firebase App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1.7.0
with:
appId: ${{secrets.FIREBASE_APP_ID}}
serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
groups: WAPP_QA
file: ${{steps.sign_app.outputs.signedReleaseFile}}
- name: Send Success Message
if: ${{ success() }}
uses: Ilshidur/action-discord@0.3.2
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
DISCORD_USERNAME: WAPP_BOT
DISCORD_AVATAR: https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true
DISCORD_EMBEDS: |
[
{
"author": {
"name": "WAPP Release",
"url": "https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true",
"icon_url": "https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true"
},
"title": "릴리즈 성공, 자 두 과 자 ~ 🔥🔥",
"color": 10478271,
"description": "메일에 새로운 릴리즈 앱 배송완료했어요!"
}
]
- name: Send Failure Message
if: ${{ failure() }}
uses: Ilshidur/action-discord@0.3.2
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
DISCORD_USERNAME: WAPP_BOT
DISCORD_AVATAR: https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true
DISCORD_EMBEDS: |
[
{
"author": {
"name": "WAPP Release",
"url": "https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true",
"icon_url": "https://github.com/pknu-wap/WAPP/blob/main/image/icon.png?raw=true"
},
"title": "릴리즈 실패, 누가 이렇게 하래. 😭😭",
"color": 13458524,
"description": "다시 릴리즈 해오세요. 삐빅"
}
]
마지막으로
이번 프로젝트를 진행하며, CI를 구축하고 이번에는 CD를 구축해보았다.
이전 CD를 구축하지 않았을 때는 직접 APK를 추출해서 Distribution에 등록해야 했는데,
자동으로 배포하니 한결 수월해진 느낌이다.
CD를 구축하는 중간중간 많은 문제가 발생해도, 다 하나하나 찾아가며 해결하니 나름 성취감과 재미도 있었다.
찐 CD[마켓 출시]도 곧 올려보겠습니다 ㅎㅎ
참고한 글
https://onlyfor-me-blog.tistory.com/844