Skip to main content

12/12/2025

Kubernetes Practice

개요​

앞서 학습한 Kubernetes 개념, 역할, 실행 환경을 실제 코드와 함께 살펴봅니다.
실습에서 사용된 코드 베이스는 c2z GitHub Repository에서 확인하실 수 있습니다.

Context​

실습 목적 및 환경​

실습을 위해 작성한 코드와 목적을 정리하면 다음과 같습니다:

  • k8s 사용 목적: 컨테이너 기반의 침투 테스트 환경 구축
    • 오픈 소스 코드로 IaC를 사용하여 어느 환경에서도 쉽게 실행될 수 있는 환경을 구축합니다.
    • k3d를 사용하여 로컬 환경에서 k8s를 실행합니다.
    • Helm을 사용하여 k8s 리소스를 관리합니다.
    • Helm Chart를 사용하여 k8s 리소스를 관리합니다.
  • k8s 실행 환경: minikube
  • OS: ARM Mac OS

실행 환경​

Kubernetes Cluster를 실행하기 위해서 k3d를 사용합니다.

  • k3d는 리눅스, 윈도우, 맥OS에서 실행할 수 있습니다.

OS 별로 k3s, k3d, kubeadm을 사용할 수 있습니다.

  • 리눅스: k3s, kubeadm
  • 윈도우: k3d
  • 맥OS: minikube, k3d

Helm Chart 구조​

실제 프로젝트에서 사용한 Helm Chart 구조는 다음과 같습니다:

charts/c2z/
├── Chart.yaml # 차트 메타데이터
├── values.yaml # 기본 설정 값
└── templates/ # Kubernetes 리소스 템플릿
├── namespaces.yaml
├── network-policies.yaml
├── resource-quotas.yaml
├── attacker-zone/
│ ├── kali-deployment.yaml
│ ├── kali-service.yaml
│ └── kali-pvc.yaml
├── monitoring/
│ ├── prometheus-deployment.yaml
│ ├── grafana-deployment.yaml
│ └── loki-deployment.yaml
└── scenarios/ # 웹 취약점 침투 시나리오
└── web-vuln/
├── dvwa-deployment.yaml
├── dvwa-service.yaml
├── juiceshop-deployment.yaml
└── juiceshop-service.yaml

Chart.yaml 메타 데이터​

apiVersion: v2
name: c2z
description: A Helm chart for the c2z penetration testing lab environment
type: application
version: 0.1.0
appVersion: "1.0.0"

values.yaml 예시​

values.yaml은 Chart의 설정을 중앙에서 관리할 수 있게 해줍니다.
템플릿 파일에서 {{ .Values.xxx }}로 참조할 수 있습니다.

global:
imageRegistry: docker.io
imagePullPolicy: IfNotPresent

# 공격자 환경
attacker:
enabled: true
kali:
image: kalilinux/kali-rolling:latest # kalilunux 이미지를 사용합니다.
resources: # 공격자 환경의 리소스를 할당
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"

# 시나리오 설정
scenarios:
webVuln:
enabled: true
dvwa:
image: vulnerables/web-dvwa:latest
replicas: 1 # 웹 취약점 시나리오의 복제본의 갯수

Helm 배포​

Helm 배포란 Chart를 사용하여 Kubernetes 리소스를 배포하는 과정을 의미합니다. 배포가 완료되면 Kubernetes 리소스가 생성되어, 쿠버네티스 클러스터에서 실행됩니다.

# Chart 설치
helm upgrade --install c2z ./charts/c2z \
--namespace c2z-system \
--create-namespace \
--wait \
--timeout 10m

# 릴리스 확인
helm list -n c2z-system

# 설정 값 확인
helm get values c2z -n c2z-system

# 동적으로 값 변경하여 업그레이드
helm upgrade c2z ./charts/c2z \
--namespace c2z-system \
--reuse-values \
--set scenarios.webVuln.enabled=true

주요 명령어 설명:

  • upgrade --install: 없으면 설치, 있으면 업그레이드
  • --reuse-values: 기존 values 유지하면서 새 값만 덮어쓰기
  • --wait: 모든 리소스가 Ready 상태가 될 때까지 대기

