🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Kubernetes 기초 및 Pod, Service 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 30. · 24 Views

Kubernetes 기초 및 Pod, Service 완벽 가이드

쿠버네티스의 핵심 개념인 Pod와 Service를 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 컨테이너 오케스트레이션의 기본부터 실무 활용까지 차근차근 알아봅니다.


목차

  1. Kubernetes_기본_개념
  2. Pod_기본_이해
  3. Deployment와_ReplicaSet
  4. Service_기본_개념
  5. Service_타입_심화
  6. 레이블과_셀렉터
  7. 네임스페이스로_리소스_격리
  8. Pod_상태_확인과_디버깅

1. Kubernetes 기본 개념

김개발 씨는 최근 회사에서 도커 컨테이너를 사용하기 시작했습니다. 처음에는 신기했지만, 컨테이너가 10개, 20개로 늘어나자 관리가 점점 힘들어졌습니다.

"이걸 어떻게 다 관리하지?" 고민하던 그때, 선배가 쿠버네티스를 추천해주었습니다.

Kubernetes(쿠버네티스, 줄여서 K8s)는 컨테이너화된 애플리케이션을 자동으로 배포, 확장, 관리해주는 오케스트레이션 플랫폼입니다. 마치 대형 오케스트라의 지휘자가 수십 명의 연주자를 조화롭게 이끄는 것처럼, 쿠버네티스는 수많은 컨테이너를 효율적으로 조율합니다.

구글이 15년간 운영해온 노하우를 바탕으로 만들어져 안정성과 확장성이 뛰어납니다.

다음 코드를 살펴봅시다.

# 쿠버네티스 클러스터 정보 확인
kubectl cluster-info

# 클러스터에 연결된 노드 목록 조회
kubectl get nodes

# 모든 네임스페이스의 리소스 확인
kubectl get all --all-namespaces

# 특정 네임스페이스의 Pod 상태 확인
kubectl get pods -n default

# 클러스터 컴포넌트 상태 확인
kubectl get componentstatuses

김개발 씨는 스타트업에서 백엔드 개발을 담당하는 2년차 개발자입니다. 회사 서비스가 성장하면서 도커 컨테이너의 수가 기하급수적으로 늘어났습니다.

처음에는 서버 한 대에서 컨테이너 몇 개를 돌리는 것으로 충분했지만, 이제는 수십 개의 컨테이너가 여러 서버에 분산되어 실행되고 있습니다. 어느 날 새벽, 김개발 씨의 휴대폰이 울렸습니다.

서버 장애였습니다. 허둥지둥 노트북을 열어 확인해보니, 컨테이너 하나가 죽어있었습니다.

수동으로 다시 시작하고 나서야 서비스가 정상화되었습니다. "이렇게 매번 수동으로 관리해야 하나?" 김개발 씨는 한숨을 쉬었습니다.

다음 날 출근해서 이 이야기를 했더니, 옆자리 박시니어 씨가 웃으며 말했습니다. "쿠버네티스 써봤어요?

그런 문제는 쿠버네티스가 알아서 해결해줘요." Kubernetes란 정확히 무엇일까요? 쉽게 비유하자면, 쿠버네티스는 마치 대형 물류 창고의 자동화 시스템과 같습니다.

아마존 물류 창고를 상상해보세요. 수만 개의 상품이 있고, 로봇들이 자동으로 물건을 분류하고, 포장하고, 배송 준비를 합니다.

어떤 로봇이 고장나면 다른 로봇이 그 일을 대신합니다. 주문이 폭주하면 자동으로 로봇을 더 투입합니다.

쿠버네티스도 마찬가지입니다. 컨테이너가 죽으면 자동으로 다시 살려주고, 트래픽이 늘어나면 자동으로 컨테이너 수를 늘려줍니다.

개발자가 일일이 신경 쓰지 않아도 시스템이 알아서 건강한 상태를 유지합니다. 쿠버네티스가 없던 시절에는 어땠을까요?

개발자들은 서버마다 SSH로 접속해서 컨테이너 상태를 확인해야 했습니다. 컨테이너가 죽으면 수동으로 재시작해야 했고, 서버 간 로드 밸런싱도 직접 설정해야 했습니다.

새벽에 장애가 나면 잠을 설치며 대응해야 했습니다. 바로 이런 문제들을 해결하기 위해 구글이 Borg라는 내부 시스템의 경험을 바탕으로 쿠버네티스를 만들었습니다.

2014년에 오픈소스로 공개된 이후, 쿠버네티스는 빠르게 업계 표준이 되었습니다. 쿠버네티스의 핵심은 선언적 구성입니다.

개발자는 "내가 원하는 상태"를 YAML 파일로 선언하기만 하면 됩니다. "이 애플리케이션은 항상 3개의 복제본으로 실행되어야 해"라고 선언하면, 쿠버네티스가 알아서 그 상태를 유지합니다.

하나가 죽으면 새로 만들고, 서버가 추가되면 적절히 분배합니다. 실제 현업에서 쿠버네티스는 어떻게 활용될까요?

