프로그래밍/Android

[Bitrise Cli] 리눅스 서버에서 CI/CD pipeline 직접 구축해보기 (1)

Lou Park 2022. 7. 27. 22:32
CI/CD pipeline 직접 구축해보기 (1편, 현재 포스트) https://jizard.tistory.com/405
CI/CD pipeline 직접 구축해보기 (2편) https://jizard.tistory.com/410

 

CI/CD가 왜 필요할까?

일하다보면 빌드 시간이 너무 길게 느껴진다. QA할 버전좀 올려주세요~ 개발서버버전 올려주세요~ 그와 동시에 배포도하고... 프로가드를 사용중인 프로젝트에서 내 컴퓨터로 빌드를하면 10분정도가 걸릴때도 있었다. 무엇보다 개발 열심히 하고있는데 흐름이 끊기는게 가장 큰 문제점이다. 

 

CI/CD는 마치 UI/UX처럼 뜻은 다르지만 따라다니는 친구들인데, 앱 개발부터 배포까지 자동화하는 방법이다.  CI는 지속적 통합(Continuos Integration)로, 코드 변경사항이 정기적으로 빌드 & 테스트 되어 통합되어 코드 충돌문제를 줄일 수 있는 방법이다.  CD는 지속적 배포(Continuous Delivery/Deplyment)를 의미하며 개발자들이 적용한 코드 변경사항이 테스트를 거쳐 프로덕션 환경으로 자동으로 배포되는 것이다. 그러니까...CI/CD가 같이 붙는 이유는 CD가 되려면 CI부터 되야하기 때문이다. 안드로이드를 예로 들자면 Git Flow로 branch 관리를 하면서, release 빌드를 원할때 release repository로 push하면 그것이 trigger가 되어서 단위 테스트를 거쳐 문제가 없을 경우 빌드를 Google Play에 배포하는 과정을 말한다.  

 

Why Bitrise?

보통 CI/CD 툴로 Jenkins나 Git Actions를 많이 사용하는데 Bitrise를 사용하게된 이유는 Firebase App Distribution 배포나 구글 플레이 배포 등 Git Actions보다 기본으로 지원하는 step들이 많고, Jenkins보다 친숙한 UI를 가지고 있기때문이다. (최근엔 이뻐졌나?) yaml 방식으로 설정을 관리하는 것도 간편해서 아직까지 큰 불만은 없다. 

 

일전에 내 포스팅에서도 볼 수 있듯이 Bitrise 웹에서도 직접할 수는 있지만, credit이 닳기도하고 또 거기서 빌려주는 가상 서버의 사양이 그닥 좋지 않기때문에 회사에서 공부하라고 제공한 서버에 Bitrlise Cli를 설치해보기로 했다. Bitrise 기본제공 서버에서 빌드하면 10분 정도가걸리는데, 회사 서버에서는 4분도 안걸린다 ㅎㅎ...

 

Gettings Started

B I T R I S E

Android SDK 설치

우선 안드로이드 SDK를 설치해야하는데, commandline 전용으로 받아야한다. 압축을 풀고 /usr/lib/android-sdk/latest 아래에 내용이 들어가있으면 된다.

# Install android cli
wget https://dl.google.com/android/repository/commandlinetools-linux-8512546_latest.zip

unzip commandlinetools-linux-8512546_latest.zip
rm commandlinetools-linux-8512546_latest.zip

sudo mv cmdline-tools/ /usr/lib/android-sdk/
cd /usr/lib/android-sdk/cmdline-tools

mkdir latest
mv !(latest) latest

 

다음으로는 sdkmanager를 편하게 사용하기 위해서 환경변수를 등록해준다.

vi .bashrc
export ANDROID_SDK_ROOT=/usr/lib/android-sdk
export ANDROID_HOME=/usr/lib/android-sdk
export PATH="/usr/lib/android-sdk/cmdline-tools/latest/bin:$PATH"
source .bashrc

등록했다면 platform-tools를 설치하자.

sudo sdkmanager "platform-tools" "platforms;android-31"

 

Java 설치

Java 11를 설치후, JAVA_HOME 역시 환경변수로 등록해준다.

sudo apt-get install openjdk-11-jdk 

export JAVA_HOME="/usr/lib/jvm/java-11-openjdk-amd64"

 

Bitrise Cli 설치

/usr/local내의 권한을 획득하고, Bitrise Cli를 다운로드 후 /usr/local/bin/bitrise에 실행 권한을 부여한다.

최신 버전의 파일 URL은 여기에서 확인하면된다.

sudo chown -R $(whoami) /usr/local
curl -fL https://github.com/bitrise-io/bitrise/releases/download/1.49.0/bitrise-"$(uname -s)"-"$(uname -m)" > /usr/local/bin/bitrise
chmod u+x /usr/local/bin/bitrise

언제나처럼 설치를 끝내면 갸루 피스~ -v

bitrise -v
1.49.0

bitrise setup을 하면 워크플로우를 위해 필요한 툴들을 설치한다. 