네임스페이스(Namespace) 격리​

Namespace는 쿠버네티스 클러스터 내에서 논리적으로 환경을 분리하기 위한 개념입니다. Linux의 네임스페이스와 유사하지만, k8s에선 쿠버네티스 리소스를 논리적으로 분리하는 역할을 합니다.

참조: 리눅스 네임스페이스

Kubernetes 클러스터 내에서 논리적으로 환경을 분리하기 위해 네임스페이스를 사용합니다. 이는 리소스 격리, 접근 제어, 리소스 쿼터 적용 등에 유용합니다.

네임스페이스 생성​

# templates/namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
name: c2z-system
labels:
app.kubernetes.io/part-of: c2z
tier: infrastructure
---
apiVersion: v1
kind: Namespace
metadata:
name: scenario-web-vuln
labels:
app.kubernetes.io/part-of: c2z
tier: scenario
scenario: web-vuln

네임스페이스별 역할​

프로젝트에서는 다음과 같이 네임스페이스를 분리했습니다:

네임스페이스용도포함 리소스
c2z-system인프라 및 공격자 환경Kali Pod, Prometheus, Grafana, Loki
scenario-web-vulnWeb 취약점 시나리오DVWA, Juice Shop
scenario-container-escapeContainer Escape 시나리오Privileged Pod 등
scenario-network-attackNetwork Attack 시나리오Legacy Services

네임스페이스 조회​

# 모든 네임스페이스 조회
kubectl get namespaces

# 특정 네임스페이스의 리소스 조회
kubectl get all -n c2z-system
kubectl get pods -n scenario-web-vuln

네트워크 정책(Network Policy)​

기본적으로 Kubernetes는 모든 Pod 간 통신을 허용합니다.
보안 강화를 위해 NetworkPolicy를 사용하여 명시적으로 허용된 트래픽만 통과시킬 수 있습니다.

이번 프로젝트에선 NetworkPolicy를 사용하여 인프라 네임스페이스에 대한 인바운드 트래픽을 제한했습니다.
추후 공격 시나리오 설계가 완성되고, 스크립트를 추가하여 침투 테스트 시뮬레이션을 할 때 수정할 것 같습니다.

기본 Deny 정책​

# templates/network-policies.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: scenario-web-vuln
spec:
podSelector: {} # 모든 Pod에 적용.
policyTypes:
- Ingress
- Egress

위 정책은 scenario-web-vuln 네임스페이스의 모든 Pod에 대해 기본적으로 모든 트래픽을 차단합니다.

선택적 허용 정책​

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-attacker
namespace: scenario-web-vuln
spec:
podSelector:
matchLabels:
tier: vulnerable-app
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
tier: infrastructure
- podSelector:
matchLabels:
role: attacker

정책 설명:

  • scenario-web-vuln 네임스페이스의 tier: vulnerable-app 레이블을 가진 Pod만 대상
  • tier: infrastructure 네임스페이스에서 오는 트래픽 허용
  • role: attacker 레이블을 가진 Pod에서 오는 트래픽 허용

DNS 및 외부 통신 허용​

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: scenario-web-vuln
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53

DNS 조회를 위해 kube-system 네임스페이스의 53번 포트(DNS)로의 Egress를 허용합니다.


리소스 쿼터(Resource Quota)​

네임스페이스별로 CPU, 메모리, Pod 개수 등을 제한하여 리소스 과다 사용을 방지할 수 있습니다.

ResourceQuota 정의​

# templates/resource-quotas.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: scenario-quota
namespace: scenario-web-vuln
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"
pods: "10"
services: "5"
persistentvolumeclaims: "3"

설정 항목:

  • requests.cpu: 요청 가능한 총 CPU 코어 수
  • requests.memory: 요청 가능한 총 메모리
  • limits.cpu/memory: 제한 가능한 최대 리소스
  • pods: 네임스페이스 내 최대 Pod 개수
  • services: 최대 Service 개수
  • persistentvolumeclaims: 최대 PVC 개수

리소스 쿼터 확인​

# ResourceQuota 조회
kubectl get resourcequota -n scenario-web-vuln

# 상세 정보 확인 (현재 사용량 포함)
kubectl describe resourcequota scenario-quota -n scenario-web-vuln