대표적인 예가 무중단 배포입니다. 새 버전을 배포할 때, 쿠버네티스는 기존 컨테이너를 하나씩 새 버전으로 교체합니다.

사용자는 서비스 중단 없이 자연스럽게 새 버전을 사용하게 됩니다. 만약 새 버전에 문제가 있다면?

한 번의 명령으로 이전 버전으로 롤백할 수 있습니다. 다시 김개발 씨의 이야기로 돌아가봅시다.

박시니어 씨의 도움으로 쿠버네티스를 도입한 후, 김개발 씨는 새벽 장애 호출에서 해방되었습니다. 컨테이너가 죽어도 쿠버네티스가 알아서 복구하고, 트래픽이 늘어도 자동으로 확장됩니다.

이제 김개발 씨는 인프라 걱정 대신 비즈니스 로직 개발에 집중할 수 있게 되었습니다.

실전 팁

💡 - 로컬에서 쿠버네티스를 연습하려면 minikubeDocker Desktop의 Kubernetes 기능을 활용하세요

  • kubectl 명령어에 익숙해지는 것이 첫 번째 단계입니다

2. Pod 기본 이해

박시니어 씨가 김개발 씨에게 물었습니다. "쿠버네티스에서 가장 작은 배포 단위가 뭔지 알아요?" 김개발 씨는 당연히 컨테이너라고 생각했지만, 답은 Pod였습니다.

컨테이너가 아니라 Pod라니, 이게 대체 무슨 차이일까요?

Pod는 쿠버네티스에서 생성하고 관리할 수 있는 가장 작은 배포 단위입니다. 하나의 Pod 안에는 하나 또는 여러 개의 컨테이너가 들어갈 수 있습니다.

마치 콩깍지(Pod) 안에 여러 개의 콩알이 들어있는 것처럼, Pod 안의 컨테이너들은 같은 네트워크와 스토리지를 공유하며 함께 움직입니다.

다음 코드를 살펴봅시다.

# simple-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-first-pod
  labels:
    app: myapp
spec:
  containers:
  - name: nginx-container
    image: nginx:1.21
    ports:
    - containerPort: 80
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"

김개발 씨는 도커에 익숙했기 때문에 컨테이너가 최소 단위라고 생각했습니다. 그런데 쿠버네티스에서는 왜 컨테이너가 아닌 Pod를 최소 단위로 삼았을까요?

박시니어 씨가 설명을 시작했습니다. "Pod라는 이름의 어원을 알아요?" 박시니어 씨가 물었습니다.

Pod는 영어로 콩깍지를 뜻합니다. 콩깍지를 생각해보세요.

하나의 깍지 안에 여러 개의 콩알이 옹기종기 모여 있습니다. 콩깍지가 움직이면 안의 콩알들도 함께 움직이고, 깍지가 떨어지면 콩알들도 함께 떨어집니다.

Pod도 마찬가지입니다. Pod 안의 컨테이너들은 항상 같은 노드에서 실행됩니다.

같은 네트워크 네임스페이스를 공유하기 때문에 localhost로 서로 통신할 수 있습니다. 같은 볼륨을 마운트할 수도 있어서 파일 공유도 쉽습니다.

그렇다면 왜 이런 구조가 필요할까요? 실무에서 흔히 마주치는 사례를 들어보겠습니다.

웹 서버를 운영하는데, 로그를 수집해서 중앙 로그 시스템으로 보내야 합니다. 이때 웹 서버 컨테이너와 로그 수집 컨테이너를 같은 Pod에 넣으면 됩니다.

로그 수집 컨테이너는 웹 서버가 쓴 로그 파일을 읽어서 전송합니다. 이런 패턴을 사이드카 패턴이라고 부릅니다.

메인 컨테이너 옆에 보조 역할을 하는 컨테이너를 붙이는 것입니다. 마치 오토바이 옆에 달린 사이드카처럼요.

위의 YAML 파일을 살펴보겠습니다. apiVersion은 쿠버네티스 API 버전을 지정합니다.

Pod는 가장 기본적인 리소스이므로 v1을 사용합니다. kind는 리소스의 종류, 여기서는 Pod입니다.

metadata 섹션에는 Pod의 이름과 레이블을 지정합니다. spec 섹션이 핵심입니다.

여기서 컨테이너들을 정의합니다. containers는 배열이므로 여러 개의 컨테이너를 정의할 수 있습니다.

각 컨테이너는 이름, 이미지, 포트, 리소스 제한 등을 지정합니다. resources 부분을 주목해주세요.

limits로 컨테이너가 사용할 수 있는 최대 메모리와 CPU를 제한합니다. 이렇게 하지 않으면 하나의 컨테이너가 노드의 모든 자원을 독점할 수 있습니다.

실무에서는 반드시 리소스 제한을 설정해야 합니다. Pod를 직접 만들어볼까요?

위의 YAML 파일을 저장한 후 kubectl apply -f simple-pod.yaml 명령을 실행하면 됩니다. kubectl get pods 명령으로 Pod가 실행 중인지 확인할 수 있습니다.

STATUS가 Running이면 성공입니다. 하지만 중요한 점이 있습니다.

