
Intro
안녕하세요. 환이s입니다 👋
지난 글에서는 Kubernetes의 전체적인 구조와 Pod라는 개념이 왜 필요한지 이론 위주로 살펴봤습니다.
이번 글부터는 본격적으로 손을 움직여 볼 차례입니다!
직접 YAML을 작성하고 리소스를 생성하면서,
Pod가 실제로 Cluster 안에서 어떻게 숨 쉬고 동작하는지 확인해 보겠습니다.
단순히 "생성되었다"는 결과만 보는 것이 아니라,
이 YAML 설정이 어떤 변화를 만드는지 직접 체감하는 것이 이번 학습의 핵심 목표입니다.
1. Container: Pod 안의 작은 사회
Pod는 Kubernetes에서 생성할 수 있는 가장 작은 배포 단위입니다.
하지만 그 안에서 하나 이상의 컨테이너가 함께 살 수 있죠.

꼭 알아두어야 할 포인트
- 유동적인 IP
- Pod IP는 생성될 때마다 새로 부여됩니다.
- 즉, 삭제 후 재생성하면 IP가 바뀌니 고정값으로 믿으면 안 돼요! (이래서 나중에 'Service'가 필요합니다.)
- 한 지붕 다가족
- Pod 안의 컨테이너끼리는 localhost로 서로 통신할 수 있습니다.
- 마치 같은 컴퓨터 안에 있는 것처럼요.
- 그 이유는 쿠버네티스에서 Pod는 컨테이너들에게 네트워크 네임스페이스를 공유하게 해줍니다.
- 즉, Pod 하나가 하나의 가상 IP를 가지고, 그 안의 컨테이너들은 포트로만 구분되는 형태죠.
- 그래서 컨테이너 1이 8000번, 컨테이너 2가 8080번을 쓸 때 서로 localhost:8000으로 부를 수 있는 것입니다.
- 포트 충돌 조심
- 한 Pod 안에서 컨테이너들이 같은 포트를 쓰려고 하면 충돌이 발생해 생성에 실패합니다.
1-1) 다중 컨테이너 Pod 생성 테스트
먼저 두 개의 컨테이너(8000번, 8080번 포트)를 가진 Pod를 만들어 봅시다.
⚠️ 주의사항:
대시보드 사용 시 Namespace를 꼭 **[default]**로 설정해 주세요.
모든 네임스페이스' 상태에서는 생성 에러(Deploying file has failed)가 발생할 수 있습니다.
apiVersion: v1
kind: Pod
metadata:
name: pod-1
spec:
containers:
- name: container1
image: kubetm/p8000
ports:
- containerPort: 8000
- name: container2
image: kubetm/p8080
ports:
- containerPort: 8080

[생성 및 상태 확인]
# yaml 생성 및 업데이트
kubectl apply -f pod-1.yaml
# pod 상태 확인
kubectl get pods
# pod의 IP와 배치된 노드까지 확인
kubectl get pods -o wide
# 외부(Master Node)에서 호출
curl <pod-ip>:8000, curl <pod-ip>:8080
# 컨테이너 내부에서 호출
kubectl exec pod-1 -it /bin/bash


📍 kubectl get pods -o wide 결과 해석
- STATUS & READY: READY가 2/2라면 Pod 안의 컨테이너 2개가 모두 정상 가동 중이라는 뜻입니다.
- IP: 여기서 확인한 IP가 바로 클러스터 내부에서 통신 가능한 Pod의 고유 주소입니다.
📍 exec 명령어 사용 시 주의사항
- 팁: Pod 안에 컨테이너가 여러 개일 때는 어떤 컨테이너로 들어갈지 지정해야 합니다. 지정하지 않으면 기본적으로 첫 번째 컨테이너로 접속됩니다.
# 특정 컨테이너를 지정해서 접속할 때 (-c 옵션)
kubectl exec pod-1 -c container2 -it /bin/bash
해당 명령어를 통해서 pod가 정상적으로 생성해서 가동 중인 상태까지 확인했습니다.
그럼 여기서 아래처럼 pod를 하나 더 생성할 때 두 컨테이너가 모두 동일하게 8000번 포트를 쓰려고 하면 어떻게 될까요?
[충돌테스트]
# pod-2: 포트 충돌 케이스
spec:
containers:
- name: container1
image: tmkube/p8000
ports: [- containerPort: 8000]
- name: container2
image: tmkube/p8000
ports: [- containerPort: 8000]

[생성 및 상태 확인]
# yaml 생성 및 업데이트
kubectl apply -f pod-2.yaml
# pod 상태 확인
kubectl get pods

위 이미지에 표출되는 것처럼 pod-2가 Error가 발생했다는 Status를 확인할 수 있습니다.
해당 로그를 확인해보면 다음과 같습니다.
[상세 에러 메시지 확인하기(Describe)]
kubectl describe pod pod-2

