튜토리얼

GitHub Actions 활용 웹 서비스 CI/CD 파이프라인 자동화: 직접 써본 구축 노하우

강코의 코딩 일기 2026. 5. 21. 09:26
반응형

GitHub Actions를 이용해 간단한 웹 서비스의 CI/CD 파이프라인을 구축하는 실전 가이드입니다. 코드 푸시부터 배포까지 자동화하는 과정을 직접 경험한 노하우와 팁을 공유합니다.

CI/CD, 왜 필요할까요? 개발자의 반복되는 고통을 끝내다

개발자라면 누구나 한 번쯤 경험했을 법한 시나리오가 있습니다. 밤새 개발한 코드를 배포하기 위해 수십 개의 터미널 명령어를 입력하고, 빌드 오류에 밤을 지새우고, 배포 후에도 제대로 작동하는지 일일이 확인하는 지난한 과정 말이죠. 특히 팀 프로젝트에서는 다른 팀원의 코드가 머지될 때마다 빌드와 테스트를 반복해야 하고, 이 과정에서 휴먼 에러가 발생할 확률도 높아집니다. 작은 웹 서비스라도 개발 주기가 빨라지고 기능 업데이트가 잦아질수록 이러한 수동 작업은 개발 생산성을 저해하는 주요 원인이 됩니다.

바로 이때, CI/CD(Continuous Integration/Continuous Delivery 또는 Continuous Deployment) 파이프라인이 빛을 발합니다. CI/CD는 코드 변경 사항이 정기적으로 빌드, 테스트, 배포되는 자동화된 프로세스를 의미합니다. 이를 통해 개발자는 코드 작성에만 집중하고, 반복적이고 오류가 발생하기 쉬운 작업은 시스템에 맡길 수 있게 됩니다.

  • 지속적 통합 (CI): 개발자들이 작업한 코드를 주기적으로 메인 브랜치에 통합하고, 통합 시마다 자동화된 빌드 및 테스트를 수행하여 잠재적인 문제를 조기에 발견합니다.
  • 지속적 배포 (CD): CI 단계를 통과한 코드를 자동으로 프로덕션 환경까지 배포하여, 새로운 기능이나 버그 수정 사항을 사용자에게 빠르게 제공합니다.

제가 직접 CI/CD 파이프라인을 구축해 보니, 개발 주기가 훨씬 빨라지고, 배포 과정에서 발생하는 스트레스가 현저히 줄어드는 것을 체감할 수 있었습니다. 특히 소규모 팀이나 개인 프로젝트에서는 초기 설정 비용이 다소 들더라도 장기적으로 엄청난 이점을 가져다주죠.

GitHub Actions, 너는 누구니? 개발 워크플로우를 자동화하는 마법

CI/CD 파이프라인을 구축하는 데는 다양한 도구와 플랫폼이 있습니다. Jenkins, GitLab CI, CircleCI 등이 대표적이죠. 그중에서도 제가 선택한 것은 바로 GitHub Actions입니다. 이미 많은 개발자가 코드 저장소로 GitHub를 사용하고 있을 텐데요, GitHub Actions는 GitHub 저장소에 내장된 CI/CD 서비스로, 별도의 서버를 구축하거나 외부 서비스를 연동할 필요 없이 바로 사용할 수 있다는 강력한 장점이 있습니다.

GitHub Actions의 핵심 구성 요소는 다음과 같습니다.

  • Workflow (워크플로우): 하나 이상의 Job으로 구성된 자동화된 프로세스입니다. `.github/workflows` 디렉터리 아래 YAML 파일로 정의합니다.
  • Event (이벤트): 워크플로우를 트리거하는 특정 활동입니다. 예를 들어, Git Push, Pull Request 생성, 스케줄링 등이 있습니다.
  • Job (작업): Runner에서 실행되는 일련의 Step입니다. 각 Job은 독립적인 가상 환경에서 실행될 수 있습니다.
  • Step (단계): Job 내에서 실행되는 개별 명령 또는 Action입니다.
  • Action (액션): 특정 작업을 수행하는 재사용 가능한 단위 코드입니다. GitHub Marketplace에서 다양한 Action을 찾아 사용할 수 있습니다.

다른 CI/CD 도구들과 비교했을 때, GitHub Actions는 특히 다음과 같은 면에서 매력적이었습니다.

특징 GitHub Actions Jenkins (예시)
설치 및 설정 GitHub 저장소 내 YAML 파일로 정의, 별도 설치 불필요 별도 서버에 설치 및 플러그인 설정 필요
통합성 GitHub 서비스에 완벽하게 통합 (코드, 이슈, PR 등) 다양한 VCS와 연동 가능하나, GitHub와는 별도 연동 필요
사용 편의성 간결한 YAML 문법, Marketplace의 풍부한 Action GUI 기반 설정, Groovy 스크립트 기반 파이프라인 정의
유지보수 GitHub가 인프라 관리, 버전 관리 용이 서버 및 플러그인 직접 관리 필요

