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 실행
개념을 잡는 비유
이 글의 주제는 여러 부품이 맞물리는 시스템으로 보시면 이해가 빠릅니다. 한 레이어(저장·네트워크·관측)의 선택이 옆 레이어에도 영향을 주므로, 본문에서는 트레이드오프를 숫자와 패턴으로 정리합니다.
목차
- 문제 시나리오
- Docker 컨테이너화
- systemd 전통 배포
- Kubernetes 배포
- CI/CD 파이프라인
- 무중단 배포
- 모니터링 및 로깅
- 자주 발생하는 에러와 해결법
- 베스트 프랙티스
- 프로덕션 패턴
- 구현 체크리스트
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_total | CPU 사용량 | - |
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
- SIGTERM 수신
- 새 연결 거부
- 진행 중 요청 완료 대기 (타임아웃 30초)
- 리소스 정리 (DB 연결, 파일 핸들)
- 종료
패턴 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/CD | GitHub 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 |