-
Android Gitlab CI/CD 파이프라인 구축기CI-CD 2025. 11. 19. 10:12반응형
현재 회사에서 Kotlin Multiplatform으로 안드로이드와 윈도우에서 작동하는 포스 앱을 만들고 있습니다.
지금까지는 이 앱의 apk, msi 빌드를 직접 제가 하여 포스 기기에 깔아왔는데, 지난 연휴에 문제점을 확인했습니다.
기존에는 제가 로컬 환경에서 직접 빌드하여 APK와 MSI 파일을 관리했습니다. 그러다 보니 지난 버전의 배포 파일이 체계적으로 아카이빙되지 않았고, 제가 부재중일 때는 현장 기기에 프로그램을 설치하거나 업데이트할 수 없는 버스 팩터 문제가 발생했습니다.
또한 새로운 기능을 만들어도 빌드를 하지 못해서 기기에 업데이트를 할 수 없었습니다.
기존의 작업 흐름은 이랬습니다.
- 프로젝트 수정 및 개발사항 deploy 브랜치에 직접 push.
- 저한테 빌드 요청. (스스로 개발한 경우엔 스스로 빌드 요청)
- android/windows 배포 파일 빌드.
- 해당 배포 파일 전달.
단순한 빌드 작업 때문에 업무 병목이 발생하고 일정이 지연되는 일이 잦았습니다. 또한, 로컬 빌드 특성상 빌드 환경 차이로 인한 정합성 문제나, 수동 작업에 따르는 휴먼 에러의 위험도 항상 존재했습니다. 이러한 불확실성을 제거하고 배포 프로세스의 일관성을 확보할 필요가 있었습니다.
이런 병목과 환경 이슈와 휴먼 에러들을 최소화 하면 실수와 문제가 더 줄어들고 앞으로도 일관적인 배포 작업이 가능해 보였습니다.
그래서 휴먼 에러와 배포 실수를 줄이는 방법으로 생각한게 Gitlab CI/CD를 활용하여 빌드 및 릴리즈에 파일을 아카이빙 해놓는 방법을 생각하게 되었습니다.
해당 방법을 사용했을 때 기대되는 효과는 다음과 같았습니다.
작업 흐름을 개선 했을 때의 기대효과
- 의존성 제거: 특정 개발자(나)에게 몰리던 빌드 요청 병목 해소.
- 일관성 확보: 사람이 개입하지 않는 격리된 환경(Runner)에서 빌드하여 환경 변수 통제.
- 자산 관리: 릴리즈 페이지를 통해 버전별 산출물 자동 아카이빙 및 팀 내 공유 용이.
- 보안 강화: Signing Key 등 민감 정보를 CI/CD Variables로 중앙 관리하여 유출 위험 감소.
위와 같은 많은 기대효과가 있었기에 아래와 같이 파이프라인을 구축하게 되었습니다.
CI/CD로 자동화를 추가한 배포 과정의 전체적인 작업 흐름
- deploy 브랜치에 merge request를 생성.
- checklist를 확인 후 변경 세부사항을 작성.
- 프로젝트 담당자의 approve를 받고, merge.
- CI 파이프라인이 libs.versions.toml 파일에 명시된 app-version 값을 파싱하여 자동으로 Git Tag를 생성합니다. (예: 1.1.1 → v1.1.1)
- 해당 tag가 생성되면 해당 버전으로 apk와 msi를 gitlab-ci가 build.
- 해당 빌드된 배포파일을 gitlab-ci가 Release에 자동으로 올림.
배포 파이프라인에서 사람이 직접 deploy 브랜치에 push를 못하게 Protected Branch로 지정하여 Allowed to push and merge를 No one으로 설정하였습니다. 왜냐면 만약에 사람이 push를 마구잡이로 하게 된다면 개발계의 코드와 운영계의 코드가 섞일 수 있고, 저는 개발계의 코드와 운영계의 코드가 섞일 여지를 줄이고 싶었기 때문입니다.

