Flutter 앱의 컨테이너화와 CI/CD 파이프라인은 어떻게 구성하나요?
질문
Flutter 앱의 개발, 테스트, 배포를 위한 컨테이너화와 CI/CD 파이프라인을 구성하는 방법을 설명해주세요.
답변
Flutter 앱의 컨테이너화와 CI/CD(지속적 통합 및 지속적 배포) 파이프라인을 구성하면 개발 프로세스를 자동화하고 일관된 환경에서 앱을 빌드, 테스트 및 배포할 수 있습니다. 이를 통해 개발 팀의 효율성을 높이고 배포 품질을 향상시킬 수 있습니다.
1. Flutter 앱의 컨테이너화
컨테이너화는 애플리케이션과 그 종속성을 격리된 환경에서 실행할 수 있게 해주는 기술입니다. Docker를 사용하여 Flutter 개발 환경을 컨테이너화할 수 있습니다.
1.1 기본 Docker 이미지 생성
Flutter 앱 개발을 위한 기본 Dockerfile:
FROM ubuntu:20.04
# 환경 변수 설정
ENV DEBIAN_FRONTEND=noninteractive
ENV FLUTTER_HOME=/opt/flutter
ENV FLUTTER_VERSION=3.10.0
ENV PATH=$PATH:$FLUTTER_HOME/bin
# 필수 패키지 설치
RUN apt-get update && apt-get install -y \
curl \
git \
unzip \
xz-utils \
libglu1-mesa \
openjdk-11-jdk \
wget \
clang \
cmake \
ninja-build \
pkg-config \
libgtk-3-dev
# Flutter SDK 다운로드 및 설치
RUN git clone -b $FLUTTER_VERSION https://github.com/flutter/flutter.git $FLUTTER_HOME
# Flutter 사전 다운로드 및 설정
RUN flutter precache && \
flutter doctor && \
flutter config --no-analytics
# Android SDK 설정 (필요한 경우)
ENV ANDROID_HOME=/opt/android-sdk
RUN mkdir -p ${ANDROID_HOME}/cmdline-tools
RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-8092744_latest.zip -O cmdline-tools.zip && \
unzip cmdline-tools.zip -d ${ANDROID_HOME}/cmdline-tools && \
mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest && \
rm cmdline-tools.zip
# 환경 변수 추가
ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools
# Android SDK 구성 요소 설치
RUN yes | sdkmanager --licenses && \
sdkmanager "platforms;android-33" "build-tools;33.0.0" "platform-tools"
# 작업 디렉토리 설정
WORKDIR /app
# 기본 명령
CMD ["bash"]
1.2 개발용 Docker 컴포즈 구성
여러 서비스와 함께 개발 환경을 구성할 때 Docker Compose를 사용할 수 있습니다:
# docker-compose.yml
version: "3"
services:
flutter:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/app
- flutter_cache:/root/.pub-cache
ports:
- "8080:8080" # 웹 앱 접근용 (필요한 경우)
command: bash -c "flutter pub get && flutter run -d web-server --web-port=8080 --web-hostname=0.0.0.0"
# 백엔드 API (옵션)
api:
image: node:16
volumes:
- ./backend:/app
working_dir: /app
ports:
- "3000:3000"
command: bash -c "npm install && npm start"
volumes:
flutter_cache:
1.3 멀티 스테이지 빌드 구성
프로덕션 배포를 위한 멀티 스테이지 Dockerfile:
# 빌드 스테이지
FROM ubuntu:20.04 AS builder
ENV DEBIAN_FRONTEND=noninteractive
ENV FLUTTER_HOME=/opt/flutter
ENV PATH=$PATH:$FLUTTER_HOME/bin
# 필수 패키지 설치
RUN apt-get update && apt-get install -y \
curl \
git \
unzip \
xz-utils
# Flutter SDK 설치
RUN git clone -b stable https://github.com/flutter/flutter.git $FLUTTER_HOME
RUN flutter precache && \
flutter doctor -v
# 앱 소스 복사
WORKDIR /app
COPY . .
# 의존성 설치 및 앱 빌드
RUN flutter pub get
RUN flutter build web --release
# 서버 스테이지
FROM nginx:alpine
COPY --from=builder /app/build/web /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2. CI/CD 파이프라인 구성
CI/CD 파이프라인은 코드 변경사항을 자동으로 빌드, 테스트 및 배포하는 자동화된 프로세스입니다. 다양한 CI/CD 도구를 사용하여 Flutter 앱의 파이프라인을 구성할 수 있습니다.
2.1 GitHub Actions를 사용한 CI/CD
GitHub Actions를 사용하여 Flutter 앱의 CI/CD 파이프라인을 구성하는 예시:
# .github/workflows/flutter-ci.yml
name: Flutter CI/CD
on:
push:
branches: [main, dev]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: "3.10.0"
channel: "stable"
- name: Install dependencies
run: flutter pub get
- name: Analyze code
run: flutter analyze
- name: Run tests
run: flutter test
- name: Build Android APK
run: flutter build apk --release
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: app-release
path: build/app/outputs/flutter-apk/app-release.apk
deploy_android:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: app-release
path: build
- name: Deploy to Firebase App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
groups: testers
file: build/app-release.apk
# Play Store 배포 (선택 사항)
- name: Deploy to Play Store
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.example.myapp
releaseFiles: build/app-release.apk
track: internal
status: completed
2.2 GitLab CI/CD 구성
GitLab에서 Flutter 앱의 CI/CD 파이프라인 구성:
# .gitlab-ci.yml
image: cirrusci/flutter:latest
stages:
- test
- build
- deploy
variables:
ANDROID_COMPILE_SDK: "33"
ANDROID_BUILD_TOOLS: "33.0.0"
ANDROID_SDK_TOOLS: "7583922"
cache:
paths:
- .pub-cache/
before_script:
- flutter pub get
test:
stage: test
script:
- flutter analyze
- flutter test
build_android:
stage: build
script:
- flutter build apk --release
artifacts:
paths:
- build/app/outputs/flutter-apk/app-release.apk
build_ios:
stage: build
tags:
- ios
script:
- flutter build ios --release --no-codesign
artifacts:
paths:
- build/ios/iphoneos
deploy_firebase:
stage: deploy
script:
- apt-get update && apt-get install -y curl
- curl -sL https://firebase.tools | bash
- firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk
--app $FIREBASE_APP_ID
--token $FIREBASE_TOKEN
--groups "testers"
--release-notes "Build from GitLab CI"
only:
- main
when: manual
2.3 CircleCI 구성
CircleCI에서 Flutter 앱의 CI/CD 파이프라인 구성:
# .circleci/config.yml
version: 2.1
orbs:
flutter: circleci/flutter@2.0
jobs:
build_and_test:
docker:
- image: cimg/base:2023.03
steps:
- checkout
- flutter/install_sdk:
version: 3.10.0
- flutter/install_android_tools
- run:
name: Install dependencies
command: flutter pub get
- run:
name: Analyze code
command: flutter analyze
- run:
name: Run tests
command: flutter test
- run:
name: Build Android APK
command: flutter build apk --release
- store_artifacts:
path: build/app/outputs/flutter-apk/app-release.apk
- persist_to_workspace:
root: .
paths:
- build/app/outputs/flutter-apk/app-release.apk
deploy_to_firebase:
docker:
- image: cimg/node:16.15
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Deploy to Firebase App Distribution
command: |
npm install -g firebase-tools
firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk \
--app $FIREBASE_APP_ID \
--token $FIREBASE_TOKEN \
--groups "testers" \
--release-notes "Build from CircleCI"
workflows:
version: 2
build_test_deploy:
jobs:
- build_and_test
- deploy_to_firebase:
requires:
- build_and_test
filters:
branches:
only: main
3. 빌드 최적화 및 자동화 전략
3.1 캐싱 전략
CI/CD 파이프라인에서 빌드 시간을 단축하기 위한 캐싱 전략:
# GitHub Actions 캐싱 예시
steps:
- uses: actions/checkout@v3
- name: Cache Flutter dependencies
uses: actions/cache@v3
with:
path: |
~/.pub-cache
.dart_tool/
build/
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-flutter-
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: "3.10.0"
channel: "stable"
3.2 병렬 빌드
여러 플랫폼을 동시에 빌드하기 위한 병렬 작업 구성:
# GitHub Actions 병렬 빌드 예시
jobs:
analyze_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: flutter analyze
- run: flutter test
build_android:
needs: analyze_and_test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: flutter build apk --release
- uses: actions/upload-artifact@v3
with:
name: android-release
path: build/app/outputs/flutter-apk/app-release.apk
build_ios:
needs: analyze_and_test
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: flutter build ios --release --no-codesign
- uses: actions/upload-artifact@v3
with:
name: ios-release
path: build/ios/iphoneos
build_web:
needs: analyze_and_test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: flutter build web --release
- uses: actions/upload-artifact@v3
with:
name: web-release
path: build/web
3.3 환경별 빌드 구성
개발, 스테이징, 프로덕션 환경에 따른 빌드 구성:
// lib/config/environment.dart
enum Environment { dev, staging, prod }
class EnvironmentConfig {
static Environment environment = Environment.dev;
static String get apiUrl {
switch (environment) {
case Environment.dev:
return 'https://dev-api.example.com';
case Environment.staging:
return 'https://staging-api.example.com';
case Environment.prod:
return 'https://api.example.com';
}
}
static bool get enableAnalytics {
return environment == Environment.prod;
}
static bool get showDebugBanner {
return environment != Environment.prod;
}
}
CI/CD 파이프라인에서 환경별 빌드 명령:
# 환경별 빌드 명령 예시
steps:
- name: Build for Development
if: github.ref == 'refs/heads/dev'
run: |
echo "const String ENVIRONMENT = 'dev';" > lib/env.dart
flutter build apk --release --dart-define=ENVIRONMENT=dev
- name: Build for Staging
if: github.ref == 'refs/heads/staging'
run: |
echo "const String ENVIRONMENT = 'staging';" > lib/env.dart
flutter build apk --release --dart-define=ENVIRONMENT=staging
- name: Build for Production
if: github.ref == 'refs/heads/main'
run: |
echo "const String ENVIRONMENT = 'prod';" > lib/env.dart
flutter build apk --release --dart-define=ENVIRONMENT=prod
4. 테스트 자동화 전략
4.1 단위 테스트 및 위젯 테스트 자동화
CI/CD 파이프라인에서 단위 테스트와 위젯 테스트 실행:
# 테스트 자동화 예시
steps:
- name: Run unit tests
run: flutter test test/unit
- name: Run widget tests
run: flutter test test/widget
- name: Generate test coverage
run: flutter test --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage/lcov.info
4.2 통합 테스트 자동화
Firebase Test Lab을 사용한 통합 테스트 자동화:
# Firebase Test Lab 통합 테스트 예시
steps:
- name: Build debug APK
run: flutter build apk --debug
- name: Build instrumentation test APK
run: pushd android && ./gradlew app:assembleAndroidTest && popd
- name: Run Firebase Test Lab
uses: asadmansr/Firebase-Test-Lab-Action@v1.0
with:
arg-spec: "tests.yml:android-pixel-4"
env:
SERVICE_ACCOUNT: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
4.3 UI/시각적 회귀 테스트
Golden 테스트를 사용한 UI 회귀 테스트:
// test/golden/button_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/widgets/custom_button.dart';
void main() {
testWidgets('CustomButton golden test', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: CustomButton(
text: 'Click Me',
onPressed: () {},
),
),
),
),
);
await expectLater(
find.byType(CustomButton),
matchesGoldenFile('golden/custom_button.png'),
);
});
}
CI/CD 파이프라인에서 Golden 테스트 실행:
steps:
- name: Run golden tests
run: flutter test test/golden
5. 배포 자동화 전략
5.1 Firebase App Distribution
Firebase App Distribution을 통한 테스트 버전 자동 배포:
# Firebase App Distribution 배포 예시
steps:
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: app-release
path: build
- name: Deploy to Firebase App Distribution
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
groups: testers,beta-testers
file: build/app-release.apk
releaseNotes: |
Changes in this build:
- ${{ github.event.head_commit.message }}
5.2 앱 스토어 배포 자동화
Play Store와 App Store 자동 배포:
# Play Store 배포 예시
steps:
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
- name: Deploy to Play Store
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.example.myapp
releaseFiles: build/app-release.apk
track: production
status: completed
# App Store 배포 예시 (fastlane 사용)
steps:
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
- name: Install Fastlane
run: gem install fastlane
- name: Deploy to App Store
env:
APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
run: |
cd ios
echo $APP_STORE_CONNECT_API_KEY_CONTENT > api_key.json
fastlane release
5.3 웹 앱 배포 자동화
Firebase Hosting을 사용한 웹 앱 자동 배포:
# Firebase Hosting 웹 앱 배포 예시
steps:
- name: Download web artifact
uses: actions/download-artifact@v3
with:
name: web-release
path: build/web
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: "${{ secrets.GITHUB_TOKEN }}"
firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT }}"
channelId: live
projectId: my-flutter-app
6. 모니터링 및 피드백 루프
6.1 에러 모니터링 통합
Firebase Crashlytics 및 Sentry 통합:
// main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Crashlytics 설정
if (!kDebugMode) {
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
}
// Sentry 설정
await SentryFlutter.init(
(options) {
options.dsn = 'https://example@sentry.io/123456';
options.tracesSampleRate = 1.0;
},
appRunner: () => runApp(MyApp()),
);
}
6.2 분석 및 사용자 피드백 통합
Firebase Analytics 및 사용자 피드백 통합:
// analytics_service.dart
import 'package:firebase_analytics/firebase_analytics.dart';
class AnalyticsService {
final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
Future<void> logAppOpen() async {
await _analytics.logAppOpen();
}
Future<void> logLogin({required String method}) async {
await _analytics.logLogin(loginMethod: method);
}
Future<void> logScreenView({required String screenName}) async {
await _analytics.logScreenView(screenName: screenName);
}
Future<void> logUserFeedback({
required String feedbackType,
required String feedback,
}) async {
await _analytics.logEvent(
name: 'user_feedback',
parameters: {
'type': feedbackType,
'feedback': feedback,
},
);
}
}
6.3 CI/CD 성능 모니터링
CI/CD 파이프라인 성능 모니터링 및 최적화:
# 빌드 시간 모니터링 예시
steps:
- name: Start build time monitoring
run: echo "BUILD_START_TIME=$(date +%s)" >> $GITHUB_ENV
# 빌드 단계들...
- name: Calculate build time
run: |
END_TIME=$(date +%s)
BUILD_TIME=$((END_TIME - ${{ env.BUILD_START_TIME }}))
echo "Build completed in $BUILD_TIME seconds"
# 빌드 시간 로깅 (예: Google Sheets API, 데이터베이스 등)
curl -X POST \
-H "Content-Type: application/json" \
-d "{\"build_id\":\"${{ github.run_id }}\",\"time\":$BUILD_TIME,\"branch\":\"${{ github.ref }}\"}" \
${{ secrets.BUILD_METRICS_ENDPOINT }}
요약
Flutter 앱의 컨테이너화와 CI/CD 파이프라인 구성을 위한, 핵심적인 단계와 전략은 다음과 같습니다:
컨테이너화: Docker를 사용하여 Flutter 개발 환경과 빌드 프로세스를 컨테이너화하여 일관된 개발 및 빌드 환경 구성
CI/CD 파이프라인 구성: GitHub Actions, GitLab CI/CD, CircleCI 등의 도구를 활용하여 자동화된 빌드, 테스트, 배포 파이프라인 구성
빌드 최적화: 캐싱, 병렬 빌드, 환경별 설정 등을 통해 빌드 프로세스 최적화
테스트 자동화: 단위 테스트, 위젯 테스트, 통합 테스트, UI 회귀 테스트 등 자동화된 테스트 프로세스 구성
배포 자동화: Firebase App Distribution, Play Store, App Store, 웹 호스팅 등 다양한 플랫폼에 자동 배포 구성
모니터링 및 피드백: 에러 모니터링, 사용자 분석, CI/CD 성능 모니터링 등을 통한 지속적인 개선
이러한 CI/CD 파이프라인을 구축하면 개발 팀은 반복적인 작업에서 벗어나 핵심 기능 개발에 집중할 수 있으며, 빠른 릴리스 주기와 높은 품질의 앱을 유지할 수 있습니다.