C++ 프로덕션 배포 완벽 가이드 | Docker·systemd·K8s·모니터링·로깅 [#50-5]

C++ 프로덕션 배포 완벽 가이드 | Docker·systemd·K8s·모니터링·로깅 [#50-5]

이 글의 핵심

C++ 프로덕션 배포 완벽 가이드에 대한 실전 가이드입니다. Docker·systemd·K8s·모니터링·로깅 [#50-5] 등을 예제와 함께 설명합니다.

들어가며: “로컬에선 되는데 서버에서 죽어요”

프로덕션 배포에서 겪는 문제

C++ 애플리케이션을 개발할 때는 로컬에서 잘 동작하는데, 서버에 배포하면 다음 문제들이 발생합니다:

  • 라이브러리 버전 불일치: “로컬은 glibc 2.35인데 서버는 2.31이라 실행이 안 돼요”
  • 메모리 한도 초과: 컨테이너 메모리 제한을 넘어 OOM Killer에 의해 강제 종료
  • 재시작 실패: 프로세스가 죽었는데 자동으로 다시 띄우지 않음
  • 로그 유실: stdout에만 출력해서 컨테이너 재시작 시 로그가 사라짐
  • 헬스 체크 부재: 트래픽이 죽은 Pod로 가서 502 에러

이 글에서는 Docker 컨테이너화, systemd 전통 배포, Kubernetes 오케스트레이션, CI/CD 파이프라인, Prometheus 모니터링, 구조화된 로깅까지 프로덕션 배포의 전체 워크플로우를 실전 코드로 다룹니다.

목표:

  • Multi-stage Docker 빌드로 최적화된 이미지 생성
  • systemd로 VM/베어메탈 서버에 안정적 서비스 등록
  • Kubernetes 배포 및 서비스 설정
  • GitHub Actions CI/CD 파이프라인
  • 무중단 배포 (Rolling Update, Blue-Green)
  • Prometheus 메트릭 + Grafana 대시보드
  • JSON 구조화 로깅 (spdlog)

요구 환경: Docker, Kubernetes (minikube 또는 클라우드), GitHub


실무에서 겪는 문제 시나리오

시나리오 1: “glibc 버전이 달라서 실행이 안 돼요”

상황: Ubuntu 24.04에서 빌드한 바이너리를 CentOS 7 서버에 복사해 실행하면 version 'GLIBC_2.34' not found 에러가 난다. 원인: 빌드 환경과 런타임 환경의 glibc 버전 불일치 해결: Docker Multi-stage 빌드로 동일 베이스 이미지 사용, 또는 정적 링크 (-static-libgcc -static-libstdc++)

시나리오 2: “컨테이너가 OOM으로 죽어요”

상황: Kubernetes Pod가 주기적으로 OOMKilled 상태로 재시작된다. 원인: resources.limits.memory 미설정 또는 애플리케이션 메모리 누수 해결: 적절한 메모리 limit 설정, Valgrind/AddressSanitizer로 누수 검사, 메트릭 모니터링

시나리오 3: “배포 중 502 에러가 잠깐 나요”

상황: 새 버전 배포 시 5~10초 동안 502 Bad Gateway가 발생한다. 원인: readinessProbe 미설정으로 트래픽이 아직 준비되지 않은 Pod로 전달 해결: readinessProbe 설정, maxUnavailable: 0 Rolling Update, Graceful Shutdown

시나리오 4: “로그를 찾을 수 없어요”

상황: 장애 발생 시 원인 분석을 위해 로그를 찾는데, 컨테이너가 재시작되어 로그가 사라졌다. 원인: stdout만 사용하고 중앙 로그 수집 미설정 해결: JSON 구조화 로깅, 로그 볼륨 마운트, Loki/ELK 등 중앙 로그 수집

시나리오 5: “수동 배포라 실수해요”

상황: scp로 바이너리 복사 후 systemctl restart 하는 과정에서 잘못된 버전을 배포했다. 원인: 수동 배포, 버전 관리 부재 해결: CI/CD 파이프라인으로 자동화, 이미지 태그에 Git SHA 사용

시나리오 6: “서버 재부팅 후 서비스가 안 떠요”

상황: 패치 후 서버 재부팅했는데 C++ 앱이 자동으로 시작되지 않는다. 원인: systemd 서비스 등록 안 됨 또는 enabled 설정 누락 해결: systemd unit 파일 작성, systemctl enable myapp 실행

개념을 잡는 비유

이 글의 주제는 여러 부품이 맞물리는 시스템으로 보시면 이해가 빠릅니다. 한 레이어(저장·네트워크·관측)의 선택이 옆 레이어에도 영향을 주므로, 본문에서는 트레이드오프를 숫자와 패턴으로 정리합니다.


목차

  1. 문제 시나리오
  2. Docker 컨테이너화
  3. systemd 전통 배포
  4. Kubernetes 배포
  5. CI/CD 파이프라인
  6. 무중단 배포
  7. 모니터링 및 로깅
  8. 자주 발생하는 에러와 해결법
  9. 베스트 프랙티스
  10. 프로덕션 패턴
  11. 구현 체크리스트

2. Docker 컨테이너화

배포 아키텍처 개요

flowchart TB
    subgraph Build["빌드 단계"]
        S1[소스 코드] --> S2[CMake 빌드]
        S2 --> S3[바이너리 생성]
    end

    subgraph Docker["Docker 이미지"]
        S3 --> D1[Multi-stage 빌드]
        D1 --> D2[런타임 이미지]
        D2 --> D3[레지스트리 푸시]
    end

    subgraph Deploy["배포 옵션"]
        D3 --> K8s[Kubernetes]
        D3 --> VM[VM + systemd]
    end

Multi-stage Dockerfile

빌드 도구와 런타임을 분리해 이미지 크기를 최소화합니다. 빌드 스테이지의 컴파일러, CMake 등은 최종 이미지에 포함되지 않습니다.

# ========== 빌드 스테이지 ==========
FROM ubuntu:22.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    libboost-all-dev \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

# Release 빌드로 성능 최적화
RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && \
    cmake --build build --parallel $(nproc)

# ========== 런타임 스테이지 ==========
FROM ubuntu:22.04

# 보안: root가 아닌 사용자로 실행
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

RUN apt-get update && apt-get install -y --no-install-recommends \
    libboost-system1.74.0 \
    libboost-thread1.74.0 \
    libssl3 \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY --from=builder /app/build/myapp /app/myapp

# 권한 설정
RUN chown -R appuser:appgroup /app
USER appuser

EXPOSE 8080

# 헬스체크 (선택)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

CMD ["./myapp"]

최적화 포인트:

  • Multi-stage로 빌드 도구 제외 (이미지 크기 50% 이상 감소)
  • --no-install-recommends로 불필요한 패키지 제외
  • non-root 사용자로 실행 (보안)
  • HEALTHCHECK로 컨테이너 헬스 자동 검사

Docker Compose: 로컬 프로덕션 시뮬레이션

로컬에서 여러 서비스(앱, Prometheus, Grafana)를 함께 띄워 테스트합니다.

# docker-compose.yml
version: '3.8'

services:
  myapp:
    build: .
    ports:
      - "8080:8080"
    environment:
      - LOG_LEVEL=info
      - METRICS_PORT=9090
    volumes:
      - ./logs:/app/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 10s
    restart: unless-stopped

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9091:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana-data:/var/lib/grafana
    depends_on:
      - prometheus
    restart: unless-stopped

volumes:
  grafana-data:

Prometheus 설정 예시

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'myapp'
    static_configs:
      - targets: ['myapp:9090']
    metrics_path: /metrics

이미지 빌드 및 푸시

# 이미지 빌드 (캐시 활용)
docker build -t myregistry/myapp:v1.0.0 .

# 이미지 크기 확인
docker images myregistry/myapp:v1.0.0

# 레지스트리 푸시
docker push myregistry/myapp:v1.0.0

3. systemd 전통 배포

Kubernetes를 사용하지 않는 VM 또는 베어메탈 서버에서는 systemd로 서비스를 등록합니다. 재부팅 후 자동 시작, 실패 시 재시작, 로그 통합 등이 가능합니다.

systemd Unit 파일

# /etc/systemd/system/myapp.service
[Unit]
Description=C++ MyApp Production Service
Documentation=https://github.com/yourorg/myapp
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=appuser
Group=appgroup

# 실행 경로
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp

# 환경 변수 (민감 정보는 EnvironmentFile 사용)
Environment="LOG_LEVEL=info"
Environment="METRICS_PORT=9090"

# 재시작 정책
Restart=on-failure
RestartSec=5s
StartLimitInterval=300
StartLimitBurst=5

# 보안 강화
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/logs /var/log/myapp

# Graceful Shutdown (SIGTERM 수신 시 30초 대기)
TimeoutStopSec=30
KillMode=mixed

# 로그
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp

[Install]
WantedBy=multi-user.target

systemd 등록 및 관리

# Unit 파일 복사
sudo cp myapp.service /etc/systemd/system/

# systemd 리로드
sudo systemctl daemon-reload

# 서비스 활성화 (부팅 시 자동 시작)
sudo systemctl enable myapp

# 서비스 시작
sudo systemctl start myapp

# 상태 확인
sudo systemctl status myapp

# 로그 확인 (최근 100줄)
journalctl -u myapp -n 100 -f

# 재시작
sudo systemctl restart myapp

배포 스크립트 예시

#!/bin/bash
# deploy.sh - systemd 환경 배포

set -e
APP_NAME=myapp
INSTALL_DIR=/opt/myapp
SERVICE_USER=appuser

echo "=== 배포 시작 ==="

# 1. 새 바이너리 복사
sudo cp ./build/myapp $INSTALL_DIR/myapp.new
sudo chown $SERVICE_USER:$SERVICE_USER $INSTALL_DIR/myapp.new

# 2. Graceful 교체 (원자적 이동)
sudo mv $INSTALL_DIR/myapp.new $INSTALL_DIR/myapp
sudo chmod +x $INSTALL_DIR/myapp

# 3. 서비스 재시작
sudo systemctl restart $APP_NAME

# 4. 헬스 체크
sleep 5
if curl -sf http://localhost:8080/health > /dev/null; then
    echo "=== 배포 성공 ==="
else
    echo "=== 배포 실패: 헬스 체크 실패 ==="
    sudo systemctl restart $APP_NAME  # 롤백
    exit 1
fi

4. Kubernetes 배포

Deployment 설정

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myregistry/myapp:latest
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 8080
            - name: metrics
              containerPort: 9090
          env:
            - name: LOG_LEVEL
              value: "info"
            - name: METRICS_PORT
              value: "9090"
          resources:
            requests:
              memory: "128Mi"
              cpu: "250m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
            timeoutSeconds: 3
            failureThreshold: 2
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 5"]

핵심 설정 설명:

  • livenessProbe: Pod가 죽었는지 확인, 실패 시 재시작
  • readinessProbe: 트래픽 수신 준비 여부, 실패 시 Service에서 제외
  • preStop: SIGTERM 전 5초 대기로 진행 중 요청 처리 완료
  • resources: OOM 방지, 스케줄링 품질 보장

Service 설정

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
    - name: metrics
      protocol: TCP
      port: 9090
      targetPort: 9090
  type: LoadBalancer

ConfigMap과 Secret

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  LOG_LEVEL: "info"
  METRICS_PORT: "9090"
---
# k8s/secret.yaml (base64 인코딩 필요)
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secret
type: Opaque
data:
  API_KEY: <base64-encoded-value>

5. CI/CD 파이프라인

GitHub Actions 워크플로우

# .github/workflows/deploy.yml
name: CI/CD Pipeline

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

env:
  REGISTRY: ghcr.io

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: |
          cmake -B build -DCMAKE_BUILD_TYPE=Release
          cmake --build build --parallel

      - name: Run Tests
        run: |
          cd build
          ctest --output-on-failure

      - name: Run Sanitizers
        run: |
          cmake -B build-asan -DCMAKE_BUILD_TYPE=Debug \
            -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined"
          cmake --build build-asan
          cd build-asan && ctest

  docker-build-push:
    needs: build-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

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

      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ github.repository }}/myapp
          tags: |
            type=sha,prefix=
            type=raw,value=latest

      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: docker-build-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to Kubernetes
        uses: azure/k8s-deploy@v4
        with:
          namespace: production
          manifests: |
            k8s/deployment.yaml
            k8s/service.yaml
          images: |
            ${{ env.REGISTRY }}/${{ github.repository }}/myapp:${{ github.sha }}
          strategy: basic

6. 무중단 배포

Rolling Update 전략

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 동시에 생성할 수 있는 추가 Pod 수
      maxUnavailable: 0  # 업데이트 중 사용 불가능한 Pod 수 (0 = 무중단)

Blue-Green 배포

# Blue (현재) 버전
kubectl apply -f deployment-blue.yaml

# Green (새) 버전 배포
kubectl apply -f deployment-green.yaml

# 트래픽 전환
kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'

# 검증 후 Blue 버전 제거
kubectl delete deployment myapp-blue

Graceful Shutdown (C++ 구현)

SIGTERM 수신 시 진행 중인 요청을 처리한 뒤 종료합니다.

#include <csignal>
#include <atomic>
#include <thread>
#include <chrono>

std::atomic<bool> g_running{true};

void signal_handler(int signum) {
    if (signum == SIGTERM || signum == SIGINT) {
        g_running = false;
    }
}

int main() {
    std::signal(SIGTERM, signal_handler);
    std::signal(SIGINT, signal_handler);

    // 서버 시작...
    while (g_running) {
        // 요청 처리
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    // Graceful shutdown: 새 연결 거부, 기존 연결 완료 대기
    // shutdown_server();
    // wait_for_connections_to_close(30s);
    return 0;
}

7. 모니터링 및 로깅

Prometheus 메트릭 (C++)

#include <prometheus/counter.h>
#include <prometheus/exposer.h>
#include <prometheus/registry.h>
#include <prometheus/histogram.h>

class MetricsServer {
    prometheus::Exposer exposer_{"0.0.0.0:9090"};
    std::shared_ptr<prometheus::Registry> registry_;
    prometheus::Counter* request_counter_;
    prometheus::Histogram* request_latency_;

public:
    MetricsServer() {
        registry_ = std::make_shared<prometheus::Registry>();
        exposer_.RegisterCollectable(registry_);

        auto& counter_family = prometheus::BuildCounter()
            .Name("http_requests_total")
            .Help("Total HTTP requests")
            .Register(*registry_);
        request_counter_ = &counter_family.Add({{"method", "GET"}});

        auto& histogram_family = prometheus::BuildHistogram()
            .Name("http_request_duration_seconds")
            .Help("Request latency")
            .Register(*registry_);
        request_latency_ = &histogram_family.Add(
            {{"method", "GET"}},
            prometheus::Histogram::BucketBoundaries{0.001, 0.01, 0.1, 0.5, 1.0}
        );
    }

    void record_request(double latency_seconds) {
        request_counter_->Increment();
        request_latency_->Observe(latency_seconds);
    }
};

HTTP 헬스/레디 엔드포인트

// /health - liveness: 프로세스가 살아있는지
// /ready - readiness: 트래픽 받을 준비가 됐는지 (DB 연결 등 확인)

void handle_health(Request& req, Response& res) {
    res.status = 200;
    res.body = R"({"status":"ok"})";
}

void handle_ready(Request& req, Response& res) {
    if (db_connection_ok() && cache_connection_ok()) {
        res.status = 200;
        res.body = R"({"ready":true})";
    } else {
        res.status = 503;
        res.body = R"({"ready":false})";
    }
}

구조화된 로깅 (spdlog + JSON)

#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>

void setup_logging() {
    // 콘솔 (개발용)
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    console_sink->set_level(spdlog::level::info);

    // 로테이팅 파일 (프로덕션)
    auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
        "logs/app.json", 1024 * 1024 * 10, 5  // 10MB, 5개 파일
    );
    file_sink->set_level(spdlog::level::debug);

    auto logger = std::make_shared<spdlog::logger>("app",
        spdlog::sinks_init_list{console_sink, file_sink});
    logger->set_level(spdlog::level::info);
    spdlog::set_default_logger(logger);

    // JSON 포맷 (Loki, ELK 연동)
    spdlog::set_pattern(
        R"({"timestamp":"%Y-%m-%dT%H:%M:%S.%eZ","level":"%l","msg":"%v"})"
    );

    spdlog::info("Application started",
        spdlog::arg("version", "1.0.0"),
        spdlog::arg("environment", "production"));
}

Grafana 대시보드 메트릭

메트릭설명임계값
http_requests_total총 요청 수-
http_request_duration_seconds요청 지연 시간p99 < 1s
process_resident_memory_bytes메모리 사용량< limit의 80%
process_cpu_seconds_totalCPU 사용량-

8. 자주 발생하는 에러와 해결법

에러 1: “version ‘GLIBC_2.34’ not found”

원인: 빌드 환경의 glibc가 런타임보다 높음

해결:

# Dockerfile에서 동일 베이스 이미지 사용
FROM ubuntu:22.04 AS builder
# ...
FROM ubuntu:22.04  # 런타임도 22.04

또는 정적 링크 (주의: glibc 정적 링크는 라이선스 이슈 있음):

# CMakeLists.txt
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")

에러 2: “OOMKilled” (Kubernetes)

원인: 메모리 limit 초과 또는 메모리 누수

해결:

# resources 조정
resources:
  requests:
    memory: "256Mi"   # 증가
  limits:
    memory: "512Mi"    # 여유 있게

메모리 프로파일링: Valgrind, AddressSanitizer로 누수 검사

에러 3: “ImagePullBackOff”

원인: 레지스트리 인증 실패 또는 이미지 경로 오류

해결:

# 이미지 풀 테스트
docker pull myregistry/myapp:latest

# Kubernetes Secret 생성 (프라이빗 레지스트리)
kubectl create secret docker-registry regcred \
  --docker-server=myregistry \
  --docker-username=user \
  --docker-password=pass

에러 4: “CrashLoopBackOff”

원인: livenessProbe 실패, 앱 크래시, 잘못된 설정

해결:

# 로그 확인
kubectl logs <pod-name> --previous

# 이벤트 확인
kubectl describe pod <pod-name>

# livenessProbe initialDelaySeconds 증가 (앱 시작 시간 고려)

에러 5: “502 Bad Gateway” (배포 중)

원인: readinessProbe 미설정으로 준비 안 된 Pod에 트래픽 전달

해결:

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 10  # 앱 초기화 시간에 맞게
  periodSeconds: 5

에러 6: “Permission denied” (Docker)

원인: root로 실행하거나 볼륨 권한 불일치

해결:

# Dockerfile에서 non-root 사용자
RUN useradd -r appuser
USER appuser

9. 베스트 프랙티스

이미지 태깅

# ❌ 나쁜 예: latest만 사용
docker tag myapp:latest

# ✅ 좋은 예: Git SHA + 시맨틱 버전
docker tag myapp:$GIT_SHA
docker tag myapp:v1.2.3

환경별 설정

# .env.production
LOG_LEVEL=warn
METRICS_PORT=9090
DATABASE_URL=...

# Docker
docker run -e LOG_LEVEL=warn myapp

보안

  • non-root 사용자: Docker, systemd 모두
  • 최소 권한: ProtectSystem, ReadWritePaths 제한
  • Secret 관리: Kubernetes Secret, Vault 사용
  • 이미지 스캔: Trivy, Snyk로 취약점 검사

리소스 제한

# 항상 requests와 limits 설정
resources:
  requests:
    memory: "128Mi"
    cpu: "100m"
  limits:
    memory: "256Mi"
    cpu: "500m"

10. 프로덕션 패턴

패턴 1: 헬스 체크 계층화

// Liveness: 프로세스 생존
bool liveness() { return true; }

// Readiness: 트래픽 수신 가능 여부
bool readiness() {
    return db_connected_ && cache_connected_ && !draining_;
}

패턴 2: Graceful Shutdown

  1. SIGTERM 수신
  2. 새 연결 거부
  3. 진행 중 요청 완료 대기 (타임아웃 30초)
  4. 리소스 정리 (DB 연결, 파일 핸들)
  5. 종료

패턴 3: 메트릭 기반 알림

# Prometheus Alertmanager 규칙
- alert: HighErrorRate
  expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
  for: 5m
  annotations:
    summary: "에러율 5% 초과"

패턴 4: 배포 전략 선택

전략장점단점적합
Rolling무중단, 자동롤백 수동대부분
Blue-Green즉시 전환/롤백리소스 2배중요 서비스
Canary위험 최소화복잡대규모

11. 구현 체크리스트

Docker

  • Multi-stage Dockerfile 사용
  • non-root 사용자로 실행
  • .dockerignore로 불필요 파일 제외
  • 이미지 태그에 Git SHA 포함
  • HEALTHCHECK 설정

Kubernetes

  • livenessProbe 설정
  • readinessProbe 설정
  • resources requests/limits 설정
  • RollingUpdate maxUnavailable: 0
  • preStop lifecycle (Graceful Shutdown)

systemd

  • Restart=on-failure 설정
  • systemctl enable 실행
  • 로그 journalctl 확인

모니터링

  • Prometheus 메트릭 노출 (/metrics)
  • /health, /ready 엔드포인트 구현
  • 구조화된 로깅 (JSON)
  • Grafana 대시보드 구성

CI/CD

  • PR 시 빌드·테스트
  • main 푸시 시 이미지 빌드·푸시
  • Sanitizer 테스트 포함
  • 배포 자동화 (선택)

정리

단계도구목적
빌드CMake + Docker Multi-stage재현 가능한 빌드, 이미지 최소화
배포Kubernetes / systemd오케스트레이션 / 전통 서버
CI/CDGitHub Actions자동 빌드·테스트·배포
모니터링Prometheus + Grafana메트릭 수집·시각화
로깅spdlog (JSON)구조화된 로그, 중앙 수집 연동

한 줄 요약: Docker, systemd, Kubernetes, CI/CD, Prometheus로 C++ 앱을 프로덕션에 안전하게 배포할 수 있습니다.

다음 글: [C++ 실전 가이드 #51-1] 프로파일링 도구 마스터


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++ 앱 프로덕션 배포: 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.

이전 글: [C++ 실전 가이드 #50-4] 데이터베이스 엔진 구현


관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |