CI/CD๋? ๐ค
GitHub Actions๋ ๊ฐ๋ฐ์๋ค์ด ์ฝ๋ ๋ณ๊ฒฝ์ ์๋์ผ๋ก ๋น๋, ํ ์คํธ, ๋ฐฐํฌํ ์ ์๋๋ก ๋์์ฃผ๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค.
์ด ๊ธ์์๋ GitHub Actions์ ๊ธฐ๋ณธ ๊ฐ๋ ๊ณผ ์ฌ์ฉ๋ฒ์ ์๊ฐํ๊ฒ ์ต๋๋ค.
์ง์์ ์ธ ํตํฉ(Continuous Integration) -> CI
์ง์์ ์ธ ๋ฐฐํฌ(Continuous Delivery or Continuous Deployment) -> CD
GitHub Actions๋? ๐ค
GitHub Actions๋ GitHub์์ ์ ๊ณตํ๋ CI/CD ๋๊ตฌ๋ก, ๋ค์ํ ์ด๋ฒคํธ(์: ์ฝ๋ ํธ์, PR ์์ฑ ๋ฑ)์ ๋ฐ์ํ์ฌ ์ํฌํ๋ก์ฐ๋ฅผ ์คํํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ์ํํธ์จ์ด ๊ฐ๋ฐ ํ๋ก์ธ์ค๋ฅผ ์๋ํํ๊ณ ํจ์จ์ฑ์ ๋์ผ ์ ์์ต๋๋ค.
๊ธฐ๋ณธ ๊ตฌ์ฑ ์์ ๐ ๏ธ
์ํฌํ๋ก์ฐ(Workflow): ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ ์คํ๋๋ ์์ ์ ์งํฉ์ ๋๋ค. YAML ํ์์ผ๋ก ์ ์๋ฉ๋๋ค.
์ก(Job): ์ํฌํ๋ก์ฐ ๋ด์์ ์คํ๋๋ ์์ ์ ๋จ์์ ๋๋ค. ์ฌ๋ฌ ๊ฐ์ ์ก์ ๋ณ๋ ฌ๋ก ์คํํ ์ ์์ต๋๋ค.
์คํ (Step): ์ก ๋ด์์ ์คํ๋๋ ๊ฐ๋ณ ๋ช ๋ น์ด ๋๋ ์คํฌ๋ฆฝํธ์ ๋๋ค.
์ก์ (Action): ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ฝ๋ ์กฐ๊ฐ์ผ๋ก, GitHub Marketplace์์ ๋ค์ํ ์ก์ ์ ์ฐพ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ฐ๋จํ ์ํฌํ๋ก์ฐ ์์ ๐
๋ค์์ ์ฝ๋ ํธ์ ์ ์๋์ผ๋ก ํ ์คํธ๋ฅผ ์คํํ๋ ๊ฐ๋จํ ์ํฌํ๋ก์ฐ ์์ ์ ๋๋ค.
name: CI
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: ์ฒดํฌ์์ ์ฝ๋
uses: actions/checkout@v2
- name: ๋
ธ๋ ์ค์น
uses: actions/setup-node@v2
with:
node-version: '14'
- name: ์์กด์ฑ ์ค์น
run: npm install
- name: ํ
์คํธ ์คํ
run: npm test
- on: ์ด๋ค ์ด๋ฒคํธ์ ๋ฐ์ํ ์ง๋ฅผ ์ค์ ํฉ๋๋ค. ์ฌ๊ธฐ์๋ main ๋ธ๋์น์ ํธ์ํ ๋ ์คํ๋ฉ๋๋ค.
- jobs: ์คํํ ์ก์ ์ ์ํฉ๋๋ค.
- steps: ๊ฐ ์ก์์ ์คํํ ์คํ ์ ๋์ดํฉ๋๋ค.
Android CI/CD ๊ตฌ์ถ ๐ซก
Github.Secrets
์ธ๋ถ์ ๋ ธ์ถ๋๋ฉด ์๋๋ ์ ๋ณด๋ค์ Github์ ์ฌ๋ ค๋๊ณ ์ฌ์ฉํ ์ ์์ผ๋ ํด๋น ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ Github Action ๋์ ์ค ๊ฐ๋ค์ ์ฐธ์กฐํ ์ ์๊ฒ ํด์ค์ผ ํฉ๋๋ค.
LOCAL_PROPERTIES_CONTENT
Local์ ์๋ local.properties๋ฅผ ๋ชจ๋ ๋ณต์ฌํ์ฌ ๊ทธ๋๋ก ๋ฃ์ด์ฃผ๋ฉด ๋ฉ๋๋ค.
FIREBASE_APP_ID
Firebase Project -> ํ๋ก์ ํธ ์ค์ -> ๋ด ์ฑ -> ์ฑ ID (์ฑ ๋๋ค์ ์์ ์์น)๋ฅผ ๋ณต์ฌํด์ ๋ฃ์ด์ฃผ์๋ฉด ๋ฉ๋๋ค.
GOOGLE_SERVICE_JSON
Firebase Project ์ถ๊ฐ ์ ๋ค์ด๋ก๋ ๋ฐ์ ์ ์๋ google-services.json์ ๋ด์ฉ์ ๋ณต์ฌํ์ฌ ๊ทธ๋๋ก ๋ฃ์ด์ฃผ์๋ฉด ๋ฉ๋๋ค.
KEYSTORE_BASE64
keystore.jks๋ฅผ ์์ฑํ๋ค๋ ๊ฐ์ ํ์ ํด๋น ํด๋น ํ์ผ์ ์์นํ ์ฅ์์์ ํฐ๋ฏธ๋์ ํตํด ์๋ ๋ช ๋ น์ ์ํํ๋ฉด base64๋ก ์ธ์ฝ๋ฉ๋ keystore ๊ฐ์ ์ป์ ์ ์์ต๋๋ค. ํด๋น ๊ฐ์ ๋ณต์ฌํ์ฌ ๊ทธ๋๋ก ๋ฃ์ด์ฃผ์๋ฉด ๋ฉ๋๋ค.
openssl base64 -in ./keystore.jks -out ./keystore.txt
CREDENTIAL_FILE_CONTENT
ํด๋น ๊ฐ์ ์๋ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
ServiceCredentialsFileContent ์์ฑ ๋ฐฉ๋ฒ (Github)
์ต์ข ์ฝ๋
Firebase Distribution์ ํตํด ํ ์คํฐ์๊ฒ ๋น๋๋ ์ฑ์ ๋น ๋ฅด๊ฒ ์ ๊ณตํ๊ธฐ ์ํ ์ฝ๋๋ก,
๋น๋ ์ workflow ๋ฒํธ๊ฐ App์ VersionCode๋ก ์ค์ ๋๋๋ก ํ์ต๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด ํน๋ณํ ์ผ์ด ์์ด ์๋ค๋ฉด ๊ณ ์ ํ ๋ฒ์ ์ฝ๋๋ฅผ ๊ฐ์ง ์ ์์ต๋๋ค.
์ถ๊ฐ๋ก ํด๋น ์ฝ๋์ path๋ค์ ๊ฐ ํ๋ก์ ํธ์ ๋ง๊ฒ ์์ ํด์ผ ํฉ๋๋ค!
์๋๋ Repository ๋ฃจํธ์ ํ๋ก์ ํธ๊ฐ ํด๋ ํํ๋ก ์กด์ฌํ๋ ๊ตฌ์กฐ์์ ๋ง์๋๋ฆฝ๋๋ค!
name: Versioning, Build And Deployment to Firebase App Distribution
on:
push:
branches: [ develop ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
env:
LOCAL_PROPERTIES_CONTENTS: ${{ secrets.LOCAL_PROPERTIES_CONTENTS }}
GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
VERSION_CODE: ${{ github.run_number }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: '17'
- name: Grant execute permission for gradlew
run: chmod +x ./3days/gradlew
- name: Create google-services.json
run: echo "$GOOGLE_SERVICES_JSON" > ./3days/app/google-services.json
- name: Create local.properties
run: echo "$LOCAL_PROPERTIES_CONTENTS" > ./3days/local.properties
- name: Decode and save keystore
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > ./3days/app/keystore.jks
- name: Verify keystore
run: |
ls -alh ./3days/app/
echo "Checking JKS format..."
keytool -list -v -keystore ./3days/app/keystore.jks -storetype JKS || echo "JKS format verification failed"
- name: Build release APK
run: |
cd ./3days
./gradlew assembleRelease -PversionCode=$VERSION_CODE
- name: Upload to Firebase App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
groups: 3days-tester
file: ./3days/app/build/outputs/apk/release/3days-release.apk
ํธ๋ฆฌ๊ฑฐ๋ develop ๋ธ๋์น์ push ํ์ ๋ ์ํ.
+ ์๋์ผ๋ก ๋์์ํค๊ธฐ ์ํ workflow_dispatch
workflow_dispatch๋ฅผ ์ถ๊ฐํ๋ฉด Github Action์์ develop ๋ธ๋์น์ push ํ์ง ์๋๋ผ๋ ์๋์ผ๋ก ์คํ์ํฌ ์ ์์
์ฐ์ ์์๋ฅผ ์ ๋ฆฌํ์๋ฉด,
1. ์ฝ๋ ์ฒดํฌ์์
2. JDK Setup
3. gradlew ๊ถํ ํ์ฉ
4. google-services.json ํ์ผ ์์ฑ
5. local.properties ํ์ผ ์์ฑ
6. keystore ๋์ฝ๋ & ํ์ผ ์์ฑ
7. ์์ฑ๋ keystore ํ์ผ verify (๊ฐ๋ ์ธ์ฝ๋ฉ์ ์๋ชปํ๊ณ ์งํํ๋ ๊ฒฝ์ฐ๊ฐ ์์ด์ ํ์ธ์ ์ํด ์ถ๊ฐ, ํฌ๊ฒ ํ์ํ์ง๋ ์์)
8. APK ๋น๋
9. Firebae Distribution
์ฌ๊ธฐ์ test, lint check ๋ฑ์ ์์ ์ ์ถ๊ฐํ ์ ์๋ค.
build.gradle(app)
val properties = gradleLocalProperties(rootDir, providers)
android {
...
defaultConfig {
...
// VersionCode = workflow Number
versionCode = if (project.hasProperty("versionCode")) {
project.property("versionCode").toString().toInt()
} else {
1
}
}
// APK ํ์ผ๋ช
์ค์
applicationVariants.all {
outputs.all {
val outputImpl = this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
val newFileName = "3days-${name}.apk"
outputImpl.outputFileName = newFileName
}
}
// Signing Configs ์ค์
signingConfigs {
create("release") {
keyAlias = properties["SIGNED_KEY_ALIAS"] as String?
keyPassword = properties["SIGNED_KEY_PASSWORD"] as String?
storeFile = properties["SIGNED_STORE_FILE"]?.let { file(it) }
storePassword = properties["SIGNED_STORE_PASSWORD"] as String?
}
}
// Build Types ์ค์
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
...
}
}
buildFeatures {
buildConfig = true
}
...
}
Path๋ค๋ง ์์ ์ ํ๋ก์ ํธ์ ๋ง๊ฒ ์ ์์ ํ๋ค๋ฉด ์ฝ๊ฒ CI/CD ๊ตฌ์ถ์ด ๊ฐ๋ฅํฉ๋๋ค!
๐ ๊ฒฐ๋ก
์ด์ GitHub develop Branch์ ์ฝ๋๋ฅผ ํธ์ํ๋ฉด, GitHub Actions๊ฐ ์๋์ผ๋ก ๋น๋๋ฅผ ์ํํ๊ณ Firebase Distribution์ ํตํด ํ ์คํฐ์๊ฒ APK๋ฅผ ๋ฐฐํฌํฉ๋๋ค. CI/CD๋ฅผ ํตํด ํจ์จ์ ์ธ ๊ฐ๋ฐ๊ณผ ๋ฐฐํฌ๊ฐ ๊ฐ๋ฅํด์ง๋๋ค. ๐
'[Etc.] > Git' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Git] Github Labels ํ ๋ฒ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ (0) | 2024.11.27 |
---|---|
[Git] CHANGELOG.md Generate (0) | 2024.10.26 |
[Git] AngularJS Commit Conventions (0) | 2024.10.26 |