Protected Branches .gitlab-ci.yml
before_script: - export GRADLE_USER_HOME=$(pwd)/.gradle - chmod +x ./gradlew cache: key: ${CI_PROJECT_ID} paths: - .gradle/ variables: GIT_SUBMODULE_STRATEGY: normal workflow: rules: # # 1. 'deploy' 브랜치로 푸시될 때 파이프라인을 실행합니다. - if: $CI_COMMIT_BRANCH == "deploy" # 2. '태그'가 푸시될 때 파이프라인을 실행합니다. - if: $CI_COMMIT_TAG stages: # List of stages for jobs, and their order of execution - auto-tag # 태그를 자동으로 달아줌. - test - build - deploy create-tag-job: stage: auto-tag image: alpine:latest rules: - if: $CI_COMMIT_BRANCH == "deploy" before_script: - apk add --no-cache git grep - git config --global user.email "ci-bot@gitlab.com" - git config --global user.name "GitLab CI Bot" # Settings > CI/CD > Variables에 'GIT_PUSH_TOKEN'이 있어야 합니다. - git remote set-url origin "https://oauth2:${GIT_PUSH_TOKEN}@gitlab.com/${CI_PROJECT_PATH}.git" - git fetch --tags script: - echo "Checking version from build.gradle.kts..." # composeApp/build.gradle.kts에서 versionName 추출 (예: 1.0.3) - VERSION_NAME=$(grep 'app-version' gradle/libs.versions.toml | sed -E 's/.*=[[:space:]]*["'\'']?([^"'\'']+)["'\'']?.*/\1/') - TAG_NAME="v$VERSION_NAME" - echo "Detected Version- $VERSION_NAME -> Tag- $TAG_NAME" # 태그가 없을 때만 생성 후 푸시 - | if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then echo "⚠️ Tag $TAG_NAME already exists. Skipping." else echo "🚀 Creating and Pushing tag: $TAG_NAME" git tag $TAG_NAME git push origin $TAG_NAME fi android-build-job: # This job runs in the build stage, which runs first. stage: build image: cimg/android:2023.12.1-node rules: # '태그'가 달렸을 때만 실행! - if: $CI_COMMIT_TAG before_script: # <-- 이 섹션을 추가합니다 - echo "Decoding Keystore file..." # 1. B64 변수를 읽어 base64 디코딩 후, 임시 파일로 저장합니다. - echo "$SIGNING_STORE_FILE_B64" | base64 -d > ./keystore-from-ci.jks # 2. 이 파일의 전체 경로를 새 환경 변수로 만듭니다. - export ANDROID_KEYSTORE_FILE_PATH=$(pwd)/keystore-from-ci.jks script: - echo "Building Android APK..." # :androidApp 모듈의 릴리즈 APK를 빌드합니다. # 위에서 설정한 CI/CD 변수(ANDROID_KEYSTORE_FILE 등)를 Gradle이 자동으로 읽어 서명합니다. - sh ./gradlew :composeApp:assembleRelease - echo "BUILD_JOB_ID=$CI_JOB_ID" >> build.env artifacts: # 빌드 성공 시 생성된 APK 파일을 30일간 보관합니다. name: "android-apk-$CI_COMMIT_SHORT_SHA" expire_in: 30 days paths: # 생성된 APK 파일의 경로 - composeApp/build/outputs/apk/release/composeApp-release.apk reports: dotenv: build.env windows-build-job: stage: build # 이 작업은 Windows Runner에서만 실행되도록 'tags'를 지정합니다. tags: - windows rules: # '태그'가 달렸을 때만 실행! - if: $CI_COMMIT_TAG script: - echo "Building Windows MSI..." # Windows는 gradlew.bat 사용 - .\gradlew.bat :desktopApp:packageReleaseMsi artifacts: # 빌드 성공 시 생성된 MSI 파일을 30일간 보관합니다. name: "windows-msi-$CI_COMMIT_SHORT_SHA" expire_in: 30 days paths: # 생성된 MSI 파일의 경로 (프로젝트 설정에 따라 다를 수 있음) - composeApp/build/compose/binaries/main/msi/*.msi deploy_release: stage: deploy image: registry.gitlab.com/gitlab-org/release-cli:latest # deploy 단계가 build 단계의 결과물을 확실히 가져오도록 의존성을 명시합니다. needs: - job: android-build-job artifacts: true rules: # 이 작업(job)은 Git 'tag'가 푸시될 때만 실행됩니다. (예: git push origin v1.2.0) - if: $CI_COMMIT_TAG script: - echo "Creating GitLab Release for tag $CI_COMMIT_TAG" - echo "Build Job ID is $BUILD_JOB_ID" release: name: "Release $CI_COMMIT_TAG" tag_name: "$CI_COMMIT_TAG" description: "New release for $CI_COMMIT_TAG" ref: '$CI_COMMIT_SHA' assets: links: - name: "Android APK" url: "${CI_PROJECT_URL}/-/jobs/${BUILD_JOB_ID}/artifacts/raw/composeApp/build/outputs/apk/release/composeApp-release.apk?job=android-build-job" - name: "Windows MSI" url: "${CI_PROJECT_URL}/-/jobs/${BUILD_JOB_ID}/artifacts/raw/desktopApp/build/compose/binaries/main/msi/imtsoft-pos-1.0.0.msi?job=build_windows"위의 .gitlab-ci.yml을 작성하고 해당 프로젝트에 CI/CD Settings에 Variables에 필요한 몇개의 변수를 등록해주어 파이프라인의 구축을 완료했습니다. CI/CD Variables 등록 시에는 Mask variable 옵션을 켜야 로그에 해당 Variable의 값이 노출되지 않습니다.
Android는 Docker 이미지가 많아서 프로젝트의 JVM과 동일한 이미지를 골라서 사용하면 됐지만, Windows msi 빌드를 위해선 Windows Runner를 직접 세팅하거나 호스트 머신에 연결해야 했습니다. (없으면 대기상태로 유지됩니다)
CI/CD Settings / Variables
- GIT_PUSH_TOKEN
- 자동으로 tag 생성하고 Remote에 올리기 위해 필요
- SIGNING_KEY_ALIAS
- Android apk에 Sign하기 위해 필요
- SIGNING_KEY_PASSWORD
- Android apk에 Sign하기 위해 필요
- SIGNING_STORE_FILE_B64
- Android apk에 Sign하기 위해 필요
- SIGNING_STORE_PASSWORD
- Android apk에 Sign하기 위해 필요
'CI-CD' 카테고리의 다른 글
Gitlab CI/CD에 Gitlab Runner 직접 붙여서 빌드하기 (0) 2025.11.26 도커와 CI환경 - (5) 도커(docker) 이미지 다뤄보기 (0) 2023.01.16 도커와 CI환경 - (4) 도커(docker) 컨테이너의 생명주기 (0) 2023.01.13 도커와 CI환경 - (3) 도커(docker) 사용해보기 (0) 2022.12.29 도커와 CI환경 - (2) m1 맥 도커(docker)설치 (2) 2022.12.28