Programming/Github

Android + CI/CD 적용하기 - CD편 (feat.Github Actions)

YK Choi 2022. 3. 11. 00:32

CI 에서는 yml 파일을 어떻게 작성하는가에 시간을 많이 썼었다면

CD 에서는 보안파일을 어떻게 외부에 노출시키지 않고, 자동화를 적용하는지에 신경을 많이 썼다.

 

쿠링 레파지토리의 release 브랜치에 Push 또는 PR이 있을 때 이를 어디에 배포할지에 대한 고민을 첫 번째로 했었고 후보는

  • 플레이스토어에 바로 배포
  • AWS S3에 apk를 배포
  • firebase 등에 apk 배포

가 있었다. 플레이스토어에 바로 배포하는 방법도 존재하긴 하지만 바로 검토를 올리는것은 조심스러워서 일단 apk/aab를 받아보기로 했다. 플레이스토어에 출시노트나 다른 세세한 설정들을 만져야 할 때 직접 손으로 하는게 낫다고도 생각했다.

 

3번째인 firebase에 올리는것이 유력 후보였는데 Github Actions 내에서 또 다시 나를 도왔다. Artifacts라고 해서 github actions으로 만든 산출물을 파일로 저장해준다. 이로서 정말 A to Z를 깃헙액션에서 다 도움을 받았다.


나의 보안 파일은 무엇이 있나?

release 브랜치에 업로드 했을 때 필요한 파일 중

gitignore 로 인하여 깃허브에 보이지 않는 보안 파일은 Secret에 올렸다. 해당파일은

  1. google-services.json (배포용)
  2. signing keyStore (용도 : 플레이 콘솔)
  3. keyStore 관련 비밀번호들 (용도 : 플레이 콘솔)
    [keyStore 비밀번호, alias, alias 비밀번호]

이다. 이것을 외부에게는 감추면서, 동시에 나는 사용할 수 있어야한다.

Github > Settings > Security > Secrets > Actions 에 있다. 

 

step 1. 보안 파일 감추기 - 깃헙에서 해야할 일

1. google-services.json 파일은 json형식으로 그냥 올리면 된다.

(위 사진에서 GOOGLE_SERVICES_JSON_RELEASE)

2. keyStore.properties 는 로컬에서 gitignore에 의하여 세팅되어 있어 github에서는 보이지 않는다. 이 파일의 형식은  각각의 비밀번호가 XXXX라고 했을때 아래와 같다.

(위 사진에서 KEY_STORE_PROPERTIES)

storePassword=XXXX
keyPassword=XXXX
keyAlias=XXXX
storeFile=signing/ku_ring_keystore.jks

여기서 storeFile은 keyStore가 있는 파일의 위치이다.

위 파일의 각 줄들을 yml 파일에서 읽어 각각의 처리를 한다. (아래에서 다시 다룰 것이다)

3. keyStore는 조금 특별하다. 파일용량을 보면 3kb 인데 내용물이 binary 코드로 적혀있다. 따라서 메모장으로 이것을 열어보면 한자같은 이상한 글자들이 보인다. 이것을 그냥 올리면 github 원격 서버에서 빌드하는 과정에서 에러가 발생하고, base64 인코딩한 뒤 저장을 한뒤, 빌드할 때 다시 binary 로 디코딩 변환해야한다.

 

인코딩 방법:

    openssl base64 -in [keystore file path]  -out [base64 file path]
이 명령어를 리눅스명령어로 터미널에서 입력하면 된다.

(윈도우 컴퓨터라면 리눅스 명령어를 칠 수 있는 bash 를 사용하면 되는데 자세한건 여기서 생략하겠다.)
디코딩 방법:

    echo '${{ secretsYOUER_KEY_NAME }}' | base64 -d > xxxxxx.keystore

이것은 yml에서 사용될 명령어이다. 아래에서 다시 등장할 것이다.

 

step 2. 보안 파일 감추기 - 내 로컬 컴퓨터에서 해야할 일

