Docker Compose 운영

왜 Self-Hosted Runner인가

GitHub-hosted runner는 편리하지만, 빌드 시간 제한(6시간)과 무료 분수 한도, 내부 네트워크 접근 불가 등 실무에서 벽에 부딪히는 순간이 옵니다. Self-hosted runner는 이런 한계를 깨고, 자체 하드웨어(GPU, 대용량 메모리)를 CI/CD에 직접 투입할 수 있는 공식 방법입니다.

Self-Hosted Runner의 핵심 구조

Self-hosted runner는 GitHub Actions 서비스와 long-poll 방식으로 연결됩니다. Runner 프로세스가 GitHub API를 주기적으로 호출하여 할당된 job을 가져오고, 로컬에서 실행한 뒤 결과를 다시 보고합니다.

  • Runner Application: .NET 기반 에이전트로, config.sh로 등록하고 run.sh 또는 systemd 서비스로 실행합니다.
  • Labels: self-hosted, linux, x64 등 기본 레이블 외에 커스텀 레이블(gpu, staging)을 붙여 워크플로에서 runs-on으로 선택합니다.
  • Runner Group: Organization 레벨에서 runner를 그룹으로 묶어 특정 리포지토리에만 할당할 수 있습니다.

설치와 등록: 단계별 절차

아래는 Ubuntu 22.04 기준입니다. 공식 문서(Adding self-hosted runners)를 따릅니다.

1단계: 전용 사용자 생성

보안을 위해 root가 아닌 전용 사용자로 실행합니다.

sudo useradd -m -s /bin/bash ghrunner
sudo usermod -aG docker ghrunner

2단계: Runner 다운로드 및 등록

su - ghrunner
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.321.0.tar.gz -L 
  https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-x64-2.321.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.321.0.tar.gz

./config.sh --url https://github.com/YOUR_ORG --token AXXXX... 
  --labels gpu,staging --name my-build-server

3단계: systemd 서비스 등록

sudo ./svc.sh install ghrunner
sudo ./svc.sh start
sudo ./svc.sh status

이렇게 하면 서버 재부팅 후에도 runner가 자동 시작됩니다.

보안: 반드시 지켜야 할 3가지 원칙

Self-hosted runner는 편리한 만큼 보안 위험도 큽니다. GitHub 공식 문서(Self-hosted runner security)에서도 아래 사항을 강조합니다.

  1. Public 리포지토리에 연결하지 마세요. 외부 PR의 워크플로가 runner에서 임의 코드를 실행할 수 있습니다. Fork PR을 통한 secret 탈취, 크립토마이닝 등 실제 피해 사례가 보고되었습니다.
  2. 최소 권한 원칙을 적용하세요. runner 사용자에게 sudo 권한을 주지 않고, Docker socket 접근도 필요한 경우에만 허용합니다.
  3. 작업 후 환경을 초기화하세요. --ephemeral 플래그로 1회용 runner를 운영하거나, 컨테이너 기반 runner(actions-runner-controller)를 사용하면 빌드 간 오염을 방지할 수 있습니다.

Ephemeral Runner: 1회용 실행으로 격리 강화

GitHub Actions runner v2.300 이상에서 --ephemeral 플래그를 지원합니다. 이 모드에서 runner는 job 하나를 실행한 뒤 자동으로 등록 해제됩니다.

./config.sh --url https://github.com/YOUR_ORG --token AXXXX... 
  --ephemeral --labels ephemeral,linux

Ephemeral runner는 외부에서 트리거하는 오케스트레이터(systemd timer, cron, Kubernetes controller)와 결합하면, 필요할 때만 runner를 띄우고 끝나면 즉시 정리하는 패턴을 구현할 수 있습니다.

Actions Runner Controller(ARC): Kubernetes 기반 자동 스케일링

Runner를 수동으로 관리하는 대신, Kubernetes 클러스터에서 Pod 단위로 자동 생성/삭제하는 것이 ARC(Actions Runner Controller)입니다. GitHub이 공식으로 지원하며, Helm chart로 설치합니다.

helm install arc 
  --namespace arc-systems --create-namespace 
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller

helm install arc-runner-set 
  --namespace arc-runners --create-namespace 
  -f values.yaml 
  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set

