포스트

Cloud Native Kubernetes 구축 가이드

Cloud Native Kubernetes 구축 가이드

대상: 운영 환경 Kubernetes 클러스터 구축 담당자

샘플 프로젝트: RummiArena (루미큐브 멀티 LLM 대전 플랫폼)

버전: 1.0 (2026-05-26)


목차

  1. 이 가이드의 구성
  2. 샘플 프로젝트: RummiArena 소개
  3. 운영 환경 클러스터 설계 원칙
  4. HTTPS 전략: 인증서 관리와 TLS 종료
  5. Ingress 전략 비교 및 선택
  6. Istio Service Mesh 구성
  7. 내부 통신에서 HTTPS는 반드시 필요한가
  8. RummiArena 실제 구성 사례
  9. 운영 체크리스트
  10. 별첨 A: OpenShift vs Vanilla Kubernetes 비교
  11. 별첨 B: 주요 YAML 예제 모음

1. 이 가이드의 구성

이 가이드는 Cloud Native 애플리케이션을 Kubernetes 위에서 운영 환경 수준으로 구축하는 실무 지침을 담는다. 단순한 “설치법”이 아니라 왜 그렇게 설계하는가를 중심에 두고 작성했다.

샘플로 RummiArena를 선택한 이유

RummiArena는 다음 요소를 동시에 갖춘 적절한 샘플이다.

요소내용
다중 서비스Go 백엔드, NestJS AI 어댑터, Next.js 프론트엔드, PostgreSQL, Redis (5개 서비스)
WebSocket실시간 대전이므로 HTTP → WebSocket 업그레이드 처리 필요
외부 API 연동LLM API (DeepSeek, OpenAI, Anthropic) — 장시간 연결, 타임아웃 설계 필수
서비스 메시Istio selective mesh (game-server + ai-adapter만 사이드카)
GitOpsArgoCD + GitLab CI 완전 자동화
보안 등급 차이퍼블릭 프론트엔드 vs 내부 전용 AI 어댑터

이 가이드의 모든 예제는 RummiArena의 실제 YAML을 기반으로 작성됐다.


2. 샘플 프로젝트: RummiArena 소개

2.1 아키텍처 개요

graph TB
    User["사용자 (HTTPS)"]

    subgraph Ingress["Ingress Layer"]
        IC["Ingress Controller\n(Traefik / NGINX / Gateway API)"]
        Cert["cert-manager\n(TLS 인증서)"]
    end

    subgraph Mesh["Service Mesh (Istio)"]
        direction LR
        GS["game-server\n(sidecar ✅)"]
        AA["ai-adapter\n(sidecar ✅ / STRICT)"]
    end

    subgraph Storage["Storage"]
        PG["PostgreSQL"]
        Redis["Redis"]
    end

    subgraph External["External APIs"]
        LLM["DeepSeek / OpenAI\n/ Anthropic"]
    end

    FE["frontend\n(Next.js, sidecar ❌)"]

    User --> IC
    IC --> FE
    IC --> GS
    FE --> GS
    GS <-->|"mTLS (mesh)"| AA
    GS --> PG
    GS --> Redis
    AA --> LLM

2.2 서비스별 특성

서비스프로토콜외부 노출Istio 사이드카특이사항
frontendHTTP/HTTPS✅ 공개SSR + 정적 자산
game-serverHTTP + WS✅ 공개✅ PERMISSIVEWebSocket 장시간 연결
ai-adapterHTTP❌ 내부 전용✅ STRICTLLM 호출 최대 700s
PostgreSQLTCP❌ 내부 전용영속 데이터
RedisTCP❌ 내부 전용게임 상태

2.3 현재 운영 구성 (실제)

graph LR
    User["사용자"] -->|"NodePort :30000"| FE["frontend"]
    User -->|"NodePort :30080"| GS["game-server"]
    GS <-->|"mTLS ClusterIP"| AA["ai-adapter"]
    GS --> PG["PostgreSQL"]
    GS --> Redis["Redis"]
    ArgoCD["ArgoCD"] -->|"GitOps sync"| GS
    ArgoCD --> AA

참고: RummiArena는 연구용 단일 노드 클러스터로 현재 NodePort를 사용 중이다. 운영 환경에서는 반드시 Ingress Controller를 통한 HTTPS 단일 진입점을 사용한다.


3. 운영 환경 클러스터 설계 원칙

3.1 단일 진입점 (Single Entry Point)

운영 환경에서는 외부 트래픽이 반드시 하나의 Ingress 레이어를 통과해야 한다.

1
2
3
4
인터넷 → LoadBalancer (클라우드) or MetalLB (베어메탈)
       → Ingress Controller (Traefik / NGINX / Gateway API)
       → TLS 종료 (cert-manager 자동 발급)
       → 내부 서비스 (HTTP 평문 또는 mTLS)

이유:

  • TLS 관리 중앙화 (인증서 갱신 자동화)
  • 인증/접근 제어 단일 지점
  • Rate limiting, WAF 적용 용이
  • 관찰 가능성 (모든 요청 로그 집중)

3.2 네임스페이스 전략

RummiArena의 실제 네임스페이스 구성을 기반으로 한 권장 패턴:

1
2
3
4
5
6
7
ingress-system      # Ingress Controller 전용
cert-manager        # TLS 인증서 관리
istio-system        # Service Mesh 컨트롤 플레인
argocd              # GitOps
monitoring          # Prometheus, Grafana
{app-name}          # 애플리케이션 (예: rummikub)
sonarqube           # 코드 품질 (CI 인프라)

