Service는 클러스터 외부로부터 요청을 받을 수 있게 IP를 노출하는 역할을 하는 리소스다. 그리고 Deployment로 Pod를 수평확장하는 상황에, 트래픽을 적절히 분산시키기는 역할을 할 수 있는 리소스이다.
Pod는 클러스터 내부에서만 접근이 가능하며, 클러스터 외부로 노출되어있지 않다. 그래서 클러스터 외부에서 해당 Pod에 직접 요청할 수가 없다. Pod를 외부로 노출시키려면 포트포워딩을 이용하거나 쿠버네티스 Service 오브젝트를 이용해야 한다.
ClusterIP는 파드들이 클러스터 내부의 다른 리소스들과 통신할 수 있도록 해주는 가상의 클러스터 전용 IP다. 이 유형의 서비스는 <ClusterIP>
로 들어온 클러스터 내부 트래픽을 해당 파드의 <파드IP>:<targetPort>
로 넘겨주도록 동작하므로, 오직 클러스터 내부에서만 접근 가능하게 된다. 쿠버네티스가 지원하는 기본적인 형태의 서비스다.
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: ClusterIP # 생략 가능
ports:
- protocol: TCP
targetPort: 9376 # 애플리케이션(파드)을 노출하는 포트
port: 80 # 서비스를 노출하는 포트
selector: # 이 서비스가 적용될 파드 정보를 지정 (선택이나 권장 사항)
app: myapp
type: frontend
TCP 포트 9376
을 수신 대기(listen)하며 app=myapp, type=frontend
라는 레이블을 공유하는 파드들에게 myapp-service
라는 이름으로 접근할 수 있게 해주는 ClusterIP
유형의 서비스를 정의하면 다음과 같다.
위에서 spec.selector
에서 지정된 레이블로 여러 파드들이 존재할 경우, 서비스는 그 파드들을 외부 요청(request)을 전달할 엔드포인트(endpoints)로 선택하여 트래픽을 분배하게 된다. 이를 이용하여 한 노드 안에 여러 파드, 또는 여러 노드에 걸쳐 있는 여러 파드에 동일한 서비스를 적용할 수 있다.
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
apiVersion: v1
kind: Endpoints
metadata:
name: myapp-service # 연결할 서비스와 동일한 name을 메타데이터로 입력
subsets: # 해당 서비스로 가리킬 endpoint를 명시
- addresses:
- ip: 192.0.2.42
ports:
- port: 9376
필요에 따라 엔드포인트(Endpoints)를 수동으로 직접 지정해줘야 할 때가 있다. 테스트 환경과 상용 환경의 설정이 서로 다르거나, 다른 네임스페이스 또는 클러스터에 존재하는 파드와의 네트워크를 위해 서비스-서비스 간의 연결을 만들어야 하는 상황 등이 있다.
이런 경우에는 spec.selector
없이 서비스를 만들고, 해당 서비스가 가리킬 엔드포인트(Endpoints) 객체를 직접 만들어 해당 서비스에 맵핑하는 방법이 있다.
NodePort는 외부에서 노드 IP의 특정 포트(<NodeIP>:<NodePort>
)로 들어오는 요청을 감지하여, 해당 포트와 연결된 파드로 트래픽을 전달하는 유형의 서비스다. 이때 클러스터 내부로 들어온 트래픽을 특정 파드로 연결하기 위한 ClusterIP 역시 자동으로 생성된다.
이 유형의 서비스에서는 spec.ports
아래에 nodePort
를 추가로 지정할 수 있다. nodePort
는 외부에서 노드 안의 특정 서비스로 접근할 수 있도록 지정된 노드의 특정 포트를 의미한다. nodePort
로 할당 가능한 포트 번호의 범위는 30000
에서 32767
사이이며, 미지정시 해당 범위 안에서 임의로 부여된다.
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: NodePort
ports:
- targetPort: 80 # 애플리케이션(파드)을 노출하는 포트
port: 80 # 서비스를 노출하는 포트
nodePort: 30008 # 외부 사용자가 애플리케이션에 접근하기 위한 포트번호(생략 가능)
selector: # 이 서비스가 적용될 파드 정보를 지정
app: myapp
type: frontend
spec.selector
에 해당하는 모든 파드들에 동일한 로드 밸런싱이 적용된다. 만약 같은 레이블의 파드들이 다른 여러 노드에 걸쳐 존재한다면, 해당 노드들에도 같은 서비스가 자동으로 생성되면서 같은 번호의 노드포트를 통한 해당 파드들의 접근이 허용된다. 예를 들어 192.168.1.2
노드와 192.168.1.3
노드에 각각 같은 레이블의 파드가 존재할 경우, NodePort 서비스를 통해 192.168.1.2:<nodePort>
또는 192.168.1.3:<nodePort>
중 어떤 경로를 이용하더라도 해당되는 파드들에 연결이 가능해진다.
별도의 외부 로드 밸런서를 제공하는 클라우드(AWS, Azure, GCP 등) 환경을 고려하여, 해당 로드 밸런서를 클러스터의 서비스로 프로비저닝할 수 있는 LoadBalancer 유형도 제공된다.
이 유형은 서비스를 클라우드 제공자 측의 자체 로드 밸런서로 노출시키며, 이에 필요한 NodePort와 ClusterIP 역시 자동 생성된다. 이때 프로비저닝된 로드 밸런서의 정보는 서비스의 status.loadBalancer
필드에 게재된다.
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: LoadBalancer
ports:
- protocol: TCP
port: 80 # 서비스를 노출하는 포트
targetPort: 80 # 애플리케이션(파드)를 노출하는 포트
clusterIP: 10.0.171.239 # 클러스터 IP
selector:
app: myapp
type: frontend
status:
loadBalancer: # 프로비저닝된 로드 밸런서 정보
ingress:
- ip: 192.0.2.127
이렇게 구성된 환경에서는, 외부의 로드 밸런서를 통해 들어온 트래픽이 서비스의 설정값을 따라 해당되는 파드들로 연결된다. 이 트래픽이 어떻게 로드 밸런싱이 될지는 클라우드 제공자의 설정에 따르게 된다.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: np-deployment
spec:
replicas: 2
selector:
matchLabels:
app: np-pods
template:
metadata:
labels:
app: np-pods
spec:
containers:
- name: np-pods
image: sysnet4admin/echo-hname
ports:
- containerPort: 80
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: np-svc
spec:
selector:
app: np-pods
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
# deployment, service 배포
$ kubectl apply -f deployment.yaml
$ kubectl apply -f service.yaml
# kubectl 를 통한 deployment, service 확인
$ kubectl get all -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/np-deployment-6b96849c57-r5c9h 1/1 Running 0 6m44s 172.16.132.6 w3-k8s <none> <none>
pod/np-deployment-6b96849c57-spcwz 1/1 Running 0 6m44s 172.16.103.138 w2-k8s <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4d21h <none>
service/np-svc NodePort 10.103.126.111 <none> 80:30000/TCP 6m41s app=np-pods
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/np-deployment 2/2 2 2 6m44s np-pods sysnet4admin/echo-hname app=np-pods
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/np-deployment-6b96849c57 2 2 2 6m44s np-pods sysnet4admin/echo-hname app=np-pods,pod-template-hash=6b96849c57
[root@m-k8s ~]# curl -v 10.103.126.111
* About to connect() to 10.103.126.111 port 80 (#0)
* Trying 10.103.126.111...
* Connected to 10.103.126.111 (10.103.126.111) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.103.126.111
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.16.1
< Date: Sun, 05 May 2024 08:51:45 GMT
< Content-Type: text/html
< Content-Length: 31
< Connection: keep-alive
<
np-deployment-6b96849c57-spcwz # 접속 시 확인할 내용
* Connection #0 to host 10.103.126.111 left intact
[root@m-k8s ~]# curl -v 10.103.126.111
* About to connect() to 10.103.126.111 port 80 (#0)
* Trying 10.103.126.111...
* Connected to 10.103.126.111 (10.103.126.111) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.103.126.111
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.16.1
< Date: Sun, 05 May 2024 08:51:48 GMT
< Content-Type: text/html
< Content-Length: 31
< Connection: keep-alive
<
np-deployment-6b96849c57-r5c9h # 접속 시 확인할 내용
* Connection #0 to host 10.103.126.111 left intact
위에서 확인된 Service의 Cluster IP는 10.103.126.111 이고, <ClusterIP>:<ServicePort>
를 통해 연결된 파드에 로드밸런싱을 해주는 것을 확인할 수 있다.
[root@m-k8s ~]# curl 192.168.1.102:30000
np-deployment-6b96849c57-spcwz
[root@m-k8s ~]# curl 192.168.1.103:30000
np-deployment-6b96849c57-r5c9h
Cluster 에서 <NodeIP>:<NodePort>
로도 동일한 내용을 확인할 수 있다.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: np-deployment
spec:
replicas: 2
selector:
matchLabels:
app: np-pods
template:
metadata:
labels:
app: np-pods
spec:
containers:
- name: np-pods
image: sysnet4admin/echo-hname
ports:
- containerPort: 80
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: np-svc
spec:
selector:
app: np-pods
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: LoadBalancer
# deployment, service 배포
$ kubectl apply -f deployment.yaml
$ kubectl apply -f service.yaml
# kubectl 를 통한 deployment, service 확인
$ kubectl get all -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/np-deployment-66787546c9-gdjr4 1/1 Running 0 44s 172.21.18.179 ip-172-21-19-13.ap-northeast-2.compute.internal <none> <none>
pod/np-deployment-66787546c9-vcvxq 1/1 Running 0 44s 172.21.13.114 ip-172-21-14-25.ap-northeast-2.compute.internal <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 172.16.0.1 <none> 443/TCP 103m <none>
service/np-svc LoadBalancer 172.16.107.200 ac0c6f11e2b974fe0a44b970b63be449-52265006.ap-northeast-2.elb.amazonaws.com 80:30000/TCP 44s app=np-pods
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/np-deployment 2/2 2 2 44s np-pods sysnet4admin/echo-hname app=np-pods
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/np-deployment-66787546c9 2 2 2 44s np-pods sysnet4admin/echo-hname app=np-pods,pod-template-hash=66787546c9
Service를 LoadBalancer로 설정 시 기본적으로 CLB로 생성이 된다. 24년 기준으로 현재 CLB는 공식적으로 제공을 하지는 않는 서비스이다.
리스너에서는 위 yaml에서 정의한 것처럼 리스너 포트는 80이며, 인스턴스의 포트는 30000으로 설정이 되어 있는 것을 확인할 수 있다.
VPC 의 Bastion EC2 에서 curl을 통해 확인했을 때 각 각의 Pod로 접속이 되는 것을 확인하였지만, 순차적인 로드밸런싱은 되지 않는 것으로 보인다.
웹페이지에서도 정상적인 로드밸런싱이 되지 않았지만, 새로운 브라우저에서는 다른 Pod로 접속을 하였다.
참고 :
쿠버네티스 사용해보기 - Service
Kubernetes 리소스 Service에 대해 이해하고 실습해보기
노드포트 서비스로 외부에서 접속하기