Docker 완벽 가이드

Dockerfile, 이미지 최적화, 컨테이너 관리

DevOps중급
8시간
5개 항목
학습 진행률0 / 5 (0%)

학습 항목

1. TypeScript
Docker|CI/CD|파이프라인|완벽|가이드
퀴즈튜토리얼
2. JavaScript
Dockerfile|최적화|기법|완벽|가이드
퀴즈튜토리얼
3. Python
Docker|컨테이너|기초|입문|가이드
퀴즈튜토리얼
4. TypeScript
Docker|기초부터|실전까지|완벽|가이드
퀴즈튜토리얼
5. Docker
Docker|로깅|모니터링|완벽|가이드
퀴즈튜토리얼
1 / 5

이미지 로딩 중...

Docker CI/CD 파이프라인 완벽 가이드 - 슬라이드 1/13

Docker CI/CD 파이프라인 완벽 가이드

Docker를 활용한 CI/CD 파이프라인 구축 방법을 실전 예제와 함께 소개합니다. 초급 개발자도 쉽게 따라할 수 있도록 단계별로 설명하며, 실무에서 바로 적용 가능한 베스트 프랙티스를 제공합니다.


목차

  1. Docker 컨테이너 기초
  2. Dockerfile 작성 전략
  3. Docker Compose로 멀티 컨테이너 관리
  4. GitHub Actions로 CI 파이프라인 구축
  5. Docker 이미지 레지스트리 활용
  6. CD 파이프라인 구축
  7. 환경별 설정 관리
  8. 파이프라인 모니터링과 알림

1. Docker 컨테이너 기초

시작하며

여러분이 팀 프로젝트를 진행할 때 "내 컴퓨터에서는 되는데요"라는 말을 들어본 적 있나요? Node.js 버전이 다르거나, 데이터베이스 설정이 달라서 같은 코드인데도 다른 결과가 나오는 상황 말이죠.

이런 문제는 실제 개발 현장에서 정말 자주 발생합니다. 개발자마다 로컬 환경이 다르고, 개발 환경과 프로덕션 환경이 달라서 예상치 못한 버그가 발생하기도 합니다.

이런 환경 차이로 인한 문제는 디버깅 시간을 크게 증가시키고, 배포 과정에서 심각한 장애를 일으킬 수 있습니다. 바로 이럴 때 필요한 것이 Docker 컨테이너입니다.

Docker를 사용하면 애플리케이션과 그 실행 환경을 하나의 패키지로 묶어서 어디서나 동일하게 실행할 수 있습니다.

개요

간단히 말해서, Docker 컨테이너는 애플리케이션과 그 실행에 필요한 모든 것을 담은 격리된 실행 환경입니다. 마치 가상머신처럼 독립적으로 실행되지만, 훨씬 가볍고 빠르게 시작할 수 있습니다.

왜 이 기술이 필요한지 실무 관점에서 보면, 개발 환경 설정에 드는 시간을 크게 줄일 수 있고, "내 컴퓨터에서는 되는데" 문제를 완전히 해결할 수 있습니다. 예를 들어, 새로운 팀원이 합류했을 때 복잡한 설치 과정 없이 docker run 명령 하나로 전체 개발 환경을 즉시 구성할 수 있습니다.

기존에는 각자 로컬에 Node.js, PostgreSQL, Redis 등을 설치하고 설정 파일을 맞춰야 했다면, 이제는 Docker를 통해 모든 팀원이 정확히 동일한 환경에서 개발할 수 있습니다. Docker의 핵심 특징은 격리성(각 컨테이너는 독립적으로 실행), 이식성(어떤 환경에서도 동일하게 실행), 그리고 효율성(가상머신보다 훨씬 가볍고 빠름)입니다.

이러한 특징들이 현대적인 DevOps 워크플로우에서 Docker가 필수 도구가 된 이유입니다.

코드 예제

// Node.js 애플리케이션을 Docker 컨테이너로 실행하기
// 먼저 Docker 이미지를 빌드합니다
docker build -t my-node-app:1.0 .

// 컨테이너를 실행합니다 (포트 3000을 호스트의 3000으로 매핑)
docker run -d \
  --name my-app-container \
  -p 3000:3000 \
  -e NODE_ENV=development \
  my-node-app:1.0

// 실행 중인 컨테이너 확인
docker ps

// 컨테이너 로그 확인 (디버깅에 유용)
docker logs -f my-app-container

// 컨테이너 내부에 접속하여 문제 해결
docker exec -it my-app-container /bin/sh

설명

이것이 하는 일: 위 명령어들은 Docker 이미지를 빌드하고, 그 이미지로부터 컨테이너를 생성하여 실행하는 전체 과정을 보여줍니다. 첫 번째로, docker build 명령은 현재 디렉토리의 Dockerfile을 읽어서 이미지를 생성합니다.

-t 옵션으로 이미지에 이름과 태그를 붙여서 나중에 쉽게 참조할 수 있게 합니다. 이 과정에서 Dockerfile에 정의된 각 단계가 레이어로 저장되어, 다음 빌드 시 변경되지 않은 부분은 캐시를 재사용하여 빌드 속도가 크게 향상됩니다.

그 다음으로, docker run 명령이 실제 컨테이너를 생성하고 실행합니다. -d 옵션은 백그라운드에서 실행하게 하고, -p 3000:3000은 컨테이너의 3000 포트를 호스트의 3000 포트와 연결합니다.

-e 옵션으로 환경변수를 설정할 수 있어서, 같은 이미지로 다른 환경 설정을 가진 컨테이너를 실행할 수 있습니다. 마지막으로, docker logsdocker exec 명령은 컨테이너를 모니터링하고 디버깅하는 데 사용됩니다.

docker logs -f는 실시간으로 로그를 보여주고, docker exec -it는 실행 중인 컨테이너 내부에 쉘로 접속하여 파일 시스템을 확인하거나 명령을 직접 실행할 수 있게 합니다. 여러분이 이 명령어들을 사용하면 개발 환경을 몇 초 만에 구성할 수 있고, 팀 전체가 정확히 동일한 환경에서 작업할 수 있습니다.

또한 프로덕션 배포 시에도 동일한 이미지를 사용하므로 환경 차이로 인한 문제가 발생하지 않습니다.

실전 팁

