Services
Kubernetes Service란 무엇인가
Kubernetes Service는 애플리케이션 내부와 외부의 컴포넌트 간 통신을 가능하게 하는 객체임.
즉 Service의 역할은 다음과 같음.
- 애플리케이션과 사용자 간 연결
- 애플리케이션 내부 서비스 간 연결
- 외부 데이터 소스와의 연결
예를 들어 애플리케이션이 다음과 같이 구성되어 있다고 가정함.
- Frontend Pod 그룹 (사용자 요청 처리)
- Backend Pod 그룹 (비즈니스 로직 처리)
- 외부 데이터베이스 연결
이때 각 Pod 그룹 간 연결을 담당하는 것이 Service임.
Service는 다음과 같은 역할을 수행함.
- 사용자 → Frontend 연결
- Frontend → Backend 연결
- Backend → 외부 데이터 연결
이렇게 마이크로서비스 간 느슨한 결합(loose coupling)을 가능하게 함.
외부에서 Pod에 접근하는 문제 해결
지금까지는 Pod끼리 내부 네트워크로 통신하는 방법을 설명했음.
하지만 이제 외부에서 Pod에 접근하는 방법을 살펴봄.
예를 들어 웹 애플리케이션 Pod가 하나 있다고 가정함.
현재 네트워크 구조는 다음과 같음.
Laptop IP : 192.168.1.1
Kubernetes Node IP : 192.168.1.2
Pod Network Range : 10.244.0.0/16
Pod IP : 10.244.0.2
문제는 다음과 같음.
왜냐하면 Pod는 클러스터 내부 네트워크에 있기 때문임.
가능한 방법
노드에 SSH 접속 :ssh 192.168.1.2
그 후 Pod에 접근 : curl 10.244.0.2
하지만 이것은 노드 내부에서만 접근 가능함.
사용자는 노드에 SSH 없이 바로 웹 서비스에 접근해야 함.
Service는 Node의 포트를 Pod의 포트로 연결해주는 역할을 함.
즉 다음과 같은 구조가 됨.
Laptop
│
NodeIP:NodePort
│
Service
│
Pod:TargetPort
이렇게 외부 요청을 Pod로 전달함.
Kubernetes Service 종류
Kubernetes에는 3가지 주요 Service 타입이 있음.
1. NodePort
Pod를 Node의 포트를 통해 외부에 노출함.
External User
│
NodeIP:NodePort
│
Service
│
Pod
2. ClusterIP
클러스터 내부에서만 사용되는 서비스임.
Frontend Pods → Backend Pods
서비스 간 내부 통신에 사용됨.
3. LoadBalancer
클라우드 환경에서 사용됨.
외부 Load Balancer를 생성하여 트래픽을 분산함.
│
LoadBalancer
│
Multiple Pods
NodePort Service
NodePort 서비스에는 3개의 포트가 있음.

1. TargetPort
Pod 내부 포트
웹 서버가 실제 실행되는 포트임.
2. Port
Service 자체의 포트
Service 내부에서 사용하는 포트임.
3. NodePort
Node에서 외부 접근에 사용하는 포트
nodePort: 30008
외부 접근 방식
http://NodeIP:30008
NodePort는 다음 범위만 사용 가능함.
NodePort Service YAML 구조
Service도 Kubernetes 리소스이므로 기본 구조는 동일함.
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
ports:
- targetPort: 80
port: 80
nodePort: 30008
selector:
app: myapp
Service YAML 핵심 요소
type: 서비스 타입 지정
- NodePort
- ClusterIP
- LoadBalancer
ports : 포트 매핑 정보
- targetPort → Pod
- port → Service
- nodePort → Node
selector : Service가 연결할 Pod를 선택함.
Pod의 label과 연결됨.
예
Pod
labels:
app: myapp
Service
selector:
app: myapp
이렇게 하면 Service가 Pod를 찾을 수 있음.
외부 접근 방법
웹 서비스 접근
http://NodeIP:NodePort
http://192.168.1.2:30008
curl 192.168.1.2:30008
Service 내부 Load Balancing
Service 뒤에 여러 Pod가 있을 경우 Kubernetes가 자동으로 로드 밸런싱함.
Pod가 요청을 보내면
│
├ Pod1
├ Pod2
└ Pod3
중 하나로 전달됨.
이 작업은 kube-proxy가 수행함.
Pod가 여러 Node에 있을 경우

