# SSL 증명서 자동화 프로젝트 (10/10): 프로덕션 배포와 운영 가이드 ## 전체 시스템 아키텍처 리뷰 8개월간의 여정을 마무리하며, 구축한 시스템의 전체 그림을 다시 그린다. ``` ┌─────────────────────────────────────────────────────────────────────┐ │ Internet │ └────────────┬────────────────────────────────────────────────────────┘ │ ┌────────────▼─────────────────────────────────────────────────────────┐ │ Load Balancer (HA) │ │ ├─ HTTPS (443) → API Gateway │ │ └─ Prometheus Metrics (9090) │ └────────────┬─────────────────────────────────────────────────────────┘ │ ┌────────────▼─────────────────────────────────────────────────────────┐ │ API Layer (FastAPI) - 3 instances │ │ ├─ /api/v1/certificates (CRUD) │ │ ├─ /api/v1/approvals (워크플로우) │ │ ├─ /api/v1/deployments (배포 관리) │ │ └─ Authentication (JWT) + RBAC │ └─────┬────────────────────────────────────┬────────────────────────────┘ │ │ ┌─────▼───────────┐ ┌─────────▼──────────┐ │ PostgreSQL │ │ Redis │ │ (Primary + │ │ ├─ Rate Limiter │ │ Replica) │ │ ├─ Celery Queue │ │ │ │ └─ Cache │ │ ├─ certificates│ └────────────────────┘ │ ├─ deployments │ │ ├─ approvals │ │ └─ audit_logs │ └─────────────────┘ │ ┌────────────▼─────────────────────────────────────────────────────────┐ │ Worker Layer (Celery) - 5 workers │ │ ├─ tasks.issuance (ACME 증명서 발급) │ │ ├─ tasks.renewal (자동 갱신) │ │ └─ tasks.deployment (배포 오케스트레이션) │ └────────┬──────────────────────────┬───────────────────────────────────┘ │ │ ┌────────▼──────────┐ ┌──────────▼────────────┐ │ ACME Client │ │ Deployment Executors │ │ ├─ GlobalSign │ │ ├─ Ansible │ │ ├─ Let's Encrypt │ │ ├─ AWS SDK (boto3) │ │ └─ DigiCert │ │ ├─ Kubernetes API │ └───────────────────┘ │ └─ F5 iControl │ └───────────────────────┘ │ ┌────────────▼─────────────────────────────────────────────────────────┐ │ Secret Management (HashiCorp Vault) │ │ ├─ ACME Account Private Keys │ │ ├─ Certificate Private Keys (encrypted) │ │ ├─ SSH Keys (deployment) │ │ └─ API Tokens (AWS, DNS providers) │ └──────────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────────┐ │ Observability Stack │ │ ├─ Prometheus (metrics scraping) │ │ ├─ Grafana (dashboards) │ │ ├─ ELK Stack (logs: Elasticsearch + Kibana) │ │ └─ AlertManager (Slack/PagerDuty 알림) │ └──────────────────────────────────────────────────────────────────────┘ ``` ## 고가용성(HA) 구성 ### 1. API Layer HA ```yaml # docker-compose.prod.yml version: '3.8' services: api: image: cert-manager-api:v1.0.0 deploy: replicas: 3 update_config: parallelism: 1 delay: 10s restart_policy: condition: on-failure max_attempts: 3 environment: - DATABASE_URL=postgresql://certmgr:pass@postgres-primary:5432/certmgr - REDIS_URL=redis://redis-cluster:6379/0 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 ports: - "8000" networks: - cert_network nginx_lb: image: nginx:1.25 volumes: - ./nginx-lb.conf:/etc/nginx/nginx.conf:ro ports: - "443:443" - "80:80" depends_on: - api networks: - cert_network networks: cert_network: driver: overlay ``` ### 2. Database HA (PostgreSQL Streaming Replication) ```bash # Primary 서버 # postgresql.conf wal_level = replica max_wal_senders = 3 wal_keep_size = 64 # pg_hba.conf host replication replicator 10.0.0.0/24 md5 # Replica 서버 # recovery.conf (PostgreSQL 12+는 postgresql.conf에) primary_conninfo = 'host=postgres-primary port=5432 user=replicator password=xxx' ``` ### 3. Redis Sentinel (HA) ```yaml # Redis Sentinel 설정 redis-master: image: redis:7-alpine command: redis-server --appendonly yes redis-sentinel: image: redis:7-alpine command: redis-sentinel /etc/redis/sentinel.conf volumes: - ./sentinel.conf:/etc/redis/sentinel.conf deploy: replicas: 3 ``` ## 재해 복구(DR) 계획 ### RTO/RPO 목표 ``` Component RTO (Recovery Time Objective) RPO (Recovery Point Objective) API Layer 5분 0 (stateless) Database 30분 5분 (WAL shipping) Redis Cache 5분 0 (cache는 재구축 가능) Certificate Storage 1시간 1시간 (S3 cross-region replication) ``` ### 백업 전략 ```python # backup/backup_service.py import subprocess from datetime import datetime class BackupService: def backup_database(self): """PostgreSQL 백업""" timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S") backup_file = f"/backups/certmgr_{timestamp}.dump" # pg_dump (logical backup) subprocess.run([ "pg_dump", "-h", "postgres-primary", "-U", "certmgr", "-Fc", # Custom format (compressed) "-f", backup_file, "certmgr" ], check=True) # S3에 업로드 self._upload_to_s3(backup_file, f"backups/database/{timestamp}.dump") # 7일 이상 된 백업 삭제 self._cleanup_old_backups(days=7) def backup_vault_snapshots(self): """Vault 스냅샷 백업""" subprocess.run([ "vault", "operator", "raft", "snapshot", "save", f"/backups/vault_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.snap" ], check=True) # Cron: 매일 오전 2시 백업 # 0 2 * * * /usr/bin/python3 /opt/cert-manager/backup_service.py ``` ## 프로덕션 배포 체크리스트 ### Phase 1: 인프라 준비 ```markdown Infrastructure Checklist: Database: - [ ] PostgreSQL 15+ 설치 (primary + replica) - [ ] Connection pooling 설정 (PgBouncer) - [ ] 백업 자동화 (pg_dump + WAL archiving) - [ ] 모니터링 (pg_stat_statements) Redis: - [ ] Redis Sentinel 클러스터 구성 (3 nodes) - [ ] Persistence 설정 (AOF + RDB) - [ ] Maxmemory policy: allkeys-lru Vault: - [ ] Vault 클러스터 구성 (3 nodes) - [ ] Auto-unseal 설정 (AWS KMS) - [ ] Audit logging 활성화 - [ ] 백업 자동화 Network: - [ ] VPC/Subnet 설계 - [ ] Security Group 최소 권한 원칙 - [ ] NAT Gateway (egress traffic) - [ ] VPN/Bastion Host (관리 접속) Secrets: - [ ] ACME EAB keys → Vault - [ ] Database credentials → Vault - [ ] API tokens (AWS, DNS) → Vault - [ ] JWT signing key → Vault ``` ### Phase 2: 애플리케이션 배포 ```bash #!/bin/bash # deploy.sh set -e echo "=== Certificate Manager Deployment ===" # 1. 환경 변수 확인 if [ -z "$DATABASE_URL" ]; then echo "ERROR: DATABASE_URL not set" exit 1 fi # 2. Database migration echo "Running database migrations..." alembic upgrade head # 3. Docker images 빌드 echo "Building Docker images..." docker build -t cert-manager-api:${VERSION} -f Dockerfile.api . docker build -t cert-manager-worker:${VERSION} -f Dockerfile.worker . # 4. Docker registry에 push docker push registry.example.com/cert-manager-api:${VERSION} docker push registry.example.com/cert-manager-worker:${VERSION} # 5. Kubernetes 배포 echo "Deploying to Kubernetes..." kubectl set image deployment/cert-api \ cert-api=registry.example.com/cert-manager-api:${VERSION} \ --record kubectl set image deployment/cert-worker \ cert-worker=registry.example.com/cert-manager-worker:${VERSION} \ --record # 6. Rollout 상태 확인 kubectl rollout status deployment/cert-api kubectl rollout status deployment/cert-worker # 7. Health check echo "Verifying health..." curl -f https://cert-api.example.com/health || exit 1 echo "✓ Deployment completed successfully!" ``` ### Phase 3: 검증 ```python # tests/integration/test_e2e.py import pytest import requests API_BASE = "https://cert-api.example.com" def test_full_certificate_lifecycle(): """전체 증명서 라이프사이클 E2E 테스트""" # 1. 증명서 요청 response = requests.post(f"{API_BASE}/api/v1/certificates", json={ "common_name": "e2e-test.example.com", "sans": [], "cert_type": "dv", "challenge_type": "dns-01", "requested_by": "e2e-test@example.com" }, headers={"Authorization": f"Bearer {TEST_TOKEN}"}) assert response.status_code == 201 cert_id = response.json()["id"] # 2. 승인 요청 확인 approval_response = requests.get( f"{API_BASE}/api/v1/approvals/my-pending", headers={"Authorization": f"Bearer {APPROVER_TOKEN}"} ) approval_id = approval_response.json()[0]["id"] # 3. 승인 requests.post( f"{API_BASE}/api/v1/approvals/{approval_id}/approve", json={"approver_id": "team_lead", "approver_role": "team_lead"}, headers={"Authorization": f"Bearer {APPROVER_TOKEN}"} ) # 4. 발급 완료 대기 (polling) import time for _ in range(60): # 최대 5분 cert = requests.get( f"{API_BASE}/api/v1/certificates/{cert_id}", headers={"Authorization": f"Bearer {TEST_TOKEN}"} ).json() if cert["status"] == "active": break time.sleep(5) else: pytest.fail("Certificate issuance timeout") # 5. 배포 deploy_response = requests.post( f"{API_BASE}/api/v1/certificates/{cert_id}/deploy", headers={"Authorization": f"Bearer {TEST_TOKEN}"} ) assert deploy_response.status_code == 202 print("✓ E2E test passed!") ``` ## 모니터링과 알림 ### Grafana 대시보드 ```json // grafana-dashboard.json { "dashboard": { "title": "Certificate Manager Overview", "panels": [ { "title": "Active Certificates", "targets": [{ "expr": "count(certificate_expiry_days{status='active'})" }] }, { "title": "Certificates Expiring Soon", "targets": [{ "expr": "count(certificate_expiry_days < 30)" }] }, { "title": "Issuance Success Rate (24h)", "targets": [{ "expr": "rate(acme_certificate_issuance_total{status='success'}[24h]) / rate(acme_certificate_issuance_total[24h])" }] }, { "title": "Deployment Failures", "targets": [{ "expr": "increase(deployment_failures_total[1h])" }] } ] } } ``` ### AlertManager 알림 규칙 ```yaml # prometheus-alerts.yml groups: - name: certificate_alerts interval: 1m rules: - alert: CertificateExpiringIn7Days expr: certificate_expiry_days < 7 and certificate_expiry_days > 0 for: 1h labels: severity: warning annotations: summary: "Certificate {{ $labels.common_name }} expiring in {{ $value }} days" - alert: CertificateIssuanceFailure expr: rate(acme_certificate_issuance_total{status="failure"}[5m]) > 0.1 for: 10m labels: severity: critical annotations: summary: "High certificate issuance failure rate" - alert: DeploymentQueueBacklog expr: celery_queue_length{queue="deployment"} > 100 for: 30m labels: severity: warning annotations: summary: "Deployment queue backlog: {{ $value }} tasks" ``` ## 트러블슈팅 가이드 ### 문제 1: 증명서 발급 실패 ```bash # 로그 확인 kubectl logs -f deployment/cert-worker --tail=100 | grep "ERROR" # ACME Order 상태 확인 curl -H "Authorization: Bearer $TOKEN" \ https://cert-api.example.com/api/v1/certificates/{id} # DNS 검증 확인 dig _acme-challenge.example.com TXT +short # Celery 작업 큐 확인 redis-cli -h redis-primary llen celery:issuance ``` ### 문제 2: 배포 실패 ```bash # Ansible 배포 로그 tail -f /var/log/ansible/deployment.log # 대상 서버 SSH 접속 테스트 ssh -i /opt/vault/ssh-keys/webserver-01.pem deploy@webserver-01 # Nginx 설정 테스트 ansible webservers -m shell -a "nginx -t" ``` ### 문제 3: Database 성능 저하 ```sql -- Slow query 확인 SELECT query, calls, total_time, mean_time FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10; -- Index 사용률 SELECT schemaname, tablename, indexname, idx_scan FROM pg_stat_user_indexes ORDER BY idx_scan ASC; ``` ## 성능 튜닝 ### API Layer ```python # uvicorn 설정 uvicorn main:app \ --host 0.0.0.0 \ --port 8000 \ --workers 4 \ --worker-class uvicorn.workers.UvicornWorker \ --limit-concurrency 1000 \ --timeout-keep-alive 5 ``` ### Database ```sql -- Connection pooling (PgBouncer) -- pgbouncer.ini [databases] certmgr = host=postgres-primary port=5432 dbname=certmgr [pgbouncer] pool_mode = transaction max_client_conn = 1000 default_pool_size = 25 ``` ### Celery Workers ```python # celeryconfig.py broker_url = "redis://redis-cluster:6379/0" result_backend = "redis://redis-cluster:6379/1" worker_prefetch_multiplier = 4 worker_max_tasks_per_child = 1000 task_acks_late = True task_reject_on_worker_lost = True # Worker concurrency # celery -A tasks worker --concurrency=10 --pool=prefork ``` ## 마무리: 8개월의 여정 이 시리즈를 통해 우리는 다음을 달성했다: 1. **ACME 프로토콜** 완전 이해와 커스텀 구현 2. **GlobalSign Atlas** 상용 CA 통합 3. **도메인 검증** 자동화 (HTTP-01, DNS-01) 4. **증명서 관리 DB**와 RESTful API 5. **배포 자동화** (Ansible, AWS, Kubernetes) 6. **승인 워크플로우**와 RBAC 7. **PQC 대응** 설계 8. **프로덕션 배포** 및 운영 가이드 ### 최종 메트릭 예상 ``` 현재 (수동): - 증명서 갱신 횟수 (2029년): 2,310회/년 - 인력 투입: 1,155시간/년 (144 영업일) - 인적 오류 발생률: 5% - 만료 사고 발생: 연 2-3건 자동화 후: - 증명서 갱신: 100% 자동 - 인력 투입: 80시간/년 (모니터링 + 예외 처리) - 인적 오류: 0.1% (대부분 자동화) - 만료 사고: 0건 (30일 전 자동 갱신 + 알림) ROI: - 개발 투자: 8개월 × 5명 = 40인월 - 연간 절감: 1,075시간 ≈ 134 인일 ≈ 6.7 인월 - 회수 기간: 6개월 ``` **감사의 말**: 이 복잡한 프로젝트를 함께 고민하고 구축한 모든 분들께 감사드립니다. 자동화가 미래다! --- **시리즈 목차** 1. SSL 증명서 자동화 필요성과 프로젝트 개요 2. ACME 프로토콜 이해와 동작 원리 3. GlobalSign Atlas ACME 연동 실습 4. 도메인 검증 방식 구현 (HTTP-01, DNS-01) 5. ACME 클라이언트 선정과 커스텀 구현 6. 증명서 관리 DB 설계와 API 구현 7. 증명서 배포 자동화 파이프라인 구축 8. 승인 워크플로우와 거버넌스 구현 9. PQC 대응 설계와 미래 확장성 10. **[현재글] 전체 시스템 통합과 프로덕션 배포 가이드** --- ## 끝