원칙: 인프라 컴포넌트는 애플리케이션 네임스페이스와 분리한다. Istio Peer Authentication 정책을 네임스페이스 단위로 적용하기 때문에 혼용하면 정책 범위가 복잡해진다.

3.3 리소스 계층

graph TB
    subgraph "클러스터 수준"
        CRQ["ClusterResourceQuota\n(전체 쿼터)"]
        NP["NetworkPolicy\n(기본 deny-all)"]
        PSA["PodSecurity Admission\n(restricted)"]
    end

    subgraph "네임스페이스 수준"
        RQ["ResourceQuota\n(네임스페이스 쿼터)"]
        LR["LimitRange\n(Pod 기본값)"]
    end

    subgraph "워크로드 수준"
        REQ["requests/limits\n(컨테이너별)"]
        SA["ServiceAccount\n(최소 권한)"]
    end

4. HTTPS 전략: 인증서 관리와 TLS 종료

운영 환경에서 사용자는 HTTPS로만 접근한다는 것을 전제로 한다.

4.1 cert-manager 개요

cert-manager(v1.16.4+)는 Kubernetes 네이티브 인증서 관리 도구다. Let’s Encrypt, Vault, 자체 CA 등 다양한 발급자를 지원하며, 만료 30일 전 자동 갱신한다.

sequenceDiagram
    participant IC as Ingress Controller
    participant CM as cert-manager
    participant LE as Let's Encrypt
    participant DNS as DNS Provider

    IC->>CM: TLS 인증서 필요 (Ingress annotation)
    CM->>LE: ACME 챌린지 요청
    alt HTTP-01 (공개 도메인)
        LE->>IC: /.well-known/acme-challenge/{token} 검증
    else DNS-01 (와일드카드 / 프라이빗)
        LE->>DNS: TXT 레코드 생성 요청
        CM->>DNS: _acme-challenge.{domain} TXT 생성
        LE->>DNS: TXT 레코드 확인
    end
    LE-->>CM: 인증서 발급
    CM-->>IC: Secret에 tls.crt / tls.key 저장

4.2 HTTP-01 vs DNS-01 선택 기준

항목HTTP-01DNS-01
와일드카드 인증서❌ 불가✅ 가능 (*.example.com)
프라이빗 클러스터❌ 80포트 공개 필요✅ 가능
설정 복잡도낮음DNS 프로바이더 설정 필요
권장 상황단일 도메인, 퍼블릭 클러스터멀티 서비스 서브도메인, 프라이빗
포트 요건80/443 공개DNS 권한만 필요

RummiArena 적용 시 권장: 여러 서브도메인 (game.example.com, api.example.com, argocd.example.com) 을 사용하는 경우 와일드카드 + DNS-01.

4.3 cert-manager 설치 및 ClusterIssuer 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# cert-manager 설치 (Helm)
# helm repo add jetstack https://charts.jetstack.io
# helm install cert-manager jetstack/cert-manager \
#   --namespace cert-manager --create-namespace \
#   --set crds.enabled=true

---
# ClusterIssuer (Let's Encrypt 프로덕션)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: ops@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
    # HTTP-01: 퍼블릭 서비스용
    - http01:
        ingress:
          ingressClassName: traefik
      selector:
        dnsNames:
        - "game.example.com"
    # DNS-01: 와일드카드 / 내부 서비스용
    - dns01:
        route53:  # 또는 cloudflare, google 등
          region: ap-northeast-2
          accessKeyID: AKIAXXXXXXXX
          secretAccessKeySecretRef:
            name: route53-credentials
            key: secret-access-key
      selector:
        dnsNames:
        - "*.example.com"

4.4 TLS 종료 위치별 전략

graph LR
    subgraph "전략 1: Edge TLS 종료"
        U1["사용자 HTTPS"] --> IC1["Ingress Controller\n(TLS 종료)"] --> S1["서비스 HTTP\n(평문)"]
    end

    subgraph "전략 2: Passthrough + 서비스 TLS"
        U2["사용자 HTTPS"] --> IC2["Ingress Controller\n(TCP 패스스루)"] --> S2["서비스 HTTPS\n(자체 TLS)"]
    end

    subgraph "전략 3: Re-encrypt (mTLS)"
        U3["사용자 HTTPS"] --> IC3["Ingress Controller\n(TLS 종료)"] -->|"mTLS"| S3["서비스 mTLS\n(Istio)"]
    end
전략보안 수준복잡도권장 상황
Edge 종료중간낮음Istio 미사용, 트러스트 경계 = Ingress
Passthrough높음중간gRPC, DB 직접 노출, 엔드투엔드 TLS
Re-encrypt (mTLS)최고높음Zero-trust, PCI-DSS, 금융 / 의료

5. Ingress 전략 비교 및 선택

5.1 현황 개요 (2026 기준)

graph LR
    Legacy["Legacy Ingress API\n(networking.k8s.io/v1)"]
    New["Gateway API\n(gateway.networking.k8s.io)"]

    Legacy -->|"마이그레이션 권장"| New

    subgraph "Legacy 구현체"
        NGINX["ingress-nginx\n(⚠️ EOL 2026-03)"]
        Traefik1["Traefik v3\n(✅ 활발히 유지)"]
        HAProxy["HAProxy Ingress\n(✅ 유지)"]
    end

    subgraph "Gateway API 구현체"
        Envoy["Envoy Gateway\n(✅ CNCF 공식)"]
        Traefik2["Traefik v3\n(Gateway API 지원)"]
        Istio["Istio Gateway\n(✅ Mesh 통합)"]
        NGINXGW["NGINX Gateway Fabric\n(✅ 신규)"]
    end

