Kubernetes

서비스 API 카테고리(서비스)

jih0ssang 2024. 1. 20. 12:04

참고 사이트: https://computing-jhson.tistory.com/106

 

작성중..

서비스 API 카테고리

컨테이너를 외부에 공개하는 엔드포인트를 제공하는 리소스

- 서비스

   - ClusterIP

   - ExternalIP  (ClusterIP의 한 종류)

   - NodePort

   - LoadBalancer

   - Headless (None)

   - ExternalName

   - None-Selector

- 인그레스

 

쿠버네티스 클러스터 네트워크와 서비스

파드 내부에는 여러 컨테이너가 존재할 수 있고, 같은 파드 내 컨테이너들은 동일한 IP 주소를 할당받는다.

따라서 같은 파드의 컨테이너로 통신하려면 localhost로 통신하고, 다른 파드에 있는 컨테이너와 통신하려면 파드의 IP 주소로 통신한다.

 

쿠버네티스 클러스터의 내부 네트워크

쿠버네티스 클러스터는 클러스터를 생성하면 노드상에 파드를 위한 내부 네트워크가 자동으로 구성된다.

내부 네트워크 구성은 사용할 CNI(Container Network Interface)라는 플러그형 모듈 구현에 따라 다르지만,

기본적으로 노드별 다른 네트워크 세그먼트를 구성하고 노드 간의 트래픽은 VXLAN이나 L2 Routing 등의 기술을 사용하여 전송함으로써 노드 간 통신이 가능하게 구성한다.

노드별 네트워크 세그먼트는 쿠버네티스 클러스터 전체에 할당된 네트워크 세그먼트를 자동으로 분할해 할당하므로 사용자가 설정하지 않아도 된다.

 

노드 네트워크(외부 네트워크), 파드 네트워크(내부 네트워크)

이러한 내부 네트워크가 자동으로 구성되므로 파드는 서비스를 사용하지 않고도 파드 간 통신이 가능하다.

그러나 서비스를 사용하면 다음과 같은 장점을 얻을 수 있다.

  • 파드에 트래픽 로드밸런싱
  • 서비스 디스커버리와 클러스터 내부 DNS

파드에 트래픽 로드 밸런싱

서비스는 수신한 트래픽을 여러 파드에 부하 분산하는 기능을 제공한다.

예를 들어 디플로이먼트를 사용해 여러 파드를 기동할 때마다 파드들이 각각 다른 IP 주소를 할당받으므로

로드 밸런싱하는 구조를 자체적으로 구현하려면 각 파드의 IP 주소를 매번 조회해야만 했다.

그러나 서비스를 사용하면 여러 파드에 대한 로드 밸런싱을 자동으로 구성할 수 있다.

또한, 서비스는 로드 밸런싱의 접속 창구가 되는 엔드포인트도 제공한다.

엔드포인트는 외부 로드밸런서가 할당하는 가상 IP 주소(Virtual IP)나

클러스터 내부에서만 사용할 수 있는 가상 IP 주소(ClusterIP) 등 여러 가지 종류를 제공한다. 

ClusterIP는 한번 지정하면 변경할 수 없고, 삭제하고 다시 생성해야 한다.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: nginx-container
        image: amsy810/echo-nginx:v2.0

이 디플로이먼트가 생성할 파드에는 app: sample-app 레이블과 자동으로 부여되는 pod-template-hash:7c6d(해시값) 레이블이 설정되어 있다. 디플로이먼트를 생성한 후 특정 JSON Path를 지정하는 옵션을 사용하여 레이블 목록을 출력해보자.

# 출력 시 특정 JSON Path(예제에서는 레이블) 값만을 출력
$ kubectl get pods sample-deployment-7c6d -o jsonpath='{.metadata.labels}'
map[app:sample-app pod-template-hash:7c6d]

 

 

엔드포인트의 서비스 종류: ClusterIP