특히 GitHub와 연동성이 뛰어나다는 점이 저에게는 가장 큰 메리트였습니다. 코드가 있는 곳에서 바로 CI/CD를 관리할 수 있다는 점은 개발 생산성을 크게 향상시켰습니다.

간단한 웹 서비스 예시 프로젝트 준비: Node.js 기반 REST API

본격적인 CI/CD 파이프라인 구축에 앞서, 자동화할 대상이 필요합니다. 여기서는 간단한 Node.js 기반의 REST API 웹 서비스를 예시로 들어보겠습니다. 이 서비스는 Express.js 프레임워크를 사용하며, 기본적인 API 엔드포인트와 테스트 스크립트를 포함하고 있습니다. 최종적으로 이 서비스를 Docker 이미지로 빌드하여 Docker Hub에 푸시하고, 원격 서버(예: AWS EC2)에 배포하는 시나리오를 가정합니다.

프로젝트 기본 구조


my-web-service/
├── .github/
│   └── workflows/
├── src/
│   └── app.js
│   └── routes.js
├── package.json
├── package-lock.json
├── Dockerfile
└── .env.example

package.json 예시

간단한 테스트 스크립트와 시작 스크립트가 포함되어 있습니다.


{
  "name": "my-web-service",
  "version": "1.0.0",
  "description": "A simple Node.js web service for CI/CD demo",
  "main": "src/app.js",
  "scripts": {
    "start": "node src/app.js",
    "test": "echo \"No tests specified\" && exit 0"
  },
  "dependencies": {
    "express": "^4.17.1"
  }
}

Dockerfile 예시

서비스를 컨테이너화하기 위한 Dockerfile입니다.


# Use an official Node.js runtime as a parent image
FROM node:16-alpine

# Set the working directory in the container
WORKDIR /usr/src/app

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install any needed packages specified in package.json
RUN npm install

# Copy the rest of the application code
COPY . .

# Expose the port the app runs on
EXPOSE 3000

# Define the command to run your app
CMD [ "npm", "start" ]

이러한 기본적인 준비가 완료되었다면, GitHub 저장소에 코드를 푸시하고 다음 단계로 넘어갈 준비가 된 것입니다.

본격적인 CI/CD 파이프라인 구축하기: GitHub Actions 워크플로우

이제 GitHub Actions 워크플로우 파일을 작성하여 CI/CD 파이프라인을 구축해 보겠습니다. 워크플로우 파일은 `.github/workflows/main.yml` 경로에 생성합니다.

워크플로우 파일 생성 및 기본 설정

먼저 워크플로우의 이름과 트리거 이벤트를 정의합니다. 여기서는 main 브랜치에 코드가 푸시될 때 워크플로우가 실행되도록 설정합니다.


# .github/workflows/main.yml
name: Node.js CI/CD Pipeline

on:
  push:
    branches:
      - main

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup Node.js environment
        uses: actions/setup-node@v3
        with:
          node-version: '16'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Build Docker image
        run: |
          docker build -t ${{ secrets.DOCKER_USERNAME }}/my-web-service:${{ github.sha }} .

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Push Docker image to Docker Hub
        run: |
          docker push ${{ secrets.DOCKER_USERNAME }}/my-web-service:${{ github.sha }}

      - name: Deploy to EC2 via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.EC2_HOST }}
          username: ${{ secrets.EC2_USERNAME }}
          key: ${{ secrets.EC2_SSH_KEY }}
          script: |
            docker pull ${{ secrets.DOCKER_USERNAME }}/my-web-service:${{ github.sha }}
            docker stop my-web-service || true
            docker rm my-web-service || true
            docker run -d --name my-web-service -p 3000:3000 ${{ secrets.DOCKER_USERNAME }}/my-web-service:${{ github.sha }}
            echo "Deployment completed successfully!"

위 워크플로우는 하나의 Job인 build-and-deploy로 구성되어 있습니다. 이 Job은 Ubuntu 환경에서 실행되며, 여러 단계를 거쳐 CI/CD 프로세스를 수행합니다.

CI (지속적 통합) 단계 구성

