본문 바로가기

Docker & Kubernetes

도커 & 쿠버네티스 스터디 3주차 - Kubernetes Availability & Network

실습 환경 배포

실습 환경 : Control Plane 1대 + Worker Node 1대로 구성

Windows Docker 설치 (WSL2) - (+Kind)

# Windows WSL2 (Ubuntu) - PowerShell / CMD (관리자 권한)
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
wsl --list -o
wsl --install -d ubuntu

# WSL2 Ubuntu
sudo snap install docker
sudo groupadd docker
sudo usermod -aG docker $USER
docker --version

# Kind 설치
[ $(uname -m) = x86_64 ] && curl -Lo ./kind <https://kind.sigs.k8s.io/dl/v0.27.0/kind-linux-amd64>
sudo chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

# Kind Cluster 생성
kind create cluster

# kubectl 설치
sudo snap install kubectl --classic
kubectl get pods -A

# Krew 설치
wget <https://github.com/kubernetes-sigs/krew/releases/download/v0.4.5/krew-linux_amd64.tar.gz>
tar zxvf krew-linux_amd64
./krew-linux_amd64 install krew
~/.bashrc >> export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
source ~/.bashrc
kubectl krew

# k9s 설치
wget <https://github.com/derailed/k9s/releases/download/v0.50.4/k9s_linux_amd64.deb>
sudo dpkg -i k9s_linux_amd64.deb
sudo apt-get install -f
k9s

# Helm 설치
sudo snap install helm --classic
helm ls

멀티노드 클러스터 with kube-ops-view & Mapping ports - macOS 사용자

윈도우를 사용하지만 강의에서 진행한 macOS용 실습 코드로 따라해보며 트러블 슈팅해보았다.

# '컨트롤플레인, 워커 노드 1대' 클러스터 배포 : 파드에 접속하기 위한 포트 맵핑 설정
cat <<EOT> kind-2node.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
    listenAddress: "0.0.0.0" # Optional, defaults to "0.0.0.0"
    protocol: tcp # Optional, defaults to tcp
  - containerPort: 30001
    hostPort: 30001
EOT

kind create cluster --config kind-2node.yaml

# 배포 확인
kind get clusters
kind get nodes

# 노드 확인
kubectl get nodes -o wide

# 노드에 Taints 정보 확인
kubectl describe node kind-control-plane | grep Taints
Taints:             node-role.kubernetes.io/control-plane:NoSchedule

kubectl describe node kind-worker | grep Taints
Taints:             <none>

# 컨테이너 확인 : 컨테이너 갯수, 컨테이너 이름 확인
# kind yaml 에 포트 맵핑 정보 처럼, 자신의 PC 호스트에 30000 포트 접속 시, 워커노드(실제로는 컨테이너)에 TCP 30000 포트로 연결
# 즉, 워커노드에 NodePort TCP 31000 설정 시 자신의 PC 호스트에서 접속 가능!
docker ps
docker port kind-worker
30000/tcp -> 0.0.0.0:30000
30001/tcp -> 0.0.0.0:30001

extraPortMapping 설명

  • nodePort → vm의 port를 사용하는 것
  • kind는 도커 기반으로 운영됨. 도커의 31000, 31001번 포트를 열어주는 것

 

 

 

 

control plane은 API 서버나 제어 부분을 담당하고 있다. 따라서 control plane에는 워크노드가 실행되는 파드가 배포되면 안 된다. 그렇게 하도록 Taint를 통해서 명시적으로 지정할 수 있다.

 

 

  • kube-ops-view
    • 쿠버네티스상에서 파드가 배포되는 것을 실시간으로 GUI 환경에서 볼 수 있는 테스트 도구
    • helm 설치 후 진행
# kube-ops-view
# helm show values geek-cookbook/kube-ops-view
helm repo add geek-cookbook <https://geek-cookbook.github.io/charts/>
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=31000 --set env.TZ="Asia/Seoul" --namespace kube-system

# 설치 확인
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

# kube-ops-view 접속 URL 확인 (2 배율)
echo -e  "KUBE-OPS-VIEW URL = http://localhost:31000/#scale=2"

  • service → 8080 포트가 31000번 포트와 매핑이 되어있음
  • TYPE : NodePort 타입으로 외부의 포트를 열어줌
  • 실제로 쿠버네티스 노드의 31000번 포트로 들어가면 컨테이너 kube-ops-view라는 파드 애플리케이션의 8080 포트로 연결이 됨을 확인할 수 있다.

[참고] macOs 사용자용 실습을 Window에서 진행했을 때 - http://localhost:31000/#scale=2 접속 안 될 때

 

  • WSL2 + Docker Desktop 환경에서는 Docker가 WSL2 VM 안에서 돌아가고, Kind 클러스터도 Docker 컨테이너 안에서 돌아가는데, 이 컨테이너 내부에서 매핑된 포트(31000)가 내 PC(윈도우 호스트)에서 바로 접근이 안 된다고 한다.
  • 즉 NodePort는 컨테이너 내부에서 잘 열려있지만, 윈도우 PC에서는 그걸 못 잡는다.
  • 해결책
