02. NHN Kubernetes Service (NKS)

정세현·2026년 7월 22일

NHN AX Internship Notes

목록 보기
7/16

0. 한 문장 정의

NHN Cloud 위에서 쿠버네티스 클러스터를 생성·관리할 수 있게 해주는 관리형(Managed) 서비스.
컨트롤 플레인은 NHN Cloud가 책임지고, 사용자는 노드·서비스·파드에 집중한다.


1. 왜 "관리형"이 중요한가

직접 쿠버네티스를 설치·운영하면(=kubeadm으로 self-managed) 해야 하는 일:

  • etcd 클러스터 구성 및 정기 백업/복구 리허설
  • apiserver 다중화 + 앞단 LB 구성
  • 인증서 만료 관리 (기본 1년, 만료되면 클러스터 접속 불가)
  • 컨트롤 플레인 버전 업그레이드 (순서·호환성 주의)

NKS는 이걸 전부 NHN Cloud가 처리한다. 고가용성도 보장된다.
소규모 팀·인턴 프로젝트에서 self-managed K8s를 굴리는 건 사실상 비추천.

책임 분계선

영역담당
컨트롤 플레인 (apiserver, etcd, scheduler, controller-manager)NHN Cloud
워커 노드 (인스턴스 사양·개수·스케일링)사용자
워크로드 (Pod, Deployment, Service, ConfigMap...)사용자
애플리케이션 코드·이미지 보안사용자

2. 주요 기능

2-1. NHN Cloud 기반 서비스와의 연동

관리형 K8s의 진짜 가치는 클라우드 리소스를 K8s 오브젝트로 다룰 수 있다는 점.

(a) 로드 밸런서를 이용한 서비스 공개

type: LoadBalancer Service를 만들면 → NHN Cloud 로드 밸런서가 자동 생성되고 → 외부 IP가 붙는다.
콘솔에서 LB를 수동으로 만들고 대상 서버를 등록하는 과정이 YAML 한 장으로 대체된다.

apiVersion: v1
kind: Service
metadata:
  name: my-api-lb
spec:
  type: LoadBalancer
  selector:
    app: my-api
  ports:
    - port: 80          # LB가 받는 포트
      targetPort: 8080  # 컨테이너 포트
kubectl get svc my-api-lb
# NAME        TYPE           EXTERNAL-IP      PORT(S)
# my-api-lb   LoadBalancer   133.186.x.x      80:31234/TCP

LoadBalancer 타입은 Service 하나당 LB 하나를 만든다. 서비스가 10개면 LB 10개 = 비용 10배.
실무에서는 Ingress Controller(nginx 등)를 하나만 LoadBalancer로 노출하고,
그 뒤에서 경로 기반으로 여러 서비스에 분배하는 구조를 쓴다.

(b) 블록 스토리지 기반 퍼시스턴트 볼륨

PVC를 만들면 NHN Cloud 블록 스토리지가 동적으로 생성되어 파드에 붙는다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 20Gi
  # storageClassName: 콘솔에서 제공되는 SC 이름 확인 후 지정

주의점:

  • 블록 스토리지는 RWO다. 파드 여러 개가 동시에 쓰지 못한다 → 파일 공유가 필요하면 NAS 계열
  • 블록 스토리지는 가용성 영역(AZ)에 묶인다. 다른 AZ 노드로 파드가 옮겨가면 마운트 실패
  • PVC를 지워도 PV 정책(Retain/Delete)에 따라 실제 볼륨이 남거나 사라진다. 운영 데이터는 Retain 권장

2-2. 웹 콘솔을 통한 조작

  • 클러스터 생성 / 삭제 / 조회
  • 노드 그룹 생성 / 삭제 / 조회
  • kubectl 설정(kubeconfig) 지원

노드 그룹(Node Group) 개념이 핵심이다.
같은 사양·같은 설정의 노드 묶음이며, 그룹 단위로 스케일링·업그레이드한다.

클러스터
 ├─ 노드 그룹 A (일반 워크로드용, 4vCPU/8GB × 3대)
 ├─ 노드 그룹 B (메모리 집약, 8vCPU/32GB × 2대)
 └─ 노드 그룹 C (GPU, 1대)

특정 워크로드를 특정 노드 그룹에 보내려면 nodeSelector / affinity / taint-toleration을 쓴다.

spec:
  nodeSelector:
    workload-type: memory-intensive

3. 클러스터 설계 시 결정할 것들

항목고려사항
리전서비스 대상 사용자와 가까운 곳. 다른 NHN 리소스(DB 등)와 같은 리전
VPC/서브넷나중에 바꾸기 어렵다. IP 대역을 넉넉히(파드마다 IP 소모)
노드 사양Spring Boot 파드는 최소 512Mi~1Gi. 노드에는 kubelet/kube-proxy/DaemonSet 몫도 필요하니 사양이 너무 작으면 실제 가용 자원이 얼마 안 남음
노드 개수최소 3대 권장. 1대면 노드 장애 = 전체 장애, 롤링 업데이트 여유 공간도 없음
K8s 버전최신-1 정도가 안전. 쓰려는 도구(Helm 차트 등)의 호환 버전 확인
인터넷 게이트웨이외부 노출·이미지 pull 필요 여부. 없으면 NCR Private URI 사용