5.2 ingress-nginx (커뮤니티) — EOL 주의

⚠️ 중요: 커뮤니티 ingress-nginx는 2026년 3월 공식 EOL 선언. 보안 패치 중단. 현재 K8s 클러스터의 약 50%가 이를 사용 중으로, 마이그레이션이 업계의 주요 과제다.

현재 선택지:

  • NGINX Inc. (nginx-ingress / nginx-gateway-fabric): 상용 지원 + Gateway API 지원 신규 구현체
  • Traefik: ingress-nginx 대체제로 가장 주목받는 옵션
  • Envoy Gateway: CNCF 공식 Gateway API 레퍼런스 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# ingress-nginx 사용 예시 (레거시 — 마이그레이션 필요)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rummiarena
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"    # WebSocket
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - game.example.com
    secretName: rummiarena-tls
  rules:
  - host: game.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend
            port:
              number: 3000
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: game-server
            port:
              number: 8080
      - path: /ws
        pathType: Prefix
        backend:
          service:
            name: game-server
            port:
              number: 8080

5.3 Traefik v3

Traefik은 GitOps 친화적이며, Kubernetes CRD(IngressRoute)와 표준 Ingress 모두 지원한다. 2026년 현재 25,000+ GitHub 스타를 보유한 가장 활발한 커뮤니티 중 하나다.

주요 특징:

  • 동적 설정 재로드 (재시작 없음)
  • 자동 TLS (Let’s Encrypt 내장)
  • 미들웨어 체인 (인증, Rate limit, Circuit breaker)
  • Dashboard UI 기본 제공
  • WebSocket 기본 지원
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# Traefik IngressRoute 예시 (RummiArena 기반)
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: rummiarena-web
  namespace: rummikub
spec:
  entryPoints:
    - websecure       # HTTPS (443)
  routes:
    # 프론트엔드
    - match: Host(`game.example.com`) && PathPrefix(`/`)
      kind: Rule
      services:
        - name: frontend
          port: 3000
      middlewares:
        - name: security-headers

    # API (game-server REST)
    - match: Host(`game.example.com`) && PathPrefix(`/api`)
      kind: Rule
      services:
        - name: game-server
          port: 8080
      middlewares:
        - name: rate-limit-api

    # WebSocket (실시간 게임)
    - match: Host(`game.example.com`) && PathPrefix(`/ws`)
      kind: Rule
      services:
        - name: game-server
          port: 8080
          # Traefik은 Connection: Upgrade 자동 감지
  tls:
    certResolver: letsencrypt    # 자동 발급
---
# 보안 헤더 미들웨어
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: security-headers
  namespace: rummikub
spec:
  headers:
    forceSTSHeader: true
    stsSeconds: 31536000
    stsIncludeSubdomains: true
    contentTypeNosniff: true
    browserXssFilter: true
    frameDeny: true
---
# Rate limit (API 보호)
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: rate-limit-api
  namespace: rummikub
spec:
  rateLimit:
    average: 100
    burst: 200
    period: 1m

Traefik 설치 (Helm):

1
2
3
4
5
6
7
helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik \
  --namespace traefik --create-namespace \
  --set providers.kubernetesCRD.enabled=true \
  --set providers.kubernetesIngress.enabled=true \
  --set service.type=LoadBalancer \
  --set logs.general.level=WARN

RummiArena 구성 참고: helm/traefik/ 디렉터리에 IngressRoute, Middleware, TLSOption, ServersTransport YAML이 있다. 현재는 적용되지 않은 상태(NodePort 운영 중)이나 운영 환경 전환 시 바로 사용 가능한 구조다.

5.4 Gateway API (차세대 표준)

Kubernetes SIG-Network에서 공식 Ingress 후속으로 지정한 API. 역할 분리(인프라/개발), 멀티-테넌트, L4/L7 통합이 핵심이다.

classDiagram
    class GatewayClass {
        +controller: envoygateway.io/gateway
        클러스터 관리자 소유
    }
    class Gateway {
        +listeners: HTTPS 443
        +tls: letsencrypt-prod
        인프라 팀 소유
    }
    class HTTPRoute {
        +rules: path-based routing
        개발 팀 소유 (네임스페이스별)
    }

    GatewayClass --> Gateway
    Gateway --> HTTPRoute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# GatewayClass (인프라 팀)
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
# Gateway (인프라 팀)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: rummiarena-gw
  namespace: ingress-system
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  gatewayClassName: envoy-gateway
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    tls:
      mode: Terminate
      certificateRefs:
      - name: rummiarena-tls
    allowedRoutes:
      namespaces:
        from: Selector
        selector:
          matchLabels:
            gateway-access: "true"
---
# HTTPRoute (개발 팀 — rummikub 네임스페이스)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: rummiarena
  namespace: rummikub
spec:
  parentRefs:
  - name: rummiarena-gw
    namespace: ingress-system
  hostnames:
  - "game.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /ws
    backendRefs:
    - name: game-server
      port: 8080
  - matches:
    - path:
        type: PathPrefix
        value: /api
    backendRefs:
    - name: game-server
      port: 8080
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: frontend
      port: 3000