메시지 중 가장 아래에 있는 Warning BackOf와 Back-off restarting failed container container2가 핵심입니다.
📍 분석 결과: 왜 이런 일이 벌어졌을까?
- container1은 성공
- 먼저 실행된 container1은 8000번 포트를 선점하고 기분 좋게 Started 되었습니다.
- container2의 수난
- 그다음 container2가 실행되면서 똑같이 8000번 포트를 열려고 시도합니다.
- 포트 충돌
- 하지만 이미 container1이 8000번을 쓰고 있죠? container2 내부의 프로세스는 에러를 내며 죽어버립니다.
- 무한 반복 (CrashLoopBackOff)
- Kubernetes는 "어? 컨테이너가 죽었네? 다시 살려야지!" 하고 계속 재시작을 시도하지만, 포트가 계속 겹치니 계속 죽게 됩니다. 이게 바로 Back-off restarting 상태입니다.
CrashLoopBackOff란?
컨테이너가 비정상 종료(Crash)되어 쿠버네티스가 재시작을 시도했는데,
또 종료되는 상황이 반복(Loop)되는 것을 말합니다.
쿠버네티스는 무의미한 재시작을 방지하기 위해 재시작 간격을 점점 늘리는데,
이를 Back-off라고 부릅니다.
현재 이슈에서는 8000번 포트 점유 실패가 원인이 되어 이 루프에 빠진 것이죠.
1-2) Deployment로 Pod 관리하기
Deployment는 Pod의 상위 개념입니다.
직접 Pod를 만드는 것이 '수동'이라면, Deployment는 '시스템에 의한 자동 관리'라고 볼 수 있습니다.
즉, Pod의 개수를 유지해 주고 관리를 편하게 해 줍니다.
Deployment를 처음 접하면 YAML이 조금 복잡해 보일 수 있는데, 핵심 구조는 '3단 구성'입니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-1
spec:
replicas: 1 # [1] 유지할 Pod의 개수
selector: # [2] 관리 대상이 될 Pod를 찾는 기준 (Label 확인)
matchLabels:
app: deploy
template: # [3] 생성할 Pod의 설계도 (여기 아래는 Pod 설정과 동일)
metadata:
labels:
app: deploy # 중요: selector의 label과 일치해야 함!
spec:
containers:
- name: container
image: kubetm/init

📍 selector와 template.labels는 왜 같은 app name을 매칭할까?
저도 처음에 이 부분이 헷갈렸는데요, 원리를 알면 간단합니다.
Deployment는 Pod를 직접 낳아서(?) 관리한다기보다, "조건에 맞는 Pod들을 클러스터에서 찾아내서 관리"하는 방식이기 때문입니다.
- selector: Deployment가 "내가 관리할 애들은 이 이름표(app: deploy)를 달고 있어야 해!"라고 관리 대상을 정의하는 필드입니다.
- template.labels: Deployment가 새로 Pod를 만들 때 "자, 너는 app: deploy라는 이름표를 달고 태어나렴" 하고 이름표를 붙여주는 설정입니다.
- 만약 두 개가 다르다면?
- Deployment가 Pod를 만들긴 하지만, 만들자마자 "어? 내가 관리할 이름표(selector)가 아니네?" 하고는 그 Pod를 방치해 버립니다.
- 그리고 다시 "어라, 내가 관리할 Pod가 없네?" 하고 또 새로운 Pod를 만드는 무한 루프에 빠질 수 있어요.
- 그래서 이 둘은 반드시 짝을 맞춰야 합니다.
2. Label: 쿠버네티스 리소스의 이름표
쿠버네티스 클러스터 안에는 수많은 Pod와 서비스들이 존재합니다.
이 수많은 리소스 중에서 내가 원하는 것들만 쏙쏙 골라내거나 서로 연결하고 싶을 때 사용하는 것이 바로 Label(라벨)입니다.

