Kubernetes Tools/Kubevirt

[KubeVirt Ep. 10] kubevirt 운영 고려사항 | 리소스, 모니터링, 보안

ygtoken 2025. 3. 21. 15:22
728x90

이 글에서는 KubeVirt를 실제 프로덕션 환경에서 운영할 때 고려해야 할 다양한 측면들을 살펴보겠습니다. VM의 리소스 할당 최적화부터 모니터링, 보안에서 발생할 수 있는 문제점과 해결 방법까지 상세히 설명합니다. Docker Desktop 환경에서의 실습과 함께 실제 멀티노드 클러스터에서 적용할 수 있는 모범 사례도 함께 알아봅니다.


KubeVirt 운영 시 고려사항

 

📌 VM 리소스 할당 및 최적화

KubeVirt VM은 Kubernetes 클러스터의 리소스를 소비합니다. 최적의 성능과 효율성을 위해 리소스 할당을 신중하게 계획해야 합니다.

✅ CPU 및 메모리 할당 이해하기

VM의 CPU 및 메모리 할당은 VM 성능에 직접적인 영향을 미칩니다.

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: resource-optimized-vm    # VM의 고유 이름
  labels:
    app: production              # VM 식별 및 선택을 위한 라벨
    tier: database               # 계층 구분을 위한 라벨
spec:
  running: true                  # VM 시작 상태 설정 (true=자동 시작)
  template:
    spec:
      domain:
        cpu:
          cores: 2               # VM에 할당할 vCPU 코어 수 (2코어)
          sockets: 1             # VM의 CPU 소켓 수 (물리적 CPU 소켓 에뮬레이션)
          threads: 1             # 코어당 스레드 수 (하이퍼스레딩 에뮬레이션)
          # 총 vCPU 수 = cores * sockets * threads = 2 * 1 * 1 = 2
          model: host-model      # CPU 모델: host-model은 호스트와 유사한 기능을 제공하는 CPU 모델 사용
          dedicatedCpuPlacement: true  # 전용 CPU 할당 (다른 워크로드와 CPU 공유하지 않음, 성능 향상)
        memory:
          guest: 4Gi             # VM 내부에서 인식하는 메모리 크기 (4GB)
          overcommitGuestOverhead: false  # VM 오버헤드를 과다할당하지 않음 (안정성 향상)
        resources:
          requests:              # 최소 필요 리소스 (보장되는 양)
            memory: 4.5Gi        # VM 메모리 + 오버헤드(0.5Gi)를 요청 
            cpu: 2               # 2 CPU 코어 요청
          limits:                # 최대 사용 가능 리소스 (상한선)
            memory: 4.5Gi        # 메모리 상한 (request와 동일하게 설정하여 QoS 보장)
            cpu: 2               # CPU 상한 (request와 동일하게 설정하여 QoS 보장)

이 예제에서 주목할 점:

  • dedicatedCpuPlacement: true: VM이 전용 CPU 코어를 사용하도록 보장 (성능 중요 워크로드)
  • guest 메모리(4Gi)와 requests 메모리(4.5Gi)의 차이: VM 오버헤드 고려
  • limits와 requests를 동일하게 설정: 예측 가능한 성능 보장

✅ 리소스 과다할당(Overcommitment) 전략

클러스터 자원 활용도를 높이기 위해 CPU 및 메모리 과다할당 전략을 구현할 수 있습니다.

 

▶️ CPU 과다할당 예시:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: cpu-overcommit-vm       # VM 이름
  annotations:
    description: "VM with overcommitted CPU" # VM 설명 (관리 목적)
spec:
  template:
    spec:
      domain:
        cpu:
          cores: 4              # VM은 4개의 vCPU 코어를 인식함 (OS 내부에서 보이는 CPU 수)
          model: host-model     # 호스트 기반 CPU 모델 사용
        resources:
          requests:
            cpu: 2              # 실제로는 2개 물리 코어만 요청 (2:1 과다할당 비율)
            memory: 4Gi         # 4GB 메모리 요청
          limits:
            cpu: 4              # 부하 상황에서 최대 4 코어까지 사용 가능 (버스트 가능)
            memory: 4Gi         # 메모리 상한

이 설정에서 VM은 4개 vCPU를 인식하지만, 실제로는 2개 물리 코어만 요청합니다. 부하에 따라 최대 4개 코어까지 사용할 수 있습니다.

 

▶️ 메모리 과다할당 고려 사항:

메모리 과다할당은 CPU보다 위험할 수 있습니다. 메모리 부족 시 OOM(Out of Memory) 킬러가 프로세스를 종료할 수 있습니다.