⚠️ On Windows + WSL2, extraPortMappings might not work as expected. Use kubectl port-forward as a reliable alternative.
  • WSL2에선 port-forward가 공식 해결책이라고 한다. (다음 명령어 실행)
kubectl port-forward -n kube-system service/kube-ops-view 31000:8080

  • 테스트 : nginx: NodePort 31001
# 디플로이먼트와 서비스 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-websrv
spec:
  replicas: 2
  selector:
    matchLabels:
      app: deploy-websrv
  template:
    metadata:
      labels:
        app: deploy-websrv
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - name: deploy-websrv
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: deploy-websrv
spec:
  ports:
    - name: svc-webport
      port: 80
      targetPort: 80
      nodePort: 31001
  selector:
    app: deploy-websrv
  type: NodePort
EOF

# 확인
docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                                  NAMES
aaf3847e7643   kindest/node:v1.32.2   "/usr/local/bin/entr…"   11 minutes ago   Up 11 minutes   0.0.0.0:31000-31001->31000-31001/tcp   kind-worker
...

kubectl get deploy,svc,ep deploy-websrv
...
NAME                    TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
service/deploy-websrv   NodePort   10.96.30.19   <none>        80:31001/TCP   57s
...

# 자신의 PC에 호스트 포트 31001 접속 시 쿠버네티스 서비스에 접속 확인
open <http://localhost:31001>
curl -s localhost:31001 | grep -o "<title>.*</title>"
<title>Welcome to nginx!</title>

# 디플로이먼트와 서비스 삭제
kubectl delete deploy,svc deploy-websrv

[참고] 멀티노드 클러스터 with kube-ops-view & Mapping ports - Window 사용자용

# '컨트롤플레인, 워커 노드 1대' 클러스터 배포 : 파드에 접속하기 위한 포트 맵핑 설정
cat <<EOT> kind-2node.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
    listenAddress: "0.0.0.0" # Optional, defaults to "0.0.0.0"
    protocol: tcp # Optional, defaults to tcp
  - containerPort: 30001
    hostPort: 30001
EOT

kind create cluster --config kind-2node.yaml

# 배포 확인
kind get clusters
kind get nodes

# 노드 확인
kubectl get nodes -o wide

# 노드에 Taints 정보 확인
kubectl describe node kind-control-plane | grep Taints
Taints:             node-role.kubernetes.io/control-plane:NoSchedule

kubectl describe node kind-worker | grep Taints
Taints:             <none>

# 컨테이너 확인 : 컨테이너 갯수, 컨테이너 이름 확인
# kind yaml 에 포트 맵핑 정보 처럼, 자신의 PC 호스트에 30000 포트 접속 시, 워커노드(실제로는 컨테이너)에 TCP 30000 포트로 연결
# 즉, 워커노드에 NodePort TCP 31000 설정 시 자신의 PC 호스트에서 접속 가능!
docker ps
docker port kind-worker
30000/tcp -> 0.0.0.0:30000
30001/tcp -> 0.0.0.0:30001

Mapping ports to the host machine

  • kube-ops-view : NodePort 30000
# kube-ops-view
# helm show values geek-cookbook/kube-ops-view
helm repo add geek-cookbook <https://geek-cookbook.github.io/charts/>
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30000 --set env.TZ="Asia/Seoul" --namespace kube-system

# 설치 확인
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view

# kube-ops-view 접속 URL 확인 (1.5 , 2 배율)
echo -e "KUBE-OPS-VIEW URL = "

http://192.168.50.10:30000/#scale=2 : ubuntu 의 enp0s8(192.168.50.10) vNIC에 TCP 30000(nodeport)로 웹 접속

# 디플로이먼트와 서비스 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-websrv
spec:
  replicas: 2
  selector:
    matchLabels:
      app: deploy-websrv
  template:
    metadata:
      labels:
        app: deploy-websrv
    spec:
      terminationGracePeriodSeconds: 0
      containers:
      - name: deploy-websrv
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: deploy-websrv
spec:
  ports:
    - name: svc-webport
      port: 80
      targetPort: 80
      nodePort: 30001
  selector:
    app: deploy-websrv
  type: NodePort
EOF

# 확인
docker ps
CONTAINER ID   IMAGE                  COMMAND                   CREATED         STATUS         PORTS                                  NAMES
117a1145a676   kindest/node:v1.29.2   "/usr/local/bin/entr…"   7 minutes ago   Up 7 minutes   0.0.0.0:30000-30001->30000-30001/tcp   myk8s-worker
...

kubectl get deploy,svc,ep deploy-websrv
...
NAME                    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/deploy-websrv   NodePort   10.96.204.112   <none>        80:30001/TCP   55s
...

# 자신의 PC에 192.168.50.10:30001 접속 시 쿠버네티스 서비스에 접속 확인
curl -s 192.168.50.10:30001
<title>Welcome to nginx!</title>
...

# 디플로이먼트와 서비스 삭제
kubectl delete deploy,svc deploy-websrv