💡 Label의 핵심 특징
- Key-Value 형태: type: web, lo: dev처럼 자유롭게 이름을 붙일 수 있습니다.
- 다중 부여 가능: 한 Pod에 여러 개의 라벨을 붙여 다양한 카테고리로 분류할 수 있습니다.
- 리소스 연결의 핵심: 단순히 정보를 주는 용도를 넘어, Service가 어떤 Pod에 트래픽을 전달할지 결정하는 기준이 됩니다.
2-1) 다양한 라벨을 가진 Pod 생성
먼저 용도(type)와 환경(lo)에 따라 서로 다른 라벨을 가진 6개의 Pod를 생성해 보겠습니다.
apiVersion: v1
kind: Pod
metadata:
name: pod-1
labels:
type: web
lo: dev
spec:
containers:
- name: container
image: kubetm/init
---
apiVersion: v1
kind: Pod
metadata:
name: pod-2
labels:
type: db
lo: dev
spec:
containers:
- name: container
image: kubetm/init
---
apiVersion: v1
kind: Pod
metadata:
name: pod-3
labels:
type: server
lo: dev
spec:
containers:
- name: container
image: kubetm/init
---
apiVersion: v1
kind: Pod
metadata:
name: pod-4
labels:
type: web
lo: production
spec:
containers:
- name: container
image: kubetm/init
---
apiVersion: v1
kind: Pod
metadata:
name: pod-5
labels:
type: db
lo: production
spec:
containers:
- name: container
image: kubetm/init
---
apiVersion: v1
kind: Pod
metadata:
name: pod-6
labels:
type: server
lo: production
spec:
containers:
- name: container
image: kubetm/init

[생성 및 상태 확인]
# yaml 생성 및 업데이트
kubectl apply -f label-pod.yaml
# pod 상태 확인
kubectl get pods

2-1) Service: 라벨을 보고 대상을 찾아가는 서비스
이제 생성된 Pod들을 서비스(Service)에 연결해 보겠습니다.
서비스는 Selector를 통해 특정 라벨을 가진 Pod들만 그룹화하여 관리합니다.
[Case 1: '웹 서버'들만 연결]
이 서비스는 개발(dev)이든 운영(production)이든 상관없이 type: web 라벨만 붙어 있다면 모두 연결합니다.
apiVersion: v1
kind: Service
metadata:
name: svc-for-web
spec:
selector:
type: web
ports:
- port: 8080

[Case 2: '운영 환경' Pod들만 연결]
이 서비스는 역할(web, db 등)에 상관없이 lo: production 라벨이 붙은 Pod들만 골라냅니다.
apiVersion: v1
kind: Service
metadata:
name: svc-for-production
spec:
selector:
lo: production
ports:
- port: 8080

[생성 및 상태 확인]
# yaml 생성 및 업데이트
kubectl apply -f svc-for-web.yaml
kubectl apply -f svc-for-production.yaml
# service 상태 확인
kubectl get service

이제 라벨이 정말 잘 동작해서 서비스와 Pod가 연결되었는지 확인해 볼 차례입니다.
[라벨 연결 상태 확인]
# 서비스 상태 확인
kubectl get service
# 현재 생성된 Pod들의 라벨 확인
kubectl get pods --show-labels
# 전체 엔드포인트 리스트 확인
kubectl get endpoints
# pod의 IP와 배치된 노드까지 확인
kubectl get pods -o wide

위 이미지처럼 Pod의 IP가 라벨만 보고 대상을 찾아가면 정상적으로 연결된 거라고 보시면 됩니다.
여기서 Pod의 IP가 아무리 바뀌어도 상관없습니다.
서비스는 오직 라벨만 보고 대상을 찾아가고, 하나의 Pod가 여러 서비스에 속할 수도 있습니다. (예: pod-4는 web 서비스에도 속하고, production 서비스에도 속하게 됩니다.)
3. Node Schedule: 내 Pod의 보금자리 정하기
모든 Pod는 클러스터 내의 특정 Node 위에 할당되어 실행되어야 합니다.
Kubernetes에는 스케줄러(Scheduler)라는 똑똑한 비서가 있어서, 기본적으로는 자원 여유가 있는 노드에 Pod를 자동으로 배치해 줍니다.
하지만 때로는 우리가 직접 배치 전략을 세워야 할 때가 있는데, 그 방법을 알아보겠습니다.
3-1) nodeSelector("난 꼭 이 노드에 살아야 해")
nodeSelector는 특정 사양의 서버나, 지정된 노드에 Pod를 배치하고 싶을 때 사용합니다.
예를 들어 k8s-worker1이라는 이름을 가진 노드에만 Pod를 띄우고 싶다면 아래와 같이 설정합니다.
apiVersion: v1
kind: Pod
metadata:
name: pod-3
spec:
nodeSelector:
kubernetes.io/hostname: k8s-worker1 # 노드의 호스트네임을 지정
containers:
- name: container
image: kubetm/init

생성하고 확인해 보면 NODE 컬럼에 k8s-worker1이 찍혀 있는지 확인할 수 있습니다.