spec:
  domain:
    memory:
      guest: 8Gi               # VM은 8GB 메모리를 인식함 (OS 내부에서 보이는 메모리)
    resources:
      requests:
        memory: 4Gi            # 실제로는 4GB만 요청 (2:1 과다할당 비율)
        cpu: 2                 # 2 코어 CPU 요청
      limits:
        memory: 10Gi           # 최대 10GB까지 확장 가능 (버스트 상황)
        cpu: 2                 # CPU 상한

✅ 스토리지 성능 최적화

VM 스토리지 성능은 전체 시스템 성능에 큰 영향을 미칩니다.

 

▶️ 스토리지 클래스 선택:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: high-perf-vm-disk      # 고성능 디스크를 위한 PVC 이름
  annotations:
    description: "High performance VM disk" # 디스크 설명
spec:
  accessModes:
    - ReadWriteOnce            # 단일 노드에서 읽기/쓰기 가능 (RWO 접근 모드)
  volumeMode: Block            # 블록 디바이스 모드 사용 (파일시스템 오버헤드 없음, 성능 향상)
  resources:
    requests:
      storage: 50Gi            # 50GB 스토리지 요청
  storageClassName: premium-ssd # 고성능 SSD 스토리지 클래스 지정 (클라우드 제공업체별 다름)

프로덕션 환경에서는 다음 스토리지 특성을 고려하세요:

  • IOPS: 초당 입출력 작업 수
  • 처리량: 초당 전송 데이터 양
  • 지연 시간: 요청 처리 시간

▶️ CDI 설정 최적화:

apiVersion: cdi.kubevirt.io/v1beta1
kind: CDIConfig
metadata:
  name: config                 # CDI 설정 이름 (항상 "config"여야 함)
spec:
  filesystemOverhead:
    global: "0.055"            # 전역 파일시스템 오버헤드 설정 (5.5%) - 기본값
    storageClass:              # 스토리지 클래스별 오버헤드 설정
      premium-ssd: "0.035"     # SSD의 경우 3.5% 오버헤드 (효율적인 파일시스템 가정)
      standard-hdd: "0.065"    # HDD의 경우 6.5% 오버헤드 (비효율적인 파일시스템 가정)

📌 프로메테우스와 그라파나로 VM 모니터링

Kubernetes 환경에서는 Prometheus와 Grafana가 표준 모니터링 솔루션입니다. KubeVirt VM도 이 도구로 모니터링할 수 있습니다.

✅ Prometheus로 KubeVirt 메트릭 수집

KubeVirt는 Prometheus 메트릭을 자동으로 노출하지만, 적절한 ServiceMonitor를 구성해야 합니다.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: kubevirt-monitoring      # ServiceMonitor 리소스 이름
  namespace: monitoring          # 모니터링 네임스페이스에 생성
  labels:
    release: prometheus          # Prometheus Operator 인스턴스와 매칭하는 라벨
    app: kubevirt                # 애플리케이션 식별 라벨
spec:
  selector:
    matchLabels:
      prometheus.kubevirt.io: "" # KubeVirt 서비스를 선택하는 라벨 셀렉터
  namespaceSelector:             # 모니터링할 네임스페이스 지정
    matchNames:
      - kubevirt                 # kubevirt 네임스페이스의 서비스만 모니터링
  endpoints:
  - port: metrics                # 메트릭을 노출하는 서비스 포트 이름
    interval: 30s                # 30초마다 메트릭 스크래핑 (수집)
    honorLabels: true            # 원본 메트릭의 라벨 충돌 시 원본 라벨 우선
    relabelings:                 # 메트릭 재라벨링 규칙
    - sourceLabels: [__name__]   # 메트릭 이름 기준
      regex: kubevirt_.*         # kubevirt_ 접두사를 가진 메트릭만 선택
      action: keep               # 매칭되는 메트릭만 유지

✅ KubeVirt용 Grafana 대시보드 구성

다음은 KubeVirt용 기본 Grafana 대시보드 설정입니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: kubevirt-grafana-dashboard   # 대시보드 ConfigMap 이름
  namespace: monitoring              # 모니터링 네임스페이스에 생성
  labels:
    grafana_dashboard: "true"        # Grafana에 의해 자동 감지되는 라벨
    app.kubernetes.io/part-of: kubevirt  # 애플리케이션 구성요소 라벨