쿠버네티스 가용성

  • 가용성 (Availability) 을 확보하기 위한 다양한 방법
    • HPA (Horizontal Pod Autoscaling) : Pod 수평 확장 (스케일 In - Out)
    • VPA (Vertical Pod Autoscaling) : Pod 수직 확장 (스케일 Up)
    • CA (Cluster Autoscaling) : Node 확장 (Cloud 환경)

구현을 위해서는 Metrics Server 설치가 필요하다. Metrics 지표를 확인하여 HPA, VPA 같은 동작을 수행하도록 한다.

  • Metrics Server 설치
    • Kubernetes 에 내장된 확장 파이프라인을 위한 컨테이너 지표 수집 서버
    • Kubelet 의 지표를 수집하고 노출하여 API Server 에 전달
    • HPA, VPA 같은 자동 확장 사용 목적 (모니터링 솔루션 X)
# Metrics Server 설치
kubectl apply -f <https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml>

# Metrics Server SSL 무시
kubectl patch deployment metrics-server -n kube-system --type=json \\
  -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'

# Metrics Server 배포 확인
kubectl get pods -n kube-system -l k8s-app=metrics-server

# 쿠버네티스 리소스 자원 사용량 확인
kubectl top node
kubectl top pods -A

# CPU, Memory 내림차순
kubectl top pods -A --sort-by=cpu
kubectl top pods -A --sort-by=memory

Metrics Server 배포하기

  • Metrics Server가 배포되면, top 명령어를 하나 더 칠 수 있다.

 

정렬 등도 가능하다.

HPA - 수평 스케일링 Horizontal Pod Autoscaling

  • HPA는 쿠버네티스를 쓰고 있으면 거의 필수적이다.
  • 애플리케이션 부하(Load)에 따라 Pod 개수를 자동으로 늘리거나 줄이는 기능
  • 모니터링 대상: CPU, Memory, 사용자 정의 지표
  • Metrics Server 가 감시한 지표를 활용하여 설정된 임계치를 초과하면 Replica 수 조정
  • 기본 구성
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-sample
spec:
  scaleTargetRef:          # Scale 타겟 지정
    apiVersion: apps/v1
    kind: Deployment
    name: my-app           # Deployment 이름
  minReplicas: 2           # 최소 Pod
  maxReplicas: 10          # 최대 Pod
  metrics:                 # Scale 기준 지표 설정
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 50  # CPU 사용률 50% 기준
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 70  # 메모리 사용률 70% 기준

HPA 구성

  • Deployment 배포
cat << EOF >> hpa-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hpa-nginx
  template:
    metadata:
      labels:
        app: hpa-nginx
    spec:
      containers:
      - name: hpa-nginx
        image: nginx
        resources:
          requests:
            cpu: 50m
          limits:
            cpu: 100m
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: hpa-nginx
  labels:
    app: hpa-nginx
spec:
  ports:
  - port: 80
  selector:
    app: hpa-nginx
EOF

cat hpa-nginx.yaml

# Deployment 배포
kubectl apply -f hpa-nginx.yaml

kubectl get deploy,pod
  • 간단한 nginx 파드

 

  • HPA 구성
# HPA 생성
kubectl autoscale deployment hpa-nginx --cpu-percent=50 --min=1 --max=10
# 퍼센트가 50 이상이면 최소 1개 최대 10개 자동 확장 축소를 해달라

# HPA 확인
kubectl get hpa
...
NAME        REFERENCE              TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
hpa-nginx   Deployment/hpa-nginx   cpu: 0%/50%   1         10        1          17s
....

# HPA 상세 정보 확인
kubectl describe hpa
  • HPA를 명령형 커맨드로 바로 실행할 수 있다.
  • 위 명령어를 yaml 형태로 뽑아내면 (kubectl get hpa -o yaml | kubectl neat)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-nginx
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-nginx
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50

 

  • 부하 발생 전 → 0%

Pod 부하 발생시키기

# 터미널 1번
while true; do kubectl get hpa; kubectl top pods; sleep 1s; done

# 터미널 2번
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while true; do wget -q -O- <http://hpa-nginx.default.svc.cluster.local>; done"

# 실습 종료 후 리소스 삭제
kubectl delete hpa --all
kubectl delete -f hpa-nginx.yaml

부하 발생 시 Replicas가 늘어나는 것을 볼 수 있다.

 

 


