안드로이드/개발기록

[Android] 안드로이드 앱 CI / CD 적용하기 with Github Actions - (1) CI 적용

sxunea 2024. 10. 27. 19:25

 

요즘 채용 공고를 보면 'CI / CD 를 적용해본적이 있는 분' 이 자주 명시되어있다. 또, 앱을 플레이 스토어에 배포하고 나면 자연스레 CI / CD를 접하게 되는데, 대체 무엇을 의미하는걸까 ? 또 어떤 장점이 있길래 이를 우대하는 것일까 ?

 

기존에 CI 는 적용하고 있다가 얼마전 CD 또한 프로젝트에 직접 적용해보아서 이를 기록해보고자 한다. 

 

 

CI / CD 란 무엇인가 

CI/CD는 각각 지속적 통합(Continuous Integration)과 지속적 배포(Continuous Delivery/Deployment)를 의미한다. 안드로이드 앱 개발에서 CI/CD는 소스 코드 변경 사항을 자동으로 테스트하고 배포하는 프로세스를 나타낸다.

지속적 통합(CI)은 개발자가 코드를 중앙 저장소에 자주 병합하는 방식을 채택한다. 이 과정에서 자동화된 빌드와 테스트가 수행되어 코드의 품질을 유지하고 버그를 조기에 발견하는 데 도움을 준다. 


지속적 배포(CD)는 CI에서 성공적으로 빌드된 앱을 프로덕션 환경에 자동으로 배포하는 것을 의미한다. 이 과정에서는 배포 준비가 완료된 빌드가 테스터 사용자에게 전달된다. CI/CD 파이프라인은 코드 변경 사항이 릴리즈 가능 상태인지 확인하고, 자동으로 사용자에게 업데이트된 앱을 제공하는 역할을 한다.

정의는 다음과 같고, 나는 안드로이드 앱 개발자이니 이를 앱에 적용시켜보자. 어떠한 효율성이 생길까 ?

 

출처 : https://medium.com/@hongseongho/ci-cd-%ED%94%8C%EB%9E%AB%ED%8F%BC%EA%B3%BC-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-8ca07a16933c

 

보통 앱 업데이트 / 출시를 할 때는 빌드 -> 테스트 -> 배포의 과정을 거치게 된다. 이때 업데이트를 할때마다 이 모든 과정을 개발자가 직접 수행하고, 또 각 단계마다의 결과를 기다려서 확인하기가 다소 귀찮을 수 있다. 또, QA 팀이나 디자인 팀에게 릴리즈될 버전의 앱을 테스트를 위해 제공할때, 파이어베이스에 올리고 테스터 초대를 매번 누르기도 번거롭게 느껴질 수 있다. 

 

만약 이 일련의 과정을 파이프라인을 통해 자동화한다면 ? 피드백 루프의 시간이 줄어들고, 협업의 능률이 향상되고, 테스트 환경의 일관성 또한 얻을 수 있을 것이다. 이게 CI / CD의 장점이다. 

 

 

 

 

 

CI / CD 도구 : Github Actions

그렇다면 이를 가능하게 하는 툴에는 무엇이 있을까 ? 다양하게 있지만 이 글에서는, 그리고 나는 Github Actions를 활용했다. 

 

GitHub Actions는 GitHub 플랫폼에서 제공하는 CI/CD 도구로, 소스 코드 리포지토리와 통합되어 자동화된 워크플로우를 만들 수 있다. 다양한 이벤트에 대해 트리거 되어 빌드, 테스트, 배포 작업을 수행할 수 있다. YAML 파일을 사용하여 워크플로우를 정의하며, 커스터마이징이 용이하다. 다른 도구로는 Jenkins, CircleCi 등이 있다. 

 

yaml 파일

후에 나오겠지만 CI / CD 스크립트는 yaml 파일로 작성된다. 그러면 여기서 yaml 파일은 어떨 때 쓰이는 지 잠깐 알아보자.

 