실무에서는 Pod를 직접 생성하는 경우가 거의 없습니다. Pod는 죽으면 그대로 사라집니다.

자동으로 재시작되지 않습니다. 그래서 보통은 DeploymentReplicaSet 같은 상위 컨트롤러를 통해 Pod를 관리합니다.

이 내용은 다음 카드에서 자세히 다루겠습니다. 다시 김개발 씨 이야기로 돌아갑시다.

Pod의 개념을 이해한 김개발 씨는 고개를 끄덕였습니다. "아, 그래서 항상 Deployment를 쓰라고 했군요." 박시니어 씨가 미소 지으며 답했습니다.

"맞아요. Pod의 개념은 알아야 하지만, 실무에서는 더 똑똑한 방법을 쓰는 거죠."

실전 팁

💡 - Pod 하나에 컨테이너 하나가 일반적인 패턴입니다. 멀티 컨테이너는 꼭 필요할 때만 사용하세요

  • Pod를 직접 생성하지 말고 Deployment를 사용하세요

3. Deployment와 ReplicaSet

김개발 씨가 Pod를 직접 생성해서 테스트하던 중, 실수로 Pod를 삭제했습니다. 그런데 Pod가 다시 살아나지 않았습니다.

"쿠버네티스가 자동으로 복구해준다고 했는데?" 당황한 김개발 씨에게 박시니어 씨가 말했습니다. "Pod를 직접 만들면 안 되고, Deployment를 써야 해요."

Deployment는 Pod의 배포와 업데이트를 관리하는 컨트롤러입니다. Deployment를 사용하면 Pod가 죽어도 자동으로 재생성되고, 원하는 개수의 Pod를 항상 유지합니다.

마치 호텔 매니저가 객실 상태를 항상 체크하며 관리하는 것처럼, Deployment는 Pod의 상태를 지속적으로 감시하고 관리합니다.

다음 코드를 살펴봅시다.

# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.21
        ports:
        - containerPort: 80

김개발 씨의 실수는 사실 많은 초보자들이 겪는 일입니다. Pod만 단독으로 생성하면, 그 Pod는 어디서도 관리되지 않습니다.

마치 주인 없는 고아처럼, 사라지면 그냥 사라지는 것입니다. 박시니어 씨가 비유를 들어 설명했습니다.

"호텔을 상상해보세요. 손님이 체크아웃하면 객실이 비게 되죠.

그런데 호텔 매니저가 있으면 어떨까요? 매니저는 항상 일정 수의 객실이 준비되어 있도록 관리합니다.

객실이 비면 다시 정돈하고, 문제가 있으면 수리합니다." Deployment가 바로 그 호텔 매니저 역할을 합니다. Deployment는 replicas라는 설정으로 "항상 3개의 Pod가 실행되어야 해"라고 선언합니다.

Pod가 하나 죽으면? Deployment가 즉시 새 Pod를 만들어서 3개를 유지합니다.

노드가 다운되어 Pod 2개가 사라졌다면? 다른 노드에 2개의 Pod를 새로 만듭니다.

사실 Deployment는 직접 Pod를 관리하지 않습니다. 중간에 ReplicaSet이라는 컨트롤러가 있습니다.

Deployment가 ReplicaSet을 만들고, ReplicaSet이 Pod를 만드는 구조입니다. 왜 이렇게 복잡할까요?

버전 관리 때문입니다. 애플리케이션을 업데이트할 때, Deployment는 새 ReplicaSet을 만듭니다.

새 ReplicaSet은 새 버전의 Pod를 생성하고, 기존 ReplicaSet의 Pod는 점진적으로 줄어듭니다. 이것이 롤링 업데이트입니다.

만약 새 버전에 문제가 있다면? 기존 ReplicaSet이 아직 남아 있으므로 빠르게 롤백할 수 있습니다.

위의 YAML 파일을 살펴보겠습니다. apiVersionapps/v1인 것을 주목하세요.

Pod는 v1이었지만, Deployment는 apps 그룹에 속합니다. replicas: 3은 항상 3개의 Pod를 유지하라는 의미입니다.

selectortemplate의 관계가 중요합니다. selector.matchLabels는 "app: nginx 레이블을 가진 Pod를 관리하겠다"는 선언입니다.

template 안의 labels는 실제로 생성될 Pod에 붙을 레이블입니다. 이 둘이 일치해야 Deployment가 자신이 만든 Pod를 인식할 수 있습니다.

실무에서 Deployment를 어떻게 활용할까요? 대표적인 것이 스케일링입니다.

갑자기 트래픽이 늘어났다면? kubectl scale deployment nginx-deployment --replicas=10 명령 한 줄로 Pod를 10개로 늘릴 수 있습니다.

트래픽이 줄어들면 다시 3개로 줄이면 됩니다. 자동 스케일링도 가능합니다.

HorizontalPodAutoscaler(HPA)를 사용하면 CPU 사용률이나 메모리 사용량에 따라 자동으로 Pod 수를 조절할 수 있습니다. 새벽에는 3개, 점심시간에는 10개, 이렇게 자동으로 조절됩니다.

주의할 점도 있습니다. replicas를 너무 크게 설정하면 노드의 리소스가 부족해질 수 있습니다.

