Android GitHub Actions CI/CD 가이드

rivermoon·2025년 3월 31일
post-thumbnail

✨ Introduction

여러 프로젝트에서 지속적 통합 및 지속적 배포는 필수가 되었어요.
빌드 시간이 길고, QA, PO, 디자이너까지 다양한 인원이 앱의 최신 버전을 빠르게 확인해야 하는 환경에서는 특히나 자동화가 중요하다고 생각해요.
이번 아티클은 Android를 기준으로 GitHub Actions CI/CD의 개념부터 실전 적용, 고급 설정, 배포, 알림까지 모든 내용을 다룰 예정 입니다.

🤔 CI/CD란? 그리고 Android에서 왜 중요한가?

CI란? (Continuous Integration)

  • Pull Request(PR)을 생성하고 머지할 때마다 자동으로 빌드, 테스트를 수행
  • Android에서의 대표적인 CI 작업
    • ./gradlew build
    • ./gradlew test
    • ./gradlew lint

CD란? (Continuous Delivery / Deployment)

  • CI 후 빌드된 결과물(APK/AAB)을 자동으로 QA, Firebase, PlayStore에 배포

Android에서 특히 필요한 이유가 있을까..?

  1. 빌드 시간이 길다 (5분 ~ 20분)
  2. Build Variant, Flavor, keystore 복잡
  3. QA팀, PO는 주기적으로 APK를 요구
  4. Signing, Proguard, Release 빌드 절차 복잡
  5. 디자이너도 테스트빌드가 필요함

-> CI/CD를 도입하게 된다면?

빌드, 배포를 자동화 → 개발자는 코드에만 집중
QA, PO에게 자동으로 APK 전달
오류가 PR 단계에서 빠르게 검출

🚩 GitHub Actions 구조 정복

구성 요소설명
Workflow.github/workflows/*.yml에 정의되는 CI/CD의 단위
EventWorkflow를 실행시키는 조건 (push, pull_request 등)
JobWorkflow 안에서 실행되는 작업 그룹 (병렬, 순차 가능)
StepJob 내의 실제 작업 단위 (shell, action)
Runner작업이 실행되는 환경 (ubuntu-latest, macos-latest)
Secret환경 변수 및 보안 정보 관리 (keystore, API Key 등)

Event

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ develop ]

Workflow 실행 구조

Event 발생 → Workflow 실행 → Job 실행 → Step 순차 실행 → Runner에서 실제 동작

Step 종류

  • uses: GitHub Action을 사용
  • run: 직접 shell 명령어를 실행
  • with: Action에 인자 전달
  • env: 환경 변수 설정

Android 프로젝트 CI/CD 사전 준비

CI/CD를 적용하기 위해 Android 프로젝트에서 반드시 준비해야 하는 것들을 살펴보겠습니다.

1. Keystore 및 Signing 정보 준비

안드로이드에서 Release 빌드를 Play Store에 업로드할 때 반드시 Signing이 필요합니다.

필수 환경 변수 목록:
ANDROID_KEYSTORE : base64로 인코딩된 keystore 파일
KEY_ALIAS
KEY_PASSWORD
STORE_PASSWORD

💡 GitHub Actions에서는 위 정보를 모두 Settings > Secrets > Actions 에 등록해야 합니다.

다만 APK로 CD를 설정한다면 굳이 해당 환경변수를 설정할 필요는 없고,build gradle의 release 환경에

signingConfig = signingConfigs.getByName("debug")

요것만 추가해주면 됩니다.

2. local.properties 자동 생성

local.properties 파일은 git에 올라가지 않는 개발용 환경 변수 파일입니다.
CI 서버에서도 baseUrl, apiKey 등을 제공해야 하므로 Workflow에서 동적으로 생성합니다.

- name: Generate local.properties
  run: |
    echo "baseUrl=$BASE_URL" >> local.properties

3 build.gradle Signing 설정

1번에서 만약 AAB로 하실려면 gradle설정에 아래 항목을 추가해야됩니다.

signingConfigs {
    release {
        storeFile file(System.getenv("ANDROID_KEYSTORE"))
        storePassword System.getenv("STORE_PASSWORD")
        keyAlias System.getenv("KEY_ALIAS")
        keyPassword System.getenv("KEY_PASSWORD")
    }
}

이렇게 설정하면 CI/CD에서도 Signing이 자동으로 처리됩니다.

4 Firebase App Distribution 준비

Firebase App Distribution을 사용한다면, google-services.json도 CI에서 decode 후 제공해야 합니다.

base64로 인코딩된 후 FIREBASE_SECRET이라는 이름으로 Secrets에 저장

CI에서는 다음과 같이 복원하면 됨!

- name: Decode google-services.json
  env:
    FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET }}
  run: echo $FIREBASE_SECRET | base64 --decode > app/google-services.json

실전 CI 예제 분석(Build 전용)

아래 예제 CI는 PR(Pull Request)이 생성될 때마다 develop 브랜치에서 자동으로 빌드, Lint를 수행하는 워크플로우입니다.

🔔 Workflow의 전체 구조

name: Clody CI

on:
  pull_request:
    branches: [ develop ]
    paths:
      - 'app/**'
      - 'build.gradle'
      - '**/*.kt'