💡 컨테이너 이름(--name)을 항상 명시하세요. 이름이 없으면 랜덤한 이름이 할당되어 나중에 관리하기 어렵습니다.

💡 개발 중에는 -v $(pwd):/app 옵션으로 로컬 코드를 마운트하면 코드 변경이 즉시 반영되어 빌드를 반복할 필요가 없습니다.

💡 docker ps -a로 중지된 컨테이너까지 확인하고, docker system prune으로 사용하지 않는 컨테이너와 이미지를 정기적으로 정리하여 디스크 공간을 확보하세요.

💡 프로덕션 환경에서는 --restart unless-stopped 옵션을 추가하여 컨테이너가 예기치 않게 종료되어도 자동으로 재시작되도록 설정하세요.

💡 민감한 정보(API 키, 데이터베이스 비밀번호)는 환경변수로 전달하되, .env 파일과 Docker Secrets를 활용하여 안전하게 관리하세요.


2. Dockerfile 작성 전략

시작하며

여러분이 Docker 이미지를 빌드할 때 10분 이상 걸리거나, 이미지 크기가 1GB를 넘어서 배포가 느린 경험을 해본 적 있나요? 아니면 빌드할 때마다 모든 의존성을 다시 설치해서 시간이 오래 걸리는 상황이었나요?

이런 문제는 Dockerfile을 효율적으로 작성하지 않았을 때 발생합니다. 비효율적인 Dockerfile은 빌드 시간을 늘리고, 이미지 크기를 불필요하게 크게 만들며, 캐시를 제대로 활용하지 못하게 합니다.

특히 CI/CD 파이프라인에서 매번 빌드할 때마다 이런 비효율이 누적되면 팀 전체의 생산성이 크게 떨어집니다. 바로 이럴 때 필요한 것이 최적화된 Dockerfile 작성 전략입니다.

레이어 캐싱을 활용하고, 멀티스테이지 빌드를 사용하여 빌드 시간을 단축하고 이미지 크기를 최소화할 수 있습니다.

개요

간단히 말해서, Dockerfile은 Docker 이미지를 만들기 위한 레시피입니다. 각 명령어가 하나의 레이어를 생성하며, 이 레이어들이 쌓여서 최종 이미지가 됩니다.

왜 이것이 중요한지 실무 관점에서 보면, Dockerfile 작성 순서와 방법에 따라 빌드 시간이 10배 이상 차이날 수 있고, 이미지 크기도 몇 배 차이가 납니다. 예를 들어, 자주 변경되지 않는 의존성 설치 부분을 먼저 작성하면 캐시가 유지되어 코드만 변경했을 때 몇 초 만에 빌드가 완료됩니다.

기존에는 모든 것을 한 번에 설치하고 빌드하는 단순한 Dockerfile을 작성했다면, 이제는 멀티스테이지 빌드를 사용하여 빌드 도구는 최종 이미지에 포함시키지 않고 실행에 필요한 파일만 남길 수 있습니다. Dockerfile의 핵심 특징은 레이어 캐싱(변경되지 않은 레이어는 재사용), 멀티스테이지 빌드(빌드와 실행 환경 분리), 그리고 최소 이미지 사용(alpine 등 경량 베이스 이미지 활용)입니다.

이러한 전략들이 빠르고 안전한 CI/CD 파이프라인의 기초가 됩니다.

코드 예제

# 멀티스테이지 빌드: 빌드 단계
FROM node:18-alpine AS builder

# 작업 디렉토리 설정
WORKDIR /app

# 의존성 파일만 먼저 복사 (캐시 활용)
COPY package*.json ./

# 의존성 설치
RUN npm ci --only=production

# 소스 코드 복사
COPY . .

# 애플리케이션 빌드
RUN npm run build

# 프로덕션 단계: 실행에 필요한 것만 포함
FROM node:18-alpine

WORKDIR /app

# 빌드 단계에서 필요한 파일만 복사
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./

# 비-root 유저로 실행 (보안)
USER node

# 헬스체크 추가
HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js || exit 1

# 애플리케이션 실행
CMD ["node", "dist/index.js"]

설명

이것이 하는 일: 위 Dockerfile은 두 단계로 나뉘어 있습니다. 첫 번째 단계에서는 애플리케이션을 빌드하고, 두 번째 단계에서는 실행에 필요한 파일만 가져와서 최종 이미지를 만듭니다.

첫 번째로, builder 단계에서는 node:18-alpine을 베이스 이미지로 사용합니다. alpine은 일반 이미지보다 10배 이상 작아서 빌드와 배포 속도가 빠릅니다.

package*.json 파일을 소스 코드보다 먼저 복사하는 것이 핵심인데, 이렇게 하면 소스 코드가 변경되어도 의존성 설치 레이어는 캐시에서 재사용됩니다. 의존성 설치는 시간이 오래 걸리는 작업이므로 이 최적화로 빌드 시간을 크게 단축할 수 있습니다.

그 다음으로, 프로덕션 단계에서는 builder 단계에서 생성된 node_modulesdist 폴더만 복사합니다. 이렇게 하면 빌드 도구, 소스 코드, 테스트 파일 등은 최종 이미지에 포함되지 않아 이미지 크기가 몇백 MB 줄어듭니다.

또한 USER node 명령으로 root가 아닌 일반 유저로 실행하여 보안을 강화합니다. 마지막으로, HEALTHCHECK를 추가하여 컨테이너가 정상적으로 실행 중인지 자동으로 확인합니다.

오케스트레이션 도구(Kubernetes, Docker Swarm)는 이 헬스체크 결과를 보고 문제가 있는 컨테이너를 자동으로 재시작합니다. 여러분이 이 Dockerfile 패턴을 사용하면 빌드 시간이 몇 분에서 몇 초로 단축되고, 이미지 크기는 절반 이하로 줄어듭니다.

또한 프로덕션 환경에서 더 안전하고 빠르게 실행되는 컨테이너를 만들 수 있습니다.

실전 팁

💡 .dockerignore 파일을 반드시 작성하세요. node_modules, .git, *.log 같은 불필요한 파일을 제외하면 빌드 컨텍스트 전송 시간이 크게 줄어듭니다.

💡 COPY 명령을 여러 번 나누어 작성하면 캐시를 더 효과적으로 활용할 수 있습니다. 자주 변경되지 않는 것부터 순서대로 복사하세요.

