생산성을 200% 올리는 비결, 알고 계신가요?🤔
오늘은 CI/CD 자동화와 Slack Bot을 활용한 배포 최적화 사례를 소개합니다!

⚡ 자동화의 필요성

불필요한 반복 작업은 생산성을 저하시키는데요.

  • 수동 배포로 인한 휴먼 에러 발생 (월 평균 2-3회)
  • 배포 과정에서의 커뮤니케이션 미스
  • 반복적인 배포 작업으로 인한 개발 시간 손실 (개발자당 주 평균 4-5시간)
  • 배포 상태 모니터링의 어려움

이러한 문제들을 해결하기 위해 CI/CD 자동화와 Slack Bot을 도입하게 되었습니다.

🛠️ CI/CD 환경 구축

Mac Studio와 Self-hosted GitHub Actions, Fastlane을 활용해 CI/CD 환경을 구축했습니다.

1.1 CI/CD 파이프라인구축

# .github/workflows/advanced-pipeline.yml
name: Advanced CI/CD Pipeline

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

env:
  DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
  APP_NAME: my-service

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Run tests with coverage
        run: npm run test:coverage
        
      - name: Upload coverage reports
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  security-scan:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Run SAST scan
        uses: github/codeql-action/analyze@v2
        with:
          languages: javascript
          
      - name: Run dependency scan
        run: |
          npm audit
          npx snyk test

  build:
    needs: [test, security-scan]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
        
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ env.DOCKER_REGISTRY }}/${{ env.APP_NAME }}:${{ github.sha }}
          cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.APP_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.APP_NAME }}:buildcache,mode=max

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: ${{ github.ref_name }}
    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2
          
      - name: Update ECS service
        run: |
          aws ecs update-service \
            --cluster ${{ secrets.ECS_CLUSTER }} \
            --service ${{ secrets.ECS_SERVICE }} \
            --force-new-deployment

이를 통해 개발과 배포 프로세스를 자동화하고 안정성을 확보했습니다.

2.1 Self-hosted GitHub Actions 설정

💡 시행착오

처음에는 GitHub-hosted runner를 사용했으나, iOS 빌드 시간이 평균 25분 정도 소요되어 개선이 필요했습니다.

Mac Studio를 self-hosted runner로 설정한 후 빌드 시간이 8분대로 단축되었습니다.

🔍 Self-hosted Runner 설정 예시

jobs:
  build:
    runs-on: [self-hosted, macOS]
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3
      
      # 빌드 성능 최적화를 위한 캐시 설정
      - name: Cache Pods
        uses: actions/cache@v3
        with:
          path: Pods
          key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
          
      # Xcode 빌드 최적화 설정
      - name: Build Optimization
        run: |
          defaults write com.apple.dt.XCBuild EnableSwiftBuildSystemIntegration 1
          defaults write com.apple.dt.XCBuild EnableBuildSystemProgressLogging 1

2.2 성능 최적화 결과

📊 도입 효과

  • 빌드 시간: 25분 → 8분 (68% 단축)
  • 빌드 성공률: 82% → 97%
  • 월간 CI/CD 비용: $200 → $50 (75% 절감)

보안 이슈 해결 사례

초기 구축 시 발생했던 주요 보안 이슈와 해결 방안

1. Webhook 보안 취약점

  • 문제: GitHub Actions Webhook URL 노출로 인한 무단 배포 위험
  • 해결: GitHub API 토큰 기반 인증으로 전환
  • 결과: 허가된 Slack 채널에서만 배포 가능하도록 제한

2. 시크릿 관리 이슈

  • 문제: GitHub Secrets에 직접 저장된 민감한 정보
  • 해결: HashiCorp Vault 도입으로 동적 시크릿 관리
  • 결과: 시크릿 정보 주기적 로테이션 자동화

🤖 Slack Bot을 활용한 배포 자동화

초기에는 터미널에서 배포를 수행했지만, 이를 Slack에서 직접 실행하고 모니터링할 수 있도록 'Rocket'이라는 Slack Bot을 개발했습니다.

Fastlane과 App Store Connect API를 연동해 앱 심사 및 배포 상태를 실시간으로 확인할 수 있습니다.

CI/CD 환경을 자동화하여 개발 생산성을 극대화하는 방법을 다룹니다.

🔍 Slack Bot 배포 명령어 처리 코드

// 배포 명령어 예시: /deploy main v1.2.3
app.command('/deploy', async ({ command, ack, respond }) => {
  await ack();
  try {
    const { branch, version } = parseCommand(command.text);
    await triggerGitHubActions(branch, version);
    // 배포 진행 상황을 실시간으로 공유
    startDeploymentMonitoring(command.channel_id, branch, version);
  } catch (error) {
    await respond(`❌ 배포 실패: ${error.message}`);
  }
});

인프라 자동화 모니터링

2.1 Terraform을 활용한 인프라 자동화

# main.tf
provider "aws" {
  region = "ap-northeast-2"
}

module "ecs_cluster" {
  source = "./modules/ecs"
  
  cluster_name = "prod-cluster"
  vpc_id      = module.vpc.vpc_id
  subnet_ids  = module.vpc.private_subnet_ids
  
  autoscaling_config = {
    min_size     = 2
    max_size     = 10
    target_value = 70
  }
  
  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

module "monitoring" {
  source = "./modules/monitoring"
  
  cluster_name = module.ecs_cluster.cluster_name
  alarm_topics = [aws_sns_topic.alerts.arn]
  
