기존의 배포 파이프라인은 다음과 같다.
▲ 그림 1. 기존의 CI/CD 파이프라인
Flavor 를 이용하여 운영(prod) 과 개발(dev) 환경을 운영하였고, Android 에서는 개발 환경으로 된 앱을 내부 테스트에 업로드가 불가한 문제로 Firebase App Distribution 을 도입했고,이 과정에서 4가지의 환경을 구성하였다.
development: Flavor - dev && Build Type: Debug
hotfix: Flavor - prod && Build Type: Debug
sandbox: Flavor - dev && Build Type: Release
production: Flavor - prod && Build Type: Release
이러한 네이밍을 지은 이유는 'Firebase App Distribution' 게시글에서 확인할 수 있다.
따라서 본 게시글과 다음 포스트할 예정인 iOS 편에서 목표는 기존 배포 파이프라인에서 공통적으로 Firebase App Distribution 을 적용하고, iOS 의 경우 Build Scheme 을 위 4가지 빌드 환경으로 구성하는 방법론에 대해 알아볼 예정이다.
우선 다음 명령어를 통해 fastlane 에 firebase_app_distribution 플러그인을 추가해줘야 한다.
fastlane add_plugin firebase_app_distribution
이전 게시글에서 이미 GCP 에서 fastlane 전용 서비스 계정을 이미 만들었으니 이에 대한 내용은 생략하겠다.
Firebase App Distribution이나 Google API를 사용할 때는 인증이 필요한데, 이 때 Google은 기본 인증 방식인 ADC(Application Default Credentials)를 제공한다. ADC는 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 서비스 계정 키(JSON 파일)의 경로를 설정하면, 해당 파일을 자동으로 참조해 인증 토큰을 생성하고 CLI나 Fastlane 같은 도구들이 이를 통해 안전하게 API 요청을 수행할 수 있도록 해준다. 따라서 인증을 자동화하고 수동 입력 없이 배포나 빌드를 진행하려면 이 환경 변수 설정이 필수적이다.
export GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/credentials/file.json
기존의 build_app(flavor) 는 다음과 같다.
platform :android do
def build_app(flavor)
if flavor == "dev"
sh "flutter build appbundle --flavor dev --release --dart-define=FLAVOR=dev"
return "../build/app/outputs/bundle/devRelease/app-dev-release.aab"
elsif flavor == "prod"
sh "flutter build appbundle --flavor prod --release --dart-define=FLAVOR=prod"
return "../build/app/outputs/bundle/prodRelease/app-prod-release.aab"
else
UI.user_error!("Unknown flavor: #{flavor}")
end
end
2개의 환경에서 4개로 변경되었기 때문에 네이밍 변경뿐 만 아니라 2개의 환경도 추가해줘야 한다.
def build_app(flavor)
if flavor == "sandbox"
sh "flutter build apk --flavor sandbox --release --dart-define=FLAVOR=dev"
return "../build/app/outputs/flutter-apk/app-sandbox-release.apk"
elsif flavor == "production-firebase"
sh "flutter build apk --flavor production --release --dart-define=FLAVOR=prod"
return "../build/app/outputs/flutter-apk/app-production-release.apk"
elsif flavor == "production"
sh "flutter build appbundle --flavor production --release --dart-define=FLAVOR=prod"
return "../build/app/outputs/bundle/productionRelease/app-production-release.aab"
else
UI.user_error!("Unknown flavor: #{flavor}")
end
end
우선 flavor == "production-firebase" 와 flavor == "production" 이 분기 처리된 이유 먼저 봐야하는데, 'production' 환경은 운영 환경에서의 실제 제품이기 때문에 이는 개발자가 만든 업로드용 키와 Play Console 에서 부여한 인증키 2개로 구성되어 있다. 이를 가지고 .apk 가 아닌 .aab 앱 번들 파일을 만들어 업로드가 가능한 것이다.
Firebase App Distribution 에서는 apk 와 aab 업로드 둘 다 지원하고 있었고, 제품 출시 때 앱 번들을 사용하니 Firebase 에도 aab 를 업로드 하려고 했으나,
▲ 그림 2. Link 에러
테스트 과정에서 계속 연결 에러가 발생했고, 이러한 원인으로는
▲ 그림 3. Firebase 설정 오류
Firebase 콘솔에서 앱을 찾을 수 없다는 에러가 발생해서 연결 자체가 되지 않아 발생하였다. 이는 Firebase Console > 프로젝트 설정 > 통합 에서 확인할 수 있다. 해결 방법으로는 Firebase 프로젝트 설정에 Play Console 에서 부여한 서명 키의 SHA-1, SHA-256 을 넣어주면 된다곤 하지만, 이미 이전부터 추가가 된 상태였었고 연결 오류에 대한 추가적인 원인을 찾기 어려워 Firebase 에는 apk 를 업로드 하는 방식으로 우회하였다.(만약에 해당 문제가 발생하지 않은 경우라면 운영 환경의 앱일 경우 Firebase 에 aab 파일을 업로드 하는 것을 권장한다)
따라서 분기처리의 의미로는 다음과 같다.
production-firebase: Firebase -> APK 업로드
production: Console 의 내부테스트, 프로덕션 -> AAB 업로드
lane 의 구성은 총 4개로,
Lane 1: 알파 테스트 -> Firebase 에 sandbox APK 업로드
Lane 2: 알파 테스트 -> Firebase 에 production APK 업로드
Lane 3: 베타 테스트 -> Play Console 의 내부테스트에 production aab 업로드
Lane 4: 제품 출시 및 버전 업데이트 > Play Console 의 프로덕션에 production aab 업로드
추가가 된 함수는 get_app_version 인데, pubspec.yaml 을 읽어 버전 정보를 가져오는 함수이다. 이는 단순히 Firebase App Distribution 에서 출시 노트에 해당 버전을 기재하기 위한 용도이다.
Lane 3,4는 기존의 Fastfile 에 있었던 내용과 동일하고, Lane 1,2가 추가 되었다.
def get_app_version
pubspec = File.read("../../pubspec.yaml")
version_line = pubspec.lines.find { |line| line.start_with?("version:") }
version, build_number = version_line.split(":").last.strip.split("+")
return version, build_number
end
... (build_app 생략)
# Lane 1: [sandbox] 환경 - 알파 테스트용 apk -> Firebase 배포
desc "Deploy sandbox .apk to Firebase App Distribution"
lane :deploy_sandbox_to_firebase do
apk_path = build_app("sandbox")
version, build_number = get_app_version()
firebase_app_distribution(
app: ENV['FIREBASE_APP_ID_SANDBOX'],
apk_path: apk_path,
groups: "qa",
release_notes: "[sandbox] Version: #{version}+#{build_number} 내부 테스트)"
)
end
# Lane 2: [production] 환경 - 알파 테스트용 aab -> Firebase 배포
desc "Deploy production .aab to Firebase App Distribution"
lane :deploy_prod_to_firebase do
apk_path = build_app("production-firebase")
version, build_number = get_app_version()
firebase_app_distribution(
app: ENV['FIREBASE_APP_ID_PRODUCTION'],
apk_path: apk_path,
groups: "qa",
release_notes: "[production] Version: #{version}+#{build_number} 배포 전 내부 테스트)",
)
end
# [Lane 2] aab 업로드 로직
# desc "Deploy production .aab to Firebase App Distribution"
# lane :deploy_prod_to_firebase do
# aab_path = build_app("production")
# version, build_number = get_app_version()
#
# firebase_app_distribution(
# app: ENV['FIREBASE_APP_ID_PRODUCTION'],
# android_artifact_path: aab_path,
# android_artifact_type: "AAB",
# groups: "qa",
# release_notes: "[production] Version: #{version}+#{build_number} 배포 전 내부 테스트)",
# service_credentials_file: "../gachiga-serviceAccount.json",
# )
# end
# Lane 3: [production] 환경 - 베타 테스트용 aab -> Play Console 내부 테스트 배포
desc "Deploy production .aab to Play Console Internal Test"
lane :deploy_prod_to_internal_test do
aab_path = build_app("production")
upload_to_play_store(
track: "internal",
aab: aab_path
)
end
# Lane 4: [production] 환경 - 프로덕션 배포
desc "Deploy production .aab to Play Console Production"
lane :deploy_prod_to_production do
aab_path = build_app("production")
upload_to_play_store(
track: "production",
aab: aab_path,
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true,
skip_upload_changelogs: true,
)
end
sandbox 환경이 추가되었기 때문에 로컬과 동일한 환경 구성을 위해 sandbox 용 앱 서명 키에 대한 keystore 를 secrets 에 추가해줘야 한다.
▲ 그림 4. 'sandbox' 용 앱 서명 키
마찬가지로 sandbox 환경의 Firebase 프로젝트 연동을 위해서 App Id 도 추가해주었다.
▲ 그림 5. 'sandbox' 용 Firebase 연동 관련 App Id
기존의 deploy_android 작업에서 production 환경만 고려했던 것을 sandbox 도 추가하여 google-services.json, key.properties 등을 추가하였다.
deploy_android:
name: Android Deployment
runs-on: self-hosted
needs: setup_environment
steps:
- name: Google-services.json 생성
working-directory: android
run: |
mkdir -p app/src/production
mkdir -p app/src/sandbox
cat <<EOF > app/src/sandbox/google-services.json
${{ secrets.ANDROID_GOOGLE_SERVICE_DEV_JSON }}
EOF
cat <<EOF > app/src/production/google-services.json
${{ secrets.ANDROID_GOOGLE_SERVICE_PROD_JSON }}
EOF
- name: local.properties 생성
working-directory: android
run: |
cat <<EOF > local.properties
${{ secrets.ANDROID_LOCAL_PROPERTIES }}
EOF
- name: Signing Key 복호화 및 key.properties 생성
working-directory: android
run: |
# keystore 전용 디렉토리 생성
mkdir -p keystore/release/dev
mkdir -p keystore/release/prod
# .jks 파일 복호화
echo "${{ secrets.ANDROID_PRODUCTION_KEY_BASE_64 }}" | base64 -d > keystore/release/prod/production-key.jks
echo "${{ secrets.ANDROID_SANDBOX_KEY_BASE_64 }}" | base64 -d > keystore/release/dev/sandbox-key.jks
# 권한 설정
chmod 600 keystore/release/prod/production-key.jks
chmod 600 keystore/release/dev/sandbox-key.jks
# production-key.properties 생성
cat <<EOF > keystore/release/prod/production-key.properties
${{ secrets.ANDROID_PRODUCTION_KEY_PROPERTIES }}
EOF
# sandbox-key.properties 생성
cat <<EOF > keystore/release/dev/sandbox-key.properties
${{ secrets.ANDROID_SANDBOX_KEY_PROPERTIES }}
EOF
이후에 커밋 메시지의 옵션에 따라 배포를 실행하기 위해서 4가지 옵션으로 구성하였다.
Option 1: [sandbox] 환경의 알파테스트 진행 -> Firebase 업로드
Option 2: [production] 환경의 알파테스트 진행 -> Firebase 업로드
Option 3: [production] 환경의 베타테스트 진행 -> Play Console 내부 테스트 업로드
Option 4: [production] 환경의 제품 출시 및 버전 업데이트 -> Play Console 프로덕션 배포
- name: 옵션에 따른 배포 실행
working-directory: android
env:
FIREBASE_APP_ID_SANDBOX: ${{ secrets.FIREBASE_APP_ID_SANDBOX }}
FIREBASE_APP_ID_PRODUCTION: ${{ secrets.FIREBASE_APP_ID_PRODUCTION }}
run: |
COMMIT_MSG=$(git log -1 --pretty=%B)
if [[ "$COMMIT_MSG" =~ deploy:[1-9] ]]; then
DEPLOY_OPTION=$(echo "$COMMIT_MSG" | grep -o 'deploy:[1-9]' | cut -d':' -f2)
else
echo "배포 옵션이 지정되지 않았습니다. 기본 옵션(1) 사용"
DEPLOY_OPTION="1"
fi
echo "선택된 배포 옵션: $DEPLOY_OPTION"
case "$DEPLOY_OPTION" in
"1")
echo "[sandbox] Firebase App Distribution 배포 시작"
fastlane deploy_sandbox_to_firebase
;;
"2")
echo "[production] Firebase App Distribution 배포 시작"
fastlane deploy_prod_to_firebase
;;
"3")
echo "[production] Play Console Internal Test 배포 시작"
fastlane deploy_prod_to_internal_test
;;
"4")
echo "[production] Play Console Production 배포 시작"
fastlane deploy_prod_to_production
;;
*)
echo "Invalid deployment option selected: $DEPLOY_OPTION"
exit 1
;;
esac
▲ 그림 6. [Option 1] 실행 결과
▲ 그림 7. 'sandbox' 용 Workflow 실행 결과
▲ 그림 8. 'sandbox' 용 Firebase 업로드 결과
▲ 그림 9. [Option 2] 실행 결과
▲ 그림 10. 'production' 용 Firebase 업로드 결과
▲ 그림 11. [Option 3] 실행 결과
▲ 그림 13. [Option 4] 실행 결과
▲ 그림 14. 'production' 용 프로덕션 배포
https://firebase.google.com/docs/app-distribution/android/distribute-fastlane?hl=ko