💡 RUN 명령을 연결할 때는 &&로 묶어서 하나의 레이어로 만드세요. RUN apt-get update && apt-get install -y pkg 이렇게 하면 레이어 개수가 줄어듭니다.

💡 프로덕션 이미지에는 개발 의존성을 포함하지 마세요. npm ci --only=production을 사용하여 devDependencies를 제외하세요.

💡 베이스 이미지 버전을 명시하세요(node:18이 아닌 node:18.19.0-alpine). 이렇게 하면 예상치 못한 업데이트로 인한 문제를 방지할 수 있습니다.


3. Docker Compose로 멀티 컨테이너 관리

시작하며

여러분이 웹 애플리케이션을 개발할 때 애플리케이션 서버, 데이터베이스, Redis 캐시, 그리고 Nginx 리버스 프록시를 모두 따로 실행하고 연결하느라 시간을 낭비한 적 있나요? 각 컨테이너의 네트워크 설정을 맞추고, 실행 순서를 신경 쓰고, 환경변수를 일일이 전달하는 작업은 정말 번거롭습니다.

이런 문제는 마이크로서비스 아키텍처나 복잡한 애플리케이션에서 더욱 심각해집니다. 컨테이너가 많아질수록 관리해야 할 설정도 늘어나고, 팀원마다 다르게 실행하면 또 다시 환경 차이 문제가 발생합니다.

수동으로 여러 docker run 명령을 실행하는 것은 실수하기 쉽고 자동화하기도 어렵습니다. 바로 이럴 때 필요한 것이 Docker Compose입니다.

단 하나의 YAML 파일로 여러 컨테이너를 정의하고, 한 번의 명령으로 전체 애플리케이션 스택을 실행할 수 있습니다.

개요

간단히 말해서, Docker Compose는 여러 컨테이너로 구성된 애플리케이션을 정의하고 실행하기 위한 도구입니다. docker-compose.yml 파일에 모든 서비스를 선언적으로 정의하면, docker-compose up 명령 하나로 전체를 실행할 수 있습니다.

왜 이것이 필요한지 실무 관점에서 보면, 로컬 개발 환경을 프로덕션과 최대한 유사하게 구성할 수 있고, 새로운 개발자가 프로젝트에 참여할 때 복잡한 설정 과정 없이 바로 시작할 수 있습니다. 예를 들어, 프론트엔드, 백엔드, 데이터베이스, 캐시 서버를 모두 포함한 전체 스택을 한 번에 실행하여 통합 테스트를 수행할 수 있습니다.

기존에는 각 서비스를 개별적으로 실행하고 IP 주소와 포트를 수동으로 연결했다면, 이제는 Docker Compose가 자동으로 네트워크를 생성하고 서비스 이름으로 서로 통신할 수 있게 해줍니다. Docker Compose의 핵심 특징은 선언적 설정(YAML로 원하는 상태를 정의), 서비스 디스커버리(컨테이너들이 서비스 이름으로 통신), 그리고 의존성 관리(서비스 시작 순서 제어)입니다.

이러한 기능들이 개발 환경 구성을 극적으로 단순화합니다.

코드 예제

# docker-compose.yml
version: '3.8'

services:
  # 백엔드 API 서버
  api:
    build: ./api
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://user:pass@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache
    volumes:
      - ./api:/app  # 코드 변경 즉시 반영
      - /app/node_modules  # node_modules는 컨테이너 것 사용
    command: npm run dev

  # PostgreSQL 데이터베이스
  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=myapp
    volumes:
      - postgres_data:/var/lib/postgresql/data  # 데이터 영속성
    ports:
      - "5432:5432"

  # Redis 캐시
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  # Nginx 리버스 프록시
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - api

volumes:
  postgres_data:  # 명명된 볼륨으로 데이터 보존

설명

이것이 하는 일: 위 설정 파일은 4개의 서비스(API, 데이터베이스, 캐시, 프록시)로 구성된 전체 애플리케이션 스택을 정의합니다. 첫 번째로, 각 서비스는 독립적으로 정의되지만 Docker Compose가 자동으로 생성하는 네트워크를 통해 연결됩니다.

API 서비스는 DATABASE_URL에서 db:5432를 사용하는데, 여기서 db는 PostgreSQL 서비스의 이름입니다. Docker Compose는 자동으로 서비스 이름을 DNS 이름으로 등록하여, 컨테이너들이 IP 주소를 몰라도 서비스 이름으로 통신할 수 있게 합니다.

그 다음으로, depends_on 설정은 서비스 시작 순서를 제어합니다. API 서비스는 dbcache가 먼저 시작된 후에 실행됩니다.

이렇게 하면 데이터베이스가 준비되기 전에 API가 연결을 시도하여 실패하는 문제를 방지할 수 있습니다. 개발 환경에서는 volumes를 사용하여 로컬 코드를 마운트하므로, 코드를 수정하면 hot-reload가 작동하여 컨테이너를 재시작할 필요가 없습니다.

마지막으로, 명명된 볼륨(postgres_data)을 사용하여 데이터베이스 데이터를 영속적으로 보관합니다. 컨테이너를 삭제하고 다시 생성해도 데이터는 유지되므로, 개발 중에 데이터를 잃어버리는 일이 없습니다.

여러분이 이 설정을 사용하면 docker-compose up 명령 하나로 전체 개발 환경이 실행되고, docker-compose down으로 모든 것을 깔끔하게 정리할 수 있습니다. 팀 전체가 동일한 설정을 사용하므로 "내 컴퓨터에서는 되는데" 문제가 완전히 사라집니다.

실전 팁

💡 개발 환경에서는 docker-compose up--build 옵션을 추가하여 코드 변경 시 이미지를 자동으로 재빌드하세요.

💡 프로덕션 설정은 별도의 docker-compose.prod.yml 파일로 분리하고, docker-compose -f docker-compose.yml -f docker-compose.prod.yml up으로 오버라이드하세요.

💡 민감한 정보는 .env 파일에 저장하고 Git에 커밋하지 마세요. .env.example 파일로 필요한 변수 목록만 공유하세요.

💡 docker-compose logs -f service_name으로 특정 서비스의 로그만 실시간으로 확인할 수 있습니다.

💡 데이터베이스 초기화 스크립트는 ./init.sql:/docker-entrypoint-initdb.d/init.sql로 마운트하면 컨테이너 시작 시 자동 실행됩니다.