data:
  kubevirt-dashboard.json: |-       # 대시보드 JSON 정의 (Grafana 대시보드 형식)
    {
      "annotations": {
        "list": []                   # 대시보드 주석 목록 (비어있음)
      },
      "editable": true,              # 사용자가 편집 가능한 대시보드
      "fiscalYearStartMonth": 0,     # 회계연도 시작월 (1월)
      "graphTooltip": 0,             # 툴팁 모드 (0: 기본)
      "links": [],                   # 대시보드 링크 (비어있음)
      "liveNow": false,              # 실시간 업데이트 비활성화
      "panels": [
        {
          "datasource": {            # 데이터 소스 설정
            "type": "prometheus",    # Prometheus 타입 데이터 소스
            "uid": "${DS_PROMETHEUS}" # 데이터 소스 UID (변수)
          },
          "fieldConfig": {           # 필드 표시 설정
            "defaults": {            # 기본 필드 설정
              "color": {             # 색상 설정
                "mode": "palette-classic" # 클래식 팔레트 사용
              },
              "custom": {            # 커스텀 시각화 설정
                "axisCenteredZero": false,
                "axisColorMode": "text",
                "axisLabel": "",
                "axisPlacement": "auto",
                "barAlignment": 0,
                "drawStyle": "line", # 선 그래프 스타일
                "fillOpacity": 10,   # 투명도 10%
                "gradientMode": "none",
                "hideFrom": {
                  "legend": false,
                  "tooltip": false,
                  "viz": false
                },
                "lineInterpolation": "linear", # 선형 보간
                "lineWidth": 1,       # 선 굵기
                "pointSize": 5,       # 점 크기
                "scaleDistribution": {
                  "type": "linear"    # 선형 스케일
                },
                "showPoints": "never", # 점 표시 안함
                "spanNulls": false,   # null 값 연결 안함
                "stacking": {
                  "group": "A",
                  "mode": "none"      # 스택킹 없음
                },
                "thresholdsStyle": {
                  "mode": "off"       # 임계값 스타일 비활성화
                }
              },
              "mappings": [],         # 값 매핑 (비어있음)
              "thresholds": {         # 임계값 설정
                "mode": "absolute",   # 절대값 모드
                "steps": [
                  {
                    "color": "green", # 초기값 색상
                    "value": null     # null 값부터 시작
                  },
                  {
                    "color": "red",   # 경고 색상
                    "value": 80       # 80% 이상일 때 빨간색
                  }
                ]
              },
              "unit": "percent"       # 퍼센트 단위 표시
            },
            "overrides": []           # 재정의 설정 (비어있음)
          },
          "gridPos": {                # 그리드 위치 및 크기
            "h": 8,                   # 높이 8 그리드
            "w": 12,                  # 너비 12 그리드
            "x": 0,                   # x 좌표 0
            "y": 0                    # y 좌표 0
          },
          "id": 1,                    # 패널 ID
          "options": {                # 패널 옵션
            "legend": {               # 범례 설정
              "calcs": [],            # 계산 통계
              "displayMode": "list",  # 리스트 모드로 표시
              "placement": "bottom",  # 하단에 배치
              "showLegend": true      # 범례 표시
            },
            "tooltip": {              # 툴팁 설정
              "mode": "multi",        # 다중 모드
              "sort": "none"          # 정렬 없음
            }
          },
          "targets": [                # 데이터 쿼리 대상
            {
              "datasource": {         # 데이터 소스
                "type": "prometheus", # Prometheus 타입
                "uid": "${DS_PROMETHEUS}" # 데이터 소스 UID
              },
              "editorMode": "code",   # 코드 에디터 모드
              "expr": "100 * (1 - avg by(pod, namespace) (rate(node_cpu_seconds_total{mode=\"idle\", pod=~\"virt-launcher-.*\"}[5m])))", # CPU 사용률 계산 PromQL 쿼리
              "legendFormat": "{{pod}} CPU", # 범례 형식 (pod 이름 + CPU)
              "range": true,          # 범위 쿼리
              "refId": "A"            # 쿼리 참조 ID
            }
          ],
          "title": "VM CPU Usage",    # 패널 제목
          "type": "timeseries"        # 시계열 차트 타입
        },
        {
          "datasource": {             # 두 번째 패널 데이터 소스
            "type": "prometheus",
            "uid": "${DS_PROMETHEUS}"
          },
          "fieldConfig": {
            "defaults": {
              "color": {
                "mode": "palette-classic"
              },
              "custom": {
                "axisCenteredZero": false,
                "axisColorMode": "text",
                "axisLabel": "",
                "axisPlacement": "auto",
                "barAlignment": 0,
                "drawStyle": "line",
                "fillOpacity": 10,
                "gradientMode": "none",
                "hideFrom": {
                  "legend": false,
                  "tooltip": false,
                  "viz": false
                },
                "lineInterpolation": "linear",
                "lineWidth": 1,
                "pointSize": 5,
                "scaleDistribution": {
                  "type": "linear"
                },
                "showPoints": "never",
                "spanNulls": false,
                "stacking": {
                  "group": "A",
                  "mode": "none"
                },
                "thresholdsStyle": {
                  "mode": "off"
                }
              },
              "mappings": [],
              "thresholds": {
                "mode": "absolute",
                "steps": [
                  {
                    "color": "green",
                    "value": null
                  },
                  {
                    "color": "red",
                    "value": 80
                  }
                ]
              },
              "unit": "bytes"         # 바이트 단위 표시
            },
            "overrides": []
          },
          "gridPos": {                # 그리드 위치 및 크기
            "h": 8,                   # 높이 8 그리드
            "w": 12,                  # 너비 12 그리드
            "x": 12,                  # x 좌표 12 (첫 번째 패널 옆)
            "y": 0                    # y 좌표 0
          },
          "id": 2,                    # 패널 ID
          "options": {
            "legend": {
              "calcs": [],
              "displayMode": "list",
              "placement": "bottom",
              "showLegend": true
            },
            "tooltip": {
              "mode": "multi",
              "sort": "none"
            }
          },
          "targets": [
            {
              "datasource": {
                "type": "prometheus",
                "uid": "${DS_PROMETHEUS}"
              },
              "editorMode": "code",
              "expr": "sum by(pod, namespace) (container_memory_working_set_bytes{pod=~\"virt-launcher-.*\", container!=\"POD\"})", # 메모리 사용량 계산 PromQL 쿼리
              "legendFormat": "{{pod}} Memory", # 범례 형식 (pod 이름 + Memory)
              "range": true,
              "refId": "A"
            }
          ],
          "title": "VM Memory Usage", # 패널 제목
          "type": "timeseries"        # 시계열 차트 타입
        }
      ],
      "refresh": "",                  # 자동 새로고침 설정 (비활성화)
      "schemaVersion": 38,            # Grafana 스키마 버전
      "style": "dark",                # 다크 테마
      "tags": [                       # 대시보드 태그
        "kubevirt",
        "vm",
        "kubernetes"
      ],
      "templating": {                 # 템플릿 변수 설정
        "list": []                    # 변수 목록 (비어있음)
      },
      "time": {                       # 시간 범위 설정
        "from": "now-6h",             # 6시간 전부터
        "to": "now"                   # 현재까지
      },
      "timepicker": {},               # 시간 선택기 설정
      "timezone": "",                 # 기본 타임존 사용
      "title": "KubeVirt VM Monitoring", # 대시보드 제목
      "uid": "kubevirt-vm-monitoring", # 대시보드 고유 ID
      "version": 1,                    # 버전 번호
      "weekStart": ""                  # 주 시작일 (기본값)
    }