5.5 Ingress 전략 최종 비교표

항목ingress-nginxTraefik v3Envoy GatewayIstio Gateway
상태⚠️ EOL 2026-03✅ 활발✅ CNCF 공식✅ Mesh 통합
API 지원IngressIngress + CRD + Gateway APIGateway API 네이티브Istio CRD + Gateway API
TLS 자동화cert-manager 연동내장 + cert-managercert-manager 연동cert-manager 연동
WebSocketannotation 필요기본 지원기본 지원기본 지원
미들웨어annotation 기반풍부한 CRDEnvoyFilterVirtualService
Istio 공존PERMISSIVE 모드 필요PERMISSIVE 모드 필요PERMISSIVE 모드 필요자연스러운 통합
학습 곡선낮음중간높음높음
운영 규모 적합소-중중-대대 (Mesh 이미 사용 시)
신규 구축 권장❌ 마이그레이션 필요✅ (Istio 사용 시)

선택 지침:

  • Istio를 사용하지 않는 경우: Traefik (운영 편의성) 또는 Envoy Gateway (Gateway API 표준 준수)
  • Istio를 사용하는 경우: Istio Gateway (Mesh와 통합) + HTTPRoute
  • 기존 ingress-nginx 사용 중: Traefik 으로 마이그레이션 우선 검토

6. Istio Service Mesh 구성

6.1 Service Mesh가 필요한 시점

Service Mesh는 모든 환경에 필요하지 않다. 도입 기준을 명확히 해야 한다.

1
2
3
4
5
6
7
8
9
10
11
도입 권장:
✅ 서비스 간 암호화 (mTLS) 가 규정/컴플라이언스 요건
✅ 10개 이상의 서비스가 복잡하게 상호작용
✅ 세밀한 트래픽 제어 (카나리, 서킷 브레이커, 리트라이 정책)
✅ 서비스 간 관찰 가능성 (분산 트레이싱, 메트릭)
✅ Zero-trust 네트워크 모델

도입 불필요:
❌ 서비스 수 5개 이하, 단순 구조
❌ 팀 규모 소규모 (운영 부담 > 이점)
❌ 레이턴시에 극도로 민감한 서비스 (사이드카 오버헤드 ~1-5ms)

6.2 Istio 아키텍처

graph TB
    subgraph "컨트롤 플레인 (istiod)"
        Pilot["Pilot\n(xDS 설정 배포)"]
        Citadel["Citadel\n(인증서 관리)"]
        Galley["Galley\n(설정 검증)"]
    end

    subgraph "데이터 플레인"
        subgraph "game-server Pod"
            GS_App["app 컨테이너"]
            GS_Proxy["envoy proxy\n(sidecar)"]
        end
        subgraph "ai-adapter Pod"
            AA_App["app 컨테이너"]
            AA_Proxy["envoy proxy\n(sidecar)"]
        end
        subgraph "frontend Pod (사이드카 없음)"
            FE_App["app 컨테이너"]
        end
    end

    Pilot -->|"xDS"| GS_Proxy
    Pilot -->|"xDS"| AA_Proxy
    Citadel -->|"인증서"| GS_Proxy
    Citadel -->|"인증서"| AA_Proxy
    GS_Proxy <-->|"mTLS"| AA_Proxy

6.3 Selective Mesh 전략 (RummiArena 실제 적용)

전체 서비스에 사이드카를 주입하면 약 250Mi 추가 메모리가 필요하다. RummiArena는 보안이 중요한 서비스만 선택적으로 사이드카를 주입한다.

1
2
3
4
5
6
7
8
# 사이드카 주입 활성화 (네임스페이스 레벨)
# kubectl label namespace rummikub istio-injection=enabled

# 특정 Pod만 제외 (frontend, postgres, redis)
# Pod spec에 annotation 추가:
metadata:
  annotations:
    sidecar.istio.io/inject: "false"   # 이 Pod는 메시 제외
서비스사이드카이유
game-serverLLM 응답 수신, 내부 통신 보호 필요
ai-adapterLLM API 키 보유, 외부 노출 금지
frontend퍼블릭 서비스, Ingress가 이미 TLS 처리
postgresDB 자체 인증으로 충분, 오버헤드 불필요
redis동일

6.4 mTLS 정책 계층

graph TB
    subgraph "네임스페이스 정책 (기본값)"
        NS_PERM["PeerAuthentication\nmode: PERMISSIVE\n사이드카 없는 Pod도 접근 가능"]
    end

    subgraph "서비스별 오버라이드"
        AA_STRICT["PeerAuthentication (ai-adapter)\nmode: STRICT\n사이드카만 접근 가능"]
    end

    GS["game-server\n(사이드카 ✅)"] -->|"mTLS — 허용"| AA["ai-adapter"]
    FE["frontend\n(사이드카 ❌)"] -->|"평문 — 차단됨"| AA
    IC["Ingress Controller\n(사이드카 ❌)"] -->|"평문 — 허용 (PERMISSIVE)"| GS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 네임스페이스 기본: PERMISSIVE (Ingress Controller 접근 허용)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: rummikub
spec:
  mtls:
    mode: PERMISSIVE
---
# ai-adapter: STRICT (사이드카만 접근 가능)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: ai-adapter-strict
  namespace: rummikub
spec:
  selector:
    matchLabels:
      app: ai-adapter
  mtls:
    mode: STRICT