4. GitHub Actions로 CI 파이프라인 구축

시작하며

여러분이 코드를 푸시할 때마다 수동으로 테스트를 실행하고, 빌드가 성공하는지 확인하고, 코드 스타일을 체크하는 작업을 반복하고 있나요? 아니면 팀원이 PR을 올렸을 때 테스트를 깜빡하고 머지해서 메인 브랜치가 깨진 경험이 있나요?

이런 문제는 수동 프로세스에 의존할 때 필연적으로 발생합니다. 사람은 실수하기 마련이고, 반복적인 작업은 지루해서 건너뛰기 쉽습니다.

특히 팀이 커질수록 모든 PR을 일일이 검증하는 것은 불가능에 가까워집니다. 테스트되지 않은 코드가 프로덕션에 배포되면 심각한 장애로 이어질 수 있습니다.

바로 이럴 때 필요한 것이 CI(Continuous Integration) 파이프라인입니다. GitHub Actions를 사용하면 코드가 푸시될 때마다 자동으로 테스트, 빌드, 그리고 다양한 검증을 수행하여 품질을 보장할 수 있습니다.

개요

간단히 말해서, CI 파이프라인은 코드 변경이 발생할 때마다 자동으로 실행되는 일련의 검증 단계입니다. 테스트 실행, 코드 품질 검사, 빌드 확인 등이 자동으로 이루어져 문제를 조기에 발견할 수 있습니다.

왜 이것이 필요한지 실무 관점에서 보면, 버그를 프로덕션이 아닌 개발 단계에서 잡을 수 있고, 코드 리뷰 시 기계적인 검증은 자동화되어 리뷰어가 비즈니스 로직에 집중할 수 있습니다. 예를 들어, PR이 올라오면 자동으로 모든 테스트가 실행되고, 커버리지가 체크되고, 보안 취약점이 스캔되어 리뷰어는 결과만 확인하면 됩니다.

기존에는 각자 로컬에서 테스트를 실행하고 "테스트 통과했어요"라고 말로 전달했다면, 이제는 CI가 객관적인 증거를 제공하고, 모든 검증이 통과해야만 머지가 가능하도록 강제할 수 있습니다. CI 파이프라인의 핵심 특징은 자동화(사람의 개입 없이 실행), 빠른 피드백(문제를 즉시 발견), 그리고 일관성(모든 코드 변경에 동일한 기준 적용)입니다.

이러한 특징들이 코드 품질을 지속적으로 높은 수준으로 유지하게 해줍니다.

코드 예제

# .github/workflows/ci.yml
name: CI Pipeline

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

jobs:
  # 테스트와 린트 실행
  test:
    runs-on: ubuntu-latest

    services:
      # 테스트용 PostgreSQL 컨테이너
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'  # 의존성 캐싱으로 속도 향상

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test -- --coverage
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json

  # Docker 이미지 빌드
  build:
    runs-on: ubuntu-latest
    needs: test  # 테스트 통과 후에만 빌드

    steps:
      - uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Build Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: false  # CI에서는 푸시하지 않음
          tags: myapp:${{ github.sha }}
          cache-from: type=gha  # GitHub Actions 캐시 활용
          cache-to: type=gha,mode=max

설명

이것이 하는 일: 위 워크플로우는 코드가 푸시되거나 PR이 생성될 때 자동으로 실행되어 코드의 품질과 정상 작동을 검증합니다. 첫 번째로, on 섹션은 워크플로우가 언제 실행될지 정의합니다.

maindevelop 브랜치에 푸시되거나, 이 브랜치들을 대상으로 하는 PR이 생성될 때 트리거됩니다. 이렇게 하면 중요한 브랜치에 문제가 있는 코드가 머지되는 것을 방지할 수 있습니다.

그 다음으로, test 잡에서는 실제 애플리케이션 환경과 유사하게 PostgreSQL 서비스 컨테이너를 함께 실행합니다. services 섹션에 정의된 컨테이너는 자동으로 시작되고, 헬스체크가 통과한 후에 테스트가 실행됩니다.

actions/setup-node@v3cache: 'npm' 옵션은 node_modules 설치를 캐시하여 다음 실행 시 몇 분을 절약합니다. 테스트가 완료되면 커버리지 리포트를 Codecov에 업로드하여 PR에 커버리지 변화를 자동으로 코멘트로 표시합니다.

마지막으로, build 잡은 needs: test로 테스트가 통과해야만 실행됩니다. Docker Buildx를 사용하여 멀티 플랫폼 이미지를 빌드할 수 있고, cache-from/to로 GitHub Actions 캐시를 활용하여 빌드 시간을 크게 단축합니다.

CI 단계에서는 이미지를 레지스트리에 푸시하지 않고 빌드만 검증합니다. 여러분이 이 CI 파이프라인을 사용하면 PR을 올리는 순간 자동으로 모든 검증이 실행되고, 문제가 있으면 머지 전에 바로 알 수 있습니다.

또한 GitHub의 브랜치 보호 규칙과 연동하여 CI가 통과하지 않으면 머지를 막을 수 있습니다.

실전 팁

💡 needs 키워드로 잡 간 의존성을 정의하여 병렬 실행으로 시간을 절약하세요. 독립적인 잡들은 동시에 실행됩니다.

💡 Secrets를 사용하여 API 키나 토큰을 안전하게 저장하고, ${{ secrets.SECRET_NAME }}으로 워크플로우에서 사용하세요.

💡 if: github.event_name == 'pull_request' 조건을 사용하여 특정 이벤트에서만 실행되는 스텝을 만들 수 있습니다.

💡 매트릭스 빌드로 여러 Node.js 버전이나 OS에서 동시에 테스트하여 호환성을 확인하세요: strategy: matrix: node-version: [16, 18, 20]

💡 워크플로우가 실패하면 Slack이나 Discord로 알림을 보내도록 설정하여 즉시 대응할 수 있게 하세요.


5. Docker 이미지 레지스트리 활용

시작하며

여러분이 로컬에서 빌드한 Docker 이미지를 프로덕션 서버에 배포하려고 할 때, 이미지를 어떻게 전달하나요? USB로 옮기거나 파일로 저장해서 전송하는 방법은 비현실적이고 비효율적입니다.

이런 문제는 팀 협업에서 더욱 심각해집니다. 각자 로컬에서 빌드한 이미지는 버전이 다를 수 있고, 누가 언제 어떤 이미지를 빌드했는지 추적하기 어렵습니다.