각 Pod의 리소스 요청량과 노드의 가용 리소스를 고려해서 적절한 값을 설정해야 합니다. 김개발 씨는 이제 Pod를 직접 만들지 않습니다.

항상 Deployment를 통해 Pod를 생성합니다. 덕분에 Pod가 죽어도 자동으로 복구되고, 배포와 롤백도 쉬워졌습니다.

"이제야 진짜 쿠버네티스다운 운영을 하는 것 같아요." 김개발 씨가 만족스럽게 말했습니다.

실전 팁

💡 - Pod를 직접 생성하지 말고 반드시 Deployment를 사용하세요

  • kubectl rollout undo deployment/이름으로 언제든 이전 버전으로 롤백할 수 있습니다

4. Service 기본 개념

김개발 씨가 Deployment로 Pod 3개를 띄웠습니다. 그런데 문제가 생겼습니다.

각 Pod는 자신만의 IP 주소를 가지고 있고, Pod가 재시작될 때마다 IP가 바뀝니다. "이렇게 매번 바뀌는 IP로 어떻게 접속하죠?" 김개발 씨의 질문에 박시니어 씨가 답했습니다.

"그래서 Service가 필요한 거예요."

Service는 Pod 집합에 대한 안정적인 네트워크 엔드포인트를 제공합니다. Pod의 IP는 언제든 바뀔 수 있지만, Service의 IP와 DNS 이름은 변하지 않습니다.

마치 회사의 대표 전화번호처럼, 직원이 바뀌어도 대표번호는 그대로이고, 전화를 걸면 적절한 담당자에게 연결해줍니다.

다음 코드를 살펴봅시다.

# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP

# Service를 통해 Pod에 접근
# curl http://nginx-service:80

김개발 씨의 고민은 쿠버네티스를 처음 접하는 모든 개발자가 겪는 문제입니다. 도커에서는 컨테이너의 IP를 알면 직접 접속할 수 있었습니다.

하지만 쿠버네티스에서는 상황이 다릅니다. Pod는 언제든 죽을 수 있고, 새로 생성될 때마다 새 IP를 받습니다.

박시니어 씨가 비유를 들어 설명했습니다. "콜센터를 생각해보세요.

고객이 상담사 개인 전화번호로 전화하면 어떻게 될까요? 상담사가 퇴사하거나 자리를 비우면 연결이 안 되겠죠.

그래서 대표번호를 쓰는 겁니다. 대표번호로 전화하면 상담 가능한 상담사에게 자동으로 연결됩니다." Service가 바로 그 대표번호 역할을 합니다.

Service는 고정된 IP 주소와 DNS 이름을 가집니다. 클라이언트는 Service의 주소로 요청을 보내면 되고, Service가 알아서 살아있는 Pod 중 하나에게 요청을 전달합니다.

Service가 Pod를 찾는 방법은 레이블 셀렉터입니다. 위의 YAML에서 selector: app: nginx를 보세요.

이 설정은 "app: nginx 레이블을 가진 모든 Pod에게 트래픽을 분산하겠다"는 의미입니다. Deployment에서 만든 Pod들이 app: nginx 레이블을 가지고 있으므로, Service는 그 Pod들을 자동으로 찾아냅니다.

porttargetPort의 차이도 알아야 합니다. port는 Service가 노출하는 포트이고, targetPort는 Pod가 듣고 있는 포트입니다.

보통은 같은 값을 쓰지만, 다르게 설정할 수도 있습니다. 예를 들어 Service는 80번 포트로 받고, Pod의 8080번 포트로 전달할 수 있습니다.

Service의 type에는 여러 종류가 있습니다. ClusterIP는 가장 기본적인 타입으로, 클러스터 내부에서만 접근 가능합니다.

외부에서는 접근할 수 없습니다. 이것이 기본값입니다.

그런데 내부에서만 접근 가능하면 어떻게 외부 사용자가 서비스를 이용할까요? 이 질문에 대한 답은 다음 카드에서 다루겠습니다.

지금은 Service의 기본 개념에 집중합시다. Service를 생성하면 쿠버네티스 내부 DNS에 자동으로 등록됩니다.

같은 네임스페이스 안에서는 Service 이름만으로 접근할 수 있습니다. 위의 예시에서는 nginx-service라는 이름으로 접근 가능합니다.

다른 네임스페이스에서는 nginx-service.default.svc.cluster.local 형태의 전체 DNS 이름을 사용합니다. 실무에서 Service는 어떻게 활용될까요?

마이크로서비스 아키텍처를 생각해보세요. 사용자 서비스, 주문 서비스, 결제 서비스가 각각 Deployment로 실행되고 있습니다.

주문 서비스가 사용자 정보를 조회하려면 사용자 서비스에 요청을 보내야 합니다. 이때 Pod IP를 직접 사용하면 안 됩니다.

대신 user-service라는 Service 이름으로 요청을 보내면, 살아있는 사용자 서비스 Pod 중 하나에게 자동으로 전달됩니다. 김개발 씨는 이제 Pod IP에 직접 접근하지 않습니다.