bitrise setup

 

SSH Keyfile 생성

새로운 서버라면 bitrise에서 저장소 내용을 다운받고 기타 등등할때 필요한 SSH Keyfile이 필요할 것이다. 여기에 passphrase는 설정할 수 없음을 주의하시라아...

ssh-keygen -t rsa -b 4096 -P '' -f ./bitrise-ssh -m PEM
mv bitrise-ssh* ~/.ssh

# optional. 기본으로 사용하려고 링크를 걸어뒀다.
ln -s bitrise-ssh id_rsa
ln -s bitrise-ssh.pub id_rsa.pub

다 만들었다면 cat bitrise-ssh.pub를 하여 내용을 Github 프로필 SSH Keys나 repository > Deploy Keys에 추가하자. 

 

Bitrise의  activate-ssh-key 스텝을 사용하기 위해서는 SSH_RSA_PRIVATE_KEY 환경변수가 필요한데, 이렇게 추가할 수 있다. 

# bitrise envman init으로 환경변수를 관리하는 .envstore.yml를 초기화시킨다.
bitrise envman init

bitrise envman add --key SSH_RSA_PRIVATE_KEY --value "$(cat ~/.ssh/bitrise-ssh)" --sensitive

 

Bitrise 환경변수 설정

이제 2갈래길로 나뉘어지는데, Bitrise Cli용 앱을 따로 절차를 따라 만들 수도 있고, (이건 Bitrise 웹에서 create new app with CLI 버튼 누르고 하라는 대로 하면됨) 기존에 yaml 파일이있으면 복사해서 수동으로 설정할 수도 있다. 나는 후자를 택했는데 이미 웹으로 쓰고있어서 yaml 파일이 준비가 되어있었기 때문에... 환경변수만 설정해주면 되었다. 

 

위에서 본 것처럼, bitrise envman을 이용해 환경변수를 설정할 수 있다. 사용법은 아래와 같다. 

# 민감한 환경변수에는 sensitive 옵션이 들어간다.
bitrise envman add --key KEY --value value --sensitive


# 실행할때는 해당 env variable이 적용된 상태로 커맨드가 실행된다.
bitrise envman run <COMMAND>
# 예시는 gn_release_apk라는 workflow를 실행하는 방법
bitrise envman run bitrise run gn_release_apk

 

그밖에 본인이 필요한 환경변수들을 모두 선언해주면된다. 예시니까 1234로 전부 값을 치환해두었다. 명령어가 귀찮으면 vim으로 .envstore.yml을 직접 수정하면된다.

bitrise envman add --key GIT_REPOSITORY_URL --value "your git repository url"
bitrise envman add --key BITRISE_SOURCE_DIR --value "$(pwd)/bitrise/src"

bitrise envman add --key FIREBASE_CI_TOKEN --value 1234 --sensitive
bitrise envman add --key SLACK_WEBHOOK --value 1234 --sensitive
bitrise envman add --key INTERNAL_FIREBASE_CI_TOKEN --value 1234 --sensitive
bitrise envman add --key GIT_USERNAME --value 1234 --sensitive
bitrise envman add --key GIT_TOKEN --value 1234 --sensitive

bitrise envman add --key BITRISEIO_ANDROID_KEYSTORE_URL --value 1234 --sensitive
bitrise envman add --key BITRISEIO_ANDROID_KEYSTORE_PASSWORD --value 1234 --sensitive
bitrise envman add --key BITRISEIO_ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD --value 1234 --sensitive
bitrise envman add --key BITRISEIO_ANDROID_KEYSTORE_ALIAS --value 1234 --sensitive

 

Debug.keystore

디버그 빌드시 등록되지 않은 Keystore로 등록하기때문에 SNS로그인에 키해시 등록이 귀찮을 수 있으므로, 사용자 컴퓨터의 /Users/USER/.android내의 debug.keystore를 복사해오는걸 추천한다. 그 밖에, release 키스토어 파일도 서버 어딘가로 옮겨두시길!

scp debug.keystore remote@remote-hots:~/.android

 

프로젝트 파일 Clone

앞서 BITRISE_SOURCE_DIR 환경 설정에서 bitrise/src내에 프로젝트 폴더가 들어간다고 했으니 폴더를 만들어준다. 폴더안에서 git clone을 하여 프로젝트 폴더를 가져오면된다. 사실 이 과정을 자동화하는게 맞긴한데,, 매번 clone하는 step 뿐이라서 그냥 script로 처리했다. (혹시 아시는 분은 댓글로 도움주세요~!!)

mkdir -p bitrise/src

 

bitrise.yaml

Bitrise 역시 yaml파일에 모든 설정이 들어가있는데, 예시는 내가 쓰고있는 설정 파일의 일부이다. workflow이름은 gn_apptester라는것이고, $BRANCH로 설정된 branch 내용을 땡겨서 $VARIANT로 빌드를 한 뒤 Firebase App Distribution에 배포하고 결과를 Slack 채널에 공유하는 예시이다. 