또한 CI/CD 파이프라인에서 빌드한 이미지를 배포 환경으로 전달하려면 중앙화된 저장소가 필수적입니다. 이미지 버전 관리가 제대로 되지 않으면 롤백이 어렵고, 어떤 버전이 프로덕션에 배포되어 있는지 알 수 없습니다.

바로 이럴 때 필요한 것이 Docker 레지스트리입니다. Docker Hub, GitHub Container Registry, AWS ECR 같은 레지스트리를 사용하면 이미지를 버전별로 저장하고, 어디서나 다운로드하여 배포할 수 있습니다.

개요

간단히 말해서, Docker 레지스트리는 Docker 이미지를 저장하고 공유하기 위한 중앙 저장소입니다. Git이 코드를 관리하듯이, 레지스트리는 Docker 이미지를 관리합니다.

왜 이것이 필요한지 실무 관점에서 보면, CI에서 빌드한 이미지를 CD에서 다운로드하여 배포할 수 있고, 이미지 태그를 통해 버전을 명확하게 관리할 수 있습니다. 예를 들어, CI 파이프라인에서 테스트를 통과한 이미지에 v1.2.3 태그를 붙여 레지스트리에 푸시하면, 배포 시 정확히 그 버전을 가져와 사용할 수 있습니다.

기존에는 각 서버에서 소스 코드를 받아 직접 빌드했다면, 이제는 레지스트리에서 이미 빌드된 이미지를 다운로드하기만 하면 되어 배포 시간이 크게 단축됩니다. Docker 레지스트리의 핵심 특징은 버전 관리(태그로 이미지 버전 추적), 접근 제어(private 레지스트리로 보안 유지), 그리고 레이어 캐싱(동일한 레이어는 한 번만 저장)입니다.

이러한 특징들이 안정적인 배포 프로세스의 기반이 됩니다.

코드 예제

# .github/workflows/publish.yml
name: Build and Publish Docker Image

on:
  push:
    tags:
      - 'v*'  # v로 시작하는 태그가 푸시될 때 실행

jobs:
  publish:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      # GitHub Container Registry에 로그인
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # 이미지 메타데이터 생성 (태그, 레이블)
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix={{branch}}-
            type=raw,value=latest,enable={{is_default_branch}}

      # 이미지 빌드 및 레지스트리에 푸시
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      # 푸시된 이미지 정보를 PR 코멘트로 작성
      - name: Comment on PR
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.repos.createCommitComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              commit_sha: context.sha,
              body: '🐳 Docker image published: `${{ steps.meta.outputs.tags }}`'
            })

설명

이것이 하는 일: 위 워크플로우는 Git 태그가 푸시될 때 자동으로 Docker 이미지를 빌드하고, GitHub Container Registry에 게시합니다. 첫 번째로, on: push: tags: - 'v*' 설정은 v1.0.0 같은 형식의 태그가 푸시될 때만 이 워크플로우를 실행합니다.

이렇게 하면 릴리즈 버전만 레지스트리에 게시되고, 모든 커밋마다 불필요하게 이미지가 쌓이는 것을 방지합니다. docker/login-action은 GitHub가 자동으로 제공하는 GITHUB_TOKEN을 사용하여 별도의 시크릿 설정 없이도 안전하게 인증합니다.

그 다음으로, docker/metadata-action은 Git 태그로부터 이미지 태그를 자동으로 생성합니다. 예를 들어 v1.2.3 태그를 푸시하면 1.2.3, 1.2, 그리고 커밋 SHA가 포함된 태그가 자동으로 만들어집니다.

type=raw,value=latest는 main 브랜치에서 실행될 때만 latest 태그를 붙이므로, 개발 브랜치의 이미지가 실수로 latest로 태그되는 것을 방지합니다. 마지막으로, docker/build-push-action이 실제로 이미지를 빌드하고 레지스트리에 푸시합니다.

push: true 옵션으로 빌드 후 자동으로 푸시하며, 생성된 모든 태그가 적용됩니다. GitHub Actions 캐시를 활용하여 이전 빌드의 레이어를 재사용하므로, 변경된 부분만 새로 빌드하여 시간을 크게 절약합니다.

여러분이 이 워크플로우를 사용하면 git tag v1.0.0 && git push --tags 명령만으로 자동으로 이미지가 빌드되고 레지스트리에 게시됩니다. 배포할 때는 docker pull ghcr.io/username/repo:1.0.0 명령으로 정확한 버전을 가져올 수 있습니다.

실전 팁

💡 private 레포지토리의 이미지는 기본적으로 private이므로, 배포 서버에서 docker login으로 인증해야 합니다.

💡 이미지 태그는 절대 덮어쓰지 마세요. 같은 태그에 다른 내용을 푸시하면 롤백이 불가능해집니다. 항상 고유한 태그를 사용하세요.

💡 AWS ECR을 사용할 때는 이미지 스캔 기능을 활성화하여 취약점을 자동으로 검사하세요.

💡 latest 태그는 개발/테스트용으로만 사용하고, 프로덕션에는 항상 명시적인 버전 태그를 사용하세요.

💡 레지스트리 스토리지 비용을 줄이기 위해 오래된 이미지를 자동으로 삭제하는 정책을 설정하세요 (예: 30일 이상 된 untagged 이미지 삭제).


6. CD 파이프라인 구축

시작하며

여러분이 새로운 기능을 개발하고 테스트를 모두 통과했는데, 프로덕션 배포는 여전히 수동으로 서버에 접속해서 스크립트를 실행하고 있나요? 아니면 배포할 때마다 긴장되고, 문제가 생기면 어떻게 롤백할지 막막한 경험이 있나요?

이런 문제는 수동 배포 프로세스의 전형적인 문제입니다. 사람이 직접 명령을 실행하면 실수하기 쉽고, 배포 과정이 문서화되지 않아 매번 다르게 진행될 수 있습니다.

특히 긴급한 핫픽스를 배포해야 할 때 실수로 잘못된 서버에 배포하거나, 이전 버전을 덮어써서 더 큰 문제가 발생할 수 있습니다. 또한 배포가 성공했는지 실패했는지 명확하게 알기 어렵고, 롤백 프로세스도 복잡합니다.