Node2 → Pod2
Node3 → Pod3
NodePort Service 생성 시
Node2:30008
Node3:30008
모든 노드에서 동일한 포트가 열림.
하지만 중요한 점은 Node1로 요청이 들어와도 Pod1으로만 가는 것이 아님.
실제 흐름
Client
│
Node1:30008
│
Service
│
├ Pod1
├ Pod2
└ Pod3
즉 Service가 Pod 레벨 Load Balancing 수행.
Service는 매우 유연함.
다음 상황 모두 동일하게 동작함.
- Pod 1개
- Pod 여러 개
- 여러 Node에 Pod 분산
그리고 Pod가 추가되거나 삭제되면 Service가 자동으로 업데이트됨.
추가 설정이 필요 없음.
EKS + Fargate
EKS Fargate에서는 NodePort를 사용하지 않음.
이유
컨테이너는 여전히 Pod 안에서 실행되고, Pod는 Kubernetes 관점에서는 Node에 스케줄링됨.
다만 EKS Fargate에서는 그 Node를 네가 관리하는 EC2 worker node로 보지 않는 것임. AWS가 Fargate용 스케줄러/컨트롤러로 Pod를 Fargate에 올리고, 각 Fargate Pod는 자기만의 compute boundary를 가지며 다른 Pod와 커널/CPU/메모리/ENI를 공유하지 않는다고 AWS가 설명함.
NodePort는 다음이 필요함.
- Node network interface
- Node IP
- Node port open
하지만 Fargate는
- Node IP 없음
- Node network 없음
- Node OS 접근 불가
즉 Node 자체가 없어서 NodePort 개념을 만들 수 없음.
그래서 AWS Load Balancer Controller도 Fargate에서는 target-type: ip 만 허용함.
그래서 구조는 다음과 같음.
Client
↓
ALB
↓
TargetGroup
↓
Pod IP
↓
Pod
ALB → Pod 직접
ClusterIP Service
일반적인 풀스택 웹 애플리케이션은 여러 종류의 Pod로 구성됨.
예를 들면 다음과 같음.
- Frontend 웹 서버를 실행하는 Pod들
- Backend 서버를 실행하는 Pod들
- Redis 같은 Key-Value Store를 실행하는 Pod들
- MySQL 같은 영속성 데이터베이스를 실행하는 Pod들
이처럼 애플리케이션은 여러 계층으로 나뉘어 동작함.
애플리케이션 내부 통신에서 생기는 문제
Frontend는 Backend와 통신해야 함.
Backend는 Database 및 Redis와 통신해야 함.
그렇다면 이런 계층 간 연결은 어떤 방식으로 해야 하는가가 문제임.
Pod는 각각 IP 주소를 할당받음.
하지만 이 IP들은 고정값이 아님.
즉,
- Pod는 언제든지 내려갈 수 있음
- 새로운 Pod가 계속 생성될 수 있음
- 생성될 때마다 IP가 바뀔 수 있음
따라서 Pod IP를 기준으로 내부 통신을 구성하면 안정적이지 않음.
예를 들어 Frontend Pod가 Backend에 요청을 보내야 할 때, Backend Pod가 3개 있다면 다음과 같은 문제가 생김.
- 어떤 Backend Pod로 연결해야 하는가
- 그 선택은 누가 하는가
이 문제를 해결하는 것이 Kubernetes Service임.
Kubernetes Service는 여러 Pod를 하나의 그룹으로 묶고,
그 그룹에 접근할 수 있는 단일 인터페이스 제공함.
예를 들어 Backend용 Service를 만들면 다음과 같은 효과가 있음.
- 여러 Backend Pod를 하나로 묶음
- 다른 Pod가 Backend에 접근할 때 하나의 주소만 사용하면 됨
- 요청은 Service가 연결된 Pod들 중 하나로 전달됨
- 어떤 Pod로 보낼지는 Service가 알아서 처리함
즉, Frontend는 더 이상 특정 Backend Pod의 IP를 알 필요가 없음.
그냥 Backend Service만 바라보면 됨.
마찬가지로 Redis용 Service를 만들면 Backend Pod는 Redis Pod의 개별 IP가 아니라
Redis Service를 통해 접근하면 됨.
ClusterIP를 사용하는 이유
이 방식의 장점은 다음과 같음.
- 마이크로서비스 구조를 쉽게 구성 가능함
- 각 계층이 독립적으로 확장 가능함
- Pod가 교체되거나 이동되어도 통신 방식은 유지됨
- 서비스 간 결합도를 낮출 수 있음
즉, 각 계층은 필요에 따라 확장(scale)되거나 재배치(move)될 수 있고,
그 과정에서 다른 서비스와의 통신에는 영향이 거의 없음.
ClusterIP의 핵심 개념
각 Service는 클러스터 내부에서 다음 두 가지를 가짐.
- IP 주소
- 이름
그리고 다른 Pod들은 이 Service 이름 또는 ClusterIP를 사용해 접근함.
이런 유형의 Service를 ClusterIP라고 부름.
즉, ClusterIP는 클러스터 내부 통신용 Service 타입임.
ClusterIP Service Yaml구조
apiVersion: v1
kind: Service
metadata:
name: back-end
spec:
type: ClusterIP
ports:
- targetPort: 80
port: 80
selector:
app: myapp
type: back-end
ClusterIP Service도 다른 Kubernetes 리소스와 마찬가지로
정의 파일로 생성함.
기본 구조는 다음과 같음.
- apiVersion
- kind
- metadata
- spec
주요 설정
- apiVersion: v1
- kind: Service
- metadata.name: backend
여기서는 Service 이름을 backend로 지정함.
spec 항목 설명
spec 아래에는 주로 다음 항목이 들어감.
1) type
Service 타입 지정함.
- type: ClusterIP
사실 ClusterIP는 기본 타입임.
따라서 명시하지 않아도 자동으로 ClusterIP로 간주함.
2) ports
Service가 어떤 포트를 통해 연결되는지 정의함.
- targetPort: 실제 Pod가 노출하는 포트
- port: Service가 노출하는 포트
예시에서는 둘 다 80 사용함.
- Backend 애플리케이션이 80 포트에서 동작함
- Service도 80 포트로 요청 받음
3) selector
어떤 Pod들을 이 Service에 연결할지 정하는 항목임.
Pod 정의 파일에 있는 label 값을 복사해서 selector에 넣으면 됨.
즉, selector와 label이 일치하는 Pod들이 이 Service의 대상이 됨.
LoadBalancer
이번에는 LoadBalancer 타입의 Service를 살펴봄.
앞에서 NodePort Service를 통해 외부에서 애플리케이션에 접근할 수 있도록 만드는 방법을 봤음.
NodePort는 워커 노드의 특정 포트를 열어서 외부 요청을 Pod로 전달하는 방식임.
이제 여기서는 Frontend 애플리케이션, 즉 예시로 나온 Voting App과 Result App에 초점을 맞춤.
이 Pod들은 Kubernetes 클러스터의 워커 노드들 위에서 실행됨.
예를 들어 4개의 노드로 구성된 클러스터가 있다고 가정함.
그리고 외부 사용자가 애플리케이션에 접근할 수 있도록 하기 위해
각 애플리케이션에 대해 NodePort 타입 Service를 생성했다고 가정함.
이 NodePort Service는 다음 역할을 함.
- 각 노드의 특정 포트에서 외부 요청 받음
- 받은 요청을 해당 Pod로 전달함
즉, 외부 사용자는 노드의 IP와 NodePort를 이용해서 애플리케이션에 접속 가능함.
이 방식은 동작은 하지만, 사용자에게 제공할 URL이 불편함.
예를 들어 사용자가 Voting App과 Result App에 접속하려면
다음과 같은 형태의 주소를 사용해야 함.
- 노드 IP + 포트
- 다른 노드 IP + 포트
- 또 다른 노드 IP + 포트
즉, 애플리케이션 하나당 여러 개의 IP:Port 조합이 생길 수 있음.
예를 들어 Voting App의 Pod가 실제로는
IP가 70, 71인 두 노드에만 배포되어 있다고 하더라도,
Service 특성상 클러스터의 모든 노드 IP에서 접근 가능함.
즉, Pod가 특정 노드에만 떠 있어도
사용자는 모든 노드의 IP와 NodePort로 접근할 수 있음.
이론적으로는 문제없지만, 실제 사용자 입장에서는 매우 불편함.
사용자가 원하는 것은 다음과 같은 형태임.
- voting-app.example.com
- result-app.example.com
즉, 하나의 고정된 URL을 원함.
이 문제를 해결하는 한 가지 방법은
별도의 VM을 하나 만들어 외부 로드밸런서 용도로 사용하는 것임.
예를 들어 그 VM에 다음과 같은 소프트웨어를 설치할 수 있음.
- HAProxy
- NGINX
그리고 그 로드밸런서가 외부 요청을 받아서
클러스터 내부의 노드들로 트래픽을 분산하도록 설정할 수 있음.
하지만 이 방식은 다음과 같은 단점이 있음.
- 별도 VM 준비 필요함
- 로드밸런서 설치 필요함
- 라우팅 설정 필요함
- 지속적인 운영 및 관리 필요함
즉, 직접 구성하고 유지보수하기에는 번거롭고 부담이 큼.
- Google Cloud
- AWS
- Azure
이런 클라우드들은 자체적으로 네이티브 Load Balancer 기능 제공함.
Kubernetes는 일부 클라우드 제공자의 네이티브 로드밸런서와 연동하는 기능을 지원함.
따라서 사용자는 직접 HAProxy나 NGINX를 설치하지 않아도 됨.
그 대신 Frontend Service의 타입을 NodePort가 아니라 LoadBalancer로 지정하면 됨.
즉,
- type: NodePort 대신
- type: LoadBalancer 사용함
그러면 Kubernetes가 클라우드 제공자의 로드밸런서와 연동하여
외부에서 접근 가능한 단일 진입점을 구성해줌.
LoadBalancer 타입을 사용하면
클라우드 환경에서 외부 로드밸런서를 자동으로 구성할 수 있음.
그 결과 사용자는 여러 노드의 IP와 포트를 알 필요가 없음.
대신 하나의 외부 주소를 통해 애플리케이션에 접근 가능함.
즉, 사용자 입장에서는 더 자연스럽고 실용적인 접근 방식이 됨.
이 기능은 지원되는 클라우드 환경에서만 제대로 동작함.
대표적으로 다음은 지원됨.
- GCP
- AWS
- Azure
반대로 다음과 같은 환경에서는 기대한 방식으로 동작하지 않음.
- VirtualBox
- 일반 로컬 환경
- 클라우드 로드밸런서 연동이 없는 환경
이런 비지원 환경에서 Service 타입을 LoadBalancer로 설정하더라도
실제로는 외부 클라우드 로드밸런서가 생성되지 않음.
결과적으로는 NodePort와 거의 같은 효과만 냄.
- 노드의 높은 포트로 서비스가 노출됨
- 하지만 별도의 외부 로드밸런서는 자동 생성되지 않음
따라서 LoadBalancer 타입이라고 해서
어떤 환경에서나 자동으로 단일 외부 주소가 생기는 것은 아님.
환경 지원 여부가 중요함.
Load Balancer Yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: LoadBalancer
ports:
- targetPort: 80
port: 80
nodePort: 30008
Namespace
Kubernetes의 Namespace는 클러스터 안의 리소스를 논리적으로 구분하는 공간임.
예를 들어 Smith 가족의 집과 Williams 가족의 집이 있다고 하면,
- 같은 집 안에서는 사람을 이름만으로 부를 수 있음
- 다른 집 사람을 부를 때는 더 정확한 구분이 필요함
- 각 집마다 규칙과 자원이 따로 있음
Kubernetes에서도 Namespace가 이와 비슷한 역할 수행함.
즉, Namespace는
- 리소스를 구분함
- 서로 다른 환경을 격리함
- 정책과 자원 제한을 따로 적용 가능하게 함
지금까지 생성한 다음 리소스들은
- Pod
- Deployment
- Service
모두 어떤 Namespace 안에서 생성된 것임.
지금까지 별도로 지정하지 않았다면
대부분 default namespace에서 작업한 것임.
이 default namespace는 Kubernetes가 자동으로 만들어주는 기본 Namespace임.
즉, 지금까지 계속 기본 집 안에서 작업하고 있었던 것과 같음.
Kubernetes는 클러스터가 처음 생성될 때 몇 가지 Namespace를 자동으로 만듦.
1) default
사용자가 별도 지정 없이 작업할 때 사용하는 기본 Namespace임.
2) kube-system
Kubernetes 내부 구성요소들이 사용하는 Namespace임.
예:
- 네트워크 관련 구성요소
- DNS 서비스
이런 시스템 리소스를 사용자 리소스와 분리해서
실수로 수정하거나 삭제하지 않게 하기 위한 목적임.
3) kube-public
모든 사용자가 접근할 수 있어야 하는 리소스를 위한 Namespace임.
Namespace를 왜 사용하는가
Namespace를 사용하는 대표적인 이유는 격리(isolation) 때문임.
예를 들어 하나의 클러스터를 다음 두 환경이 함께 사용한다고 가정함.
- dev
- prod
이때 Namespace를 나누지 않으면
개발 작업 중 운영 리소스를 실수로 건드릴 수 있음.
그래서 다음처럼 나눌 수 있음.
- dev namespace
- prod namespace
이렇게 하면 환경별 리소스를 분리 가능함.
즉, Namespace는 같은 클러스터 안에서 환경을 안전하게 나누는 방법임.
Namespace마다 따로 적용할 수 있는 것
각 Namespace는 독립적으로 다음 설정을 가질 수 있음.
1) 정책(Policy)
누가 무엇을 할 수 있는지 정의 가능함.
즉, 접근 제어나 권한 정책을 Namespace 단위로 나눌 수 있음.
2) 자원 할당량(Resource Quota)
각 Namespace가 사용할 수 있는 자원의 한계를 정할 수 있음.
예를 들면 다음과 같음.
- Pod 개수 제한
- CPU 사용량 제한
- Memory 사용량 제한
이렇게 하면 특정 Namespace가 과도하게 자원을 사용하지 못하게 할 수 있음.
같은 Namespace 안에서는 이름만으로 접근 가능함
같은 집 안에서는 사람을 이름만으로 부를 수 있다고 했듯이,
같은 Namespace 안의 리소스도 이름만으로 서로 접근 가능함.
예를 들어 webapp Pod가 같은 Namespace 안의 DB Service에 접근할 때는
그냥 서비스 이름만 사용하면 됨.
예: db-service
즉, 같은 Namespace 안에서는 이름만으로 충분함.
이유는 Kubernetes에서 namespace를 명시하지 않은 DNS query는 Pod 자신의 namespace 기준으로 해석되기 때문이다.
또한 공식문서에 따르면 kubelet은 각 Pod의 /etc/resolv.conf를 구성하며, 짧은 이름 질의는 search domain을 이용해 확장될 수 있다. 예를 들어 현재 Namespace가 test라면:
→ db-service.test.svc.cluster.local
예시 resolv.conf
nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
따라서 같은 Namespace 안의 Service 호출에는 FQDN을 항상 풀로 적을 필요가 없고,
<service-name>만으로도 충분하다.
다른 Namespace의 Service에 접근할 때만 <service-name>.<namespace> 또는 전체 FQDN을 사용하면 된다.
출처 : https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
다른 Namespace의 Service에 접근하는 방법
만약 다른 Namespace에 있는 Service에 접근하려면
서비스 이름만으로는 부족함.
이때는 다음 형식을 사용함.
service-name.namespace
or
service-name.namespace.svc.cluster.local
예를 들어 default namespace의 web Pod가
dev namespace에 있는 db-service에 접근하려면 다음처럼 사용함.
db-service.dev
or
db-service.dev.svc.cluster.local (FQDN, 명시적임)
이 형식은 Kubernetes가 Service 생성 시 자동으로 만든 DNS 이름임.
구성을 보면 다음과 같음.
- db-service → 서비스 이름
- dev → Namespace 이름
- svc → 서비스 서브도메인
- cluster.local → 클러스터 기본 도메인
다른 Namespace에서는 최소한 <service-name>.<namespace> 형태로 namespace를 명시해야 한다.
가장 명시적인 전체 이름은 <service-name>.<namespace>.svc.cluster.local 이다.
kubectl get pods
이 명령은 기본적으로 default namespace의 Pod만 조회함.
예를 들어 kube-system의 Pod를 보려면:
리소스를 다른 Namespace에 생성하는 방법
Pod 정의 파일로 리소스를 생성할 때
Namespace를 지정하지 않으면 기본적으로 default namespace에 생성됨.
방법 1) 명령어에서 지정
방법 2) YAML 파일 안에 지정
metadata:
name: my-pod
namespace: dev
이렇게 해두면 항상 dev namespace에 생성됨.
실수 방지 측면에서 YAML에 명시하는 것이 유용함.
Namespace 생성 방법
Namespace도 Kubernetes 객체이므로 YAML로 생성 가능함.
apiVersion: v1
kind: Namespace
metadata:
name: dev
## 생성
kubectl create -f namespace.yaml
## 명령어로
kubectl create namespace dev
매번 -n dev를 붙이는 것이 번거로우면
현재 context의 기본 Namespace를 변경할 수 있음.
이렇게 하면 이후에는
만 입력해도 dev namespace 기준으로 동작함.
다른 Namespace를 보고 싶으면 그때만 -n 옵션을 사용하면 됨.
클러스터 전체 Pod를 보고 싶다면 다음 명령 사용함.
또는
이 명령은 모든 Namespace의 Pod를 한 번에 보여줌.
ResourceQuota로 Namespace 자원 제한하기
특정 Namespace가 사용할 수 있는 자원을 제한하려면
ResourceQuota를 생성하면 됨.
예를 들어 다음과 같은 제한 설정 가능함.
- Pod 10개
- CPU 10개
- Memory 10GB
이렇게 하면 해당 Namespace는 정해진 범위 내에서만 자원 사용 가능함.
즉, ResourceQuota는 Namespace 단위의 자원 통제 수단임.
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: dev
spec:
hard:
pods: "10"
requests.cpu: "4
requests.memory: 5Gi
limits.cput: "10"
limits.memory: 10Gi'Infra > K8S' 카테고리의 다른 글
| [CKA 준비] Scheduling (0) | 2026.04.11 |
|---|---|
| [CKA 준비] - Core Concepts - 2 (0) | 2026.03.15 |
| [CKA 준비] Core Conecepts - 1 (4) | 2026.03.04 |
| KEDA 필요성, HPA 와의 차이점 (0) | 2026.01.18 |