클릭 한 번으로 안드로이드 앱 배포시켜버리기

Team return·2024년 9월 14일
1
post-thumbnail

오늘은 자동 배포를 위한 CI/CD 파이프라인 구축에 대해 이야기를 해보겠습니다. 저의 많은 애정이 담겨 있는 작업이니 잘 읽어주세요~~

배경

배포하는 임무를 인수인계 받고 배포를 진행하려고 하니 너무 귀찮아졌습니다. 그래서 바로 자동 배포에 대해 알아보았습니다. (사실 iOS 배포되었다고 문자오는게 좀 부러웠습니다...ㅠ). 재미있는 경험이 될 거 같아 파이프라인을 구축하기로 했습니다.

아래는 수동 배포의 과정입니다.

  1. 배포할 내역들을 main 브렌치에 머지합니다.
  2. 릴리즈 모드로 .aab or .apk 버전을 생성합니다.
  3. 구글 플레이 스토어 콘솔로 들어갑니다.
  4. 배포할 앱을 선택합니다.
  5. 파일 업로드 및 변경사항을 적습니다.
  6. 검수 요청을 합니다.

환경

CI/CD 파이프라인 구축하는 환경을 알아본 결과 Github Action으로 하는게 가장 편할 거 같아 사용하게 되었습니다. Gradle Play Publisher이라는 앱 배포를 도와주는 라이브러리를 사용했습니다. Google Cloud Platform에서 프로젝트를 생성하여 계정을 만든 다음 Goole Play Console과 연결해서 배포되도록 하였습니다.

코드 뜯어보기

build.gradle(app)

android {
    signingConfigs {
        create("release") {
            storeFile = file("./keystore/jobis_v2_key.jks")
            storePassword = System.getenv("SIGNING_STORE_PASSWORD")
            keyAlias = System.getenv("SIGNING_KEY_ALIAS")
            keyPassword = System.getenv("SIGNING_KEY_PASSWORD")
        }
    }
    
    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro",
            )
        }
    }
}

signingConfigs 파일을 생성하여 배포 파일을 만들 수 있게 세팅합니다. 그리고 release로 빌드 할 때 signingConfig를 등록하여 앱 서명을 진행하게 합니다. release 빌드는 Github Action에서 자동으로 되게끔 구축했습니다.

play {
    serviceAccountCredentials.set(file("src/main/play/google-cloud-platform.json"))
    defaultToAppBundles.set(true)
    releaseStatus.set(ReleaseStatus.IN_PROGRESS)
    track.set("production")
}

tasks.register("release") {
    dependsOn(tasks["clean"])
    dependsOn(tasks["bundleRelease"])
    mustRunAfter(tasks["clean"])
}

Google Play Publisher 플러그인을 사용하여 Google Play Store에 배포할 때 사용하는 설정입니다.
google-cloud-platform.json에 있는 서비스 계정 인증 정보를 사용합니다.

defaultToAppBundles.set(true): App Bundle 형식으로 배포하게 됩니다.
App Bundle: Google Play에서 권장하는 배포 방식으로, APK보다 더 작은 크기의 설치 파일을 제공합니다.
releaseStatus.set(ReleaseStatus.IN_PROGRESS): 점진적으로 릴리즈하여 일정 비율로 배포됩니다.
track.set("production"): 실제 사용자에게 배포됩니다.

tasks.register("release"): ./gradlew release를 실행하면 태스크가 호출 됩니다.
dependsOn(tasks["clean"]): 기존 빌드 파일들을 삭제 시켜줍니다.
dependsOn(tasks["bundleRelease"]): 앱 번들을 빌드합니다.
mustRunAfter(tasks["clean"]): clean 태스크가 먼저 실행된 후에 release 태스크가 실행됩니다.

android-production-deploy-play-store.yml

- name: Create google-services.json
  env:
  	DATA: ${{ secrets.GOOGLE_SERVICES_JSON }}
  run: echo $DATA > /home/runner/work/JOBIS-ANDROID-V2/JOBIS-ANDROID-V2/app/google-services.json

- name: Create google-cloud-platform.json
  env:
  	DATA: ${{ secrets.GOOGLE_CLOUD_PLATFORM }}
  run: echo $DATA | base64 --decode > /home/runner/work/JOBIS-ANDROID-V2/JOBIS-ANDROID-V2/app/src/main/play/google-cloud-platform.json

파이어베이스에서 사용되는 google-services.json 파일과 배포할 때 사용되는 google-cloud-platform.json 파일을 가져오는 내용입니다. google-cloud-platform.jsonGoogle Cloud Platform에서 사용되는 api 정보를 담고 있고 base64로 디코딩하여 데이터를 가져옵니다.

- name: Create local.properties
  run: |
  	echo "BASE_URL_PROD=\"${{ secrets.BASE_URL_PROD }}\"" >> ${{ github.workspace }}/local.properties

local.properties파일에 BASE URL을 넣기 위해 깃허브 시크릿 파일에서 데이터를 가져오는 내용입니다.

- name: Create keystore directory
  run: mkdir -p ${{ github.workspace }}/app/keystore
        
- name: Decode Keystore
  run: |
  	echo "$KEYSTORE" > ${{ github.workspace }}/app/keystore/keystore.b64
    base64 -d -i ${{ github.workspace }}/app/keystore/keystore.b64 > ${{ github.workspace }}/app/keystore/jobis_v2_key.jks
  env:
  	KEYSTORE: ${{ secrets.APP_RELEASE_KEY_STORE }}