항상 Service를 통해 접근합니다. 덕분에 Pod가 재시작되어 IP가 바뀌어도 문제없이 서비스가 동작합니다.

실전 팁

💡 - Service 이름은 DNS 이름이 되므로 간결하고 명확하게 짓는 것이 좋습니다

  • 같은 네임스페이스 안에서는 Service 이름만으로 접근 가능합니다

5. Service 타입 심화

김개발 씨가 드디어 애플리케이션을 완성했습니다. 그런데 아직 외부에서 접속이 안 됩니다.

"ClusterIP는 내부에서만 접근 가능하다고 했는데, 외부 사용자는 어떻게 우리 서비스에 접속하죠?" 박시니어 씨가 말했습니다. "Service 타입을 바꿔야 해요.

NodePort나 LoadBalancer를 쓰면 됩니다."

Service에는 ClusterIP, NodePort, LoadBalancer, ExternalName 네 가지 타입이 있습니다. ClusterIP는 내부 전용, NodePort는 노드의 특정 포트로 외부 노출, LoadBalancer는 클라우드 로드밸런서 연동, ExternalName은 외부 DNS 이름 매핑에 사용됩니다.

상황에 맞는 타입을 선택하는 것이 중요합니다.

다음 코드를 살펴봅시다.

# NodePort Service 예시
apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080

# LoadBalancer Service 예시
# apiVersion: v1
# kind: Service
# metadata:
#   name: nginx-lb
# spec:
#   type: LoadBalancer
#   selector:
#     app: nginx
#   ports:
#   - port: 80
#     targetPort: 80

박시니어 씨가 화이트보드 앞에 섰습니다. "Service 타입을 그림으로 설명해줄게요." 그리고 네 가지 타입을 하나씩 그리기 시작했습니다.

첫 번째는 ClusterIP입니다. 가장 기본적인 타입이고, 클러스터 내부에서만 접근 가능합니다.

마이크로서비스 간 통신에 주로 사용됩니다. 예를 들어 백엔드 서비스가 데이터베이스에 접근할 때, 데이터베이스 Service는 ClusterIP로 충분합니다.

외부에서 데이터베이스에 직접 접근할 필요가 없으니까요. 두 번째는 NodePort입니다.

클러스터의 모든 노드에서 특정 포트를 열어줍니다. 위의 예시에서 nodePort: 30080을 보세요.

어떤 노드의 IP든 상관없이 30080 포트로 접근하면 해당 Service에 연결됩니다. NodePort 범위는 기본적으로 30000-32767입니다.

NodePort의 장점은 간단하다는 것입니다. 로드밸런서 없이도 외부 접근이 가능합니다.

하지만 단점도 있습니다. 포트 번호가 30000번대라 보기 좋지 않고, 노드가 다운되면 해당 노드로의 접근은 불가능합니다.

그래서 개발 환경이나 간단한 테스트에 주로 사용합니다. 세 번째는 LoadBalancer입니다.

클라우드 환경(AWS, GCP, Azure 등)에서 사용합니다. LoadBalancer 타입을 지정하면, 클라우드 프로바이더가 자동으로 로드밸런서를 생성하고 연결해줍니다.

외부 IP가 할당되고, 그 IP로 접근하면 Pod들에게 트래픽이 분산됩니다. LoadBalancer의 장점은 진짜 로드밸런서를 사용한다는 것입니다.

고가용성이 보장되고, 트래픽 분산도 효율적입니다. 하지만 클라우드 비용이 발생합니다.

AWS의 ELB, GCP의 Cloud Load Balancing은 시간당 요금이 붙습니다. 서비스가 많아지면 비용이 꽤 늘어날 수 있습니다.

네 번째는 ExternalName입니다. 이건 좀 특별한 타입입니다.

외부 DNS 이름을 클러스터 내부 Service 이름에 매핑합니다. 예를 들어 외부의 my-database.example.comdb-service라는 이름으로 접근할 수 있게 해줍니다.

외부 서비스를 내부 서비스처럼 다루고 싶을 때 유용합니다. 그렇다면 실무에서는 어떤 타입을 쓸까요?

박시니어 씨가 정리했습니다. "내부 서비스 간 통신은 ClusterIP, 개발 환경이나 간단한 노출은 NodePort, 프로덕션 환경은 LoadBalancerIngress를 씁니다." 잠깐, Ingress라는 새로운 개념이 나왔네요.

Ingress는 Service와는 다른 리소스입니다. HTTP/HTTPS 트래픽을 라우팅하는 규칙을 정의합니다.

도메인 기반 라우팅, 경로 기반 라우팅, TLS 종료 등 L7 레벨의 기능을 제공합니다. 여러 Service를 하나의 진입점으로 묶을 수 있어서, LoadBalancer 비용을 절약할 수 있습니다.

김개발 씨는 개발 환경에서는 NodePort를 사용하고, 스테이징과 프로덕션 환경에서는 Ingress를 사용하기로 했습니다. "환경에 맞는 타입을 선택하는 게 중요하군요." 김개발 씨가 고개를 끄덕였습니다.

실전 팁

