파드는 쿠버네티스 안에서 IP 주소를 부여받는데 쿠버네티스의 서비스는 파드에 탑재된 애플리케이션이 외부와 상호 통신 가능하도록 만들어준다. 이번 글에서는 쿠버네티스 운영에 필요한 서비스에 대해 알아보는 시간을 가져보도록 하자.
- 서비스 개념
- 서비스 타입
- 서비스 사용하기
- Headless Service
- kube-proxy
파드는 클러스터 내부에서만 접근이 가능하며 클러스터 외부로 노출되어 있지 않은 특성을 가지고 있다. 따라서 클러스터 외부에서는 해당 파드에 직접 요청을 할 수 없는데 쿠버네티스의 서비스는 클러스터의 외부로부터 요청을 받을 수 있게 IP를 노출시키고 파드가 수평 확장하는 상황에서 트래픽을 적절히 분산시키는 역할을 한다.
아래처럼 3개의 webui 파드가 동작 중 이라고 할때 Cluster IP 서비스를 생성해보자.
$ kubectl get pod -w wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webui-5f67dcf6d-2pplj 1/1 Running 0 6s 10.244.3.17 test-cluster-worker <none> <none>
webui-5f67dcf6d-2tkwb 1/1 Running 0 6s 10.244.3.16 test-cluster-worker <none> <none>
webui-5f67dcf6d-rlb7f 1/1 Running 0 6s 10.244.1.22 test-cluster-worker2 <none> <none>
apiVersion: v1
kind: Service
metadata:
name: clusterip-service
spec:
type: clusterIP
clusterIP: 10.96.100.100 // 생략 가능
selector:
app: webui
ports:
- protocol: TCP
port: 80 // cluster ip 의 포트
targetPort: 80 // 파드의 포트
$ kubectl create -f clusterip-service.yaml
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
clusterip-service ClusterIP 10.96.100.100 <none> 80/TCP 6s
서비스를 생성한 후 서비스를 확인해보면 다음과 같이 진입점이 되는 IP가 부여된 것을 확인할 수 있다. describe 명령어를 통해 더 자세히 확인해보자.
$ kubectl describe svc
Name: clusterip-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=webui
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.100.100
IPs: 10.96.100.100
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.22:80,10.244.3.16:80,10.244.3.17:80
Session Affinity: None
Events: <none>
Endpoints 부분을 확인해보면 동작중인 3개의 webui 파드의 IP가 나열되어 있다. 이 세 파드를 묶어서 서로 연결한 것을 확인할 수 있다.
이제 로드밸런싱이 잘 동작하는지 각 파드에에 접속해서 index.html 파일을 변경하는 것으로 테스트 해보자.
$ kubectl exec webui-5f67dcf6d-2pplj -it -- /bin/bash
$ echo "webui #1" > /usr/share/nginx/html/index.html
위처럼 파드에 접속하여 index.html의 내용을 각각 webui #1, webui #2, webui #3로 변경하는 것으로 로드밸런싱을 확인할 것이다.

apiVersion: v1
kind: Service
metadata:
name: nodeport-service
spec:
type: NodePort
clusterIP: 10.96.100.200
selector:
app: webui
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30200
$ kubectl create -f nodeport-nginx.yaml
$ kubect get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nodeport-service NodePort 10.96.100.200 <none> 80:30200/TCP 4s
nodeport-nginx.yaml에 정의된 대로 port가 매핑된 것을 확인할 수 있다. 만일 External IP가 설정되어 있다면 EXTERNAL-IP:30200를 통해 외부에서 접근이 가능할테지만 지금은 설정된 Cluster IP를 이용하여 내부에서 로드밸런싱의 여부를 확인할 수 있다.
우선 워커 노드에서 32000번 포트가 열려있는지 확인후 curl 명령어로 로드밸런싱 테스트를 해보자.
$ iptables -t nat -L KUBE-NODEPORTS
Chain KUBE-NODEPORTS (1 references)
target prot opt source destination
KUBE-EXT-QATUYZLPU4I4HE4S tcp -- anywhere anywhere /* default/nodeport-service */ tcp dpt:30200