사실 실무에서는 nodeSelector보다 더 유연한 Node Affinity를 많이 사용합니다.
nodeSelector는 '아니면 말고'가 안 되는 고집불통 설정이라고 불리는데,
입문 단계에서는 '내가 원하는 노드에 Pod를 직접 보내본다'는 개념을 익히기에 가장 좋은 도구입니다.
이 기초를 알아야 나중에 Affinity나 Taint 같은 고급 스케줄링 기술도 쉽게 이해할 수 있습니다.
3-2) Requests & Limits( 자원 할당과 제한 )
Pod가 사용할 수 있는 CPU와 메모리 양을 설정하는 것은 안정적인 운영을 위해 필수입니다.
- Requests (요청량): "최소한 이만큼의 자원은 보장해 줘!" (스케줄러가 노드를 선택하는 기준)
- Limits (제한량): "아무리 많이 써도 이 선은 넘지 마!"
[Case 1: 메모리 제한 테스트]
메모리를 최소 5Gi, 최대 6Gi로 설정한 예시입니다.
apiVersion: v1
kind: Pod
metadata:
name: pod-4
spec:
containers:
- name: container
image: kubetm/init
resources:
requests:
memory: 5Gi
limits:
memory: 6Gi

pod-4는 메모리 5Gi를 요청합니다.
만약 워커 노드 가용 메모리가 5Gi 미만이라면 어떻게 될까요?
아래와 같은 명령어 순서대로 작성해서 확인해 보겠습니다.
# 파드 생성
kubectl apply -f pod-4.yaml
# 파드 생성 확인
kubectl get pods
# 로그 확인
kubectl describe pod pod-4

생성하고 확인해 보니 상태가 Pending에서 멈춰 있는 걸 확인했습니다.
이유를 확인하기 위해 Events를 확인해 보겠습니다.
kubectl describe pod pod-4

Event에 찍혀 있는 내용을 확인해 보면 Insufficient memory라는 메시지가 떠 있습니다.
해당 내용은 "현재 5Gi를 줄 수 있는 노드가 없다."라고 보고하는 상황입니다.
Requests & Limits를 설정할 때는 반드시 노드별로 '할당 가능한 자원(Allocatable)'이 얼마나 남았는지 먼저 확인해 주세요.
노드의 실제 여유 공간보다 더 큰 자원을 Requests로 설정하면,
위 이미지처럼 Pod가 어느 노드에도 가지 못하고 Pending 상태에 계속 머물게 됩니다.
[Case 2: 더 엄격한 제한 설정]
사용량을 일정하게 유지하고 싶다면 요청량과 제한량을 동일하게 맞출 수도 있습니다.
apiVersion: v1
kind: Pod
metadata:
name: pod-5
spec:
containers:
- name: container
image: kubetm/init
resources:
requests:
memory: 0.5Gi
limits:
memory: 0.5Gi

[생성 및 상태 확인]
# 생성
kubectl apply -f pod-5.yaml
# 상태 확인
kubectl get pod pod-5
# 상세 확인
kubectl describe pod pod-5

위 이미지를 확인해 보면
정상적으로 Running 상태인 걸 확인할 수 있습니다.
📝 마무리
이번 글에서는 Kubernetes 환경에서
Pod를 직접 YAML로 정의하고 생성하면서,
컨테이너 구성부터 라벨을 통한 리소스 연결,
그리고 노드 스케줄링과 리소스 제한까지의 흐름을 처음부터 끝까지 살펴봤습니다.
단순히 “Pod를 띄운다”가 아니라,
Pod 안에서 여러 Container가 어떻게 동작하는지
Label이 왜 Service와 리소스를 연결하는 핵심 요소인지
그리고 requests / limits 설정이 실제로 어떤 결과를 만드는지
를 하나씩 확인해 보는 과정이었습니다.
아직 Service를 붙이지 않았기 때문에
Pod는 클러스터 내부에서만 접근 가능한 상태이지만,
이 구조를 정확히 이해하고 나면
왜 Kubernetes에서 Service가 필수인지도 자연스럽게 연결됩니다.
다음 글에서는
이번 글에서 생성한 Pod를 기준으로,
Service가 어떤 역할을 하는지
그리고 ClusterIP, NodePort, LoadBalancer가
각각 어떤 상황에서 사용되는지를
전체 흐름 관점에서 하나씩 정리해 보려고 합니다.
궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면
언제든지 편하게 말씀해 주세요! 🙌😊
위 포스팅 글은 일프로님의 대세는 쿠버네티스(초급~중급편) 강의를 참고했습니다.
'[ Infra ] > Kubernetes' 카테고리의 다른 글
| [Kubernetes] Volume - emptyDir, hostPath, PV/PVC (2) | 2026.03.19 |
|---|---|
| [Kubernetes] Service - ClusterIP, NodePort, LoadBalancer (3) | 2026.03.18 |
| [Kubernetes] Kubernetes Overview - 전체 구조부터 흐름 잡기 (0) | 2026.02.04 |
| [Kubernetes] Getting started (0) | 2026.02.04 |
| [Kubernetes] VM vs Container (1) | 2026.02.03 |