바로 이럴 때 필요한 것이 CD(Continuous Deployment) 파이프라인입니다. 코드가 머지되면 자동으로 빌드, 테스트, 그리고 배포까지 이루어져 안정적이고 반복 가능한 배포 프로세스를 구축할 수 있습니다.

개요

간단히 말해서, CD 파이프라인은 코드 변경이 프로덕션까지 자동으로 전달되는 프로세스입니다. CI에서 검증된 코드가 자동으로 패키징되고, 배포 환경으로 전달되어, 최종적으로 사용자에게 도달합니다.

왜 이것이 필요한지 실무 관점에서 보면, 배포 빈도를 크게 높일 수 있고(하루에 여러 번 배포 가능), 배포 시간을 몇 시간에서 몇 분으로 단축할 수 있습니다. 예를 들어, 버그 수정이나 작은 기능 추가를 즉시 사용자에게 전달할 수 있어 비즈니스 민첩성이 크게 향상됩니다.

기존에는 배포가 큰 이벤트여서 야간이나 주말에 진행했다면, 이제는 언제든 자신 있게 배포할 수 있고, 문제가 생기면 이전 버전으로 즉시 롤백할 수 있습니다. CD 파이프라인의 핵심 특징은 자동화(사람의 개입 최소화), 재현성(항상 동일한 프로세스), 그리고 추적 가능성(모든 배포 이력 기록)입니다.

이러한 특징들이 안정적이고 빠른 배포를 가능하게 합니다.

코드 예제

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com

    steps:
      - uses: actions/checkout@v3

      # Docker 이미지 빌드 및 푸시 (CI에서 검증된 코드)
      - name: Login to Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

      # AWS ECS에 배포
      - 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: us-east-1

      - name: Deploy to ECS
        run: |
          # 새 태스크 정의 생성
          aws ecs register-task-definition \
            --family myapp \
            --container-definitions '[{
              "name": "app",
              "image": "ghcr.io/${{ github.repository }}:${{ github.sha }}",
              "portMappings": [{"containerPort": 3000}]
            }]'

          # 서비스 업데이트 (롤링 배포)
          aws ecs update-service \
            --cluster production \
            --service myapp-service \
            --task-definition myapp \
            --force-new-deployment

          # 배포 완료 대기
          aws ecs wait services-stable \
            --cluster production \
            --services myapp-service

      # 배포 성공 알림
      - name: Notify Slack
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {
              "text": "✅ Deployment successful: ${{ github.sha }}"
            }

설명

이것이 하는 일: 위 워크플로우는 main 브랜치에 코드가 머지될 때 자동으로 Docker 이미지를 빌드하고 프로덕션 환경에 배포합니다. 첫 번째로, environment: production 설정은 GitHub의 환경 보호 규칙을 활용합니다.

프로덕션 환경에는 승인자를 지정하여 자동 배포 전에 검토 단계를 추가할 수 있고, 환경별 시크릿을 분리하여 관리할 수 있습니다. 이렇게 하면 실수로 잘못된 코드가 프로덕션에 배포되는 것을 방지할 수 있습니다.

그 다음으로, Docker 이미지를 커밋 SHA로 태그하여 빌드합니다. 이렇게 하면 각 배포마다 고유한 이미지를 사용하므로, 문제가 발생했을 때 정확히 어떤 코드가 배포되었는지 알 수 있고, 이전 이미지로 빠르게 롤백할 수 있습니다.

AWS ECS를 사용한 배포 예시이지만, Kubernetes, Azure, GCP 등 다른 플랫폼도 비슷한 패턴을 따릅니다. 마지막으로, aws ecs update-service는 롤링 배포를 수행합니다.

새 버전의 컨테이너를 하나씩 시작하고, 헬스체크가 통과하면 기존 컨테이너를 하나씩 종료합니다. 이렇게 하면 다운타임 없이 배포할 수 있고, 문제가 발생하면 자동으로 롤백됩니다.

aws ecs wait services-stable로 배포가 완전히 완료될 때까지 기다린 후 성공 알림을 보냅니다. 여러분이 이 CD 파이프라인을 사용하면 PR을 머지하는 순간 자동으로 프로덕션 배포가 시작되고, 문제없이 완료되면 Slack으로 알림을 받습니다.

배포 과정이 투명하게 추적되고, 언제든 이전 버전으로 롤백할 수 있습니다.

실전 팁

💡 블루-그린 배포를 사용하면 새 버전을 완전히 배포한 후 트래픽을 전환하여 더 안전하게 배포할 수 있습니다.

💡 배포 후 자동으로 스모크 테스트를 실행하여 주요 기능이 정상 작동하는지 확인하세요.

💡 롤백 기능을 미리 테스트해두세요. 긴급 상황에서 롤백이 작동하지 않으면 큰 문제가 됩니다.

💡 환경별로 다른 워크플로우 파일을 사용하세요 (deploy-staging.yml, deploy-production.yml). 스테이징은 자동 배포, 프로덕션은 승인 필요하도록 설정할 수 있습니다.

💡 배포 실패 시 자동으로 롤백하는 로직을 추가하고, 실패 원인을 Slack이나 PagerDuty로 즉시 알리세요.


7. 환경별 설정 관리

시작하며

여러분이 개발, 스테이징, 프로덕션 환경에서 서로 다른 데이터베이스와 API 엔드포인트를 사용해야 하는데, 환경변수를 하드코딩하거나 파일로 관리해서 실수로 프로덕션 설정이 개발 환경에 들어간 경험이 있나요? 이런 문제는 환경별 설정을 제대로 분리하지 않았을 때 발생합니다.

코드에 환경변수를 하드코딩하면 환경을 바꿀 때마다 코드를 수정해야 하고, 설정 파일을 Git에 커밋하면 민감한 정보가 노출될 위험이 있습니다. 또한 로컬 개발자마다 다른 설정을 사용하면 "내 컴퓨터에서는 되는데" 문제가 다시 발생합니다.

특히 데이터베이스 URL이나 API 키 같은 민감한 정보가 실수로 공개 레포지토리에 커밋되면 심각한 보안 사고로 이어질 수 있습니다. 바로 이럴 때 필요한 것이 체계적인 환경별 설정 관리입니다.

Docker와 Docker Compose를 활용하면 환경별로 다른 설정을 깔끔하게 분리하고, 민감한 정보는 안전하게 관리할 수 있습니다.

개요