✅ 주요 모니터링 메트릭

KubeVirt VM 모니터링을 위해 수집해야 할 핵심 메트릭은 다음과 같습니다:

  1. 리소스 사용량:
    • VM CPU 사용률: 100 * (1 - avg by(name) (rate(kubevirt_vmi_cpu_usage_seconds_total{vm_name="<vm-name>"}[5m])))
    • VM 메모리 사용량: kubevirt_vmi_memory_resident_bytes{vm_name="<vm-name>"}
    • VM 디스크 I/O: rate(kubevirt_vmi_storage_read_traffic_bytes_total{vm_name="<vm-name>"}[5m])
  2. 운영 상태:
    • VM 전원 상태: kubevirt_vmi_phase{vm_name="<vm-name>"}
    • VM 가용성: up{pod=~"virt-launcher-<vm-name>.*"}
  3. 시스템 지표:
    • 네트워크 트래픽: rate(kubevirt_vmi_network_receive_bytes_total{vm_name="<vm-name>"}[5m])
    • VM 개수: count(kubevirt_vmi_phase) by (phase)

✅ 알림 규칙 구성

중요한 상황에 대한 알림을 구성하여 문제를 신속하게 감지할 수 있습니다.

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: kubevirt-alerts          # 알림 규칙 이름
  namespace: monitoring          # 모니터링 네임스페이스에 생성
  labels:
    prometheus: k8s              # Prometheus 인스턴스 식별 라벨
    role: alert-rules            # 규칙 역할 표시