6.5 트래픽 관리 (VirtualService + DestinationRule)

RummiArena의 실제 ai-adapter 설정:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# VirtualService: LLM 장시간 호출을 위한 타임아웃 설정
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: ai-adapter
  namespace: rummikub
spec:
  hosts:
  - ai-adapter
  http:
  - timeout: 1010s      # AI_ADAPTER_TIMEOUT_SEC(1000) + 10s 버퍼
    retries:
      attempts: 1        # LLM 비용 때문에 재시도 1회로 제한
      perTryTimeout: 1010s
      retryOn: "5xx,reset"
    route:
    - destination:
        host: ai-adapter
---
# DestinationRule: 서킷 브레이커
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: ai-adapter
  namespace: rummikub
spec:
  host: ai-adapter
  trafficPolicy:
    outlierDetection:
      consecutive5xxErrors: 3      # 연속 3회 5xx → 격리
      interval: 60s                 # 평가 주기 (LLM 700s 응답 고려해 여유있게)
      baseEjectionTime: 120s        # 2분 격리 후 재시도
      maxEjectionPercent: 50
      splitExternalLocalOriginErrors: true

타임아웃 체인 설계 원칙 (RummiArena ADR #41):

1
script_ws(770s) > gs_ctx(760s) > http_client(760s) > istio_vs(1010s) > adapter_floor(1000s) > llm_vendor

각 레이어의 타임아웃은 하위 레이어보다 반드시 크게 설정한다. 역전 시 정상 응답이 타임아웃으로 오분류된다.

6.6 Istio Ambient Mode (사이드카 없는 Mesh)

Istio v1.24 (2024년 11월)에서 Ambient Mode GA. 사이드카 없이 Service Mesh 기능을 제공한다.

graph TB
    subgraph "Sidecar Mode (현재)"
        Pod1_App["앱 컨테이너"] --- Pod1_Proxy["envoy sidecar\n(Pod 내부)"]
        Pod2_App["앱 컨테이너"] --- Pod2_Proxy["envoy sidecar\n(Pod 내부)"]
    end

    subgraph "Ambient Mode (신규)"
        Pod3_App["앱 컨테이너\n(사이드카 없음)"]
        Pod4_App["앱 컨테이너\n(사이드카 없음)"]
        ZT["ztunnel\n(노드당 1개, L4 mTLS)"]
        WP["waypoint proxy\n(서비스당 1개, L7 정책)"]
        Pod3_App --> ZT --> WP
        Pod4_App --> ZT
    end
항목Sidecar ModeAmbient Mode
배포 단위Pod마다 envoy노드마다 ztunnel + 서비스마다 waypoint
메모리 오버헤드Pod당 ~50-100Mi노드당 ztunnel ~50Mi (전체적으로 적음)
롤링 업데이트Pod 재시작 필요노드 수준 무중단
L7 정책항상 가능waypoint 배포 시 가능
성숙도검증된GA이나 신규 (2024-11)

마이그레이션 전략: 신규 클러스터는 Ambient Mode 검토. 기존 Sidecar Mode는 안정적이므로 유지.


7. 내부 통신에서 HTTPS는 반드시 필요한가

이 질문은 “트러스트 모델”의 문제다. 답은 환경과 요구사항에 따라 다르다.

7.1 세 가지 관점

관점 1: 클러스터 내부는 신뢰할 수 있다 (Plain HTTP 허용)

1
2
3
4
5
6
7
8
9
10
11
12
전제: Kubernetes NetworkPolicy로 Pod 간 통신이 제어되고,
      클러스터 자체가 물리적으로 격리된 환경.

근거:
- 클러스터 내부 공격자 = 이미 Pod 탈취 = 더 큰 문제
- TLS 오버헤드 (CPU, 레이턴시) 회피
- 단순한 디버깅 환경

적합 환경:
- 소규모 단일 팀 프로젝트
- 연구/개발 환경 (RummiArena 현재 구성)
- 클라우드 플랫폼 자체 암호화를 신뢰하는 경우

관점 2: Zero-Trust — 내부도 암호화 필수

1
2
3
4
5
6
7
8
9
10
11
12
13
전제: "네트워크 위치는 신뢰의 근거가 아니다."
      컨테이너 탈취, 악의적 내부자, 측면 이동(lateral movement) 대비.

근거:
- NIST SP 800-207 Zero Trust Architecture
- PCI-DSS 4.0: 전송 중 데이터 암호화 (클러스터 내부 포함)
- HIPAA: PHI 데이터 암호화 의무
- 실제 클러스터 침해 사례: Pod 탈취 후 평문 내부 통신 도청

적합 환경:
- 금융, 의료, 공공 등 규정 적용 대상
- 멀티 테넌트 클러스터
- 민감 데이터 처리 서비스

관점 3: 실용적 중간 지점 (L4 mTLS만으로 충분)

1
2
3
4
5
6
7
8
Istio Ambient Mode의 ztunnel이 제공하는 L4 mTLS:
- 전송 암호화 + 상호 인증 제공
- L7 정책(헤더 기반 인증 등)은 불필요하면 생략
- waypoint 없이 ztunnel만으로 기본 zero-trust 달성

이 접근이 유효한 경우:
- 서비스 간 인증은 필요하지만 세밀한 L7 정책은 불필요
- 성능과 보안의 균형점

7.2 RummiArena의 선택과 이유

통신 구간프로토콜이유
사용자 → IngressHTTPS공개 인터넷, 필수
Ingress → game-serverHTTP (PERMISSIVE)Ingress Controller가 사이드카 없음
game-server → ai-adaptermTLS (Istio)LLM API 키 보호, STRICT 정책
game-server → PostgreSQLTCP (평문)동일 클러스터 내부, 연구용
game-server → RedisTCP (평문)동일

결론: 완전한 내부 HTTPS는 연구 환경에서 과도한 복잡도. 핵심 보안 경계(ai-adapter)만 STRICT mTLS 적용하는 Selective 전략이 실용적이다.

7.3 결정 프레임워크

flowchart TB
    Start["내부 통신 암호화 검토"] --> Q1{규정 준수 대상인가?\nPCI/HIPAA/금융}
    Q1 -->|Yes| MandatoryTLS["전 구간 mTLS 필수\n(Istio STRICT + 전체 사이드카)"]
    Q1 -->|No| Q2{민감 데이터가\n내부 통신에 있는가?}
    Q2 -->|Yes| SelectiveTLS["핵심 서비스 STRICT mTLS\n(RummiArena 방식)"]
    Q2 -->|No| Q3{멀티 테넌트\n클러스터인가?}
    Q3 -->|Yes| NetworkPolicy["NetworkPolicy + L4 mTLS\n(Ambient Mode 검토)"]
    Q3 -->|No| PlainHTTP["내부 HTTP 허용\n(Ingress에서 TLS 종료)"]

8. RummiArena 실제 구성 사례

8.1 운영 환경 전환 시나리오

현재 RummiArena는 연구용 NodePort 구성이다. 운영 환경으로 전환하면:

graph TB
    subgraph "현재 (연구용)"
        NP_FE["NodePort :30000"] --> FE1["frontend"]
        NP_GS["NodePort :30080"] --> GS1["game-server"]
    end

    subgraph "운영 환경 전환"
        LB["LoadBalancer / MetalLB"] --> Traefik["Traefik Ingress Controller"]
        Traefik -->|"TLS 종료"| FE2["frontend"]
        Traefik -->|"TLS 종료"| GS2["game-server"]
        GS2 <-->|"mTLS (Istio STRICT)"| AA2["ai-adapter"]
        CM["cert-manager"] -->|"인증서 발급"| Traefik
    end

8.2 단계별 구축 순서

gantt
    title RummiArena 운영 환경 구축 순서
    dateFormat  X
    axisFormat  %s단계

    section 기반 인프라
    Kubernetes 클러스터 (멀티노드)  :done, 0, 1
    cert-manager 설치               :done, 1, 2
    Istio 설치 (istiod)            :done, 2, 3

    section Ingress
    Traefik 또는 Gateway API 설치  :active, 3, 4
    DNS 설정 + TLS 인증서 발급     :4, 5

    section 애플리케이션
    Helm Chart 배포 (5개 서비스)   :5, 7
    Istio PeerAuthentication 적용  :6, 7

    section 검증
    E2E 테스트 + 헬스체크          :7, 8
    보안 스캔 (Trivy)              :7, 8

8.3 실제 배포 커맨드 순서

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1. cert-manager 설치
helm repo add jetstack https://charts.jetstack.io && helm repo update
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set crds.enabled=true

# 2. Istio 설치 (istioctl 권장)
istioctl install --set profile=default -y
kubectl label namespace rummikub istio-injection=enabled

# 3. Traefik 설치
helm repo add traefik https://traefik.github.io/charts && helm repo update
helm install traefik traefik/traefik \
  --namespace traefik --create-namespace \
  -f helm/traefik/values.yaml

# 4. RummiArena 배포 (Helm umbrella)
helm upgrade --install rummiarena helm/ \
  --namespace rummikub --create-namespace \
  -f helm/values.yaml

# 5. Istio 정책 적용
kubectl apply -f istio/

# 6. 헬스 확인
bash helm/deploy.sh health

8.4 WebSocket 특수 처리

RummiArena의 실시간 대전은 WebSocket이 필수다. Ingress에서 WebSocket을 올바르게 처리하지 않으면 연결이 즉시 끊어진다.

Traefik: 기본 지원 (별도 설정 불필요, Connection: Upgrade 자동 처리)

NGINX (레거시 사용 시):

1
2
3
4
5
6
annotations:
  nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
  nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
  nginx.ingress.kubernetes.io/configuration-snippet: |
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

Gateway API:

1
2
3
# HTTPRoute에서 WebSocket 명시적 허용 (구현체마다 다름)
# Envoy Gateway: 기본 지원
# Traefik via Gateway API: 기본 지원

8.5 LLM 장시간 요청 처리

AI 어댑터는 DeepSeek Reasoner 호출 시 최대 700초를 기다린다. 이 요청이 타임아웃으로 끊기지 않도록 전체 체인을 설계해야 한다.

1
2
3
4
5
6
클라이언트 (브라우저) — 게임 결과는 WebSocket으로 받으므로 HTTP 타임아웃 무관
game-server (Go) — context timeout: 760s
HTTP client → ai-adapter: 760s
Istio VirtualService: 1010s (가장 길게 — 내부 체인 모두 커버)
ai-adapter 내부: 1000s
LLM API (DeepSeek): ~200-700s

핵심: Istio VirtualService 타임아웃이 항상 실제 LLM 응답 시간보다 길어야 한다.


9. 운영 체크리스트

9.1 보안

  • Ingress Controller insecure: false (Traefik dashboard 외부 노출 금지)
  • 모든 시크릿 Sealed Secrets 또는 Vault로 관리 (평문 Secret 금지)
  • 민감 서비스 (ai-adapter) PeerAuthentication STRICT
  • NetworkPolicy 기본 deny-all + 필요한 접근만 허용
  • 이미지 digest pinning (image:sha256:...)
  • Trivy 이미지 스캔 CI 게이트
  • RBAC 최소 권한 (서비스별 개별 ServiceAccount)
  • ArgoCD 접근 Internal 전용 (외부 노출 금지)
  • PostgreSQL NodePort 제거 (ClusterIP만)

9.2 가용성

  • 각 서비스 replica ≥ 2 (PodDisruptionBudget 설정)
  • HPA (Horizontal Pod Autoscaler) 설정
  • ResourceQuota + LimitRange (OOMKill 방지)
  • Liveness / Readiness / Startup probe 모두 설정
  • PVC ReadWriteMany 또는 StatefulSet (DB)

9.3 관찰 가능성

  • Prometheus + Grafana (메트릭)
  • Jaeger / Tempo (분산 트레이싱, Istio 연동)
  • EFK / Loki (로그 집중화)
  • Istio Kiali (서비스 토폴로지 시각화)
  • AlertManager (장애 알림)

9.4 CI/CD

  • 이미지 빌드 → 레지스트리 → GitOps 레포 업데이트 자동화
  • ArgoCD auto-sync + self-heal 활성화
  • 배포 전 Playbook (smoke test) 자동 실행
  • 롤백 절차 문서화 (helm rollback 또는 ArgoCD UI)

별첨 A: OpenShift vs Vanilla Kubernetes 비교

OpenShift는 Red Hat이 만든 엔터프라이즈 Kubernetes 배포판이다. Kubernetes가 빌딩 블록이라면 OpenShift는 완성된 플랫폼에 가깝다.

A.1 핵심 차이점 비교표

카테고리Vanilla KubernetesOpenShift (OCP)
IngressIngress 리소스 (구현체 별도 설치)Route 리소스 (내장 HAProxy Router) + Ingress 호환
TLS 관리cert-manager 수동 설치 필요Route에서 Edge/Passthrough/Re-encrypt 기본 지원
컨테이너 보안PodSecurity Admission (PSA) - 3단계SCC (Security Context Constraints) - 세밀한 제어
기본 사용자 권한root 허용 (설정에 따라)root 실행 기본 차단 (임의 UID 강제)
레지스트리외부 레지스트리 사용내장 레지스트리 (OpenShift Image Registry)
운영자 관리Helm, kubectlOLM (Operator Lifecycle Manager) + OperatorHub
빌드 시스템없음 (외부 CI 사용)BuildConfig + S2I (Source-to-Image) 내장
네트워크CNI 플러그인 선택 (Calico, Flannel 등)OVN-Kubernetes 기본 (SDN 대체)
스토리지StorageClass (CSI 드라이버 설치 필요)동일 + 내장 스토리지 솔루션
멀티클러스터별도 구성 (Submariner, Cilium)ACM (Advanced Cluster Management) 내장
콘솔없음 (별도 설치)웹 콘솔 기본 제공 (개발자/관리자 뷰)
인증OIDC, RBAC (수동 설정)OAuth 서버 내장 + LDAP/AD 통합 용이
업그레이드수동 또는 kubeadmOTA 업그레이드 (Operator 기반 자동화)
지원CNCF 생태계 (커뮤니티 + 상용)Red Hat 공식 엔터프라이즈 지원
라이선스오픈소스 (무료)상용 구독 (OCP), 무료 버전 (OKD)
Kubernetes 버전 래그없음 (최신)약 1-2 마이너 버전 래그

A.2 Ingress: Route vs Ingress/Gateway API

graph LR
    subgraph "Vanilla K8s"
        IC["Ingress Controller\n(별도 설치 필요)"]
        IR["Ingress 리소스"]
        GW["Gateway API\n(미래 표준)"]
    end

    subgraph "OpenShift"
        Router["HAProxy Router\n(기본 설치됨)"]
        Route["Route 리소스\n(OpenShift 전용)"]
        ING["Ingress 리소스\n(호환 지원)"]
    end

OpenShift Route 예시 (Traefik IngressRoute와 기능적으로 동일):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: rummiarena-frontend
  namespace: rummikub
spec:
  host: game.example.com
  path: /
  to:
    kind: Service
    name: frontend
    weight: 100
  port:
    targetPort: 3000
  tls:
    termination: edge           # Edge TLS 종료 (cert-manager 불필요)
    certificate: |
      -----BEGIN CERTIFICATE-----
      ...
    key: |
      -----BEGIN PRIVATE KEY-----
      ...
    insecureEdgeTerminationPolicy: Redirect

A.3 SCC vs PSA (컨테이너 보안)

OpenShift SCC는 Kubernetes PSA보다 훨씬 세밀한 제어를 제공한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Vanilla K8s: PodSecurity Admission (네임스페이스 레이블)
apiVersion: v1
kind: Namespace
metadata:
  name: rummikub
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted

---
# OpenShift SCC (훨씬 세밀한 제어)
apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
  name: rummiarena-scc
allowPrivilegedContainer: false
allowPrivilegeEscalation: false
runAsUser:
  type: MustRunAsRange      # 특정 UID 범위 강제
  uidRangeMin: 1000
  uidRangeMax: 65535
seLinuxContext:
  type: MustRunAs
fsGroup:
  type: MustRunAs
  ranges:
  - min: 1000
    max: 65535
volumes:
- configMap
- emptyDir
- persistentVolumeClaim
- secret

실무 영향: RummiArena를 OpenShift로 이관할 경우, Go 및 Node.js 컨테이너가 root로 실행되면 기본 SCC에 의해 Pod 시작이 거부된다. Dockerfile에서 non-root 사용자 설정이 필수다.

1
2
3
4
5
6
7
8
9
10
# OpenShift 호환 Dockerfile 예시 (game-server)
FROM golang:1.23-alpine AS builder
# ... 빌드 ...

FROM alpine:3.21
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser
USER 1001                    # ← OpenShift SCC 대응: non-root 필수
WORKDIR /app
COPY --from=builder /app/game-server .
ENTRYPOINT ["./game-server"]

A.4 OLM vs Helm

항목HelmOLM (OpenShift)
패키지 형식Chart (YAML 템플릿)Operator + Bundle
업그레이드helm upgrade (수동)자동 채널 구독
CRD 관리수동Operator가 자동 관리
의존성제한적Operator 의존성 그래프
생태계ArtifactHubOperatorHub (Red Hat 인증)
롤백helm rollbackSubscription 채널 변경

RummiArena 이관 시: Helm Chart를 그대로 사용 가능 (OpenShift도 Helm 지원). OLM 변환은 선택사항.

A.5 네트워크: OVN-Kubernetes

OpenShift 4.12+는 기본 CNI로 OVN-Kubernetes를 사용한다.

기능Calico (Vanilla 대표)OVN-Kubernetes (OpenShift)
NetworkPolicy
Egress IP✅ (Enterprise)✅ 기본 지원
Egress 방화벽✅ (EgressNetworkPolicy)
멀티캐스트
IPsec 암호화
BGP 라우팅❌ (OVS 기반)

A.6 마이그레이션 체크리스트 (Vanilla K8s → OpenShift)

  • 모든 컨테이너 non-root UID 확인 (USER 1001 등)
  • SCC 정책 검토 (기존 runAsRoot 사용 서비스 파악)
  • Ingress 리소스 → Route 또는 OpenShift Ingress 호환 확인
  • PVC StorageClass 매핑 (기존 CSI 드라이버 → OpenShift 스토리지)
  • Docker Hub → OpenShift 내장 레지스트리 또는 Quay.io 이관
  • BuildConfig / ImageStream 활용 여부 결정
  • OAuth / LDAP 통합 설정
  • ArgoCD (OCP용 ArgoCD Operator 또는 OpenShift GitOps Operator 사용)
  • Helm Chart 테스트 (OCP 호환성 검증)
  • SonarQube, GitLab Runner 등 CI 인프라 재배포

별첨 B: 주요 YAML 예제 모음

B.1 NetworkPolicy (기본 deny-all + 허용 규칙)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 기본 deny-all
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: rummikub
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
# game-server: Ingress Controller + ai-adapter 접근 허용
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-game-server
  namespace: rummikub
spec:
  podSelector:
    matchLabels:
      app: game-server
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: traefik
    ports:
    - port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: ai-adapter
    ports:
    - port: 3001
  - to:
    - podSelector:
        matchLabels:
          app: postgres
    ports:
    - port: 5432
  - to:
    - podSelector:
        matchLabels:
          app: redis
    ports:
    - port: 6379
  # DNS 허용
  - ports:
    - port: 53
      protocol: UDP

B.2 ResourceQuota + LimitRange

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# ResourceQuota (네임스페이스 전체 쿼터)
apiVersion: v1
kind: ResourceQuota
metadata:
  name: rummikub-quota
  namespace: rummikub
spec:
  hard:
    requests.cpu: "8"
    requests.memory: 16Gi
    limits.cpu: "16"
    limits.memory: 32Gi
    pods: "30"
    persistentvolumeclaims: "10"
---
# LimitRange (Pod 기본값)
apiVersion: v1
kind: LimitRange
metadata:
  name: rummikub-limits
  namespace: rummikub
spec:
  limits:
  - type: Container
    default:
      cpu: "500m"
      memory: "256Mi"
    defaultRequest:
      cpu: "100m"
      memory: "128Mi"
    max:
      cpu: "4"
      memory: "4Gi"

B.3 HPA (Horizontal Pod Autoscaler)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# game-server HPA (트래픽 급증 대응)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: game-server-hpa
  namespace: rummikub
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: game-server
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300  # 5분 안정화 (게임 중 스케일다운 방지)

B.4 PodDisruptionBudget

1
2
3
4
5
6
7
8
9
10
11
# 게임 진행 중 최소 1개 Pod 보장
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: game-server-pdb
  namespace: rummikub
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: game-server

참고 문서

문서경로
K8s 아키텍처 (RummiArena)docs/05-deployment/04-k8s-architecture.md
K8s 보안 평가docs/05-deployment/12-k8s-security-assessment.md
타임아웃 체인 SSOTdocs/02-design/41-timeout-chain-breakdown.md
배포 가이드 (Helm)docs/05-deployment/01-deployment-guide.md
Istio 설치 스크립트scripts/istio/
Traefik YAMLhelm/traefik/

*작성일: 2026-05-26버전: 1.0*

작성자: Claude Sonnet 4.6

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.