간단히 말해서, 환경별 설정 관리는 개발, 스테이징, 프로덕션 등 각 환경에서 필요한 설정을 분리하여 관리하는 방법입니다. 같은 코드와 이미지를 사용하되, 환경변수로 동작을 다르게 제어합니다.

왜 이것이 필요한지 실무 관점에서 보면, 한 번 빌드한 이미지를 모든 환경에서 재사용할 수 있고, 환경을 바꿀 때 코드를 수정할 필요가 없습니다. 예를 들어, 개발 환경에서는 디버그 로그를 활성화하고 개발용 데이터베이스를 사용하지만, 프로덕션에서는 에러 로그만 출력하고 프로덕션 데이터베이스를 사용하도록 설정할 수 있습니다.

기존에는 환경별로 다른 Dockerfile을 만들거나 설정 파일을 환경별로 복사했다면, 이제는 하나의 이미지에 환경변수만 다르게 주입하여 모든 환경에서 사용할 수 있습니다. 환경별 설정 관리의 핵심 특징은 분리(코드와 설정의 분리), 보안(민감한 정보 암호화), 그리고 일관성(모든 환경에서 동일한 이미지 사용)입니다.

이러한 특징들이 안전하고 관리하기 쉬운 배포 환경을 만듭니다.

코드 예제

# docker-compose.yml (개발 환경)
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    env_file:
      - .env.development  # 개발 환경 변수
    environment:
      - NODE_ENV=development
      - LOG_LEVEL=debug
    volumes:
      - .:/app  # 코드 변경 즉시 반영

---
# docker-compose.prod.yml (프로덕션 오버라이드)
version: '3.8'

services:
  app:
    image: ghcr.io/myapp:${VERSION}  # 미리 빌드된 이미지 사용
    restart: unless-stopped
    env_file:
      - .env.production  # 프로덕션 환경 변수
    environment:
      - NODE_ENV=production
      - LOG_LEVEL=error
    volumes: []  # 프로덕션에서는 코드 마운트 안 함
    deploy:
      replicas: 3  # 여러 인스턴스 실행
      resources:
        limits:
          cpus: '1.0'
          memory: 512M

---
# .env.example (팀과 공유, Git에 커밋)
NODE_ENV=development
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp_dev
REDIS_URL=redis://localhost:6379
API_KEY=your_api_key_here
LOG_LEVEL=debug

---
# .env.production (민감한 정보, Git에 커밋 안 함)
NODE_ENV=production
DATABASE_URL=postgresql://user:password@prod-db.example.com:5432/myapp
REDIS_URL=redis://prod-cache.example.com:6379
API_KEY=prod_api_key_12345
LOG_LEVEL=error

설명

이것이 하는 일: 위 설정 파일들은 개발과 프로덕션 환경에서 다른 설정을 사용하면서도 같은 코드베이스를 유지하는 방법을 보여줍니다. 첫 번째로, .env.example 파일은 필요한 환경변수 목록을 팀과 공유하기 위한 템플릿입니다.

실제 값은 더미 값으로 채워져 있고, 각 개발자는 이를 복사하여 .env.development 파일을 만들고 자신의 로컬 설정을 입력합니다. .gitignore.env.*를 추가하여 실제 환경변수 파일은 Git에 커밋되지 않도록 합니다.

이렇게 하면 민감한 정보가 레포지토리에 노출되는 것을 방지할 수 있습니다. 그 다음으로, docker-compose.yml은 기본(개발) 설정이고, docker-compose.prod.yml은 프로덕션에서 오버라이드할 설정을 담고 있습니다.

개발 환경에서는 docker-compose up만 실행하면 되고, 프로덕션에서는 docker-compose -f docker-compose.yml -f docker-compose.prod.yml up으로 두 파일을 합쳐서 실행합니다. 프로덕션 설정은 코드 마운트를 제거하고, 리소스 제한을 추가하고, 여러 레플리카를 실행하도록 되어 있습니다.

마지막으로, environmentenv_file의 차이를 이해하는 것이 중요합니다. environment는 명시적으로 값을 설정하거나 오버라이드할 때 사용하고, env_file은 많은 환경변수를 파일에서 읽어올 때 사용합니다.

environmentenv_file보다 우선순위가 높으므로, 기본값은 파일에서 읽고 특정 값만 오버라이드할 수 있습니다. 여러분이 이 패턴을 사용하면 한 번 빌드한 이미지를 개발, 스테이징, 프로덕션 모든 환경에서 사용할 수 있습니다.

환경을 바꾸려면 환경변수만 변경하면 되므로, "개발에서는 되는데 프로덕션에서는 안 돼" 같은 문제가 사라집니다.

실전 팁

💡 .env 파일을 절대 Git에 커밋하지 마세요. .gitignore.env*를 추가하고, .env.example만 커밋하세요.

💡 프로덕션 환경변수는 AWS Secrets Manager, HashiCorp Vault 같은 시크릿 관리 서비스를 사용하여 암호화하고 관리하세요.

💡 환경변수 이름에 일관된 네이밍 컨벤션을 사용하세요 (예: DB_ 접두사는 데이터베이스 관련, API_ 접두사는 외부 API 관련).

💡 필수 환경변수가 없을 때 애플리케이션이 명확한 에러 메시지와 함께 시작을 거부하도록 검증 로직을 추가하세요.

💡 환경별 설정을 문서화하고, 새 환경을 추가할 때 체크리스트를 만들어 빠뜨리는 설정이 없도록 하세요.


8. 파이프라인 모니터링과 알림

시작하며

여러분이 CI/CD 파이프라인을 구축했는데, 배포가 실패해도 한참 후에 알게 되거나, 어떤 단계에서 문제가 발생했는지 로그를 일일이 찾아봐야 하는 경험이 있나요? 아니면 배포가 성공했는지 실패했는지 확인하기 위해 GitHub Actions 페이지를 계속 새로고침하고 있나요?

이런 문제는 파이프라인 모니터링과 알림이 부족할 때 발생합니다. 파이프라인이 자동화되어 있어도 결과를 능동적으로 확인해야 한다면 진정한 자동화가 아닙니다.

특히 프로덕션 배포가 실패했는데 아무도 모르고 있다가 사용자가 문제를 먼저 발견하는 상황은 치명적입니다. 또한 파이프라인이 실패하는 패턴을 분석하지 못하면 반복적인 문제를 개선할 수 없습니다.