다음은 엔드포인트의 서비스 종류에 ClusterIP를 사용하는 서비스를 생성해보겠다.

ClusterIP는 클러스터 내부에서만 사용 가능한 가상 IP를 가진 엔드포인트를 제공하는 로드밸런서를 구성한다.

서비스는 spec.selector에 정의할 셀렉터 조건에 따라 트래픽을 전송한다. 

위의 예제에서는 app: sample-app 레이블을 가진 파드에 트래픽을 로드 밸런싱하여 전송한다.

apiVersion: v1
kind: Service
metadata:
  name: sample-clusterip
spec:
  type: ClusterIP
  ports:
  - name: "http-port"
    protocol: "TCP"
    port: 8080
    targetPort: 80
  selector:
    app: sample-app
# 지정한 레이블을 가진 파드 정보 중 특정 JSON Path를 컬럼으로 출력
$ kubectl get pods -l app=sample-app -o custom-columns="NAME:{metadata.name}, IP:{status.podIP}"
NAME		    	IP
sample-deployment-7c6d  10.0.1.26
sample-deployment-7c87  10.0.2.124
sample-deployment-7c64  10.0.0.34

서비스를 생성하면 생성된 로드 밸런서의 트래픽이 전송되는 파드를 확인해보자.

먼저 app: sample-app 레이블을 가진 파드 IP 주소를 확인해야 한다.

 

# 서비스 생성
$ kubectl apply -f sample-clusterip.yaml
service/sample-clusterip created

# 서비스 정보 확인
$ kubectl get services sample-clusterip
NAME		TYPE		CLUSTER-IP	EXTERNAL-IP	PORT(S)	AGE
sample-clusterip ClusterIP  10.3.251.192  <none>  8080/TCP  3h

# 서비스 상세 정보 확인
$ kubectl describe service sample-clusterip
Name:		    sample-clusterip
Namespace:	    default
Lables:		    <none>
Annotations:        Selector:	app=sample-app
Type:		    ClusterIP
IP:		    10.3.251.192
Port: 		    http-port	8080/TCP
TargetPort:	    80/tcp
Endpoints:	    10.0.0.34:80. 10.0.1.26:80, 10.0.2.124:80
Session Affinity:   None
Events:		    <none>

다음으로, 생성된 서비스 상세 정보를 출력하면 Endpoints 항목에서 트래픽의 목적지 IP주소(및 포트)를 확인할 수 있다.

방금 확인한 app: sample-app 레이블을 가진 파드 IP 주소가 같기 때문에 

트래픽 전송이 셀렉터 조건에 따라 선택된 것을 확인할 수 있다.

또한, 로드 밸런싱을 위한 엔드포인트의 가상 IP는 CLUSTER-IP 항목이나 IP 항목에서 10.3.251.192가 할당된 것을 확인할 수 있다.

 

Endpoints 항목에 아무것도 표시되지 않을 경우 셀렉터 조건이 맞지 않을 가능성이 있다.

파드에 레이블이 정확히 설정되었는지, 레이블 값이 셀렉터 조건과 동일한지를 확인해봐야 한다.

 

마지막으로 생성된 로드 밸런서가 각 파드에 트래픽을 로드 밸런싱하여 전송하고 있는지를 확인한다.

 

이번 예제에서는 엔드포인트 서비스 종류에 ClusterIP를 지정했기 때문에 엔드포인트 가상 IP는 클러스터 내부에서만 접속 가능한 IP주소다. 그러므로 클러스터 내 별도 컨테이너를 기동하고 그 컨테이너에서 엔드포인트로 HTTP 요청을 보내 동작을 확인한다. HTTP 요청은 로드 밸런싱되어 어느 하나의 파드에서 요청을 처리하고 처리한 파드의 호스트명을 포함한 http 응답이 반환된다. 따라서 HTTP 응답을 보면 어떤 파드에서 요청을 처리했는지 확인할 수 있다.