---
format_version: '11'
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
project_type: android
workflows:
  gn_apptester:
    steps:
    - activate-ssh-key@4:
        run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
    - script@1:
        inputs:
        - content: |-
            #!/usr/bin/env bash
            ########################################
            # local.properties 생성
            ########################################
            cd ~/bitrise/src/1234
            git fetch --all
            git checkout $BRANCH
            git pull origin $BRANCH -f
            echo 'Generating local.properties ...'

            touch .local.properties

            cat > "./local.properties" <<- FILE_CONTENT
            sdk.dir=$ANDROID_SDK_PATH
            keyStore.path=$BITRISEIO_ANDROID_KEYSTORE_URL
            keyStore.storePassword=$BITRISEIO_ANDROID_KEYSTORE_PASSWORD
            keyStore.keyAlias=$BITRISEIO_ANDROID_KEYSTORE_ALIAS
            keyStore.keyPassword=$BITRISEIO_ANDROID_KEYSTORE_PRIVATE_KEY_PASSWORD
            FILE_CONTENT

            content=$(<local.properties)
            echo "$content"


            ########################################
            # release note와 test note 저장
            ########################################
            test_note=$(<test_note.txt)
            release_note=$(<release_note.txt)

            envman add --key MY_TEST_NOTE --value "$test_note"
            envman add --key MY_RELEASE_NOTE --value "$release_note"

            echo "==== MY TEST NOTE ===="
            echo "$test_note"
            echo "==== MY RELEASE NOTE ===="
            echo "$release_note"
    - android-build@1:
        inputs:
        - module: "$MODULE"
        - build_type: apk
        - variant: "$VARIANT"
        is_always_run: true
    - firebase-app-distribution@0:
        inputs:
        - firebase_token: "$FIREBASE_CI_TOKEN"
        - service_credentials_file: "~/firebase_service_account.json"
        - groups: gna
        - is_debug: 'true'
        - release_notes_file: ''
        - release_notes: "$MY_TEST_NOTE"
        - app: 1234
    - slack@3:
        inputs:
        - channel: "$SLACK_PLANNING"
        - emoji: ":hatching_chick:"
        - text: ''
        - pretext: |-
            QA용 AppTester버전이 등록되었습니다. ($VARIANT)
            *[테스트 노트]*
            $MY_TEST_NOTE
        - author_name: ''
        - footer: Playio QA $BITRISE_TRIGGERED_WORKFLOW_ID
        - fields: git_branch|${BITRISE_GIT_BRANCH}
        - buttons: |-
            빌드상세|${BITRISE_BUILD_URL}
            설치하기|1234
        - webhook_url_on_error: "$SLACK_WEBHOOK"
        - channel_on_error: "$SLACK_APPDEPLOY"
        - text_on_error: 빌드실패..!
        - pretext_on_error: "$BITRISE_BUILD_URL"
        - from_username: Username
        - color: "#FDD835"
        - webhook_url: "$SLACK_WEBHOOK"
app:
  envs:
  - opts:
      is_expand: false
    PROJECT_LOCATION: "~/bitrise/src/1234"
  - opts:
      is_expand: false
    MODULE: app
  - opts:
      is_expand: false
    SLACK_PLANNING: 1234
  - opts:
      is_expand: false
    SLACK_GENERAL: 1234
  - opts:
      is_expand: false
    SLACK_APPDEPLOY: 1234

 

실행 ShellScript 작성

자동화가 목표지만 trigger가 작동하는지 테스트를 못했기 때문에 반자동이다. 내가 원하는 걸 실행할 수 있도록 run.sh 를 만들어주었다. 

#!/bin/bash

if [[ "$1" != "" ]]; then
        WORKFLOW="$1"
else
        WORKFLOW="gn_apptester"
fi


if [[ "$2" != "" ]]; then
        VARIANT="$2"
else
        VARIANT="productionRelease"
fi

if [[ "$3" != "" ]]; then
        BRANCH="$3"
else
        BRANCH="apptester"
fi


echo "** START WORKFLOW: $WORKFLOW **"
echo "** SELECTED VARIANT: $VARIANT **"
echo "** SELECTED BRANCH: $BRANCH **"

sudo bitrise envman add --key VARIANT --value $VARIANT
sudo bitrise envman add --key BRANCH --value $BRANCH
# CI를 해야 activate-ssh-key 스텝이 실행됨
sudo bitrise envman run bitrise run --ci $WORKFLOW

이렇게 해두면 내가 원하는 빌드를 골라서 수행시킬 수도 있다. 

# workflow = apptester
# variant = productionDebug
# branch = feature/global
./run.sh gn_apptester productionDebug feature/global

 

Next Step

다음은 자동화다. 아직은 Bitrise 웹에서 사용하는 것 처럼 완전한 자동화가 되어있지 않다. 다음 포스팅에서 Github Webhook을 이용해 자동화를 시켜볼 것이다!

 

 

참고자료

https://www.redhat.com/ko/topics/devops/what-is-ci-cd