VPA - 수직 스케일링 Vertical Pod Autoscaling

  • Pod 의 리소스 요청값(Request)을 자동으로 조정하는 기능
  • 적용 대상 : Request (CPU, Memory)
  • Pod 의 개수를 늘리는 HPA 와 다르게 Pod 의 리소스 크기를 조정
    • VPA Recommender 에 의해 최적의 리소스 상태를 찾아서 조정
  • 하나의 Deployment 에 HPA, VPA 를 같이 사용할 수 없음
    • (상호간에 충돌하기 때문)
  • Kubernetes v1.33 버전 부터는 기본 활성화 상태
  • but, kind 는 현재 v1.32 까지만 사용가능하므로 별도 controller 설치 필요
  • 기본 구성
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: my-app-vpa
spec:
  targetRef:                   # Scale 대상
    apiVersion: apps/v1
    kind: Deployment
    name: my-app               # Deployment 명칭
  updatePolicy:
    updateMode: "Auto"         # VPA Recommender 에 의해 자동 조정 활성화
  resourcePolicy:
    containerPolicies:
      - containerName: my-app-container # Container 명칭 "*" 사용 가능
        minAllowed:            # 컨테이너가 할당받을 수 있는 최소 리소스
          cpu: "200m"
          memory: "512Mi"
        maxAllowed:            # 컨테이너가 할당받을 수 있는 최대 리소스
          cpu: "2"
          memory: "2Gi"
  • VPA 배포
# EKS Workshop 소스 사용
git clone <https://github.com/kubernetes/autoscaler.git>

# VPA 배포
cd autoscaler/vertical-pod-autoscaler/
./hack/vpa-up.sh

# VPA Controller 확인
kubectl get pods -n kube-system | grep vpa

# VPA 제거
./hack/vpa-down.sh                         

 

  • VPA 테스트
    • 0.1 cpu 를 요청한 2개 Pod 배포 (실제 사용량보다 부족한 상태)
    apiVersion: "autoscaling.k8s.io/v1"
    kind: VerticalPodAutoscaler
    metadata:
      name: hamster-vpa
    spec:
      targetRef:
        apiVersion: "apps/v1"
        kind: Deployment
        name: hamster
      resourcePolicy:
        containerPolicies:
          - containerName: '*'
            minAllowed:
              cpu: 100m
              memory: 50Mi
            maxAllowed:
              cpu: 1
              memory: 500Mi
            controlledResources: ["cpu", "memory"]
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: hamster
    spec:
      selector:
        matchLabels:
          app: hamster
      replicas: 2
      template:
        metadata:
          labels:
            app: hamster
        spec:
          securityContext:
            runAsNonRoot: true
            runAsUser: 65534 # nobody
          containers:
            - name: hamster
              image: registry.k8s.io/ubuntu-slim:0.14
              resources:
                requests:
                  cpu: 100m
                  memory: 50Mi
              command: ["/bin/sh"]
              args:
                - "-c"
                - "while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done"
    
# 터미널 1번
while true;
 do date "+%Y-%m-%d %H:%m:%S";
 kubectl get pod -l app=hamster; 
 kubectl get vpa; 
 kubectl describe pod | grep "Requests:" -A2; 
 echo "=============="; 
 sleep 5s; 
done

# 터미널 2번
kubectl apply -f examples/hamster.yaml        

# 자원 삭제
kubectl delete -f examples/hamster.yaml

 

 

Pod 재시작 없이 Resize - kind feature gate (InPlacePodVerticalScaling) 설정 필요

  • kubernetes v1.27 ~v1.32 까지 feature gate 활성화 시 사용 가능
  • kubernetes v1.33 부터는 기본 활성화
apiVersion: v1
kind: Pod
metadata:
  name: resize-demo
spec:
  containers:
  - name: pause
    image: registry.k8s.io/pause:3.8
    resizePolicy:
    - resourceName: cpu
      restartPolicy: NotRequired # Default, but explicit here
    - resourceName: memory
      restartPolicy: RestartContainer
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"
# 터미널 1번
while true;
 do date "+%Y-%m-%d %H:%m:%S";
 kubectl get pod; 
 kubectl get vpa; 
 kubectl describe pod | grep "Requests:" -A2; 
 echo "=============="; 
 sleep 5s; 
done

# 터미널 2번
kubectl apply -f vpa.yaml
kubectl patch pod resize-demo --subresource resize --patch \\
  '{"spec":{"containers":[{"name":"pause", "resources":{"requests":{"cpu":"800m"}, "limits":{"cpu":"800m"}}}]}}'

# 자원 삭제
kubectl delete -f vpa.yaml

CA (Cluster Autoscaler)

  • Cluster Autoscaler
    • 쿠버네티스 클러스터에서 노드 수를 자동으로 확장, 축소하는 도구
    • HPA, VPA 는 Pod 단에서의 동작이지만, CA 는 워커 노드 단에서의 동작
    • 클라우드 환경에서 사용되며 파드를 배포할 노드의 리소스가 부족해지면 노드를 자동으로 확장
  • Karpenter
    • CA 와 동일하게 쿠버네티스 노드 수를 자동으로 확장, 축소하는 도구
    • CA 와는 다르게 Node 의 크기까지 자동으로 변경 (HPA + VPA 기능)
    • CA 에 비해 노드의 빠른 확장 및 축소 가능

KEDA

  • KEDA (Kubernetes Event-Driven Autoscaler)
    • 기존 HPA(Horizontal Pod Autoscaler)는 리소스(CPU, Memory) 메트릭 기반 스케일링
    • KEDA 는 이벤트 기반의 Autoscaler
    • HPA 와 함께 사용하여 이벤트 기반으로 유연하게 확장 가능
      • ex) 특정 시간대에 미리 확장