# 일시적으로 파드를 시작하여 서비스 엔드포인트로 요청
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.3.251.192:8080
Host=10.3.251.192 Path=/ From=sample-deployment-7c6d ClientIP=10.0.2.144 XFF=
pod "testpod" deleted

(여러 번 실행하면 비슷한 빈도로 세 개의 파드명이 표시된다.)
Host=10.3.251.192 Path=/ From=sample-deployment-7c87 ClientIP=10.0.2.145 XFF=
pod "testpod" deleted

Host=10.3.251.192 Path=/ From=sample-deployment-7c64 ClientIP=10.0.2.147 XFF=
pod "testpod" deleted

 

 

여러 포트 할당

위의 예제에서는 포트가 하나였지만 하나의 서비스에 여러 포트를 할당할 수도 있다.

예를 들어, HTTP와 HTTPS에서 ClusterIP가 다르면 불편한 경우가 많으므로, 하나의 서비스에서 여러 포트를 가질 수 있또록 설정하는 것이 바람직하다.

예제를 보면 ClusterIP의 8080/TCP포트로의 요청은 파드 80/TCP 포트로 로드 밸런싱하고,

ClusterIP의 8443/TCP 포트로의 요청은 파드 443/TCP 포트로 로드 밸런싱 한다.

(그러나 사용하고 있는 디플로이먼트의 파드에서는 443 포트가 Listen 상태가 아니므로 https-port로는 통신할 수 없다)

 

 

apiVersion: v1
kind: Service
metadata:
  name: sample-clusterip-multi
spec:
  type: ClusterIP
  ports:
  - name: "http-port"
    protocol: "TCP"
    port: 8080
    targetPort: 80
  - name: "https-port"
    protocol: "TCP"
    port: 8443
    targetPort: 443
  selector:
    app: sample-app

 

---
apiVersion: v1
kind: Pod
metadata:
  name: sample-named-port-pod-80
  labels:
    app: sample-app
spec:
  type: ClusterIP
  ports:
  - name: nginx-container
    image: amsy810/echo-nginx:v2.0
    protocol: "TCP"
    ports: 
    - name: http                    # 포트에 이름 지정
    containerPort: 80

---
apiVersion: v1
kind: Pod
metadata:
  name: sample-named-port-pod-81
  labels:
    app: sample-app
spec:
  type: ClusterIP
  ports:
  - name: nginx-container
    image: amsy810/echo-nginx:v2.0
    env:
    - name: NGINX_PORT
      value: "81"
    ports:
    - name: http                    # 포트에 이름 지정
      containerPort: 81

 

apiVersion: v1
kind: Service
metadata:
  name: sample-named-port-service
spec:
  type: ClusterIP
  ports:
  - name: "http-port"
    protocol: "TCP"
    port: 8080
    targetPort: http                # 이름으로 포트를 참조
  selector:
    app: sample-app

 

# 서비스 목적지 엔드포인트 확인
$ kubectl describe service sample-named-port-service
Name:		    sample-named-port-service
Namespace:	    default
Lables:		    <none>
Annotations:        Selector:	app=sample-app
Type:		    ClusterIP
IP:		    10.3.250.174
Port: 		    http-port	8080/TCP
TargetPort:	    http/tcp
Endpoints:	    10.0.2.153:80. 10.0.1.27:81
Session Affinity:   None
Events:		    <none>

# 파드 IP 주소 확인
$ kubectl get pods -o wide
NAME		          READY	STATUS	RESTARTS  AGE	IP
sample-named-port-pod-80  1/1  Running   0        42s   10.0.2.153
sample-named-port-pod-81  1/1  Running   0     	  42s   10.0.1.27

 

클러스터 내부 DNS와 서비스 디스커버리

쿠버네티스에서는 서비스 디스커버리 기능을 서비스(Service)가 제공하고 있다. 서비스 디스커버리는 간단히 말하면 특정 조건의 대상이 되는 멤버를 보여주거나 이름에서 엔드포인트를 판별하는 기능이다.