  thresholds = {
    cpu_utilization    = 80
    memory_utilization = 75
    error_rate        = 5
  }
}

3. 자동 장애 감지 및 대응

// src/services/incident/IncidentManager.ts
import { SlackNotifier } from '../slack';
import { MonitoringService } from '../monitoring';
import { DeploymentService } from '../deployment';

export class IncidentManager {
  private slack: SlackNotifier;
  private monitoring: MonitoringService;
  private deployment: DeploymentService;

  constructor() {
    this.slack = new SlackNotifier();
    this.monitoring = new MonitoringService();
    this.deployment = new DeploymentService();
  }

  public async handleIncident(alert: Alert): Promise<void> {
    // 1. 장애 정보 수집
    const incidentInfo = await this.gatherIncidentInfo(alert);
    
    // 2. Slack에 장애 알림 전송
    const thread = await this.slack.createIncidentThread(incidentInfo);
    
    // 3. 자동 조치 수행
    const actions = await this.determineAutomaticActions(incidentInfo);
    for (const action of actions) {
      try {
        await this.executeAction(action);
        await this.slack.updateIncidentThread(thread, {
          status: 'action_completed',
          action: action,
        });
      } catch (error) {
        await this.slack.updateIncidentThread(thread, {
          status: 'action_failed',
          action: action,
          error: error.message,
        });
      }
    }
    
    // 4. 담당자 호출
    if (this.shouldEscalate(incidentInfo)) {
      await this.escalateToOnCall(incidentInfo, thread);
    }
  }

  private async gatherIncidentInfo(alert: Alert) {
    const metrics = await this.monitoring.getDetailedMetrics(alert.period);
    const logs = await this.getLogs(alert.period);
    const recentDeployments = await this.deployment.getRecentDeployments();
    
    return {
      alert,
      metrics,
      logs,
      recentDeployments,
      timestamp: new Date(),
    };
  }

  private async determineAutomaticActions(info: IncidentInfo): Promise<Action[]> {
    const actions: Action[] = [];
    
    // CPU/메모리 과부하 대응
    if (this.isResourceOverload(info)) {
      actions.push({
        type: 'scale_up',
        target: info.alert.service,
        factor: 2,
      });
    }
    
    // 최근 배포 문제 대응
    if (this.isDeploymentRelated(info)) {
      actions.push({
        type: 'rollback',
        deploymentId: info.recentDeployments[0].id,
      });
    }
    
    return actions;
  }

  private async executeAction(action: Action): Promise<void> {
    switch (action.type) {
      case 'scale_up':
        await this.deployment.scaleService(action.target, action.factor);
        break;
      case 'rollback':
        await this.deployment.rollback(action.deploymentId);
        break;
      // 기타 자동화 액션 구현
    }
  }
}

4. 성능 최적화와 비용 절감

4-1. 빌드 캐시 최적화

// src/services/build/CacheOptimizer.ts
export class BuildCacheOptimizer {
  private readonly CACHE_BUCKET = process.env.CACHE_BUCKET;
  private readonly s3: AWS.S3;

  constructor() {
    this.s3 = new AWS.S3();
  }

  public async optimizeBuildCache(): Promise<void> {
    // 1. 캐시 사용량 분석
    const cacheStats = await this.analyzeCacheUsage();
    
    // 2. 불필요한 캐시 정리
    if (cacheStats.totalSize > this.MAX_CACHE_SIZE) {
      await this.cleanupOldCache(cacheStats);
    }
    
    // 3. 캐시 압축
    await this.compressCacheFiles();
    
    // 4. 캐시 메타데이터 업데이트
    await this.updateCacheMetadata();
  }

  private async analyzeCacheUsage(): Promise<CacheStats> {
    const objects = await this.s3.listObjectsV2({
      Bucket: this.CACHE_BUCKET,
    }).promise();
    
    return {
      totalSize: objects.Contents.reduce((acc, obj) => acc + obj.Size, 0),
      fileCount: objects.Contents.length,
      lastModified: new Date(Math.max(
        ...objects.Contents.map(obj => obj.LastModified.getTime())
      )),
    };
  }
}

결론

이번 CI/CD 자동화와 Slack Bot 도입을 통해 다음과 같은 중요한 교훈을 얻었습니다

  1. 자동화는 수단이지 목적이 아님
  • 자동화는 팀의 실제 문제를 해결하기 위한 도구로 사용되어야 함
  • 과도한 자동화는 오히려 복잡성만 증가시킬 수 있음
  1. 점진적 개선의 중요성
  • 한 번에 모든 것을 완벽하게 만들려고 하지 않음
  • 작은 개선부터 시작하여 점진적으로 발전시킴
  1. 팀원 모두의 참여가 중요
  • 자동화 도구는 팀원 모두가 이해하고 사용할 수 있어야 함
  • 정기적인 피드백과 개선 사항 반영이 필요

개선을 통해 생산성을 더욱 향상시키고, 더 나은 제품을 더 빠르게 제공할 수 있을 것으로 생각됩니다.

마지막으로, 이 모든 과정에서 가장 중요한 것은 '사람'임을 잊지 말아야 한다는것을 느꼈습니다.
자동화는 개발자들이 더 가치 있는 일에 집중할 수 있게 도와주는 도구라는것을 배웠습니다.
성장과 만족도를 높이는 방향으로 지속적인 개선을 이어나가야 할 것입니다.

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글