어플리케이션 변수 관리

ConfigMap

 

  • Kubernetes 애플리케이션의 구성 파일이나 환경 설정Key-Value 형태로 저장하고 관리
  • 애플리케이션의 설정 정보를 외부에서 관리하고 Pod 와 컨테이너에서 참조
  • 주요 사용 용도
    1. 애플리케이션 설정 관리
      • 애플리케이션 구성 정보 (DB URL, 변수 등)을 ConfigMap 에 저장하여 Pod 환경 변수나 파일로 사용
    2. 애플리케이션 환경에 맞는 설정 값 변경
      • 애플리케이션을 재빌드 하지 않고 설정 값 변경
      • DEV, STG, PRD 환경에 따라 각각 다른 파일 구성으로 관리 목적
  • 기본 구성
# ConfigMap 샘플 구성
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config # ConfigMap 명칭
data:
  key1: value1    # Key : Value 형태 값 주입
  key2: value2
# ConfigMap 사용 예시
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
    - name: my-container
      image: my-image
      env:
        - name: MY_CONFIG_KEY  # 컨테이너에서 사용할 변수 Key 값
          valueFrom:
            configMapKeyRef:
              name: my-config  # 사용할 ConfigMap의 이름
              key: key1        # ConfigMap 내의 키 -> 값: value1

ConfigMap 기본 활용

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
data:
  DBNAME: mydatabase
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-configmap
spec:
  containers:
  - image: nginx
    name: nginx-configmap
    env:
    - name: DB
      valueFrom:
        configMapKeyRef:
          name: mysql
          key: DBNAME
EOF

# 오브젝트 확인
kubectl get cm,pod

# 상세 정보 조회
kubectl describe cm mysql
kubectl describe pod nginx-configmap

# pod 내부 변수 확인
kubectl exec -it nginx-configmap -- /bin/bash -c env
...
DB=mydatabase
...

# 리소스 삭제
kubectl delete pod --all
kubectl delete cm nginx-configmap

 

 

 

  • 변수명에서 DB라는 변수를 찾아볼 수 있다.

ConfigMap 으로 설정 파일 관리

# 테스트 파일 생성
cat << EOF >> config-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-configmap-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-configmap
  template:
    metadata:
      labels:
        app: nginx-configmap
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: config-volume
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
      volumes:
      - name: config-volume
        configMap:
          name: nginx-config
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx-configmap
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 31001
  type: NodePort
EOF

cat << EOF >> configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  nginx.conf: |
    events {}
    http {
        server {
            listen 80;
            location / {
                return 200 'Hello from nginx configmap!';
            }
        }
    }
EOF
  • 리소스 배포
kubectl apply -f configmap.yaml -f config-deploy.yaml

#
kubectl get cm,deploy,pod
kubectl describe deploy
...
    Mounts:
      /etc/nginx/nginx.conf from config-volume (rw,path="nginx.conf")
  Volumes:
   config-volume:
    Type:          ConfigMap (a volume populated by a ConfigMap)
    Name:          nginx-config
    Optional:      false
...

# Nginx 접속
open <http://localhost:31001>

# Nginx ConfigMap 변경
vim configmap.yaml
...
return 200 'Modify from nginx configmap!';
...

#
kubectl apply -f configmap.yaml

# pod 재시작
kubectl rollout restart deploy nginx-configmap-deploy

# 리소스 삭제
kubectl delete -f configmap.yaml -f config-deploy.yaml
  • configMap 내용을 바꿔도, 다시 배포가 되지 않으면 내용이 바뀌지 않음 → pod 재시작 필요
  • configMap을 수정한 부분이 pod에도 잘 적용이 됨을 확인할 수 있다.
  • 참고) Reloader
  • ConfigMap 변경 시 Pod 수동 재배포가 필요 → 비효율적
  • ConfigMap, Secret 의 변동 사항을 주기적으로 확인해서 자동으로 Rollout 을 해주는 오픈소스 도구

 

Secret

  • Kubernetes 에서 민감 정보를 안전하게 관리하는 객체 (비밀번호, 토큰, SSH 키 등)
  • ConfigMap 과 달리 Base64 로 인코딩된 형태로 데이터 저장
    • Base64는 단순히 인코딩(encoding) 방식. 암호화가 아님.
  • → Base64 는 암호화인가??
  • Secret 파일에 Base64 로 인코딩된 값이 아니면 사용할 수 없음
  • 주요 사용 용도
    • 비밀번호, API 키 등의 민감정보 저장
    • ConfigMap 과 동일하게 Pod 의 환경 변수나 파일로 주입 가능
  • 기본 구성
# Secret 샘플
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
data:
  username: bXl1c2Vy  # base64로 인코딩된 값
  password: bXlwYXNzd29yZA==  # base64로 인코딩된 값