쿠버네티스에서 서비스 디스커버리란 서비스에 속해 있는 파드를 

 

환경 변수를 사용한 서비스 디스커버리

 

# 환경 변수에 등록된 서비스 정보 확인
$ kubectl exec -it sample-deployment-7c6d -- env | grep -i kubernetes
KUBERNETES_PORT_443_TCP=tcp://10.3.240.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.3.240.1
KUBERNETES_SERVICE_HOST=10.3.240.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.3.240.1:443

 

spec:
  enableServieLinks: false
  containers:
  - name: nginx-container
    image: amsy810/echo-nginx:v2.0

 

DNS A 레코드를 사용한 서비스 디스커버리

 

# 일시적으로 파드를 기동하여 컨테이너 내부에서 sample-clusterip:8080으로 HTTP 요청
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod \ --command -- curl -s http://sample-clusterip:8080
Host=sample-clusterip Path=/ From=sample-deployment-7c6d ClusterIP=10.0.1.157 XFF=
pod "testpod" deleted

(여러 번 실행하면 비슷한 빈도로 세 개의 파드명이 표시된다)
Host=sample-clusterip Path=/ From=sample-deployment-7c87 ClusterIP=10.0.1.159 XFF=
pod "testpod" deleted

Host=sample-clusterip Path=/ From=sample-deployment-7c64 ClusterIP=10.0.1.160 XFF=
pod "testpod" deleted

 

 

# 일시적으로 파드를 기동하여 컨테이너 내부에서 sample-clusterip.default.svc.cluster.local의 이름 해석 확인
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod \ --command -- dig sample-clusterip.default.svc.cluster.local
...(생략)...

;; QUESTION SECTION:
;sample-clusterip.default.svc.cluster.local.	IN	A

;; ANSWER SECTION:
sample-clusterip.default.svc.cluster.local.	30	IN	A

...(생략)...

 

 

# 일시적으로 파드를 기동하여 기본으로 /etc/resolv.conf에 포함되어 있는 옵션을 확인
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- cat /etc/resolv.conf
nameserver 10.3.240.10
search default.svc.cluster.local svc.cluster.local cluster.local c.psu-satest-20200113.internal google.internal
options ndots:5
pod "testpod" deleted

 

# 일시적으로 파드를 기동하여 컨테이너 내부에서 10.3.255.181의 이름 해석 확인
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig -x 10.3.251.192
...(생략)...
;; QUESTION SECTION:
;192.251.3.10.in-addr.arpa.		IN	A	PTR

;; ANSWER SECTION:
192.251.3.10.in-addr.arpa.	30	IN	A	PTR	sample-clusterip.default.svc.cluster.local.

...(생략)...

 

 

DNS SRV 레코드를 사용한 서비스 디스커버리

apiVersion: v1
kind: Service
metadata:
  name: sample-clusterIP
spec:
  type: ClusterIP
  ports:
  - name: "http-port"
    protocol: "TCP"
    port: 8080
    targetPort: http              
  selector:
    app: sample-app

 

# 일시적으로 파드를 기동하여 SRV 레코드가 다른 파드에서 해석이 가능한지를 확인
$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig _http-port._tcp.sample-clusterip.default.svc.cluster.local SRV
...(생략)...
;; QUESTION SECTION:
;_http-port._tcp.sample-clusterip.default.svc.cluster.local.	IN	SRV

;; ANSWER SECTION:
_http_port._tcp.sample-clusterip.default.svc.cluster.local.	30	IN	SRV	10	100	8080	sample-clusterip.default.svc.cluster.local.

;; ADDITIONAL SECTION:
sample-clusterip.default.svc.cluster.local.	30	IN	A	10.3.251.192
...(생략)...

 

클러스터 내부 DNS와 클러스터 외부 DNS

 

노드 로컬 DNS 캐시