💡 - 개발 환경에서는 NodePort로 빠르게 테스트하고, 프로덕션에서는 Ingress를 사용하세요

  • LoadBalancer는 비용이 발생하므로 서비스마다 만들기보다 Ingress로 통합하는 것이 좋습니다

6. 레이블과 셀렉터

김개발 씨가 Service를 만들었는데, Pod에 연결이 안 됩니다. "분명히 설정을 잘 했는데 왜 안 되죠?" 박시니어 씨가 설정을 확인해보니, Service의 selector와 Pod의 label이 일치하지 않았습니다.

"레이블과 셀렉터가 정확히 매칭되어야 해요. 쿠버네티스의 모든 연결은 이 방식으로 동작합니다."

**레이블(Label)**은 쿠버네티스 리소스에 붙이는 키-값 쌍의 태그입니다. **셀렉터(Selector)**는 특정 레이블을 가진 리소스를 찾는 조건입니다.

Service가 Pod를 찾고, Deployment가 Pod를 관리하고, 이 모든 연결이 레이블과 셀렉터로 이루어집니다. 마치 도서관의 분류 기호처럼, 레이블은 리소스를 체계적으로 분류하고 찾을 수 있게 해줍니다.

다음 코드를 살펴봅시다.

# 레이블이 있는 Pod
apiVersion: v1
kind: Pod
metadata:
  name: my-app-pod
  labels:
    app: myapp
    environment: production
    tier: frontend
spec:
  containers:
  - name: nginx
    image: nginx:1.21

# 셀렉터로 Pod 조회
# kubectl get pods -l app=myapp
# kubectl get pods -l environment=production
# kubectl get pods -l 'app=myapp,tier=frontend'

박시니어 씨가 말했습니다. "쿠버네티스를 제대로 이해하려면 레이블과 셀렉터를 반드시 알아야 해요.

이게 쿠버네티스의 핵심 연결 메커니즘이거든요." 레이블이 왜 필요할까요? 대형 도서관을 상상해보세요.

수십만 권의 책이 있는데, 분류 체계가 없다면 어떨까요? 원하는 책을 찾는 것이 불가능에 가까울 겁니다.

그래서 도서관에서는 책마다 분류 기호를 붙입니다. 문학은 800번대, 역사는 900번대 하는 식으로요.

레이블도 마찬가지입니다. 수백, 수천 개의 Pod가 실행되는 클러스터에서 원하는 Pod를 어떻게 찾을까요?

레이블로 분류하면 됩니다. 위의 예시에서는 세 개의 레이블을 붙였습니다.

app: myapp은 애플리케이션 이름, environment: production은 환경, tier: frontend는 아키텍처 계층을 나타냅니다. 셀렉터는 이런 레이블을 조건으로 리소스를 찾습니다.

kubectl get pods -l app=myapp 명령은 "app이 myapp인 Pod를 모두 보여줘"라는 의미입니다. 여러 조건을 조합할 수도 있습니다.

**-l 'app=myapp,tier=frontend'**는 두 조건을 모두 만족하는 Pod만 찾습니다. Service와 Pod의 연결도 이 방식입니다.

Service의 selectorapp: nginx를 지정하면, Service는 app: nginx 레이블을 가진 모든 Pod를 찾아서 트래픽을 전달합니다. Pod가 추가되거나 삭제되어도 자동으로 반영됩니다.

레이블만 일치하면 됩니다. Deployment와 Pod의 관계도 마찬가지입니다.

Deployment의 selector.matchLabels와 Pod 템플릿의 labels가 일치해야 합니다. 그래야 Deployment가 자신이 만든 Pod를 인식하고 관리할 수 있습니다.

레이블 네이밍에는 몇 가지 권장 사항이 있습니다. 쿠버네티스 공식 문서에서는 app.kubernetes.io/name, app.kubernetes.io/version 같은 표준 레이블을 권장합니다.

하지만 실무에서는 팀마다 자체 규칙을 정하기도 합니다. 중요한 것은 일관성입니다.

자주 사용되는 레이블 키를 알아봅시다. app 또는 app.kubernetes.io/name은 애플리케이션 이름입니다.

version 또는 app.kubernetes.io/version은 버전입니다. environment 또는 env는 개발/스테이징/프로덕션 환경을 구분합니다.

tier는 frontend, backend, database 같은 아키텍처 계층입니다. 주의할 점이 있습니다.

레이블 값을 잘못 지정하면 Service가 엉뚱한 Pod에 트래픽을 보낼 수 있습니다. 예를 들어 두 개의 다른 애플리케이션이 같은 app: nginx 레이블을 가지고 있다면?

Service가 두 애플리케이션의 Pod에 모두 트래픽을 보내게 됩니다. 레이블은 고유하게 식별할 수 있도록 신중하게 정해야 합니다.

김개발 씨가 문제를 해결했습니다. Service의 selector는 app: myapp이었는데, Pod의 label은 app: my-app이었습니다.

하이픈 하나 차이로 연결이 안 됐던 것입니다. "앞으로는 레이블을 꼼꼼히 확인해야겠어요." 김개발 씨가 다짐했습니다.

실전 팁