1. keystore.properties 와 keyStore 가 있는 경로를 gitignore에서 추가한다. keystore.properties의 생김새는 이렇다

 

storePassword=XXXX
keyPassword=XXXX
keyAlias=XXXX
storeFile=signing/ku_ring_keystore.jks

2. build.gradle (module) 에서 keystore.properties 를 가져온다. 경로가 내 프로젝트에 잘 맞는지 잘 확인해야한다.

def keystorePropertiesFile = rootProject.file("app/signing/keystore.properties")

이런식으로 keystore.properties가 있는 파일 위치를 정의하고, 

android { } 내에서 signingConfig{ } 내에 release { } 를 만들고, 그 안에 storeFile, keyAlias, keyPassword, storePassword 값들을 각각 대입한다. 그러면 이런식이 될 것이다.

def keystorePropertiesFile = rootProject.file("app/signing/keystore.properties")

android {
    signingConfigs {
        release {
            def keystoreProperties = new Properties()
            keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

            storeFile file(keystoreProperties['storeFile'])
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storePassword keystoreProperties['storePassword']
        }
    }
}

그 뒤에 singinConfigs 값을 buildTypes { } 내의 release { } 내에 이런식으로 작성하여 사용할 수 있도록 한다.

signingConfig signingConfigs.release

 

step 3. Github Actions 의 workflow 작성

이제 준비는 끝났다. CI/CD를 만들어낼 yml 파일을 작성해보자.

release 브랜치를 하나 만들고, CI 편에서 했던 대로 github Actions 에서 work flow를 하나 더 만들어 작성한다.

 

name: Android-release CI/CD