spec:
  groups:
  - name: kubevirt.rules         # 규칙 그룹 이름
    rules:
    - alert: KubevirtVMHighCPUUsage          # 알림 이름: VM CPU 사용량 높음
      expr: 100 * (1 - avg by(name, namespace) (rate(kubevirt_vmi_cpu_usage_seconds_total[5m]))) > 80  
      # CPU 사용률 80% 초과 감지하는 PromQL 쿼리
      for: 15m                   # 15분 동안 지속되면 알림 발생
      labels:
        severity: warning        # 경고 수준 알림
        category: resource       # 리소스 카테고리
      annotations:
        summary: "VM High CPU Usage"  # 알림 요약
        description: "VM {{ $labels.name }} in namespace {{ $labels.namespace }} has CPU usage above 80% for 15 minutes."  
        # 알림 상세 설명 (VM 이름과 네임스페이스 포함)
    
    - alert: KubevirtVMHighMemoryUsage       # 알림 이름: VM 메모리 사용량 높음
      expr: kubevirt_vmi_memory_resident_bytes / kubevirt_vmi_memory_domain_bytes * 100 > 90
      # 메모리 사용률 90% 초과 감지하는 PromQL 쿼리
      for: 15m                   # 15분 동안 지속되면 알림 발생
      labels:
        severity: warning        # 경고 수준 알림
        category: resource       # 리소스 카테고리
      annotations:
        summary: "VM High Memory Usage"  # 알림 요약
        description: "VM {{ $labels.name }} in namespace {{ $labels.namespace }} has memory usage above 90% for 15 minutes."
        # 알림 상세 설명
    
    - alert: KubevirtVMDown                  # 알림 이름: VM 다운됨
      expr: absent(kubevirt_vmi_phase{phase="Running"})  
      # Running 상태의 VM이 없음을 감지하는 PromQL 쿼리
      for: 5m                    # 5분 동안 지속되면 알림 발생
      labels:
        severity: critical       # 심각 수준 알림
        category: availability   # 가용성 카테고리
      annotations:
        summary: "VM Down"       # 알림 요약
        description: "VM {{ $labels.name }} in namespace {{ $labels.namespace }} is not in Running phase for 5 minutes."
        # 알림 상세 설명

📌 VM 보안 설정 및 강화

KubeVirt VM의 보안을 강화하기 위한 다양한 설정과 접근 방법을 알아보겠습니다.

✅ SecurityContext 설정

VM의 보안 컨텍스트를 구성하여 보안을 강화할 수 있습니다:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: secure-vm                # 보안 강화된 VM 이름
  labels:
    security-tier: restricted    # 보안 티어 라벨
spec:
  template:
    spec:
      securityContext:
        runAsNonRoot: true       # 루트가 아닌 사용자로 Pod 실행 (보안 강화)
        runAsUser: 1000          # UID 1000으로 Pod 실행 (루트 아닌 사용자)
        fsGroup: 2000            # 파일시스템 그룹 ID (파일 접근 권한)
        seLinuxOptions:          # SELinux 컨텍스트 설정
          level: "s0:c123,c456"  # SELinux 보안 레벨
        seccompProfile:          # Seccomp 프로필 설정
          type: RuntimeDefault   # 런타임 기본 프로필 사용
      domain:
        devices:
          disks:
          - name: rootdisk       # 루트 디스크 이름
            disk:
              bus: virtio        # 가상 I/O 버스 사용
          interfaces:
          - name: default        # 기본 네트워크 인터페이스
            masquerade: {}       # NAT 타입 네트워킹
        # ... 기타 설정 ...

✅ 네트워크 정책 적용

NetworkPolicy를 사용하여 VM의 네트워크 트래픽을 제한할 수 있습니다:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: vm-network-policy        # 네트워크 정책 이름
  namespace: default             # 기본 네임스페이스에 적용
spec:
  podSelector:
    matchLabels:
      kubevirt.io/vm: secure-vm  # secure-vm이라는 VM의 Pod에 적용
  policyTypes:
  - Ingress                      # 인바운드 트래픽 정책 적용
  - Egress                       # 아웃바운드 트래픽 정책 적용
  ingress:
  - from:
    - podSelector:               # 특정 Pod에서 오는 트래픽만 허용
        matchLabels:
          app: frontend          # frontend 라벨을 가진 Pod에서 오는 트래픽
    ports:
    - protocol: TCP              # TCP 프로토콜
      port: 22                   # SSH 포트(22)만 허용
  egress:
  - to:
    - namespaceSelector:         # 특정 네임스페이스로 가는 트래픽만 허용
        matchLabels:
          kubernetes.io/metadata.name: kube-system  # kube-system 네임스페이스
    - podSelector:               # 특정 Pod로 가는 트래픽만 허용
        matchLabels:
          k8s-app: kube-dns      # DNS 서비스 Pod로 가는 트래픽
    ports:
    - protocol: UDP              # UDP 프로토콜
      port: 53                   # DNS 쿼리(53)만 허용

✅ 시크릿 관리 및 주입

VM에 민감한 정보를 안전하게 주입하는 방법입니다:

apiVersion: v1
kind: Secret
metadata:
  name: vm-credentials           # 시크릿 이름
  labels:
    app: secure-vm               # 애플리케이션 라벨
type: Opaque                     # 불투명 타입 (일반 시크릿)
data:
  username: YWRtaW4=             # base64 인코딩된 'admin'
  password: cGFzc3cwcmQ=         # base64 인코딩된 'passw0rd'
---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: secret-vm                # VM 이름
  labels:
    app: secure-vm               # 애플리케이션 라벨
spec:
  template:
    spec:
      domain:
        devices:
          disks:
          - name: rootdisk       # 루트 디스크 이름
            disk:
              bus: virtio        # 가상 I/O 버스 사용
          - name: secret-disk    # 시크릿을 마운트할 디스크
            disk:
              bus: virtio        # 가상 I/O 버스 사용
      volumes:
      - name: rootdisk           # 루트 디스크 볼륨
        containerDisk:
          image: quay.io/kubevirt/ubuntu:22.04  # Ubuntu 22.04 이미지
      - name: secret-disk        # 시크릿 볼륨
        secret:                  # 시크릿 볼륨 타입
          secretName: vm-credentials  # 참조할 시크릿 이름

✅ VM 이미지 보안 스캐닝

ContainerDisk 이미지는 컨테이너 이미지이므로 표준 컨테이너 보안 스캐너를 사용할 수 있습니다.

# Trivy를 사용한 이미지 스캐닝 예시
# Trivy: 취약점 스캐너로, 이미지의 OS 패키지 및 언어별 의존성 취약점 검사
trivy image quay.io/kubevirt/ubuntu:22.04

# 스캐닝 결과에는 CVE ID, 심각도, 취약점 설명 및 수정 버전 정보가 포함됨
# 검색된 심각한 취약점은 수정 조치 필요

# Clair 등의 다른 스캐너도 사용 가능
# Clair: 정적 분석을 통해 컨테이너 이미지의 취약점 스캔

프로덕션 환경에서는 CI/CD 파이프라인에 이미지 스캐닝을 통합하는 것이 좋습니다.


📌 운영 시 발생하는 문제와 해결 방법

KubeVirt를  운영할 때 발생하는 문제와 해결 방법을 알아보겠습니다.

✅ VM 시작 실패 문제 해결

VM 시작 실패는 다양한 원인이 있을 수 있습니다.

 

▶️ 문제 진단 단계:

  1. VM 상태 확인:
# VM 리소스 상태 확인 - Running 또는 Stopped 등의 상태 확인
kubectl get vm <vm-name>

# VM 인스턴스 상태 확인 - 더 자세한 실행 상태 확인
kubectl get vmi <vm-name>

# VM 상세 정보 확인 - 구성 설정, 이벤트 등 확인
kubectl describe vm <vm-name>

 

    2. 이벤트 확인:

# VM 관련 이벤트 확인 - 시간순으로 정렬하여 최근 이벤트부터 확인
kubectl get events --field-selector involvedObject.name=<vm-name> --sort-by='.lastTimestamp'

# 네임스페이스 전체 이벤트 확인 - VM 관련 문제가 더 넓은 범위의 문제일 수 있음
kubectl get events -n <namespace> | grep <vm-name>

   

    3. virt-launcher Pod 로그 확인:

# Pod 이름 찾기 - virt-launcher Pod 식별
kubectl get pods -l kubevirt.io/vm=<vm-name>

# 로그 확인 - compute 컨테이너의 로그 (VM 실행 관련)
kubectl logs <pod-name> -c compute

# 마지막 100줄 로그 확인 - 최근 문제에 집중
kubectl logs <pod-name> -c compute --tail=100

# 이전 Pod의 로그 확인 (재시작된 경우)
kubectl logs <pod-name> -c compute --previous

 

▶️ 일반적인 문제 및 해결책:

  1. 리소스 부족:
    • 증상: 0/3 nodes are available: 3 Insufficient memory
    • 원인: 클러스터 노드에 요청된 메모리/CPU를 할당할 공간이 부족
    • 해결: VM 리소스 요청을 줄이거나 클러스터 용량 확장
  2. 이미지 문제:
    • 증상: Failed to pull image
    • 원인: 컨테이너 이미지를 찾을 수 없거나 접근 권한 부족
    • 해결: 이미지 경로 확인, 레지스트리 접근성 검증, 인증 정보 확인
  3. 네트워크 구성 문제:
    • 증상: failed to start virtual machine: error configuring network
    • 원인: 잘못된 네트워크 설정 또는 CNI 플러그인 문제
    • 해결: 네트워크 정의 확인, CNI 플러그인 설정 검증, NetworkAttachmentDefinition 확인