💡 - kubectl get pods --show-labels 명령으로 Pod의 레이블을 확인하세요

  • 레이블 네이밍 규칙을 팀 내에서 미리 정해두면 실수를 줄일 수 있습니다

7. 네임스페이스로 리소스 격리

김개발 씨가 팀 동료 이개발 씨와 같은 클러스터를 사용하고 있었습니다. 그런데 어느 날, 김개발 씨가 만든 Service와 이개발 씨가 만든 Service의 이름이 같아서 충돌이 발생했습니다.

"같은 이름을 쓰면 안 되는 건가요?" 박시니어 씨가 말했습니다. "네임스페이스를 분리하면 같은 이름도 쓸 수 있어요."

**네임스페이스(Namespace)**는 쿠버네티스 클러스터 내의 가상 분리 공간입니다. 같은 네임스페이스 안에서는 리소스 이름이 유일해야 하지만, 다른 네임스페이스에서는 같은 이름을 사용할 수 있습니다.

마치 아파트 단지에서 동 번호가 다르면 같은 호수가 있어도 주소가 구분되는 것처럼, 네임스페이스로 리소스를 논리적으로 분리할 수 있습니다.

다음 코드를 살펴봅시다.

# 네임스페이스 생성
apiVersion: v1
kind: Namespace
metadata:
  name: team-a

# 특정 네임스페이스에 Pod 생성
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: team-a
spec:
  containers:
  - name: nginx
    image: nginx:1.21

# 네임스페이스 조회 명령어
# kubectl get namespaces
# kubectl get pods -n team-a
# kubectl get all -n team-a

박시니어 씨가 비유를 들어 설명했습니다. "아파트 단지를 생각해보세요.

101동 501호와 102동 501호는 다른 집이잖아요. 호수는 같지만 동이 다르니까요.

네임스페이스도 마찬가지예요. 네임스페이스가 다르면 같은 이름의 리소스도 공존할 수 있어요." 쿠버네티스를 처음 설치하면 기본적으로 몇 개의 네임스페이스가 있습니다.

default는 이름 그대로 기본 네임스페이스입니다. 네임스페이스를 지정하지 않으면 여기에 생성됩니다.

kube-system은 쿠버네티스 시스템 컴포넌트가 실행되는 공간입니다. kube-public은 모든 사용자가 읽을 수 있는 공개 리소스용입니다.

kube-node-lease는 노드 하트비트 정보를 저장합니다. 네임스페이스를 어떻게 활용할까요?

가장 흔한 패턴은 환경별 분리입니다. dev, staging, production 네임스페이스를 만들어서 각 환경의 리소스를 분리합니다.

개발자가 실수로 프로덕션 리소스를 건드리는 것을 방지할 수 있습니다. 팀별 분리도 많이 사용합니다.

team-a, team-b 네임스페이스를 만들어서 각 팀의 리소스를 분리합니다. 팀마다 독립적으로 작업할 수 있고, 리소스 이름 충돌도 방지됩니다.

프로젝트별 분리도 가능합니다. shop, blog, api 네임스페이스를 만들어서 각 프로젝트의 리소스를 분리합니다.

프로젝트마다 필요한 리소스를 명확히 파악할 수 있습니다. 네임스페이스 간 통신은 어떻게 할까요?

같은 네임스페이스 안에서는 Service 이름만으로 접근할 수 있습니다. 다른 네임스페이스의 Service에 접근하려면 전체 DNS 이름을 사용해야 합니다.

예를 들어 team-a 네임스페이스의 nginx-service에 접근하려면 nginx-service.team-a.svc.cluster.local을 사용합니다. 네임스페이스와 함께 ResourceQuota를 사용하면 리소스 사용량을 제한할 수 있습니다.

"team-a 네임스페이스는 최대 10개의 Pod만 생성할 수 있다"거나 "CPU는 4코어, 메모리는 8GB까지만 사용할 수 있다"같은 제한을 걸 수 있습니다. 공유 클러스터에서 한 팀이 자원을 독점하는 것을 방지할 수 있습니다.

RBAC(Role-Based Access Control)과 결합하면 더 강력해집니다. "team-a 사용자는 team-a 네임스페이스의 리소스만 접근할 수 있다"같은 권한 제어가 가능합니다.

보안과 격리가 중요한 환경에서 필수적인 기능입니다. 주의할 점도 있습니다.

네임스페이스는 논리적 분리일 뿐, 물리적 분리가 아닙니다. 네트워크 정책을 설정하지 않으면 다른 네임스페이스의 Pod도 접근할 수 있습니다.

보안이 중요하다면 NetworkPolicy를 함께 설정해야 합니다. 김개발 씨와 이개발 씨는 각자의 네임스페이스를 갖게 되었습니다.

이제 같은 이름의 리소스를 만들어도 충돌이 발생하지 않습니다. "네임스페이스로 나누니까 훨씬 깔끔하네요." 김개발 씨가 만족스럽게 말했습니다.

실전 팁

💡 - kubectl config set-context --current --namespace=team-a로 기본 네임스페이스를 변경할 수 있습니다

  • 네임스페이스 이름에는 소문자, 숫자, 하이픈만 사용할 수 있습니다