on:
  push:
    branches: [ release ]
  pull_request:
    branches: [ release ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: set up JDK 11
      uses: actions/setup-java@v2
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
      
    - name: Create google-services
      run: |
        mkdir ./app/src/release
        echo '${{ secrets.GOOGLE_SERVICES_JSON_RELEASE }}' > ./app/src/release/google-services.json

    - name: Create KeyStore File and Properties
      run: |
        mkdir ./app/signing
        echo '${{ secrets.KU_RING_KEY_STORE_JKS }}' | base64 -d > ./app/signing/ku_ring_keystore.jks
        echo '${{ secrets.KEY_STORE_PROPERTIES }}' > ./app/signing/keystore.properties

    # Build APK Release
    - name: Build release Apk
      run: ./gradlew assembleRelease

    # Build AAB Release
    - name: Build release Bundle
      run: ./gradlew bundleRelease

    # Run unit test
    - name: Run unit test
      run: ./gradlew testreleaseUnitTest
      
    # Upload AAB
    - name: Upload a Build AAB Artifact
      uses: actions/upload-artifact@v3.0.0
      with:
        # Artifact name
        name: app-release.aab
        # A file, directory or wildcard pattern that describes what to upload
        path: ./app/build/outputs/bundle/release/app-release.aab
        retention-days: 14

    # Upload APK
    - name: Upload a Build APK Artifact
      uses: actions/upload-artifact@v3.0.0
      with:
        # Artifact name
        name: app-release.apk
        # A file, directory or wildcard pattern that describes what to upload
        path: ./app/build/outputs/apk/release/app-release.apk
        retention-days: 14

이 yml 파일에서 CI편과 다른 점은 'Create KeyStore File and Properties' 부터인데

원격 서버 위에 app/signing 폴더를 만들고 (폴더가 없는데 접근하려하면 에러가 난다.)

echo '${{ secrets.KU_RING_KEY_STORE_JKS }}' | base64 -d > ./app/signing/ku_ring_keystore.jks

를 통해 keyStore를 복호화하여 jks 파일을 만든다.

비슷한 맥락으로 keystore.properties가 필요하므로 

echo '${{ secrets.KEY_STORE_PROPERTIES }}' > ./app/signing/keystore.properties

를 통해 keystore.properties를 만든다.

 

이제 깃헙 원격 서버에서도 나의 로컬 컴퓨터와 같은 배포 환경을 가진 것이다.

 

더 아랫쪽을 보면 Upload a Build AAB Artifact가 있는데, 여기서 aab 파일을 생성한다. artifacts를 만드는 것은 uses: actions/upload-artifact@v3.0.0 을 사용하면 되는데, 이것은 다른 사람이 만든 오픈소스이다. 이것을 이용해서 만드는데, 결과물(aab)의 파일명은 무엇일지, 어떤 위치의 파일을 업로드할 것인지, 보존 기간은 얼마로 할것인지 등을 with 구문 내에서 설정하면 된다.

 


 

추가 - Github Actions 에서 추출한 aab가 잘 동작하는지 테스트하려면?

aab 는 플레이콘솔에 업로드할때는 사용될 수 있지만, 스마트폰에 넣었을 때 apk처럼 앱이 되지는 않는다. 따라서 이를 잘 동작하는지 테스트하기 위하여 apk로 변환했었던 기록을 작성한 것이다.

 

준비물 : [aab파일, cmd 창에서 java를 실행시킬 수 있는 환경, keyStore파일]

 

1. artifacts를 다운로드 받으면 app-release.aab.zip 파일이 생길 것이다. 이것을 압축을 풀어 app-release.aab로 만든다.

2. https://github.com/google/bundletool/releases 요기 사이트에 들어가서 jar 파일을 다운받는다.

2-2. 편의를 위하여 다운받은 jar 파일과 aab 파일과 keyStore파일을 한 폴더에 집어넣는다. 

3. cmd 창에서 아래의 명령어를 친다. 보기 편하기 위해 -- 문자열 마다 개행을 넣었는데, 개행 대신 그냥 띄어쓰기하면 된다. (자세한 사용법은 이 링크를 참고하자)

java -jar bundletool-all-1.9.0.jar build-apks 
--bundle=app-release.aab 
--output=app-release.apks 
--ks=ku_ring_keystore.jks 
--ks-pass=pass:XXXX
--ks-key-alias=XXXX 
--key-pass=pass:XXXX 
--mode=universal

3. app-release.apks 라는 파일이 생겼을 것이다. 왜 apks 이지? 라는 의문이 들었지만 나중에 다시 apk로 바뀌니 걱정안해도 된다.

4. 이 apks 파일의 확장자를 zip으로 바꾼다. (파일명의 확장자만을 바꾸는 것이다.)

5. 이것을 압축 해제하면 apk 가 생긴다.

 

도움된 사이트

https://stefma.medium.com/how-to-store-a-android-keystore-safely-on-github-actions-f0cef9413784

https://dnight.tistory.com/entry/Github-Actions-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98%EB%A1%9C-Keystore-%EC%A0%80%EC%9E%A5%ED%95%98%EC%97%AC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

Github Actions - 환경변수로 Keystore 저장하여 사용하기

터미널에서 Keystore 파일을 base64로 변경하여 파일로 저장합니다. openssl base64 -in [keystore file path] -out [base64 file path] 저장된 Base64의 파일내용을 복사하여 Github의 프로젝트 Settings -> Secr..

dnight.tistory.com

https://developer.android.com/studio/publish/app-signing?hl=ko#groovy 

 

앱 서명  |  Android 개발자  |  Android Developers

앱 서명 및 보안과 관련된 중요한 개념을 알아보고, Android 스튜디오를 사용하여 Google Play에 출시하기 위해 앱에 서명하는 방법과 Play 앱 서명을 선택하는 방법을 알아보세요.

developer.android.com

https://ajh322.tistory.com/289

 

안드로이드 aab 설치하는 방법

열심히 일을 하다보면 aab 파일에 문제가 없는지 궁금할때가 있다. 유니티 build and run을 사용하지 않고 aab 파일을 핸드폰에 설치하는 방법에 대해서 알아보자 쉬운 방법은 없다 그냥 아래 써있는

ajh322.tistory.com