CI 단계는 코드 체크아웃, 환경 설정, 의존성 설치, 테스트 실행, 빌드 등으로 이루어집니다. 이 과정에서 코드의 유효성을 검증하고 배포 가능한 아티팩트를 생성합니다.

  1. Checkout code: actions/checkout@v3 액션을 사용하여 GitHub 저장소의 코드를 runner로 가져옵니다.
  2. Setup Node.js environment: actions/setup-node@v3 액션을 사용하여 Node.js 환경을 설정합니다. 여기서는 Node.js 16 버전을 지정했습니다.
  3. Install dependencies: npm ci 명령어를 실행하여 프로젝트 의존성을 설치합니다. npm cipackage-lock.json 파일을 기반으로 정확한 버전의 의존성을 설치하여 일관된 빌드 환경을 보장합니다.
  4. Run tests: npm test 명령어를 실행하여 단위 테스트나 통합 테스트를 수행합니다. 이 단계에서 테스트가 실패하면 워크플로우는 중단되고 배포는 이루어지지 않습니다.
  5. Build Docker image: docker build 명령어를 사용하여 Dockerfile을 기반으로 웹 서비스의 Docker 이미지를 빌드합니다. 이미지 태그에는 ${{ github.sha }}를 사용하여 Git 커밋 SHA를 포함시켜, 각 배포 버전을 명확하게 관리할 수 있도록 합니다.

이 단계들을 통해 코드가 안정적으로 통합되고, 배포 준비가 된 상태임을 확인할 수 있습니다.

CD (지속적 배포) 단계 구성

CI 단계를 성공적으로 마친 후에는 CD 단계를 통해 빌드된 아티팩트를 실제 서비스 환경에 배포합니다.

  1. Log in to Docker Hub: docker/login-action@v2 액션을 사용하여 Docker Hub에 로그인합니다. 이때, GitHub Secrets에 저장된 DOCKER_USERNAMEDOCKER_PASSWORD를 사용합니다. 중요한 인증 정보는 절대로 워크플로우 파일에 직접 노출해서는 안 됩니다.
  2. Push Docker image to Docker Hub: 빌드된 Docker 이미지를 Docker Hub 저장소에 푸시합니다.
  3. Deploy to EC2 via SSH: appleboy/ssh-action@master 액션을 사용하여 원격 EC2 인스턴스에 SSH로 접속하고 배포 명령어를 실행합니다.
    • host, username, key는 모두 GitHub Secrets에 등록된 값으로, EC2 접속 정보를 안전하게 관리합니다. 특히 EC2_SSH_KEY는 EC2 접속에 사용되는 프라이빗 키 내용을 그대로 Secrets에 등록해야 합니다.
    • 스크립트 내용은 다음과 같습니다: 최신 Docker 이미지를 풀(pull)하고, 기존 컨테이너를 중지 및 삭제한 후, 새로운 이미지로 컨테이너를 실행합니다.

이 모든 과정이 코드를 푸시하는 순간 자동으로 실행됩니다. 처음에는 설정할 것이 많아 보이지만, 한 번 구축해두면 매번 수동으로 배포하는 수고를 덜 수 있습니다.

CI/CD 파이프라인, 직접 적용해 보니 어땠을까?

제가 이 파이프라인을 직접 구축하고 운영해 보니, 기대 이상으로 많은 이점을 얻을 수 있었습니다.

  1. 배포 시간의 혁신적인 단축: 수동으로 빌드하고 SSH 접속 후 배포 스크립트를 실행하던 과정은 숙련된 개발자에게도 약 15~20분이 소요되었습니다. 하지만 GitHub Actions를 도입한 후에는 코드 푸시부터 실제 서비스 반영까지 평균 3~5분 내외로 단축되었습니다. 이는 개발 속도에 직접적인 영향을 주어, 더 잦은 기능 업데이트와 빠른 버그 수정이 가능해졌습니다.
  2. 휴먼 에러의 획기적인 감소: 수동 배포 시에는 환경 변수 오타, 잘못된 브랜치 배포, 빌드 과정 누락 등 다양한 인적 오류가 발생할 수 있었습니다. CI/CD 파이프라인은 정해진 절차에 따라 일관되게 작업을 수행하므로, 이러한 휴먼 에러 발생률을 90% 이상 줄일 수 있었습니다. 덕분에 개발팀은 배포 실패에 대한 걱정 없이 개발에만 집중할 수 있게 되었습니다.
  3. 일관된 개발 및 배포 환경: 모든 빌드와 테스트, 배포가 동일한 환경(GitHub Actions runner)에서 이루어지므로, "내 로컬에서는 되는데..." 와 같은 불필요한 논쟁이 사라졌습니다. 항상 일관된 환경에서 검증된 코드만이 배포되기 때문에 서비스 안정성이 크게 향상되었습니다.
  4. 코드 품질 향상: CI 단계에서 자동화된 테스트가 강제되면서, 개발자들은 코드를 푸시하기 전에 더욱 꼼꼼하게 테스트를 작성하고 실행하는 습관을 갖게 되었습니다. 이는 전반적인 코드 품질 향상에 기여했습니다.

