오늘은 Android 앱에 대해서 Jenkins를 통해 CI/CD를 구축했던 기록을 남기려고 한다.
총 2개의 글로 나눠서 작성할 예정이며, 1부에서는 환경구축과 Push Event에 따라 Unit Test, Build를 자동화할 예정이고, 2부에서는 Tag Push 했을 때 Tag를 파싱해서 Firebase App Distribution으로 QA 빌드를 Deploy하거나, Google Play Console의 내부테스트로 Deploy하도록 구축할 예정이다.
이번 글에서는 Android 프로젝트를 위한 Jenkins 설치 및 Github에서 Push Event가 발생했을 때 UnitTest, Build를 진행하고 성공, 실패 여부를 Github에 표시하는 것까지 기록하고 다음 글에서 Deploy에 대한 주제를 다룰 예정이다.
먼저, Jenkins를 설치할 때 크게 두 가지 방법이 있다. docker를 이용한 설치, 그냥 순수 설치. Jenkins를 공부 목적에서 적용하려 했고 어떠한 설정이 필요한지에 대해서 공부하고 싶었기 때문에 docker를 이용하지 않고 설치했었다. 그리고 Jenkins CI/CD를 적용하기에 앞서서 적용하려는 Android 프로젝트에 사전 작업이 조금 필요하다. 필자는 개인 포트폴리오 겸 공부 목적을 위해 구글 플레이에 출시했던 앱이 있었는데, 이 앱에 Jenkins CI/CD를 적용했던 거라서 적용하려는 프로젝트에 사전 작업이 되어 있었다. 이 사전 작업은 Build Variant에 대한 설정, SiginigConfigs에 대한 설정 그리고 구글 플레이에 출시 중인 Android 프로젝트여야 한다. 해당 설정은 여기를 참고하면 좋을 것 같다.
(사전 작업이 설정되지 않아도 jenkins는 설치하고 적용하는데는 문제 없긴 하지만 더 실무와 적합하게 공부하기 위해서 적용해보는 것을 추천한다..!)
Android를 위한 Jenkins 환경 구축하기
환경 구축 순서는 아래와 같다. (docker를 사용하면 대부분의 환경 설정이 끝난 상태에서 사용하기 때문에 간편하긴 하다..)
1. Java 설치
이 서버에 Java 설치하고 환경변수 설정하는 작업은 많은 다른 블로그에 더 자세하게 소개되어 있으니 본 게시글에서는 다루지 않을 예정이다.
2. Git 설치
$ yum install git
위의 명령어를 통해 git을 다운로드 받는다.
3. Jenkins 설치 및 Config 파일 수정
$ wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
$ rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
$ yum install jenkins
위의 명령어를 순서대로 입력 후 Jenkins를 설치한다. 그리고 아래의 명령어를 입력해서 Jenkins config 파일을 연다.
$ vi /etc/sysconfig/jenkins
그리고 중간에 있는 JENKINS_PORT
, JENKINS_HOME
과 같이 Jenkins 웹 페이지에 대한 포트 번호, Jenkins의 홈 디렉토리 경로를 수정할 수 있다. 홈 디렉토리를 수정하지 않으면 /var/lib/jenkins
로 설정된다. 추가적으로 Jenkins 계정은 쉘 권한이 없으므로 오로지 Jenkins 웹 페이지에서만 사용할 수 있는 계정이다. 이제 아래의 명령어를 통해 Jenkins를 시작해보자
$ systemctl start jenkins.service
그리고 설정파일에서 포트를 변경했다고 하면 firewall이나 SELinux에서 포트가 허용되었는지 확인하고, 집에서 서버를 운영 중이라고 하면 포트 포워딩까지 설정해준다. 그 다음 브라우저에서 서버로 접근하면 아래의 초기 페이지가 보일 것이다.
위의 사진 속 경로를 따라 들어가보면 초기 비밀번호가 있는 것을 볼 수 있다. 아래의 명령어를 통해서 해당 파일 안의 암호를 복사해서 Administrator password
입력창에 넣고 다음으로 넘어간다.
$ cat {위에 사진 중 빨간 글씨의 initialAdminPassword 경로}
그 다음 기억에 Admin 계정 설정한 후 플러그인 권장 설치하거나 커스텀으로 설치하는 선택지가 있었던 걸로 기억나고 권장 설치를 해주었다.
이렇게 설치 완료하면 아래의 사진처럼 Jenkins 메인 화면이 뜰 것이다.
4. Android-sdk 설치
$ cd {Jenkins 홈 디렉토리 경로, 따로 설정하지 않았으면 /var/lib/jenkins 입력}
$ mkdir android-sdk
$ cd android-sdk
$ wget https://dl.google.com/android/repository/commandlinetools-linux-8092744_latest.zip
$ unzip commandlinetools-linux-8092744_latest.zip
위와 같이 Command line tools only
로 sdkmanager를 다운받는다. (최신 버전은 공식 다운로드 홈페이지 맨 하단에서 확인할 수 있다.)
그리고 아래와 같이 sdkmanager에 대해서 절대경로를 맞춰준다.
$ {Jenkins 홈 디렉토리 경로, 따로 설정 안 했으면 /var/lib/jenkins}/android-sdk/cmdline-tools/latest/bin/sdkmanager
아마 경로 맞춰주는 작업을 해보면 알 수 있겠지만 unzip으로 압축을 풀면 cmdline-tools/bin/sdkmanager
경로로 지정된다. 그래서 아마 sdkmanger가 Could not determine SDK root.
이러한 에러 메세지를 뱉으면서 정상작동하지 않을 것이다. 이러한 이슈가 발생한 것은 sdkmanager의 새로운 업데이트 이후에 문서에 누락된 내용이 있었다고 하는데, 압축 해제 후 생성되는 최상위 폴더가 변경됨에 따라 경로 이슈가 발생하는 것 같다. 자세한 내용은 여기를 참고하면 좋을 것 같다.
그리고 sdkmanager가 있는 디렉토리로 이동하고 아래의 명령어를 통해 라이센스 동의와 Android SDK를 다운받는다.
$ yes | sdkmanager --licenses
$ sdkmanager "build-tools;30.0.1"
위의 첫번째 줄은 라이센스에 대해서 동의한 내용이고, 두번째 줄이 SDK 빌드 툴을 다운받은 것이다. 필자는 Android 프로젝트에서 build.gradle(:app)
파일 안에 있는 buildToolsVersion
값과 동일한 버전(30.0.1)으로 다운 받았다. 위와 같이 설치했다고 하면 /var/lib/jenkins/android-sdk
디렉토리 안에 이것저것 많이 생겼을 것이다. 그리고 build-tools
디렉토리로 이동하면 다운로드한 빌드 툴 버전 이름의 디렉토리가 보일 것이다. 그리고 아래의 명령어를 통해 마지막으로 jenkins 계정으로 소유권을 변경해야 한다.
$ cd {Jenkins 홈 디렉토리 경로, 따로 설정 안 하면 /var/lib/jenkins}
$ chown -R jenkins:jenkins ./android-sdk
5. Jenkins의 시스템 설정 (Android-sdk 경로 설정)
다시 Jenkins 웹 페이지로 이동해서 좌측 메뉴 중 Jenkins 관리 > 시스템 설정으로 이동한다. 그리고 아래의 사진처럼 Android SDK에 대한 경로를 지정해준다.
위의 값 입력하는 부분은 4번 작업 중에서 android-sdk 디렉토리의 경로를 입력해주면 된다. Jenkins 홈 디렉토리를 변경하지 않았으면 /var/lib/jenkins/android-sdk
를 입력하면 된다. 그리고 저장한다.
6. Jenkins의 Global Tool Configuration 설정 (Java, Git 경로 설정)
다시 Jenkins 메인 홈에서 좌측 Jenkins 관리
> Global Tool Configuration
으로 이동한다. 그리고 아래의 사진과 같이 Java와 Git에 대한 경로를 설정해준다.
위의 JAVA_HOME
입력창에는 자바 설치할 때 환경변수 $JAVA_HOME
의 경로(bin 디렉토리 포함 x)를 입력해준다. 그리고 특별히 Git에 대한 경로를 변경하지 않았으면 /usr/bin/git
의 경로가 맞을 것이다. 하지만 다를 수도 있으니 한번 체크해보면 좋을 것 같다. 그리고 저장한다.
7. Jenkins의 플러그인 설치
Jenkins 메인 화면에서 좌측 Jenkins 관리
> 플러그인 관리
로 이동한다. 그리고 설치 가능 탭으로 이동하여 아래의 플러그인을 설치해준다.
Github Custom Notification Context SCM Behavior
Blue Ocean
Basic Branch Build Strategies Plugin
8. CI/CD를 적용하려는 Github Repository에 대해서 접근 권한이 있는 계정의 Personal Access Token 발급
CI/CD를 적용하려는 Github Repository에 대해서 접근 권한이 있는 계정에 로그인한다. 그리고 https://github.com/settings/tokens
를 복사해서 이동한다. 그리고 우측 상단의 Generate new token 버튼을 클릭한다. 그 다음 Expiration
을 No expiration
으로 설정한다. 그리고 하단 권한 중에 repo에 대한 전체 권한을 체크한 후 Generate Token
버튼을 통해 생성한다. 그리고 초록색 배경으로 표시되는 token 문자열을 9번 작업에서 사용할 것이기 때문에 메모장에 복사 붙여넣기 해서 잠시 갖고 있는다.
9. Jenkins에서 Multibranch Pipeline 생성 후 레포 등록 및 Certification 등록
Jenkins 메인 화면 좌측에서 새로운 item을 클릭하면 아래의 사진과 같은 화면이 나오는데, 여기서 상단에 원하는 Pipieline의 제목을 입력하고 Multibranch Pipeline
을 선택한 후 OK 버튼을 누른다.
그리고 아래와 같이 Branch Sources
가 뜨도록 Add sources
에서 Github
를 선택해준다.
위의 사진의 Repository HTTPS URL 부분에 CI/CD를 적용하려는 Github Repository를 입력해준다. 그리고 Credentials
의 열쇠모양 Add
버튼을 클릭해주고 Jenkins
를 선택한다. 그리고 아래의 사진처럼 값을 채워준다.
위의 Kind
와 Scope
은 똑같이 설정해주면 되고, Username
에는 Github 계정 아이디, Password
에는 8번 작업에서 메모장에 임시 저장했던 Personal Access Token을 입력해준다. ID는 지금 만들고 있는 Credential을 구분해주는 고유 ID이라서 간단하게 {프로젝트 이름}-ci 이렇게 입력해준다. (다르게 해도 상관 X) 그리고 Description에는 해당 Credential에 대한 설명을 기입해준다. XXX 이렇게 적혀있는 곳을 프로젝트 이름으로 간단하게 입력해줘도 괜찮을 것 같다. 그리고 Add 버튼을 통해 생성해주고 아래의 화면과 같이 우측 끝에 Validate를 클릭해주면 연동되었는지 확인할 수 있다.
추가적으로 설명을 덧붙이면 Multibranch Pipeline에서는 아까 Credential 생성할 때 지정했던 Kind 중에 Username and password
만 지원한다고 한다. 다른 종류의 Jenkins는 다른 인증 방식을 지원하지 않는 GitHub API를 사용하기 때문이라고 한다.
(원본: Note that only "username with password" credentials are supported. Existing credentials of other kinds will be filtered out. This is because Jenkins uses the GitHub API, which does not support other ways of authentication.)
이제 만들고 있던 Pipeline에 설정을 해주어야 한다. 그리고 아래의 화면과 같이 Behavior를 설정해준다.
Add 버튼과 우측의 X 버튼을 적당히 활용해서 위의 사진처럼 지정해준다. 위의 내용에 대해서 간략하게 설명하면,
Discover branches
Custom Github Notification Context
우측 상단에 초록색 체크 표시가 있는데 Jenkins에서 빌드가 성공적으로 완료되면 해당 아이콘으로 표시되고, 진행 중이면 노란색 동그라미, 실패하면 빨간색 X로 표시가된다. 그리고 이 아이콘을 클릭하면 위의 사진처럼 팝업창이 뜨면서 Label의 내용이 표시된다. 그리고 다음으로 아래와 같이 설정한다. 아래의 사진은 특정 branch를 PR했을 때 사진이다. PR되면 자동으로 Jenkins에서 이를 트리거하고 Pipeline script에 따라 모든 stage를 성공적으로 수행하면 아래의 사진 중 Merge pull request
버튼이 초록색으로 변경된다.
그리고 이어서 아래와 같이 나머지 설정을 진행한다.
Script path는 CI/CD를 적용할 프로젝트 안에 Script 파일의 위치를 설정하면 된다. 일단 위와 같이 동일하게 만들고 11번 작업에서 같은 경로에 Script 파일을 만들어주면 된다. (변경하고 싶으면 변경해도 무관.) 그리고 Save 버튼을 클릭해서 Pipeline을 생성해준다.
10. github repo에서 webhook 연결
이번에 해줄 작업은 Github Repository에서 특정 이벤트가 발생했을 때 지정한 웹으로 이벤트를 전달하도록 설정해주는 작업이다. CI/CD를 적용하려는 Github Repository에 들어간 후 Setting
> 좌측 메뉴 중 Webhooks
> 우측 상단의 Add webhook
을 클릭한다. 그리고 아래의 화면처럼 입력해준다.
그리고 그 다음에 Let me select individual events.
를 체크해주고 아래의 사진 안에 체크되어 있는 Event
만 체크해서 Add webhook
버튼 통해 생성한다.
그리고 생성된 webhook으로 들어가서 Recent Deliveries 탭을 클릭한 후 정상적으로 연동되는지 확인해본다. 혹여 실패했는데 Response 옆에 302 에러 코드를 뱉고 있다면 아까 위의 사진에서 Payload URL 설정할 때 맨 뒤에 / 슬래시가 빠지지 않았는지 확인해본다. (필자는 이것 때문에 삽질을..) 그리고 추가적으로 403 같은 에러를 뱉고 있다고 한다면, Jenkins 메인 홈 > 좌측 Jenkins 관리
> Manage Users
> 계정
클릭 > 좌측의 설정
클릭 > Add new token
버튼을 통해 Token을 발급 받고 이 Token을 Payload URL 아래쪽에 Secret
에 넣고 Update webhook
해본다.
11. 프로젝트에 jenkinsFile 작성
이제 마지막으로 Script를 작성해줄 것이다. Github Repository에 올라가 있는 Android 프로젝트를 clone하고 아래의 사진처럼 최상위 디렉토리에 ci라는 디렉토리를 만들어 주고 그 안에 jenkinsFileForFeature
이라는 TEXT 파일을 만들어 준다.
위의 사진에 보이는 jenkinsFileForDeploy
는 추후 Jenkins에서 QA Build를 Firebase App Distribution으로 Deploy하거나 Production Build를 Google Play Console의 내부테스트로 Deploy하도록(Regression Test를 위해) 구축하는 글을 포스팅할 때 사용할 예정이다.
그리고 아래의 Script를 입력해준다.
pipeline {
agent any
options {
skipStagesAfterUnstable()
}
tools {
jdk("JAVA8")
}
environment {
// Build Variant에 대해서 설정했었다면 어떤 Variant로 Build할지 선택하기.
BUILD_VARIANT = "Debug" //"DevAlpha"
}
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("Unit Test") {
steps {
sh "./gradlew test${env.BUILD_VARIANT}UnitTest --stacktrace"
junit "**/TEST-*.xml"
}
}
stage("Assemble") {
steps {
sh "./gradlew assemble${env.BUILD_VARIANT} --stacktrace"
archiveArtifacts artifacts: "**/*.apk, **/mapping.txt", fingerprint: true
}
}
}
}
위의 내용 중 9번째 줄에 JAVA8
문자열은 6번 JDK 설정 과정에서 입력했던 JDK 이름이다. 그리고 중간에 주석 처리된 부분이 있다. 이 부분에 대해서 간단하게 요약하면, 보통 Android 앱을 배포할 때 Signing을 해야 한다. 이 Signing을 위해서 jks 파일이 있어야 하고 이 파일에 대한 암호가 있어야 한다. 보통 jks 파일은 프로젝트와 같이 Github Repository에 push 하지만, 이에 대한 암호는 push 하지 않는다. 따로 파일로 만들어서 로컬에 저장하고 .gitignore로 무시하도록 설정해서 로컬에서만 저장하고 있는 형태다. 흔히 로컬 컴퓨터의 Android Studio에서 빌드할 때 사용된다. 그러나 Jenkins 입장에서는 이 암호가 없으니 빌드를 할 수 없다. 이러한 문제점을 해결할 수 있는 법은 다음과 같다.
이 jks에 대한 암호를 Jenkins 홈에서 좌측 메뉴 Jenkins 관리
> Manage Credential
에서 Credential을 Scret Text
형태로 추가해서 jks에 대한 암호를 저장하고, 이 생성한 Credential의 ID를 통해 가져와서 위의 Script의 내용처럼 env에 저장한다. 그리고 Android 프로젝트에서는 jks에 대한 암호 파일이 없으면 env에서 jks 파일 암호를 찾도록 구성하고 그래도 없으면 에러를 뱉도록 구축하면 된다.
하지만 공부목적에서 만든 프로젝트고 Build Variant나 SigningConfigs에 대한 설정이 안 되어 있다고 하면, 위의 Script처럼 주석처리하고 저장하면 우선 사용할 수 있다..!
이제 환경에 대한 설정은 모두 끝났으니 방금 작성한 Script를 Commit하고 Push하면 Jenkins 페이지에서 빌드 돌아가는 모습을 볼 수 있을 것이다. 그러나 필자는 이렇게 설정한 후 Push 했을 때, Jenkins가 빌드를 시도하긴 했으나 gradlew 파일에 대해서 Permission Denied 에러를 만났었다. 이 에러를 만나는 사람도 있을 거고 아닌 사람도 있을 것이다. 왜냐하면 이 문제는 Android 프로젝트를 WindowOS에서 생성한 사람들에 한에서 발생하기 때문이다. WindowOS에서 Android Studio를 통해 프로젝트를 만들면 기본적으로 gradlew 파일이 644 권한을 갖게된다. 그래서 이 부분을 755로 수정해주어야 한다. 해당 Android 프로젝트에서 git을 실행하고 아래의 명령어를 입력하고 Commit & Push 하면 정상적으로 빌드가 돌아갈 것이다.
$ git update-index --chmod=+x gradlew
이렇게 Jenkins 설치와 Github 연동해서 Push Event에 대해 Unit Test, Build 여부를 체크해주도록 공부해보았다. 이후에는 특정 Tag를 Push 했을 때 Jenkins가 이를 파싱해서 Firebase App Distribution이나 Google Play Console의 내부테스트로 자동 Deploy 하도록 구성해볼 것이다. Google Play에 Deploy할 때는 출시 노트도 같이 언어별로 전달할 수도 있다.
이번 글에서는 환경구축과 Pipeline 생성까지 한번에 했으므로 양이 많았던 것 같다. (docker image 사용하면 편했을 것 같은데 나름 값진 공부였다..ㅎ)