📌 설명

  • pull_request 이벤트가 발생할 때만 동작함
  • 대상 브랜치는 develop
  • app 모듈, build.gradle, Kotlin 소스코드가 변경될 때만 동작 → 불필요한 빌드 방지

🟣 defaults

defaults:
  run:
    working-directory: ./
  • 모든 run 블록의 작업 디렉토리를 프로젝트 루트로 고정

🟣 Job 정의

jobs:
  build:
    runs-on: ubuntu-latest
  • build라는 이름의 Job
  • ubuntu-latest 환경에서 실행됨

🟣 Step 1 - Gradle Cache 설정

- name: Cache Gradle packages
  uses: actions/cache@v4
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties', '**/*.gradle*', '**/gradle-wrapper.properties') }}
  • Gradle Cache를 저장하여 CI 속도를 개선할 수 있음.
  • hashFiles()로 Cache Key를 생성하여 의존성 변경 시 캐시 갱신

🟣 Step 2 - Android SDK Cache 설정

- name: Cache Android SDK
  uses: actions/cache@v4
  with:
    path: ~/.android
    key: ${{ runner.os }}-android
  • Android Emulator, SDK 다운로드 속도 개선을 위해 사용했음.

🟣 Step 3 - Checkout

- name: Checkout the code
  uses: actions/checkout@v4
  • CI 서버에 소스코드 체크아웃

🟣 Step 4 - JDK & Android SDK 설치

- name: Setup JDK
  uses: actions/setup-java@v4
  with:
    distribution: 'corretto'
    java-version: '17'

- name: Setup Android SDK
  uses: android-actions/setup-android@v3
  • Android 공식 권장이 JDK 17 이므로 JDK 17설치
  • Android SDK 환경 구성

🟣 Step 5 - gradlew 권한 부여

- name: Grant execute permission for gradlew
  run: chmod +x gradlew
  • CI 서버의 gradlew는 기본적으로 실행 권한이 없기 때문에 권한 부여가 필수

🟣 Step 6 - google-services.json 복원(옵션)

만약 firebase를 사용하지 않는다면 굳이 필요 없다.

- name: Decode google-services.json
  env:
    FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET }}
  run: echo $FIREBASE_SECRET | base64 --decode > app/google-services.json
  • 로컬에서는 gitignore로 관리되는 firebase 설정 파일을 CI에서 복원
  • 보안을 위해 base64로 secrets에 등록 후 복호화

🟣 Step 7 - local.properties 동적 생성

- name: Generate local.properties
  env:
    BASE_URL: ${{ secrets.BASE_URL }}
    ...
  run: |
    echo "baseUrl=$BASE_URL" >> local.properties
    ...
  • CI 환경에서도 API Endpoint, API Key 등 환경 변수를 전달하기 위해 사용
  • build.gradle에서 사용되는 값을 제공

🟣 Step 8 - Lint & Build 수행

- name: Run Lint and Build
  run: ./gradlew --no-daemon --configuration-cache ktlintCheck assembleDebug
  • ktlintCheck → Kotlin Lint 검사
  • assembleDebug → Debug APK 빌드
  • --configuration-cache로 캐시 활성화
  • --no-daemon으로 데몬 모드 비활성화 (CI에서는 권장함!)

실전 CD 예제 분석(배포, Firebase, Discord)

아래 예제는 Staging 브랜치로 PR이 생성되거나 병합되었을 때 자동으로 Release APK를 생성하고, Firebase App Distribution을 통해 배포하며, Discord에 배포 완료 알림을 전송하는 워크플로우입니다.
CI와 겹치는 부분은 설명을 생략하겠습니다.

시작하기에 앞서...

Firebase + Credential 왜 필요한가?

D 서버는 사람이 Firebase Console에 직접 로그인할 수 없습니다.
그래서 CD가 대신 인증을 받아야 하는데, 이때 사용되는 것이 바로 Service Account Credential입니다.

내부에서 어떻게 동작할까요?
1. Firebase Console에서 Service Account JSON 발급
2. 해당 JSON을 base64로 인코딩하여 GitHub Secrets에 저장
3. CI/CD 환경에서 base64 디코딩으로 다시 JSON 복원
4. 환경변수 GOOGLE_APPLICATION_CREDENTIALS로 등록
5. Firebase CLI가 이 Credential을 읽어 Firebase API 인증 수행
6. 인증이 완료된 후, 배포 API를 통해 AppDistribution에 APK 업로드

요 과정을 통해 CI/CD 환경에서도 안전하게 인증된 Firebase 배포가 가능합니다!

🟢 전체 구조

name: Clody CD

on:
  pull_request:
    branches: [ staging ]

jobs:
  cd:
    name: Continuous Deployment
    runs-on: ubuntu-latest
  • staging 브랜치 전용 Workflow
  • 배포 전용 Job으로 구성