apiVersion: v1
kind: Service
metadata:
name: lb-service
spec:
type: LoadBalancer
selector:
app: webui
ports:
- protocol: TCP
port: 80
targetPort: 80
$ kubectl create -f lb-nginx.yaml
$ kubect get sec
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
lb-service LoadBalancer 10.96.167.165 <pending> 80:32686/TCP 2s
nodeport-service NodePort 10.96.100.200 <none> 80:30200/TCP 11m
LoadBalancer는 로드밸런서를 자동으로 구성 요청하고 NodePort를 예약 후 해당 NodePort로 외부 접근을 허용한다. 즉, 외부 로드밸런서로 접근했을 때 현재 존재하는 노드로 먼저 분산시키고 이후 워커 노드 내부에서 동작하는 여러 파드로 분산시키는 과정을 거친다.
apiVersion: v1
kind: Service
metadata:
name: externalname-service
spec:
type: ExternalName
externalName: google.com
$ kubectl create -f externalname.yaml
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
externalname-service ExternalName <none> google.com <none> 2s
ExternalName 서비스를 생성하고 새로운 파드를 동작시켜 곧바로 테스트 해보도록하자.
$ kubectl run testpod -it --image=centos:7
[root@testpod /]# curl externalname-service.default.svc.cluster.local

쿠버네티스 서비스를 생성할 때 clusterIP를 None으로 설정하면 클러스터 IP가 없는 서비스를 만들 수 있다. 이런 서비스를 '헤드리스 서비스(Headless Service)'라고 하며 단일 진입점이 필요 없을때 사용되며 서비스와 연결된 파드의 엔드포인트로 DNS 레코드가 생성된다.
생성된 DNS 레코드를 쿠버네티스의 coreDNS에 등록시키고 파드들의 엔드포인트에 DNS resolving service를 지원한다. 즉, 파드의 DNS 주소로 접근이 가능하다는 것이다.
Pod DNS addr: pod-ip-addr.namespace.pod.cluster.local
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webui-5f67dcf6d-5jfvx 1/1 Running 0 11s 10.244.1.25 test-cluster-worker2 <none> <none>
webui-5f67dcf6d-dfx69 1/1 Running 0 11s 10.244.1.24 test-cluster-worker2 <none> <none>
webui-5f67dcf6d-zt5pd 1/1 Running 0 11s 10.244.3.18 test-cluster-worker <none> <none>
우선 테스트를 위해 새로운 Nginx 파드를 만들어주자.
apiVersion: v1
kind: Service
metadata:
name: headless-service
spec:
type: ClusterIP
clusterIP: None
selector:
app: webui
ports:
- protocol: TCP
port: 80
targetPort: 80
$ kubectl create -f headless-nginx.yaml
$ kubect get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
headless-service ClusterIP None <none> 80/TCP 6s
$ kubectl describe svc headless-service
Endpoints: 10.244.1.24:80,10.244.1.25:80,10.244.3.18:80
생성된 헤드리스 서비스의 상세 정보를 확인해보면 Endpoints에 동작중인 파드들이 묶여있는 것을 확인할 수 있다.
테스트 파드 컨테이너에서 다음과 같이 curl 명령어를 통해 접근해보면 실제 DNS resolve된 파드와 연결시켜 존다.
$ kubectl run -it testpod --image=centos:7 /bin/bash

쿠버네티스 헤드리스 서비스는 자주 변경이 이루어지는 서비스보다는 statefaulset과 같이 파드 이름이 보존되어있는 서비스에서 유용하게 사용할 수 있다.
쿠버네티스 네트워크 뒤에서는 내부에서 작동하는 구성요소가 있는데 이는 서비스를 사용가능한 네트워킹 규칙으로 변환하는 작업을 한다. 이것이 바로 kube-proxy이다.
kube-proxy는 쿠버네티스 클러스터의 모든 노드에 agent로 설치되어 서비스 오브젝트와 엔드포인트 간 일어나는 모든 변화를 모니터링 하고 있다.
엔드포인트 연결을 위한 iptables를 구성하고 NodePort로의 접근과 파드 연결을 구현하는 역할을 한다.
[참고자료]
https://kimjingo.tistory.com/151
https://kgw7401.tistory.com/114