Skip to main content

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를 구성하는 Helmyaml 포맷 및 문법을 학습해보도록 하겠습니다.


참고 자료