✅ 성능 문제 해결

VM 성능 문제는 다양한 요인에 의해 발생할 수 있습니다.

 

▶️ CPU 성능 최적화:

spec:
  domain:
    cpu:
      model: host-passthrough    # 호스트 CPU 기능을 VM에 직접 전달 (최고 성능)
                                 # (대안: host-model, Skylake-Server 등 특정 모델)
      dedicatedCpuPlacement: true  # 전용 CPU 할당 (다른 워크로드와 경합 방지)
      features:
      - name: apic               # 고급 프로그래머블 인터럽트 컨트롤러 활성화
        policy: require          # 필수 기능으로 설정 (없으면 시작 실패)
      - name: vmx                # Intel 가상화 확장 (중첩 가상화 지원)
        policy: optional         # 있으면 사용, 없어도 시작 가능
      numa:                      # NUMA 토폴로지 설정 (메모리 접근 최적화)
        guestMappingPassthrough: {}  # 호스트 NUMA 토폴로지를 VM에 그대로 전달

 

▶️ I/O 성능 개선:

spec:
  domain:
    devices:
      disks:
      - name: rootdisk           # 디스크 이름
        disk:
          bus: virtio            # 가상화에 최적화된 I/O 버스 (최고 성능)
                                 # (대안: sata, scsi 등)
        cache: writethrough      # 디스크 캐시 정책 (writethrough: 성능과 안정성 균형)
                                 # (대안: none, writeback 등)
        io: native               # I/O 모드 (native: 네이티브 AIO 사용, 성능 향상)
                                 # (대안: threads, io_uring 등)
      - name: datadisk           # 데이터 디스크 이름
        disk:
          bus: virtio            # 고성능 I/O 버스
        dedicatedIOThread: true  # 전용 I/O 스레드 할당 (I/O 성능 향상)

✅ 클러스터 업그레이드 시 고려사항

KubeVirt 또는 Kubernetes 클러스터 업그레이드 시 고려해야 할 사항입니다.

  1. 업그레이드 전 고려사항:
    • VM 중요도 평가 및 다운타임 계획 수립
    • 현재 상태 백업 (VM 정의, PV 스냅샷 등)
    • 테스트 환경에서 먼저 업그레이드 검증
  2. 작업 순서:
    • 먼저 KubeVirt 오퍼레이터 업그레이드 (컨트롤 플레인)
    • 그 다음 KubeVirt CR 업그레이드 (기능 활성화)
    • 마지막으로 필요한 경우 VM 재시작 (새 기능 적용)
  3. 롤백 계획:
    • 업그레이드 실패 시 롤백 절차 준비
    • 이전 버전 매니페스트 보관
    • 중요 데이터 백업 보관

✅ VM 마이그레이션 실패 해결

Live Migration 문제 해결 방법입니다.

 

▶️ 마이그레이션 상태 확인:

# VirtualMachineInstanceMigration 리소스 확인 - 마이그레이션 상태 확인
kubectl get vmim    # VirtualMachineInstanceMigration의 약어

# 세부 정보 확인 - 마이그레이션 실패 원인 파악
kubectl describe vmim <migration-name>

# KubeVirt 컨트롤러 로그 확인 - 마이그레이션 관련 내부 로그 확인
kubectl logs -n kubevirt -l kubevirt.io=virt-controller

 

 

▶️ 일반적인 마이그레이션 실패 원인:

  1. 대상 노드 리소스 부족:
    • 증상: NodeUnschedulable 또는 UnschedulableHost 오류
    • 원인: 마이그레이션 대상 노드에 충분한 리소스가 없음
    • 해결: 적절한 리소스가 있는 노드 확보, 노드 라벨 확인
  2. 네트워크 대역폭 제한:
    • 증상: 마이그레이션 타임아웃 오류
    • 원인: 메모리 전송에 필요한 네트워크 대역폭 부족
    • 해결: bandwidthPerMigration 값 증가, 네트워크 인프라 개선
  3. 공유 스토리지 문제:
    • 증상: VolumeNotMigrateable 오류
    • 원인: VM 볼륨이 마이그레이션을 지원하지 않음
    • 해결: PVC 스토리지 클래스가 RWX 접근 모드 지원 확인, 스토리지 구성 변경

📌 백업 및 재해 복구 전략

VM 데이터와 구성을 보호하기 위한 백업 전략을 구현해야 합니다.

✅ VM 스냅샷 구성

VM 볼륨의 스냅샷을 생성하여 상태를 보존할 수 있습니다:

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: vm-backup               # 스냅샷 이름
  labels:
    app: vm-backup              # 백업 식별 라벨
    vm: important-vm            # 대상 VM 라벨