출력 예시:

Name:                   scenario-quota
Namespace: scenario-web-vuln
Resource Used Hard
-------- ---- ----
limits.cpu 2 8
limits.memory 4Gi 16Gi
pods 3 10
requests.cpu 1 4
requests.memory 2Gi 8Gi
services 2 5

Pod 리소스 요청 및 제한​

각 Pod(컨테이너)는 requests(최소 보장 리소스)와 limits(최대 사용 가능 리소스)를 설정할 수 있습니다.

Deployment에서 리소스 설정​

# templates/attacker-zone/kali-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kali
namespace: c2z-system
spec:
replicas: 1
selector:
matchLabels:
app: kali
role: attacker
template:
metadata:
labels:
app: kali
role: attacker
spec:
containers:
- name: kali
image: kalilinux/kali-rolling:latest
command: ["/bin/sleep", "infinity"]
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
volumeMounts:
- name: kali-storage
mountPath: /root
volumes:
- name: kali-storage
persistentVolumeClaim:
claimName: kali-pvc

리소스 설정 의미:

  • requests.cpu: "1000m": 1 CPU 코어 보장 (1000m = 1 core)
  • requests.memory: "2Gi": 2GB 메모리 보장
  • limits.cpu: "2000m": 최대 2 CPU 코어 사용 가능
  • limits.memory: "4Gi": 최대 4GB 메모리 사용 가능

리소스 단위​

  • CPU: 1 = 1 vCPU/Core, 1000m = 1 vCPU, 500m = 0.5 vCPU
  • 메모리: Gi (Gibibyte), Mi (Mebibyte), Ki (Kibibyte)

영구 스토리지(Persistent Volume)​

Pod는 삭제되면 데이터도 함께 사라집니다.

PersistentVolumeClaim(PVC)를 사용하면 Pod의 생명주기와 무관하게 데이터를 유지할 수 있다고 합니다.

PVC 정의​

# templates/attacker-zone/kali-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kali-pvc
namespace: c2z-system
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: local-path # K3s 기본 StorageClass

주요 필드:

  • accessModes:
    • ReadWriteOnce: 단일 노드에서 읽기/쓰기
    • ReadOnlyMany: 여러 노드에서 읽기 전용
    • ReadWriteMany: 여러 노드에서 읽기/쓰기
  • storageClassName: 스토리지 프로비저너 지정

Pod에서 PVC 마운트​

volumes:
- name: kali-storage
persistentVolumeClaim:
claimName: kali-pvc
volumeMounts:
- name: kali-storage
mountPath: /root

이렇게 하면 Kali Pod를 삭제하고 재생성해도 /root 디렉토리의 데이터는 보존됩니다.


서비스(Service) 노출​

Pod는 IP가 동적으로 변경되므로 Service를 통해 안정적인 엔드포인트를 제공합니다.

Service 타입​

타입용도접근 범위
ClusterIP클러스터 내부 통신내부 전용 (기본값)
NodePort노드 포트를 통한 외부 노출외부 접근 가능
LoadBalancer클라우드 LB 프로비저닝외부 접근 (클라우드)
ExternalName외부 DNS 매핑외부 서비스 참조

ClusterIP Service 예시​

# templates/scenarios/web-vuln/dvwa-service.yaml
apiVersion: v1
kind: Service
metadata:
name: dvwa
namespace: scenario-web-vuln
spec:
type: ClusterIP
selector:
app: dvwa
ports:
- protocol: TCP
port: 80
targetPort: 80

클러스터 내부에서 dvwa.scenario-web-vuln.svc.cluster.local:80으로 접근할 수 있습니다.

Port-Forward를 통한 로컬 접근​

# 로컬 8080 포트를 Pod의 80 포트로 포워딩
kubectl port-forward -n scenario-web-vuln svc/dvwa 8080:80

# 브라우저에서 http://localhost:8080 접속

모니터링 스택​

실시간 메트릭과 로그 수집을 위해 Prometheus, Grafana, Loki를 배포했습니다.

Prometheus (메트릭 수집)​