바로 이럴 때 필요한 것이 파이프라인 모니터링과 알림 시스템입니다. 파이프라인 실행 상태를 실시간으로 추적하고, 문제가 발생하면 즉시 알림을 받아 빠르게 대응할 수 있습니다.

개요

간단히 말해서, 파이프라인 모니터링은 CI/CD 파이프라인의 실행 상태, 성공률, 실행 시간 등을 추적하고, 중요한 이벤트가 발생하면 팀에게 알리는 시스템입니다. 왜 이것이 필요한지 실무 관점에서 보면, 배포 실패를 즉시 인지하고 대응할 수 있으며, 파이프라인의 병목 지점을 찾아 최적화할 수 있습니다.

예를 들어, 테스트 단계가 매번 5분 이상 걸린다면 병렬화를 고려하고, 특정 테스트가 자주 실패한다면 플래키 테스트를 수정할 수 있습니다. 기존에는 파이프라인 실패를 우연히 발견하거나, 주기적으로 확인해야 했다면, 이제는 Slack, Discord, 이메일로 즉시 알림을 받아 능동적으로 대응할 수 있습니다.

파이프라인 모니터링의 핵심 특징은 실시간 알림(문제 즉시 인지), 상태 대시보드(전체 파이프라인 상태 한눈에 파악), 그리고 이력 추적(과거 배포 기록 분석)입니다. 이러한 특징들이 안정적이고 투명한 배포 프로세스를 만듭니다.

코드 예제

# .github/workflows/ci-cd-with-monitoring.yml
name: CI/CD with Monitoring

on: [push, pull_request]

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Build
        run: npm run build

      - name: Test
        run: npm test

      # 성공 시 Slack 알림
      - name: Notify success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {
              "attachments": [{
                "color": "good",
                "title": "✅ Build Success",
                "text": "${{ github.repository }} - ${{ github.ref_name }}",
                "fields": [
                  {"title": "Commit", "value": "${{ github.event.head_commit.message }}", "short": false},
                  {"title": "Author", "value": "${{ github.actor }}", "short": true},
                  {"title": "Duration", "value": "${{ job.duration }}", "short": true}
                ],
                "actions": [{
                  "type": "button",
                  "text": "View on GitHub",
                  "url": "${{ github.event.head_commit.url }}"
                }]
              }]
            }

      # 실패 시 Slack 알림 (더 자세한 정보)
      - name: Notify failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {
              "attachments": [{
                "color": "danger",
                "title": "❌ Build Failed",
                "text": "${{ github.repository }} - ${{ github.ref_name }}",
                "fields": [
                  {"title": "Failed Step", "value": "${{ job.status }}", "short": true},
                  {"title": "Author", "value": "${{ github.actor }}", "short": true},
                  {"title": "Commit", "value": "${{ github.event.head_commit.message }}", "short": false}
                ],
                "actions": [{
                  "type": "button",
                  "text": "View Logs",
                  "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
                }]
              }]
            }

      # 메트릭 수집 및 분석
      - name: Collect metrics
        if: always()
        run: |
          echo "Pipeline duration: ${{ job.duration }}"
          echo "Status: ${{ job.status }}"
          # 메트릭을 DataDog, Prometheus 등으로 전송 가능

설명

이것이 하는 일: 위 워크플로우는 파이프라인 실행이 완료될 때마다 결과를 Slack으로 전송하고, 성공과 실패에 따라 다른 형식의 알림을 보냅니다. 첫 번째로, if: success()if: failure() 조건을 사용하여 파이프라인 결과에 따라 다른 알림을 보냅니다.

성공 시에는 간단한 확인 메시지를, 실패 시에는 어떤 단계에서 실패했는지, 로그를 어디서 볼 수 있는지 등 더 자세한 정보를 제공합니다. 이렇게 하면 실패했을 때 즉시 로그를 확인하고 문제를 해결할 수 있습니다.

그 다음으로, Slack 알림은 단순한 텍스트가 아닌 rich attachment 형식을 사용하여 커밋 메시지, 작성자, 실행 시간 등 유용한 정보를 구조화된 형태로 표시합니다. "View on GitHub" 버튼을 클릭하면 바로 해당 커밋이나 워크플로우 실행 페이지로 이동하여 추가 정보를 확인할 수 있습니다.

색상 코드(good=녹색, danger=빨간색)를 사용하여 시각적으로 성공/실패를 쉽게 구분할 수 있습니다. 마지막으로, if: always() 조건을 사용하여 성공/실패 여부와 관계없이 항상 메트릭을 수집합니다.

이 메트릭을 DataDog, Prometheus, CloudWatch 같은 모니터링 도구로 전송하면 파이프라인 성공률, 평균 실행 시간, 실패 패턴 등을 대시보드에서 시각화하고 분석할 수 있습니다. 예를 들어, 특정 시간대에 실패율이 높다면 인프라 문제를 의심할 수 있습니다.

여러분이 이 모니터링 시스템을 사용하면 파이프라인 상태를 실시간으로 추적할 수 있고, 문제가 발생하면 Slack 알림으로 즉시 알 수 있어 GitHub Actions 페이지를 계속 확인할 필요가 없습니다. 또한 팀 전체가 배포 상태를 공유하여 투명한 개발 프로세스를 만들 수 있습니다.

실전 팁

💡 알림 피로를 방지하기 위해 main 브랜치나 프로덕션 배포 실패만 알림을 보내고, 개인 브랜치는 본인에게만 DM으로 알리세요.

💡 배포 성공 시 애플리케이션 버전, 배포 시간, 변경사항 요약을 함께 알려서 팀이 무엇이 배포되었는지 바로 알 수 있게 하세요.

💡 GitHub의 Status Check를 활용하여 PR에 CI 결과를 직접 표시하고, 모든 체크가 통과해야 머지 가능하도록 브랜치 보호 규칙을 설정하세요.

💡 주기적으로(예: 매주) 파이프라인 메트릭 리포트를 생성하여 성공률, 평균 실행 시간 추이를 팀과 공유하고 개선점을 논의하세요.

💡 PagerDuty나 Opsgenie를 연동하여 프로덕션 배포 실패 시 온콜 엔지니어에게 즉시 알림이 가도록 설정하여 빠른 대응을 보장하세요.


#Docker#CI/CD#GitHub Actions#Container#Deployment#TypeScript