# Secret 사용 예시
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
    - name: my-container
      image: my-image
      env:
        - name: DB_USER        # Container 에서 사용할 변수명
          valueFrom:
            secretKeyRef:
              name: my-secret  # 사용할 Secret의 이름
              key: username    # Secret 내의 키
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: my-secret  # 사용할 Secret의 이름
              key: password    # Secret 내의 키
...
# 마운트 방법
      volumeMounts:
        - name: secret-volume     # Volume 명칭
          mountPath: /etc/secrets # 컨테이너 내부 마운트 위치
  volumes:
    - name: secret-volume         # Volume 명칭
      secret:
        secretName: my-secret     # 사용할 Secret의 이름

Secret 기본 활용

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: secret-test
type: Opaque
data:
  username: YWRtaW4=  # 'admin'을 base64 인코딩한 값
  password: cGFzc3dvcmQ=  # 'password'를 base64 인코딩한 값
EOF

# Base64 인코딩 방법
echo -n 'admin' | base64
echo -n 'password' | base64

#
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: secret-pod
spec:
  containers:
  - name: nginx
    image: nginx
    env:
    - name: DB_USER
      valueFrom:
        secretKeyRef:
          name: secret-test
          key: username
    - name: DB_PASS
      valueFrom:
        secretKeyRef:
          name: secret-test
          key: password
EOF

#
kubectl get pod,secret

# 상세 정보 조회
kubectl describe secret secret-test
kubectl describe pod secret-pod

# pod 내부 변수 확인
kubectl exec -it secret-pod -- /bin/bash -c env
...
DB_USER=admin
DB_PASS=password
...

# 리소스 삭제
kubectl delete pod --all
kubectl delete secret secret-test

 

  • secret 값을 바로 조회했을 때는 나오지 않지만, pod에 mount 할 때는 api 서버를 통해서 값을 가져오기 때문에 이미 복호화가 되어있다.
  • Secret 을 관리하는 다양한 도구
    • ASCP (AWS Secret Store CSI Driver) - AWS Secret Manager 활용
    • HashiCorp Vault
    • Sealed Secret - OSS

참고) Sealed Secret

K8S Secret 을 암호화할 수 있는 OSS 도구

  • 기존 Base64 로 인코딩 되어 저장하는 Secret 이 아닌 암호화 되어 저장
  • Code Repository (Git) 에 암호화된 Secret 이 올라가기 때문에, GitOps Flow 를 그대로 사용할 수 있는 장점이 있음
  • Client Side(암호화) - Server Side (복호화)
# Mac Brew 설치 (Client-Side)
brew install kubeseal

# Server-Side 설치 - SealedSecret Controller
helm repo add sealed-secrets <https://bitnami-labs.github.io/sealed-secrets>
helm install my-release sealed-secrets/sealed-secrets

# SealedSecret Controller 조회
kubectl get pods -l app.kubernetes.io/name=sealed-secrets

# Secret 생성
kubectl create secret generic mysecret \\
  --from-literal hello=world \\
  --dry-run=client \\
  -o yaml > mysecret.yaml

# 
cat mysecret.yaml

# base64 디코딩
grep 'hello:' mysecret.yaml | awk '{print $2}' | base64 --decode

# Sealed Secret 생성
cat mysecret.yaml | \\
  kubeseal \\
   --controller-name my-release-sealed-secrets \\
   --controller-namespace default -o yaml > mysealed-secret.yaml

# Sealed Secret 암호화 적용 확인
cat mysealed-secret.yaml
grep 'hello:' mysealed-secret.yaml | awk '{print $2}' | base64 --decode

#
kubectl apply -f mysealed-secret.yaml
kubectl get secret

# secret 확인
kubectl get secret mysecret -o json
kubectl get secret mysecret -o jsonpath="{.data.hello}" | base64 -d

네트워크

서비스 종류

  • Service -
    • 외부와 접하는 단일 엔드포인트
    • 서비스 뒷단의 애플리케이션으로 외부 트래픽을 전송
  • ClusterIP
    • 쿠버네티스 클러스터 범위의 가상 IP 부여
    • 클러스터 내부에서만 ClusterIP 로 접근 가능
    • 서비스 타입을 지정하지 않을 경우 기본값
  • NodePort
    • 고정 포트로 각 노드(Host) 의 포트를 노출
    • 클러스터 외부에서 노드(Host) 의 IP:Port 를 통해 접근 가능
  • LoadBalancer
    • 외부 로드 밸런서를 지원하는 클라우드 공급자 상에서 활용
    • AWS, Azure, GCP 등의 LB 서비스와 쿠버네티스 서비스를 연결

ClusterIP

테스트 환경 배포

# 어플리케이션 배포
cat << EOF >> cluster-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: cluster-pod-1
  labels:
    app: cluster-pod
spec:
  containers:
  - name: container
    image: traefik/whoami
---
apiVersion: v1
kind: Pod
metadata:
  name: cluster-pod-2
  labels:
    app: cluster-pod
spec:
  containers:
  - name: container
    image: traefik/whoami
EOF

# Test 파드
cat << EOF >> netshoot-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: netshoot-pod
spec:
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
EOF