8. Pod 상태 확인과 디버깅

김개발 씨가 Deployment를 생성했는데, Pod가 Running 상태가 되지 않습니다. STATUS가 CrashLoopBackOff라고 표시됩니다.

"이게 무슨 뜻이죠? 어떻게 해결해야 하나요?" 박시니어 씨가 다가와서 말했습니다.

"Pod 디버깅 방법을 알려줄게요. kubectl 명령어 몇 개만 알면 대부분의 문제를 파악할 수 있어요."

Pod가 정상적으로 실행되지 않을 때는 상태를 확인하고 원인을 파악해야 합니다. kubectl describe로 상세 정보를 확인하고, kubectl logs로 컨테이너 로그를 볼 수 있습니다.

Pod 상태에는 Pending, Running, Succeeded, Failed, CrashLoopBackOff 등이 있으며, 각 상태는 다른 원인과 해결책을 가지고 있습니다.

다음 코드를 살펴봅시다.

# Pod 상태 확인
kubectl get pods

# Pod 상세 정보 확인
kubectl describe pod my-pod

# 컨테이너 로그 확인
kubectl logs my-pod

# 이전 컨테이너(크래시된) 로그 확인
kubectl logs my-pod --previous

# 실시간 로그 스트리밍
kubectl logs -f my-pod

# Pod 내부에 접속하여 디버깅
kubectl exec -it my-pod -- /bin/sh

Pod가 제대로 실행되지 않는 것은 쿠버네티스를 사용하다 보면 자주 겪는 상황입니다. 박시니어 씨가 말했습니다.

"당황하지 마세요. 차근차근 확인하면 대부분 해결할 수 있어요." 먼저 Pod의 상태부터 알아봅시다.

Pending은 Pod가 스케줄링을 기다리고 있는 상태입니다. 노드에 자원이 부족하거나, 스케줄링 조건을 만족하는 노드가 없을 때 이 상태가 됩니다.

kubectl describe pod를 실행하면 Events 섹션에서 원인을 파악할 수 있습니다. Running은 정상 실행 상태입니다.

모든 컨테이너가 실행 중이고, 최소 하나의 컨테이너가 아직 시작 중이거나 재시작 중일 수 있습니다. Succeeded는 모든 컨테이너가 성공적으로 종료된 상태로, 주로 Job에서 볼 수 있습니다.

Failed는 모든 컨테이너가 종료되었고, 최소 하나가 실패로 종료된 상태입니다. 가장 당황스러운 것이 CrashLoopBackOff입니다.

컨테이너가 시작했다가 바로 죽고, 다시 시작했다가 또 죽는 것을 반복하는 상태입니다. 쿠버네티스는 재시작 간격을 점점 늘려가며(10초, 20초, 40초...) 재시도합니다.

이 상태라면 컨테이너 로그를 확인해야 합니다. kubectl logs my-pod 명령으로 로그를 확인합니다.

컨테이너가 죽어서 로그가 없다면 --previous 옵션으로 이전(죽기 전) 컨테이너의 로그를 볼 수 있습니다. 대부분의 경우 로그에 에러 메시지가 있습니다.

설정 파일 오류, 데이터베이스 연결 실패, 환경 변수 누락 등이 흔한 원인입니다. kubectl describe pod my-pod 명령은 더 자세한 정보를 보여줍니다.

특히 Events 섹션을 주목하세요. 이미지를 찾을 수 없다(ImagePullBackOff), 메모리가 부족하다(OOMKilled), 볼륨을 마운트할 수 없다 같은 정보가 여기에 나옵니다.

더 깊이 디버깅해야 할 때는 kubectl exec를 사용합니다. kubectl exec -it my-pod -- /bin/sh 명령으로 Pod 내부에 쉘로 접속할 수 있습니다.

여기서 파일을 확인하거나, 네트워크 연결을 테스트하거나, 환경 변수를 확인할 수 있습니다. 마치 SSH로 서버에 접속하는 것과 비슷합니다.

실무에서 자주 발생하는 문제와 해결책을 정리해봅시다. ImagePullBackOff는 이미지를 가져올 수 없다는 뜻입니다.

이미지 이름이 맞는지, 프라이빗 레지스트리라면 인증 정보가 설정되어 있는지 확인하세요. OOMKilled는 메모리 부족으로 컨테이너가 강제 종료된 것입니다.

리소스 limit을 늘리거나, 애플리케이션의 메모리 사용을 최적화해야 합니다. 김개발 씨의 문제도 로그를 확인해서 해결했습니다.

환경 변수로 설정해야 할 데이터베이스 주소가 빠져있었습니다. 환경 변수를 추가하니 Pod가 정상적으로 Running 상태가 되었습니다.

"앞으로는 describe와 logs를 먼저 확인해야겠어요." 김개발 씨가 말했습니다.

실전 팁

💡 - 문제 발생 시 kubectl describe pod의 Events 섹션을 가장 먼저 확인하세요

  • kubectl logs --previous로 크래시된 컨테이너의 마지막 로그를 볼 수 있습니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Kubernetes#Pod#Service#Container#Orchestration#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.