4. 시작하기 — 실전 순서

4-1. 클러스터 생성 후 kubectl 연결

# 1. kubectl 설치 확인
kubectl version --client

# 2. 콘솔에서 kubeconfig 다운로드 → 배치
mkdir -p ~/.kube
mv ~/Downloads/kubeconfig.yaml ~/.kube/config
chmod 600 ~/.kube/config

# 3. 연결 확인
kubectl cluster-info
kubectl get nodes -o wide

여러 클러스터를 쓴다면 kubectx/kubens 설치를 강력 추천.
실수로 prod 컨텍스트에서 delete 치는 사고를 줄여준다.

4-2. 첫 워크로드 배포

kubectl create deployment nginx --image=nginx:1.25
kubectl expose deployment nginx --type=LoadBalancer --port=80
kubectl get svc nginx -w    # EXTERNAL-IP 할당될 때까지 대기

4-3. 실전 Spring Boot 매니페스트

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
  labels: { app: my-api }
spec:
  replicas: 3
  selector:
    matchLabels: { app: my-api }
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels: { app: my-api }
    spec:
      imagePullSecrets:
        - name: ncr-secret            # NCR 인증 (03 문서 참고)
      containers:
        - name: app
          image: <레지스트리주소>/my-api:1.0.3   # latest 쓰지 말 것
          ports:
            - containerPort: 8080
          envFrom:
            - configMapRef: { name: my-api-config }
            - secretRef:    { name: my-api-secret }
          env:
            - name: JAVA_OPTS
              value: "-XX:MaxRAMPercentage=75.0"
          resources:
            requests: { cpu: "300m", memory: "512Mi" }
            limits:   { cpu: "1000m", memory: "1Gi" }
          startupProbe:
            httpGet: { path: /actuator/health, port: 8080 }
            failureThreshold: 30
            periodSeconds: 5
          readinessProbe:
            httpGet: { path: /actuator/health/readiness, port: 8080 }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /actuator/health/liveness, port: 8080 }
            periodSeconds: 10
      terminationGracePeriodSeconds: 45
---
apiVersion: v1
kind: Service
metadata:
  name: my-api
spec:
  type: ClusterIP                      # 내부 통신용. 외부 노출은 Ingress로
  selector: { app: my-api }
  ports:
    - port: 8080
      targetPort: 8080

무중단 배포를 위한 추가 설정 (Spring Boot 측)

# application.yml
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
management:
  endpoint:
    health:
      probes:
        enabled: true
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true

K8s가 SIGTERM을 보내면 → Spring이 새 요청을 안 받고 진행 중 요청만 마무리 → 정상 종료.
이게 없으면 배포 때마다 처리 중이던 요청이 끊긴다.


5. NKS에서 자주 만나는 문제

증상원인 후보확인 명령
ImagePullBackOff이미지 경로 오타 / NCR 인증 실패 / IGW 없음kubectl describe pod
Pending (스케줄 안 됨)노드 자원 부족 / PVC 미바인딩 / nodeSelector 불일치kubectl describe pod 하단 Events
CrashLoopBackOff앱 기동 실패 (DB 접속 실패 등)kubectl logs <pod> --previous
LB EXTERNAL-IP가 <pending>서브넷에 인터넷 게이트웨이 미연결 / 쿼터 초과콘솔 네트워크 설정
배포 중 502/504readinessProbe 미설정 / graceful shutdown 미설정Deployment strategy 재점검
파드가 계속 OOMKilledJVM 힙이 컨테이너 limits 초과MaxRAMPercentage 조정
PVC PendingStorageClass 이름 오류 / AZ 불일치kubectl describe pvc

6. 다음 단계 — 프로덕션에 가까워지려면

  1. Ingress Controller 도입 (LB 비용 절감 + 경로 기반 라우팅 + TLS 종료)
  2. Helm 으로 매니페스트 템플릿화 (dev/staging/prod 값만 바꿔 배포)
  3. HPA + Cluster Autoscaler 로 트래픽 대응
  4. 모니터링: Prometheus + Grafana, 로그는 Loki 또는 NHN Log & Crash
  5. GitOps: ArgoCD로 Git = 단일 진실 공급원(SSOT)
  6. PodDisruptionBudget: 노드 업그레이드 중에도 최소 N개는 살아있게 보장
  7. NetworkPolicy: 파드 간 통신을 화이트리스트로 제한

7. 체크리스트

  • 콘솔에서 클러스터 + 노드 그룹을 만들어봤다
  • kubeconfig로 kubectl get nodes가 성공한다
  • LoadBalancer Service로 외부에서 접속해봤다
  • PVC를 만들어 블록 스토리지를 마운트해봤다
  • 이미지 태그를 바꿔 kubectl apply로 롤링 업데이트를 봤다
  • kubectl rollout undo로 롤백해봤다
  • 노드를 하나 죽여보고 파드가 재배치되는 걸 확인했다
profile
I'm the best

0개의 댓글