Github Action 가상 환경에서 keystore를 저장 할 수 있게 폴더를 만들어줍니다. base64로 디코딩한 keystore을 가져와 jobis_v2_key.jks 파일을 생성합니다. 이렇게 되면 배포 세팅이 끝나게 됩니다.

- name: Build Release And Publish AAB
  run: ./gradlew publishReleaseBundle
  env:
  	SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
    SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
    SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}

AAB형식으로 빌드를 한 후, Google Play Store에 자동으로 업로드하게 됩니다.
keystore에서 필요한 정보를 환경 변수로 설정된 GitHub Secrets에서 가져옵니다.

- name: Get version
  id: get_version
  run: |
  	echo "::set-output name=code::$(grep VERSION_CODE buildSrc/src/main/kotlin/ProjectProperties.kt | awk '{print $5}')"
  	echo "::set-output name=name::$(grep VERSION_NAME buildSrc/src/main/kotlin/ProjectProperties.kt | awk '{print $5}' | tr -d '"' )"

- name: Get tag name
  id: get_tag
  run: echo "::set-output name=name::v${{ steps.get_version.outputs.name }}"
          
- name: Generate Release
  uses: actions/create-release@latest
  env:
  	GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
  	release_name: "🚀 :: ${{ steps.get_tag.outputs.name }}"
  	tag_name: ${{ steps.get_tag.outputs.name }}
  	draft: false
  	prerelease: false

깃허브에서 릴리즈 태그를 자동으로 만들기 위해 추가하였습니다. 출시되는 앱 버전을 가져와 태그를 만들어 생성합니다.

- name: Read and format release notes
  id: read_release_note
  run: |
  	RELEASE_NOTE=$(cat ./app/src/main/play/release-notes/ko-KR/default.txt | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g')
  	echo "RELEASE_NOTE=$RELEASE_NOTE" >> $GITHUB_ENV

- name: Notify Slack on Success
  if: ${{ success() }}
  id: slack-success
  uses: slackapi/slack-github-action@v1.24.0
  with: 
  	payload: | 
    	{
          "channel": "${{ secrets.SLACK_DEPLOY_CHANNEL_ID }}",
          "attachments": 
          [
            {
              "color": "#36a64f", 
              "title": "${{ github.repository }}",
              "title_link": "https://github.com/${{github.repository}}",
              "text": "🚀 앱이 배포되었습니다.",
              "fields": 
              [
                {
                  "title": "Repository",
                  "value": "${{ github.repository }}",
                   "short": true
                },
                {
                  "title": "Tag",
                  "value": "${{ github.ref_name }}",
                  "short": true
                },
                {
                  "title": "Version",
                  "value": "${{ steps.get_tag.outputs.name }}",
                  "short": true
                },
                {
                  "title": "Release Note",
                  "value": "${{ env.RELEASE_NOTE }}",
                  "short": false
                }
              ]
            }
          ]
        }
	env: 
   	  SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

CD가 성공적으로 실행되었을 슬랙으로 알림이 가는 작업을 추가하였습니디. slack api를 사용하여 문자를 커스텀하여 보낼 수 있습니다. 배포에 어떤 내용이 추가되었는지 한눈에 알기 위해 릴리즈 노트도 같이 보내게 만들었습니다.

- name: Notify Slack on Failure
  if: ${{ failure() }}
  id: slack-failure
  uses: slackapi/slack-github-action@v1.24.0
  with: 
  	payload: | 
    	{
          "channel": "${{ secrets.SLACK_DEPLOY_CHANNEL_ID }}",
          "attachments": 
          [
            {
              "color": "#ff0000", 
              "title": "${{ github.repository }}",
              "title_link": "https://github.com/${{github.repository}}",
              "text": "💣 앱 배포에 실패했어요 ㅠㅠ",
              "fields": 
              [
                {
                  "title": "Repository",
                  "value": "${{ github.repository }}",
                   "short": true
                },
                {
                  "title": "Tag",
                  "value": "${{ github.ref_name }}",
                  "short": true
                },
                {
                  "title": "Version",
                  "value": "${{ steps.get_tag.outputs.name }}",
                  "short": true
                }
              ]
            }
          ]
        }
	env: 
   	  SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

CD가 실패하였을 때 슬랙으로 알림이 가게 됩니다.


아래는 깃 시크릿에 저장된 값들입니다.

느낀 점


이 작업을 하면서 기도를 정말 많이 했던거 같습니다. 일주일 동안 CI/CD만 돌리면서 돌아가는거 보고 많이 쫄렸었는데 결국 해낼 수 있어서 정말 좋았습니다. 이상한 오류도 많이 만나고 답답함도 있었지만 이런걸 해쳐나가니 더 성장할 수 있다는걸 느낄 수 있었습니다.

위 사진은 CD 고치고 rebase 받아서 실행하는거 반복하니 9780커밋이라는 살면서 처음보는 커밋수를 찍게 되었습니다. 커밋수가 너무 많아 pr은 결국 닫고 새로하였습니다...ㅠㅠ

추가 할 점

자비스 서비스가 커지면 내부에서 테스트를 돌릴 수 있도록 파이어베이스를 사용하여 내부 테스트 배포 자동화를 만들어보려고 합니다. 저의 로망이긴 하지만..ㅎㅎ

나는 결코 성공에 대해 꿈꾸지 않았다, 나는 꿈을 위해 행동했다. -에스티 로더

profile
Team return 기술 블로그입니다.

0개의 댓글