🟢 Step 1 - Checkout

- name: Checkout code
  uses: actions/checkout@v4

CI 서버에 현재 브랜치의 소스코드 다운로드

🟢 Step 2 - Gradle Cache 설정

- name: Cache Gradle dependencies
  uses: actions/cache@v4
  • 의존성 캐시로 빌드 속도 최적화

🟢 Step 3 - JDK & Android SDK 설치

- name: Set up JDK 17
  uses: actions/setup-java@v4
  with:
    java-version: 17
    distribution: 'corretto'
    cache: gradle

- name: Setup Android SDK
  uses: android-actions/setup-android@v3

🟢 Step 4 - gradlew 권한 부여

- name: Change gradlew permissions
  run: chmod +x gradlew

🟢 Step 5 - Firebase CLI 설치

- name: Install Firebase CLI
  run: curl -sL https://firebase.tools | bash
  • Firebase 배포를 위한 CLI 설치

🟢 Step 6 - google-services.json 복원

- name: Decode google-services.json
  env:
    FIREBASE_SECRET: ${{ secrets.FIREBASE_SECRET }}
  run: echo $FIREBASE_SECRET | base64 --decode > app/google-services.json
  • google-services.json 복원

🟢 Step 7 - local.properties 동적 생성

- name: Generate local.properties

🟢 Step 8 - Release APK 빌드

- name: Build Release APK
  run: ./gradlew assembleRelease --stacktrace
  • 서명된 Release APK 빌드
  • Release 빌드는 반드시 Signing이 적용되어야 함!

🟢 Step 9 - Firebase Credentials 준비

- name: Set up Firebase Service Account Credentials
  env:
    GOOGLE_APPLICATION_CREDENTIALS_JSON: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_JSON }}
  • Firebase CLI에서 사용할 Service Account Key 등록

🟢 Step 10 - Firebase App Distribution 배포

- name: Upload APK to Firebase App Distribution
  run: firebase appdistribution:distribute app/build/outputs/apk/release/app-release.apk ...
  • Tester Group에게 APK 배포
  • --release-notes 로 간단한 배포 메세지 작성 가능

🟢 Step 11 - Discord 알림

- name: Notify Discord
  run: |
    curl -H "Content-Type: application/json" -X POST ...
  • Discord Webhook을 통한 실시간 배포 알림

Android CI/CD에서 할 수 있는 추가 작업들

Android Emulator에서 Instrumentation Test 실행

  • Github Actions에서 AVD를 띄우고 Instrumentation Test 수행 가능
  • CI 환경에서도 Espresso, UIAutomator Test 가능

Artifact 업로드 및 다운로드

- name: Upload Debug APK
  uses: actions/upload-artifact@v4
  with:
    name: debug-apk
    path: app/build/outputs/apk/debug/app-debug.apk
  • 빌드된 APK, Lint Report, Coverage Report 등을 업로드해 PR에 첨부가 가능함!

Play Store 자동 배포

  • gradle-play-publisher 사용 시 Google Play Console로 직접 AAB 업로드 및 트랙 관리 가능
  • 이 부분은 추후 포스팅 하겠습니다.

GitHub Actions YAML 옵션 분석

옵션설명
onWorkflow Trigger 지정
concurrency동일한 Workflow 중복 실행 방지
matrix여러 환경(버전) 병렬 빌드
needsJob 간 실행 순서 지정
timeout-minutesWorkflow 실행 제한 시간
continue-on-error오류 발생 시 다음 Step 진행 여부
defaultsWorkflow 전역 기본 설정
outputsJob 결과 값을 다른 Job에 전달
env환경 변수 설정

적합한 Git Branching 전략

main        → 실제 서비스 출시 버전
staging     → QA, Tester 배포용
develop     → 개발 및 기능 통합
feature/*   → 기능 단위 브랜치

저 같은 경우는 feat/* -> develop -> staging -> release를 따르고 있습니다.

Production-ready Workflow 설계

모듈화 전략

  • ci.yml : PR CI 전용
  • cd.yml : QA 및 Firebase 배포
  • release.yml : Play Store 업로드 및 Production 배포

환경 변수 관리

  • baseUrl, API Key, Keystore 등은 모두 Secrets로 관리
  • local.properties는 CI 내에서만 생성

Pipeline 추천 구조

  • lint → unit test → build → instrumentation test → upload artifact → deploy
  • 실패 시 자동 알림 (Slack, Discord)은 선택

정리하며...

CI/CD는 안드로이드 개발에 있어서 필수가 되었습니다.
대부분의 회사에서 사용중이죠.
여러분들도 프로젝트에 꼭 CI/CD를 적용해보세요.
초기 세팅이 번거로울 뿐, 유지보수와 팀 생산성이 눈에 띄게 향상된답니다.

참고하면 좋은 자료

https://docs.github.com/ko/actions
https://github.com/Triple-T/gradle-play-publisher

profile
Android Developer

0개의 댓글