spec:
  volumeSnapshotClassName: csi-hostpath-snap  # 스냅샷 클래스 (CSI 드라이버 지원 필요)
  source:
    persistentVolumeClaimName: vm-rootdisk    # 스냅샷 대상 PVC 이름

✅ 정기적인 백업 자동화

CronJob을 사용하여 정기적인 VM 백업을 자동화합니다:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: vm-backup-job           # CronJob 이름
  labels:
    app: vm-backup              # 애플리케이션 라벨
spec:
  schedule: "0 2 * * *"         # cron 표현식 - 매일 오전 2시에 실행
  concurrencyPolicy: Forbid     # 동시 실행 금지 (이전 작업 완료 후 실행)
  successfulJobsHistoryLimit: 3 # 성공한 작업 히스토리 보존 수
  failedJobsHistoryLimit: 1     # 실패한 작업 히스토리 보존 수
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: backup-sa  # 백업 권한 있는 서비스 계정
          containers:
          - name: vm-backup             # 컨테이너 이름
            image: bitnami/kubectl:latest  # kubectl 명령어 실행용 이미지
            command:                    # 실행할 커맨드
            - /bin/sh
            - -c
            - |
              # VM 중지 - 일관된 백업을 위해
              kubectl virt stop my-important-vm
              # 스냅샷 생성 - 날짜 포함 이름으로 생성
              cat <<EOF | kubectl apply -f -
              apiVersion: snapshot.storage.k8s.io/v1
              kind: VolumeSnapshot
              metadata:
                name: vm-backup-$(date +%Y%m%d)  # 날짜 형식의 스냅샷 이름 (YYYYMMDD)
              spec:
                volumeSnapshotClassName: csi-hostpath-snap  # 스냅샷 클래스
                source:
                  persistentVolumeClaimName: vm-rootdisk  # 대상 PVC
              EOF
              # VM 재시작 - 백업 완료 후 정상 운영 재개
              kubectl virt start my-important-vm
              # 백업 완료 로그
              echo "Backup completed successfully at $(date)"
          restartPolicy: OnFailure   # 실패 시 재시작 정책

✅ 재해 복구 절차

VM 장애 발생 시 복구 절차의 예시입니다:

  1. 스냅샷에서 새 PVC 생성:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: vm-rootdisk-restored    # 복구된 디스크 PVC 이름
spec:
  storageClassName: csi-hostpath  # 스토리지 클래스 (원본과 동일해야 함)
  dataSource:
    name: vm-backup-20230615    # 복구할 스냅샷 이름
    kind: VolumeSnapshot        # 데이터 소스 종류
    apiGroup: snapshot.storage.k8s.io  # API 그룹
  accessModes:
    - ReadWriteOnce             # 액세스 모드 (단일 노드 읽기/쓰기)
  resources:
    requests:
      storage: 20Gi             # 스토리지 요청량 (원본 이상)

 

     2. 복구된 디스크로 VM 생성:

apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
  name: recovered-vm            # 복구된 VM 이름
spec:
  running: true                 # 생성 후 자동 시작
  template:
    spec:
      domain:
        devices:
          disks:
          - name: rootdisk      # 디스크 이름
            disk:
              bus: virtio       # 디스크 버스 타입
      volumes:
      - name: rootdisk          # 볼륨 이름 (디스크와 매칭)
        persistentVolumeClaim:
          claimName: vm-rootdisk-restored  # 복구된 PVC 이름

Summary

이번 글에서는 KubeVirt VM을 실제 프로덕션 환경에서 운영할 때 고려해야 할 다양한 측면들을 알아보았습니다. 핵심 내용을 요약하면:

  1. VM 리소스 할당은 성능과 효율성의 균형을 고려해 최적화해야 함
  2. 과다할당 전략은 클러스터 효율성을 높이지만 신중하게 계획해야 함
  3. Prometheus와 Grafana를 활용한 VM 모니터링으로 성능과 상태 감시 가능
  4. 보안 설정을 통해 VM과 관련 워크로드를 보호해야 함
  5. 운영 시 다양한 문제 상황에 대비한 해결 방법 알아두기
  6. 정기적인 백업과 복구 절차를 구현하여 데이터 손실 방지하기

KubeVirt는 계속 발전하고 있는 기술로, 최신 문서와 커뮤니티 리소스를 참고하여 운영 전략을 발전시켜 나가는 것이 중요합니다. 실무 환경에 적용하기 전에 테스트 환경에서 충분히 검증하고, 점진적으로 도입하는 것을 권장합니다.

 

728x90