# ClusterIP 서비스 생성
cat <<EOF>> cluster-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: cluster-svc
spec:
  type: ClusterIP
  selector:
    app: cluster-pod
  ports:
    - name: cluster
      port: 8080 # service의 포트
      targetPort: 80 # service가 pod를 호출할 때의 포트
EOF

# 배포
kubectl apply -f cluster-pod.yaml -f cluster-svc.yaml -f netshoot-pod.yaml
  • service로 진입했을 때, pod의 로드밸런싱이 어떻게 되는지 확인하기 위해 2개를 배포함

 

Pod 생성 확인

# 파드 대역 확인
kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}'
...
10.244.0.0/24 10.244.1.0/24
...

# SVC 대역 확인
kubectl -n kube-system get pods -l component=kube-controller-manager -o yaml | grep service-cluster-ip-range
...
--service-cluster-ip-range=10.96.0.0/16
...

# 확인
kubectl get pod -o wide
...
NAME                                         READY   STATUS    RESTARTS   AGE     IP            NODE          NOMINATED NODE   READINESS GATES
cluster-pod-1                                1/1     Running   0          8m20s   10.244.1.66   kind-worker   <none>           <none>
cluster-pod-2                                1/1     Running   0          8m20s   10.244.1.65   kind-worker   <none>           <none>
netshoot-pod                                 1/1     Running   0          8m20s   10.244.1.67   kind-worker   <none>           <none>
...

# 서비스 확인
kubectl get svc cluster-svc
...
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
cluster-svc   ClusterIP   10.96.138.114   <none>        8080/TCP   9m18s
...

# Endpoint 확인 (Pod IP:Port)
kubectl get endpoints cluster-svc
...
NAME                                ENDPOINTS                       AGE
cluster-svc                         10.244.1.65:80,10.244.1.66:80   9m37s
...

 

 

ClusterIP 확인

# 클라이언트(TestPod) Shell 실행
kubectl exec -it netshoot-pod -- zsh

# 서비스 ClusterIP 주입
SVC=10.96.138.114

curl $SVC:8080
curl -s $SVC:8080 | grep Hostname

# 100 번 반복 호출
for i in {1..100}; do curl -s $SVC:8080 | grep Hostname; done | sort | uniq -c | sort -nr
...
55 Hostname: cluster-pod-1
45 Hostname: cluster-pod-2
...

 

 

이런 식으로 service는 application의 단일 진입점이 된다.

완벽하게 라운드로빈 되지는 않는다. (심화 설정 필요)

 

NodePort

테스트 환경 배포

# 어플리케이션 배포
cat << EOF > nodeport-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodeport-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nodeport-deploy
  template:
    metadata:
      labels:
        app: nodeport-deploy
    spec:
      containers:
      - name: container
        image: traefik/whoami
EOF

# ClusterIP 서비스 생성
cat <<EOF> nodeport-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: nodeport-svc
spec:
  type: NodePort
  selector:
    app: nodeport-deploy
  ports:
    - name: nodeport-svc
      port: 80          # 서비스 포트 (Cluster 내부에서 사용)
      targetPort: 80    # 실제 컨테이너 포트
      nodePort: 31001   # 외부에서 접근할 NodePort
EOF

생성 및 확인

# 생성
kubectl apply -f nodeport-pod.yaml -f nodeport-svc.yaml

# 확인
kubectl get pod,svc
...
NAME                   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
service/nodeport-svc   NodePort    10.96.66.182   <none>        80:31001/TCP   100s
...

kubectl get endpoints nodeport-svc
NAME           ENDPOINTS                       AGE
nodeport-svc   10.244.1.10:80,10.244.1.11:80   2m50s

 

 

NodePort 동작 확인

# 노드의 Port 로 curl 요청
curl <http://localhost:31001>
curl -s <http://localhost:31001> | grep Hostname

# 100 번 반복 호출
for i in {1..100}; do curl -s <http://localhost:31001> | grep Hostname; done | sort | uniq -c | sort -nr
...
58 Hostname: nodeport-deploy-59b68567d7-6h562
42 Hostname: nodeport-deploy-59b68567d7-k2cpb
...
  • nodePort는 좋지 않다 → Ingress를 많이 쓴다.