YAML(YAML Ain't Markup Language)은 데이터를 표현하기 위한 포맷으로, 사람에게 읽기 쉬운 형식을 제공한다. 주로 설정 파일이나 데이터 교환 형식으로 사용된다. xml, json과 마찬가지로 데이터 포맷을 정의하지만 그 문법과 가시성이 좋아 잘 쓰인다. 더 자세히 알아보기 위해서는 이 글 을 추천한다. 설명이 잘 되어있다. 

 

 

 

 

CI 적용하기 

자 그러면 이제 실전으로 넘어가보자 ! 이번글에서는 CI 적용만 다루고, 다음 글에 CD 적용을 다루도록 하겠다. 

 

 

 

 

 

스크립트 생성하기

먼저 CI를 적용할 깃허브 레포지토리는 당연히 있어야 한다. 해당 레포지토리에 들어가면, 상단에 Actions 카테고리가 있다. 이를 클릭해 들어가면 아래와 같이 workflow를 생성할 수 있다. 

 

 

위의 화면까지 들어왔으면, 검색창에 'android'를 검색한다. 그러면 다음과 같은 검색 결과가 나온다. 

 

 

우리는 안드로이드 앱에 CI 를 적용할 것이니, 아래의 Android CI Configure를 클릭한다. 

 

 

클릭하고 나면, 다음과 같은 기본 스크립트의 yaml 파일이 생성된다. 각 라인 별 설명은 주석을 확인하자 

name: Android CI  # 워크플로우의 이름을 "Android CI"로 설정한다.

# push 또는 pull request가 발생할 때 CI가 트리거되도록 설정한다.
on:
  push:  # 코드 푸시 이벤트에 대한 설정
    branches: [ "develop" ]  # "develop" 브랜치에 푸시될 때만 트리거된다.
  pull_request:  # 풀 리퀘스트 이벤트에 대한 설정
    branches: [ "develop" ]  # "develop" 브랜치로의 풀 리퀘스트가 생성될 때만 트리거된다.
    

jobs:  # CI에서 수행할 작업을 정의한다.
  build:  

    runs-on: ubuntu-latest  # 이 작업은 최신 버전의 Ubuntu 환경에서 실행된다.

    steps:  # 이 작업에서 수행할 단계들을 정의한다.
    - uses: actions/checkout@v3  # 코드 리포지토리에서 소스 코드를 체크아웃한다.
    
    - name: set up JDK 11  # JDK 11을 설정하는 단계
      uses: actions/setup-java@v3  # Java 환경을 설정하기 위한 액션을 사용한다.
      with:
        java-version: '11'  # 사용할 Java 버전을 11로 지정한다.
        distribution: 'temurin'  # Java 배포판으로 Temurin을 사용한다.
        cache: gradle  # Gradle 캐싱을 활성화하여 빌드 시간을 단축한다.

    - name: Grant execute permission for gradlew  # gradlew 파일에 실행 권한을 부여하는 단계
      run: chmod +x gradlew  # gradlew 파일에 실행 권한을 추가한다.

    - name: Build with Gradle  # Gradle을 사용하여 빌드를 수행하는 단계
      run: ./gradlew build  # gradlew 스크립트를 실행하여 Android 앱을 빌드한다.

 

 

간략하게 설명하면 언제 CI가 트리거 될지, JDK 환경은 어떻게 세팅할지, gradle은 어떻게 세팅할지 등을 지정한다. 여기까지만 하면, CI 워크플로우는 빌드를 해주고, 개발자는 변경사항의 PR를 올릴 때, 변경사항이 정상적으로 작동해 빌드가 성공하는 지의 여부를 확인할 수 있다. 

 

 

여기에 그치지 않고, 추가적인 테스트 및 알림 기능까지 추가해보자. 

 

 

 

 

감춰야할 데이터 Github Secrets로 관리하기 

우리가 앱을 릴리즈하다보면, 깃허브에는 올리면 안되는 민감한 정보들이 있다. 이를 GitHub Actions에서 비밀 데이터(API 키, 설정 값, BASE URL 등)를 관리하고 사용할 수 있다. 이 과정에서 GitHub Secrets를 활용하면 환경 변수와 비슷하지만 보안성이 더 높다. Secrets에 저장된 값은 워크플로우에서 참조할 수 있으며, 로그에 출력되지 않기 때문에 안전하게 사용할 수 있다. 

 

 

Github Secrets는 다시 레포지토리로 돌아가서, Setting 카테고리에 진입해 하단 Security-Secrets and variables-Actions 로 들어가면 된다. 아래와 같은 화면에 진입하면, 감춰야할 데이터, 즉 local properties로 공유했던 값들을 넣어 관리할 수 있다. 

 

내 프로젝트에서는 Google Services JSON 파일과 BASE URL을 secrets로 관리하려 했으므로, CI yaml 스크립트에 다음과 같이 작성할 수 있다. 

      - name: Add Local Properties
        env:
          BASE_URl: ${{secrets.BASE_URL}}
        run:
          echo base.url=\"$_BASE_URL\" >> ./local.properties

      - name: Get Google Services JSON
        env:
          GOOGLE_SERVICES_JSON: ${{secrets.GOOGLE_SERVICES_JSON}}
        run:
          echo $GOOGLE_SERVICES_JSON > ./app/google-services.json

 

  • Add Local Properties에서는 GitHub Secrets에 저장된 BASE_URL 값을 가져와 환경 변수로 설정하고, run 명령어를 통해 local.properties 파일에 base.url 항목을 추가하고, $BASE_URL 을 통해 해당 값을 참조할 수 있게 한다. 
  • Get Google Services JSON에서는 Google Services JSON 파일을 가져와 app 디렉토리에 생성해준다.

 

이를 통해 GitHub Secrets를 사용하여 비밀 정보를 코드에 직접 하드코딩하지 않고, 안전하게 관리할 수 있다. 또, CI / CD 파이프라인에 이러한 로컬 설정 파일을 자동으로 구성해주어 효율성을 높일 수 있다. 

 

 

 

 

 

코드 스타일 검사

- name: Run ktlint  # Ktlint 실행 단계
  run: ./gradlew ktlintCheck  # Gradle Wrapper를 사용하여 Ktlint 검사 수행

 

 

CI 단계에서는 위와 같이 코드 스타일 검사 또한 두줄의 스크립트로 가능하다. 이 때 KtLint를 활용했는데, Ktlint는 프로젝트의 모든 Kotlin 파일을 검사하여 코딩 스타일이 정의된 규칙을 준수하는지를 확인시켜준다. 이때 KtLint에 대한 종속성이 미리 추가되어있어야 한다.

 

 

[Kotlin] Ktlint와 Detekt 도입하기

코틀린 프로젝트에서 코드 퀄리티 관리를 자동화하기 위해, Ktlint와 Detekt를 도입합니다.

velog.io

 

KtLint 말고도 DeteKt와 같은 툴이 있으며, 이를 도입하는 과정이나 무엇인지에 대해 더 자세히 알아보려면 구글링 또는 위의 블로그를 추천한다. 이 글은 CI에대한 글이니까 따로 더 자세히 설명하지는 않겠다. 

 

이때, 검사 결과는 GitHub Actions의 로그에 출력되며, 스타일 규칙 위반이 있을 경우, 해당 부분이 어떤 규칙을 위반했는지를 보여준다.

 

 

 

 

 

빌드 성공 여부 슬랙 알림

로컬 환경 변수를 관리하고, 이를 이용해 빌드하고, 코드 스타일 검사까지 통과해 성공적으로 마무리 되었으면 CI 스크립트를 성공적으로 넘어가 PR 을 머지할 수 있음을 확인할 수 있다. 이때, CI Job을 보면서 계속 기다리는 것보다는, 협업 툴에 성공 여부 알림을 받으면 훨씬 효율적일 것이다. 이를 위해 팀 슬랙을 연동하고, 빌드 성공 여부에 따라 알림을 받을 수 있다. 

 

 

Sending messages using incoming webhooks

Create an incoming webhook with a unique URL to which you send a JSON payload with message text and options.

api.slack.com

 

방법은 슬랙 공식문서에 아주 잘 나와있다. 그 방법도 아주 간단한데, 슬랙에서 앱을 생성하고, 웹훅을 활성화한다. 그다음, Incoming Webhooks 에 들어가 WebHook URL을 확인하고 이를 복사한다. 이때 이 URL 또한 Github Secrets로 관리하면 편하다. 

 

이를 마치면, URL을 활용해 다음과 같이 스크립트를 추가해주면 된다. 

      - name: If Success, Send notification on Slack
        if: ${{success()}}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_COLOR: '#60E0C5'
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_TITLE: 'Plantory PR Checker have passed ✅'
          MSG_MINIMAL: true
          SLACK_USERNAME: Plantory Android
          SLACK_MESSAGE: 'Plantory Android PR Check Success 🎉'

      - name: If Fail, Send notification on Slack
        if: ${{failure()}}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_COLOR: '#ff0000'
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_TITLE: 'Plantory PR Checker have failed ✅'
          MSG_MINIMAL: true
          SLACK_USERNAME: Plantory Android
          SLACK_MESSAGE: 'Plantory Android PR Check Failure - Should Check Up'

 

성공하면 슬랙에 PR Checker have passed 를, 실패하면 PR Checker have failed 라는 알림을 보낸다. 이때, 메시지나 알림 색상, 유저 네임 또한 커스텀이 가능하다. 

 

 

 

 

전체 코드 

name: Plantory CI - PR Checker

on:
  pull_request:
    branches: [ develop ]

jobs:
  build:
    name: PR Checker
    runs-on: ubuntu-latest

    steps:
      - name: Checkout the code
        uses: actions/checkout@v3

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

      - name: Set up Android SDK
        uses: android-actions/setup-android@v2

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Add Local Properties
        env:
          BASE_URl: ${{secrets.BASE_URL}}
        run:
          echo base.url=\"$_BASE_URL\" >> ./local.properties

      - name: Get Google Services JSON
        env:
          GOOGLE_SERVICES_JSON: ${{secrets.GOOGLE_SERVICES_JSON}}
        run:
          echo $GOOGLE_SERVICES_JSON > ./app/google-services.json

      - name: Build with Gradle
        run: ./gradlew build

      - name: Run ktlint
        run: ./gradlew ktlintCheck

      - name: If Success, Send notification on Slack
        if: ${{success()}}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_COLOR: '#60E0C5'
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_TITLE: 'Plantory PR Checker have passed ✅'
          MSG_MINIMAL: true
          SLACK_USERNAME: Plantory Android
          SLACK_MESSAGE: 'Plantory Android PR Check Success 🎉'

      - name: If Fail, Send notification on Slack
        if: ${{failure()}}
        uses: rtCamp/action-slack-notify@v2
        env:
          SLACK_COLOR: '#ff0000'
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
          SLACK_TITLE: 'Plantory PR Checker have failed ✅'
          MSG_MINIMAL: true
          SLACK_USERNAME: Plantory Android
          SLACK_MESSAGE: 'Plantory Android PR Check Failure - Should Check Up'

 

 

이 모든걸 마치면 다음과 같이 Pull Request(PR)를 검사하는 CI(Continuous Integration) 워크플로우를 정의할 수 있다. 개발 환경에 맞게 JDK 를 설정하고, url 말고도 api key 등의 비밀 로컬 프로퍼티등을 추가하여 적용하면 된다. 

 

이렇게 CI 워크플로우를 통해 Android 앱의 품질을 지속적으로 유지하고, 팀원 간의 효율적인 소통을 이루어낼 수 있다. 자동화된 빌드 및 검사 과정을 통해 코드 변경 시 발생할 수 있는 오류를 조기에 발견하고 수정할 수 있는 환경이 마련되며, 그 결과를 협업 툴로 즉시 공유받을 수 있기 때문에 불필요한 기다림과 소통 또한 줄일 수 있다. 

 


 

안드로이드 앱의 출시가 정책 변경으로 인해 좀 어려워지긴 했다만, 기존에 개발자 계정이 있고, 출시된 프로젝트가 있다면 CI / CD를 도입해보는 걸 추천한다. 위의 장점과 별개로도 스크립트를 직접 작성해보면서 빌드에는 어떤 게 필요한지, 또 이후에 작성할 CD에서는 출시에는 어떤 과정이 필요한지 간단하게 정의할 수 있어 도움이 되기 때문이다. 

 

그러면 CI는 이만 마치고, CD를 도입하는 글로 돌아오겠다 !! 

 

 

 

 

 

참고 자료

 

CI/CD 플랫폼과 기본 개념 알아보기

이번 글에서는 CI/CD가 무엇인지, 어떤 CI 플랫폼이 있는지, 동작을 이해할 때 도움되는 용어들을 알아보겠습니다.

medium.com

 

 

 

워크플로 작성 - GitHub Docs

트리거, 구문, 고급 기능을 포함하여 GitHub Actions 워크플로의 개략적인 개요를 가져옵니다.

docs.github.com