물론 처음 파이프라인을 설정하는 데는 시행착오가 있었습니다. YAML 문법에 익숙해지는 시간, GitHub Secrets 설정 방법, 그리고 특정 Action 사용법을 익히는 데 시간이 필요했죠. 하지만 그 초기 투자 비용은 자동화로 얻는 장기적인 이점에 비하면 미미한 수준이었습니다.

마주친 문제점과 해결 팁

아무리 편리한 도구라도 실전에서 마주치는 문제점은 있기 마련입니다. 제가 겪었던 몇 가지 문제와 해결 팁을 공유합니다.

  1. Secrets 관리의 중요성: 처음에는 환경 변수를 워크플로우 파일에 직접 넣을 뻔한 실수도 했습니다. GitHub Actions는 Secrets 기능을 통해 민감한 정보를 안전하게 관리할 수 있도록 제공합니다. Docker Hub 자격 증명, EC2 접속 키 등은 반드시 Secrets에 등록하여 사용해야 합니다. 절대 워크플로우 파일에 하드코딩하지 마세요!
  2. Workflow 디버깅의 어려움: 워크플로우가 실패했을 때, 어디서 문제가 발생했는지 파악하기 어려울 때가 있었습니다. 이때 가장 중요한 것은 로그를 꼼꼼히 확인하는 것입니다. GitHub Actions UI에서 각 Step별로 상세한 로그를 제공하므로, 에러 메시지를 기반으로 문제의 원인을 추적해야 합니다. 필요한 경우 run 스크립트에 echo 명령어를 추가하여 디버깅 정보를 출력하는 것도 좋은 방법입니다.
  3. 빌드 시간 최적화: 프로젝트 규모가 커지면서 의존성 설치나 빌드 시간이 길어지는 문제가 발생할 수 있습니다. 이때는 캐싱(Caching) 액션을 활용하여 의존성 설치 시간을 단축할 수 있습니다. 예를 들어, actions/cache@v3 액션을 사용하여 node_modules 디렉터리를 캐싱하면, 다음 빌드 시에는 이미 캐시된 의존성을 재활용하여 시간을 절약할 수 있습니다.
  4. 네트워크 문제 및 권한 설정: 원격 서버에 배포하는 과정에서 SSH 접속 문제나 권한 문제가 발생할 수 있습니다. EC2 인스턴스의 보안 그룹에서 GitHub Actions runner의 IP 대역을 허용하거나, SSH 키의 권한 설정(chmod 400)이 올바른지 확인해야 합니다.

이러한 문제들은 대부분 공식 문서나 커뮤니티 자료를 통해 해결할 수 있었습니다. 꾸준히 학습하고 적용하는 과정 자체가 개발 역량을 높이는 데 큰 도움이 되었습니다.

마무리하며: 자동화된 개발 프로세스의 미래

GitHub Actions를 활용한 간단한 웹 서비스 CI/CD 파이프라인 구축은 저에게 개발 프로세스 자동화의 중요성과 가능성을 다시 한번 일깨워준 경험이었습니다. 처음에는 다소 복잡하게 느껴질 수 있지만, 한 번 구축해두면 장기적으로 개발팀의 생산성과 서비스 안정성을 크게 향상시킬 수 있습니다.

이 글에서 다룬 내용은 기본적인 CI/CD 파이프라인에 불과합니다. 실제 서비스에서는 더 복잡한 테스트 전략, 스테이징 환경 배포, 롤백 전략, 모니터링 연동 등 다양한 요소들을 고려해야 할 것입니다. 하지만 이 가이드를 통해 첫걸음을 떼셨다면, 앞으로 더 정교하고 견고한 CI/CD 파이프라인을 구축할 수 있는 단단한 기반을 마련한 것이나 다름없습니다.

개발자가 코드를 작성하는 본연의 업무에 집중하고, 반복적인 작업은 자동화 시스템에 맡기는 것이야말로 효율적인 개발의 핵심이라고 생각합니다. 여러분도 GitHub Actions를 통해 자신만의 CI/CD 파이프라인을 구축해 보시길 강력히 추천합니다.

이 글이 여러분의 개발 여정에 도움이 되셨기를 바라며, 혹시 GitHub Actions를 사용하시면서 겪었던 재미있는 경험이나 유용한 팁이 있다면 댓글로 자유롭게 공유해 주세요!

📌 함께 읽으면 좋은 글

  • [기술 리뷰] Node.js, Deno, Bun 비교 분석: 자바스크립트 런타임 선택 가이드
  • [튜토리얼] Prometheus와 Grafana를 활용한 애플리케이션 성능 모니터링 시스템 구축 실전 가이드
  • [커리어 취업] 개발자 연봉 협상 필승 전략: 시장 가치 분석부터 성공적인 제안 수락까지

이 글이 도움이 되셨다면 공감(♥)댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.

반응형