1부에 이어서 오늘은 Jenkins를 통해서 자동화 배포를 구축할 예정이다. 이전 포스팅은 여기에서 확인할 수 있다.
먼저 Jenkins를 배포하기 위해서는 어디에 Deploy할 것인가를 결정해야 한다. 필자는 QA Build는 Firebase App Distribution에 Deploy할 예정이고, Release Build는 Googe Play Console에 Deploy할 예정이다.
Git에는 Tag라는 개념이 있다. 특정 Commit에 Tag를 붙일 수 있고 흔한 Git 개념처럼 Local에서 Tag를 붙일 수도 있고 이 Tag를 원격 저장소에 Push할 수도 있다. 2부에서 소개할 자동화 배포는 이 Tag Push를 사용할 예정이다. CI/CD를 적용할 Android 앱 Github 저장소에 qa/버전명
형식 혹은 release/버전명
형식으로 Tag를 Push하면 Jenkins에서 해당 태그를 트리거하고 qa/버전명
형식으로 Tag Push 했을 때는 Firebase App Distribution으로 Deploy, release/버전명
형식으로 Tag Push 했을 때는 Google Play Console로 Deploy하도록 구축할 것이다.
Android 프로젝트에서 특정 파일에 QA Build에 대한 출시 노트를 입력하면, 이 출시 노트에 대한 내용도 반영되어 Firebase App Distribution에 Deploy이 할 수 있다. 그리고 이와 똑같이 Googe Play Console로 Release Build를 Deploy할 때 포함되는 출시 노트도 지정할 수 있고, 언어 별로 배포할 수도 있다.
그리고 지금 본 글에서 소개하려는 작업을 하기 전에 1부와 마찬가지로 Build Variant와 SiginingConfigs를 설정해야 하는 작업을 먼저 해야한다. 이 부분은 Android 앱을 Deploy하는 부분에 대한 작업이기 때문에 앱 Signing이 필수로 되어야 하기 때문에 Build Variant는 나누지 않더라도 SiginingConfigs에 대한 부분은 필수로 설정해야할 것 같다.
(필자는 SigningConfigs를 설정 안 하고 테스트 안 해봐서 안 해도 되는 지는 정확히 모르겠다..)
먼저 QA Deploy 정리하고, 그 다음에 Release Deploy에 대해서 정리하며 소개할 예정이다.
Firebase App Distribution
QA Build를 Firebase App Distribution에 Deploy하기에 앞서서 CI/CD를 적용하려는 Android 프로젝트가 이미 Firebase와 연동되어 있는 것을 가정하려고 한다. 이 연동 작업이 되어 있지 않다면, 여기를 참고해서 연동 작업을 한 후에 이어서 작업하면 좋을 것 같다.
작업 순서는 아래와 같다.
build.gradle
에서 com.google.firebase.appdistribution
plugin 설정build.gradle
에서 원하는 productFlavor
에 firebaseAppDistribution
설정
1. Firebase App Distribution에서 Tester Group 만들기
먼저 Firebase Console에 들어간 다음 프로젝트로 이동한다. 그리고 아래의 사진과 같이 좌측 메뉴에서 Firebase App Distribution을 선택하고 테스터 그룹을 선택한다.
그 다음 나오는 화면 상단에서 + 서비스 계정 만들기를 클릭해서 아래의 사진과 같이 값을 채워준다. 아래의 XXXX는 본인 Android 프로젝트의 이름으로 지정하면 좋을 것 같다.
2. GCP에서 Service Account와 Key를 생성하고 프로젝트에 저장하기
Firebase는 Google Console Project 위에서 동작하므로 Service Account와 그에 대한 Key를 발급 받아서 Android 프로젝트에 저장하고 Firebase App Distribution에 대한 설정을 해야 한다. GCP의 프로젝트 Dashboard 화면에서 아래의 사진처럼 IAM 및 관리자
> 서비스 계정
으로 이동해준다.
그 다음 나오는 화면 상단에서 + 서비스 계정 만들기를 클릭해서 아래의 사진과 같이 값을 채워준다. 아래의 XXXX는 본인 Android 프로젝트의 이름으로 지정하면 좋을 것 같다.
그리고 만들고 계속하기를 클릭한 다음 아래의 사진처럼 Firebase > Firebase 품질 관리자에 대한 권한을 추가한다.
그리고 계속 버튼을 눌러서 권한 부여를 저장하고, 그 다음 바로 완료를 눌러서 생성을 완료한다. 3번에 대한 설정은 스킵해도 된다. 그 다음에 만들어진 서비스 계정에서 이메일 쪽을 클릭해서 세부정보 화면으로 이동하고 아래의 사진처럼 키 탭으로 이동한 뒤, 키 추가 > 새 키 만들기 > JSON > 만들기를 클릭해서 json 파일을 다운받는다.
그 다음 이 Key json 파일을 Android 프로젝트의 가장 최상단 디렉토리에 이름을 XXXX-firebase.json 이라고 변경한 뒤 저장한다. 여기에서 XXXX 역시 본인의 Android 프로젝트 이름으로 생각하면 좋을 것 같다.
3. QA Build 출시 노트 파일 생성
QA Build를 Firebase App Distribution으로 Deploy하면 아래의 사진처럼 출시 노트를 작성할 수 있는데, 이 내용을 Android 프로젝트에서 QA Build를 위해 versionName
, versionCode
를 변경하는 시점에 md 파일로 미리 저장해서 자동으로 출시 노트에 들어갈 수 있도록 할 수 있다.
저 출시 노트 쪽에 들어가는 내용을 만들기 위해 2번의 XXXX-firebase.json 파일을 저장했던 위치와 동일하게, Android 프로젝트의 최상단 디렉토리 위치에 QA_RELEASE_NOTE.md
파일을 만들어 주고 특정 출시 노트에 대한 내용을 채워준 뒤 저장한다.
4. build.gradle에서 'com.google.firebase.appdistribution' plugin 설정
build.gradle(:project)
에서 buildscript
> dependencies
안에 아래 코드를 넣어준다.
classpath 'com.google.firebase:firebase-appdistribution-gradle:3.0.0'
그리고 build.gradle(:app)
에서 plugin을 추가해준다.
plugins {
id 'com.google.firebase.appdistribution'
}
5. build.gradle에서 원하는 productFlavor에 firebaseAppDistribution 설정
이제 QA Build할 때 사용될 Product Flavor에 Firebase App Distribution에 대한 설정을 해줄 것이다. 아래의 사진처럼 입력해주면 된다.
위의 appId는 Android 프로젝트와 Firebase 연동했을 때 다운받아서 Android 프로젝트 안에 저장했던 google-service.json
안에서 client
> client_info
> mobilesdk_app_id
라고 있는데 이 값을 복사해서 붙여 넣으면 된다. 그리고 serviceCredentialsFile
은 위의 2번 과정에서 저장한 Key json 파일 이름을 입력해주면 된다. 그리고 releaseNotesFile
은 3번 과정에서 만들었던 QA Build 출시 노트 저장할 파일 이름을 입력해주면 되고, groups
는 1번 과정에서 Tester Group 만들었을 때 사용한 이름을 넣어주면 된다.
6. Jenkins에서 Multibranch Pipeline 생성
1부에서 구축했던 Jenkins 홈으로 이동해서 좌측 메뉴의 새로운 Item
> 이름 입력 > Multibranch Pipeline
선택 후 생성한다. 그리고 아래와 같이 설정한다.
1부에서 Feature에 대한 Push Event가 발생할 때마다 실행되는 Pipeline 만들 때 사용했던 Credential로 기존의 Github와 동일하게 연결하면 된다. 그리고 이어서 아래의 사진을 참고해서 설정한다.
1부에서 사용했던 Plugin도 있어서 익숙할 것이다. 하지만 이번에 다른 부분이 있다. 바로 Tag Push 할 때만 이 Pipeline이 실행되어야 하는 부분인데 그 역할을 하는 것이 바로 위 사진 중 Build strategies
> Tags
설정으로 인해서 가능한 것이다. 이 설정은 Jenkins에서 Basic Branch Build Strategies Plugin
을 설치해야 사용할 수 있었던 것인데 1부에서 해당 Plugin을 미리 설치했기 때문에 1부 글부터 읽으신 분들은 아마 설치가 되어 있을 것이다. 이 설정 안에 Ignore tags older than
은 생성된지 특정 일수가 지난 Tag는 무시한다는 옵션이다. 추가적으로 위의 내용 중에서 Custom Github Notification Context
는 Github Custom Notification Context SCM Behavior
라는 Plugin을 설치해야 사용할 수 있으며 이것 역시 1부에서 Plugin 설치할 때 같이 설치했었다.
이어서 나머지 설정은 아래의 사진과 같이 설정한다.
그리고 좌측 하단에 Save를 통해서 저장한다.
7. Android 프로젝트 안에 Deploy에 대한 Pipeline script 작성
1부에서 Script를 작성한 위치와 동일한 곳인 프로젝트 최상단 디렉토리 > ci 디렉토리 안에 jenkinsFileForDeploy
파일을 만들고 아래의 Script를 입력하고 저장한다.
pipeline {
agent any
options {
skipStagesAfterUnstable()
}
tools {
jdk('JAVA8')
}
environment {
// Build Variant에 대해서 설정했다면 어떤 Variant로 Delpoy할 지 선택하기.
BUILD_VARIANT_FOR_QA = "Debug" // "DevAlpha"
BUILD_VARIANT_FOR_RELEASE = "Release" // "ProductRelease"
}
stages {
stage("Environment") {
steps {
script {
//withCredentials([string(credentialsId: "build-pwd", variable: "PWD")]) {
// env.DevKeyPassword = "${PWD}"
// env.DevStorePassword = "${PWD}"
// env.ProductKeyPassword = "${PWD}"
// env.ProductStorePassword = "${PWD}"
//}
}
}
}
// 아래 stage와 94~100번째 코드는 참고용. 지워도 무관.
//stage("Get app version") {
// steps {
// script {
// def data = readFile(file: 'version.properties')
// env.VERSION_CODE = getVersionCode(data)
// env.VERSION_NAME = getVersionName(data)
// sh "echo VERSION_CODE: ${env.VERSION_CODE} VERSION_NAME: ${env.VERSION_NAME}"
// }
// }
//}
stage("QA Unit Test") {
when {
tag "qa/*"
}
steps {
sh "./gradlew test${env.BUILD_VARIANT_FOR_QA}UnitTest --stacktrace"
junit "**/TEST-*.xml"
}
}
stage("QA Deploy") {
when {
tag "qa/*"
}
steps {
echo "Start Deploying to Firebase.."
sh "./gradlew assemble${env.BUILD_VARIANT_FOR_QA} appDistributionUpload${env.BUILD_VARIANT_FOR_QA} --stacktrace"
archiveArtifacts artifacts: "**/*.apk, **/mapping.txt", fingerprint: true
}
}
stage("Release Unit Test") {
when {
tag "release/*"
}
steps {
sh "./gradlew test${env.BUILD_VARIANT_FOR_RELEASE }UnitTest --stacktrace"
junit "**/TEST-*.xml"
}
}
stage("Release Deploy") {
when {
tag "release/*"
}
steps {
echo "Start Deploying to Google Play.."
// 나중에 3부에서 추가 작업할 예정.
}
}
}
}
//def getVersionCode(String body) {
// return (body =~ /VERSION_CODE\s*=\s*(\d+)/)[0][1]
//}
//def getVersionName(String body) {
// return (body =~ /VERSION_NAME\s*=\s*(\d+\.\d+\.\d+(\.\d+)?)/)[0][1]
//}
위의 Script 중에서 상세한 설명은 1부에서 다루었기 때문에 넘어갈 예정이다. 12~16번째 줄에 보면 QA, Release Build 시 각각 어떠한 Build Variant로 Deploy할 지 선택할 수 있다. 앞의 글자는 꼭 대문자여야 한다. 왜냐하면 65번째 줄처럼 gradlew의 명령어 뒤에 붙일 예정이기 때문이다. 23~28번째 줄 주석된 부분에 대한 설명은 1부에서 다루었기 때문에 넘어갈 예정이다. 그 다음에 34번째 줄에 주석되어 있는 stage는 필자의 build.gradle
환경과 똑같다면 사용할 수 있기 때문에 주석으로 참고를 위해 남겨두었다.
Android 앱에서는 versionCode
와 versionName
이 있는데, 배포 시에 이 부분을 변경해주어야 한다. 그러나 이걸 따로 파일로 분리하지 않으면 build.gradle
안에서 매번 수정해야 하니, 불편함을 해소하기 위해 versionCode
와 versionName
에 대한 정보만 version.properties
라는 파일로 분리했다. 그리고 이러한 환경으로 구성되어 있다고 가정할 때 위의 주석처리 되어 있는 34번째 줄 안의 코드와 94~100번째 줄 안의 코드를 사용하면, versionCode
와 versionName
을 가져와서 env 환경 변수에 저장할 수 있고, 추후에 이 버전 이름을 사용해서 다른 추가적인 작업을 할 수 있다.
그리고 위의 Script에서 주의 깊게 봐야할 점은 47, 57, 70, 80번째 줄에서 사용하는 when에 대한 부분이다. Tag Push할 때 Tag 이름이 qa/버전명
, release/버전명
이렇게 Push 되었을 때 QA일 때와 Release일 때를 분기하는 부분이 47, 57, 70, 80번째 줄이다. 그럼 의문이 들 수도 있다. qa/123123
처럼 Tag Push해도 qa 빌드가 돌아가는 게 아닌가라는 의문이 들 수도 있다. 정답은 맞다. 하지만 Firebase App Distribution이나 Google Play Console에 Deploy될 때는 build.gradle
안에서 지정되는 versionCode
와 versionName
을 기준으로 사용되니 크게 신경쓰지 않아도 된다.(당연하겠지만, Tag 이름을 버전에 맞게 잘 작성하면 나중에 Commit History에서 보기 편하니까 막 짓지 않았으면 좋겠다..ㅎ) qa/~
혹은 release/~
뒤에 오는 버전 형식에 대해서 조금 엄격하게 가져간다고 하면, 48, 58, 71, 81번째 줄에서 *을 사용하는 것이 아니라 정규식을 활용할 수 있다. 이 부분은 구글에 좋은 레퍼런스가 많으니 심심풀이로 리서치 해보고 간단히 적용해보면 좋을 것 같다.
처음에는 1부와 2부로 끝낼 계획이었으나, 작성하다보니 내용이 길어져서 Firebase App Distribution으로 Deploy하는 본 게시글과, Google Play Console에 Deploy하는 3부로 마무리 지어야할 것 같다.