# templates/monitoring/prometheus-deployment.yaml (간소화)
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: c2z-system
spec:
replicas: 1
selector:
matchLabels:
app: prometheus
template:
spec:
containers:
- name: prometheus
image: prom/prometheus:latest
args:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=7d'
ports:
- containerPort: 9090

Grafana (시각화)​

# templates/monitoring/grafana-deployment.yaml (간소화)
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
namespace: c2z-system
spec:
replicas: 1
selector:
matchLabels:
app: grafana
template:
spec:
containers:
- name: grafana
image: grafana/grafana:latest
env:
- name: GF_SECURITY_ADMIN_PASSWORD
value: "admin"
ports:
- containerPort: 3000

모니터링 접근​

# Prometheus 접근
kubectl port-forward -n c2z-system svc/prometheus 9090:9090

# Grafana 접근
kubectl port-forward -n c2z-system svc/grafana 3000:3000

배포 플로우​

전체 시스템을 배포하는 과정을 정리하면 다음과 같습니다:

1. 자동 설치 스크립트​

#!/bin/bash
# install.sh (핵심 로직만 발췌)

# Python 가상환경 설정
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# OS별 K8s 설치
OS_TYPE=$(uname -s)
if [[ "$OS_TYPE" == "Darwin" ]]; then
# macOS -> k3d
k3d cluster create c2z --port "80:80@server:0" ...
else
# Linux -> K3s
curl -sfL https://get.k3s.io | sh -s - ...
fi

# Helm 설치
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# c2z Chart 배포
helm upgrade --install c2z ./charts/c2z \
--namespace c2z-system \
--create-namespace \
--wait \
--timeout 10m

2. CLI를 통한 시나리오 관리​

# c2z-cli.py (핵심 로직)
import click
import subprocess

@click.command()
@click.argument('scenario_id')
def deploy(scenario_id):
"""시나리오 배포 (활성화)"""
key = to_config_key(scenario_id) # 'web-vuln' -> 'webVuln'

cmd = [
"helm", "upgrade", "c2z", "./charts/c2z",
"--namespace", "c2z-system",
"--reuse-values",
"--set", f"scenarios.{key}.enabled=true",
"--wait"
]

subprocess.run(cmd, check=True)

3. 배포 확인​

# 전체 Pod 상태 확인
kubectl get pods -A

# 특정 네임스페이스 리소스 확인
kubectl get all -n c2z-system
kubectl get all -n scenario-web-vuln

# 로그 확인
kubectl logs -n scenario-web-vuln deployment/dvwa --tail=50 -f

Hindsight​

사이버 보안 과정의 네트워크 보안 수업에서 들은 내용이 다수 있어서, 복습과 새로 공부할 내용이 많습니다.
정리하자면, 이번 실습을 통해 다음과 같은 Kubernetes 핵심 개념을 학습했습니다:

  1. 클러스터 구축

    • K3s (Linux 네이티브)
    • k3d (Docker 기반)
    • OS별 차이점과 선택 기준
  2. Helm을 통한 패키지 관리

    • Chart 구조와 템플릿화
    • values.yaml을 통한 중앙 설정 관리
    • 동적 values 변경을 통한 업그레이드
  3. 네임스페이스 격리

    • 논리적 환경 분리
    • 라벨을 통한 그룹핑
    • 네임스페이스별 리소스 관리
  4. 네트워크 정책

    • 기본 Deny 정책
    • 선택적 Ingress/Egress 허용
    • 레이블 기반 트래픽 제어
  5. 리소스 관리

    • ResourceQuota를 통한 네임스페이스별 제한
    • Pod별 requests/limits 설정
    • 리소스 효율성과 안정성 균형
  6. 영구 스토리지

    • PVC를 통한 데이터 지속성
    • StorageClass와 프로비저닝
    • Volume 마운트 방식
  7. 서비스 노출

    • ClusterIP, NodePort, LoadBalancer 차이
    • Port-forward를 통한 로컬 접근
    • 서비스 디스커버리 (DNS)
  8. 모니터링

    • Prometheus 메트릭 수집
    • Grafana 대시보드
    • Loki 로그 집계

다음 글에서는 Pod를 구성하는 Helm의 yaml 포맷 및 문법을 학습해보도록 하겠습니다.


참고 자료​