Ingress

  • Ingress는 Service 위에 있다.
  • Ingress가 도메인 url path를 읽고 어떤 service로 트래픽을 흘러줄지, 라우팅 역할을 한다.

 

  • 클러스터 외부에서 내부 서비스로 트래픽을 라우팅 하는 방법을 제공하는 리소스
  • 클러스터 내부 서비스 (ClusterIP, NodePort, LoadBalancer) 를 외부로 노출 (HTTP / HTTPS) - Web Proxy 역할
  • Ingress 를 사용하기 위해서는 Ingress Controller 가 필요
  • 대표적인 Ingress Controller
    • Nginx Ingress Controller
    • Cloud Provider Ingress Controllers
      • (ex. AWS ALB Ingress Controller, GCP Ingress Controller)
  • 주요 기능
    1. 호스트 기반 라우팅
      • 호스트 이름 (도메인)을 기반으로 트래픽 라우팅 가능
      • api.example.com / www.example.com 을 각각 다른 Service 리소스와 연결
    2. 경로 기반 라우팅
      • 요청 경로 기반으로 트래픽 라우팅 가능
      • /growth , /log 경로를 각각 다른 Service 리소스와 연결
    3. TLS 설정
      • TLS 인증서를 활용하여 HTTPS 구성 가능
    4. 로드 밸런싱
      • 내부 서비스에 대한 로드 밸런싱
  • 기본 구성
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  namespace: default
spec:
  rules:
  - host: example.com       # Domain Host
    http:
      paths:
      - path: /service1     # URL Path (example.com/service1)
        pathType: Prefix
        backend:
          service:
            name: service1  # /service1 로 들어온 트래픽을 전송할 service 명
            port:
              number: 80
      - path: /service2     # URL Path (example.com/service2)
        pathType: Prefix
        backend:
          service:
            name: service2
            port:
              number: 80    # /service2 로 들어온 트래픽을 전송할 service 명

Nginx Ingress Controller 설치

  • Ingress Controller 중 가장 대중적인 도구
  • Ingress 리소스 및 동작 방식을 구현
# 기존 리소스 삭제
kind delete cluster

# kind cluster 재배포
kind create cluster --config kind-2node.yaml

# Nginx Ingress Controller 설치
kubectl apply -f <https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml>

# Service 타입 변경
kubectl patch svc ingress-nginx-controller -n ingress-nginx -p \\
'{"spec":{"type":"NodePort","ports":[{"port":80,"targetPort":80,"nodePort":31000},{"port":443,"targetPort":443,"nodePort":31001}]}}'

# Nginx Ingress Controller 리소스 확인
kubectl get -n ingress-nginx svc,deploy,pod

 

서비스 생성

# Growth 서비스
cat << EOF > svc-growth.yaml
apiVersion: v1
kind: Service
metadata:
  name: growth-service
spec:
  selector:
    app: growth
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: growth-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: growth
  template:
    metadata:
      labels:
        app: growth
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
          volumeMounts:
            - name: growth-html
              mountPath: /usr/share/nginx/html
      volumes:
        - name: growth-html
          configMap:
            name: growth-html
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: growth-html
data:
  index.html: |
    <html>
    <body>
      <h1>hello growth</h1>
    </body>
    </html>
EOF

kubectl apply -f svc-growth.yaml

 

# Log 서비스
cat << EOF > svc-log.yaml
apiVersion: v1
kind: Service
metadata:
  name: log-service
spec:
  selector:
    app: log
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: log-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: log
  template:
    metadata:
      labels:
        app: log
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
          volumeMounts:
            - name: log-html
              mountPath: /usr/share/nginx/html
      volumes:
        - name: log-html
          configMap:
            name: log-html
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: log-html
data:
  index.html: |
    <html>
    <body>
      <h1>hello log</h1>
    </body>
    </html>

EOF

kubectl apply -f svc-log.yaml

 

# 배포 확인
kubectl get pod,svc,cm

# ConfigMap 확인
kubectl describe cm growth-html
kubectl describe cm log-html

 

 

Ingress 배포

cat << EOF > ingress-sample.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: growth-log-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: / # /growth, /log 요청을 서비스로 전달할 때 접두사 제거. ex) /growth -> growth-service '/'
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /growth
        pathType: Prefix
        backend:
          service:
            name: growth-service
            port:
              number: 80
      - path: /log
        pathType: Prefix
        backend:
          service:
            name: log-service
            port:
              number: 80
EOF

kubectl apply -f ingress-sample.yaml
kubectl get ing,svc

#
kubectl describe ingress growth-log-ingress

 

 

Ingress 동작 확인

# growth 경로 호출
curl <http://localhost:31000/growth>
...
<html>
<body>
  <h1>hello growth</h1>
</body>
</html>
...

# Log 경로 호출
curl <http://localhost:31000/log>
...
<html>
<body>
  <h1>hello log</h1>
</body>
</html>
...

Storage

Kubernetes Storage 소개

  • emptyDir
    • Pod 내부에 존재하며 동일한 Pod 의 컨테이너 간 공유될 수 있는 스토리지
    • Pod 삭제 시 함께 삭제
    • Container 가 사용하는 임시 저장소
  • hostPath
    • Pod 가 배포된 Worker Node 의 Directory Path 를 Pod 에 마운트
    • Pod 가 삭제되어도 데이터는 Worker Node 에 존재
    • 하지만 해당 Pod 가 배포된 Node 에만 데이터를 저장하므로, 다른 노드에 배포된 파드와는 공유할 수 없음
  • Persistent volume (PV)
    • 쿠버네티스 클러스터 전체의 공유 볼륨
    • 다양한 크기의 볼륨을 가진 PV 를 미리 만들어 두고 Pod 가 필요할 때 연결하여 사용
      • 이 때, Pod 와 PV 를 연결하는 작업이 PVC(PersistentVolumeClaim)