ARC는 webhook 이벤트를 받아 runner Pod를 동적으로 생성합니다. job이 끝나면 Pod가 삭제되므로 ephemeral 특성이 자동 보장됩니다.

성능 최적화 팁

  • Tool cache 활용: actions/setup-node 등이 RUNNER_TOOL_CACHE 디렉터리에 런타임을 캐시합니다. 이 경로를 persistent volume으로 마운트하면 매 빌드마다 다운로드를 건너뜁니다.
  • Docker layer cache: docker buildx--cache-from/--cache-to를 결합하면 이미지 빌드 시간을 줄일 수 있습니다. Self-hosted runner에서는 로컬 레지스트리를 캐시 백엔드로 쓰면 네트워크 비용도 절약됩니다.
  • 병렬 job 분산: 러너를 여러 대 두고 같은 레이블을 붙이면 GitHub이 자동으로 라운드로빈 할당합니다. ARC에서는 maxRunners로 상한을 지정합니다.

모니터링과 장애 대응

Runner가 offline 상태로 바뀌면 워크플로가 pending에서 멈춥니다. 아래 사항을 점검하세요.

  • systemctl status actions.runner.*.service로 프로세스 상태 확인
  • _diag/ 디렉터리의 Runner 로그에서 연결 오류, 인증 만료 메시지 확인
  • runner 토큰은 1시간 유효이므로, 자동 재등록 스크립트(PAT 기반 REST API 호출)를 준비하면 안정적입니다.
  • GitHub Status(githubstatus.com)에서 Actions 서비스 장애 여부도 함께 확인하세요.

실전 체크리스트

  1. 전용 사용자 생성, root 실행 금지
  2. Public repo에 self-hosted runner 연결 금지
  3. --ephemeral 또는 ARC로 빌드 간 격리 확보
  4. systemd 서비스로 자동 재시작 설정
  5. tool cache를 persistent storage에 마운트
  6. Docker layer cache 전략 수립
  7. Runner offline 알림(Slack/Telegram webhook) 연동
  8. 토큰 자동 갱신 스크립트 준비

Self-hosted runner는 CI/CD 파이프라인의 성능과 유연성을 높이는 강력한 도구입니다. 보안 원칙을 지키고, ephemeral 패턴이나 ARC를 도입하면 운영 부담도 줄일 수 있습니다.

더 깊은 DevOps 주제가 궁금하다면 Kubernetes NetworkPolicy 심화 가이드Terraform Workspace 심화 가이드도 확인해 보세요.

온프레미스 CI/CD 구축이나 인프라 자동화에 대해 더 알고 싶으신가요?
문의 페이지에서 편하게 질문해 주세요.

📥 관련 무료 이북

쿠버네티스 입문 가이드 — 실전 가이드 무료 제공

무료로 받기 →

7) 실전 docker-compose.yml: NestJS + MySQL + Redis

version: "3.9"

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
      target: runner          # Multi-Stage 빌드의 runner 스테이지
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=mysql://app:secret@mysql:3306/mydb
      - REDIS_URL=redis://redis:6379
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
    restart: unless-stopped

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_pw
      MYSQL_DATABASE: mydb
      MYSQL_USER: app
      MYSQL_PASSWORD_FILE: /run/secrets/mysql_app_pw
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    secrets:
      - mysql_root_pw
      - mysql_app_pw
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    restart: unless-stopped

volumes:
  mysql_data:
  redis_data:

secrets:
  mysql_root_pw:
    file: ./secrets/mysql_root_pw.txt
  mysql_app_pw:
    file: ./secrets/mysql_app_pw.txt

8) Compose 운영 명령어 치트시트

# 기본 운영
docker compose up -d                    # 백그라운드 시작
docker compose down                     # 중지 + 네트워크 제거
docker compose down -v                  # + 볼륨도 제거 (주의!)
docker compose restart api              # 특정 서비스만 재시작

# 로그
docker compose logs -f api              # 실시간 로그
docker compose logs --tail=100 mysql    # 최근 100줄

# 스케일링
docker compose up -d --scale api=3      # API 서비스 3개로

# 빌드
docker compose build --no-cache api     # 캐시 없이 재빌드
docker compose up -d --build            # 빌드 + 시작

# 상태 확인
docker compose ps                        # 서비스 상태
docker compose top                       # 프로세스 목록
docker compose stats                     # 리소스 사용량

9) 관련 글

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux