[Retry, k8s] 10. 서비스 API

Date:     Updated:

카테고리:

태그:

[Retry, k8s] 10. 서비스 API



🔔 서비스 API 개요

클러스터상의 컨테이너에 대한 엔드포인트를 제공하거나 레이블과 일치하는 컨테이너의 디스커버리에 사용되는 리소스


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

쿠버네티스는 CNI 플러그인을 사용하여 파드 간 네트워킹을 구현하고, 서비스와 인그레스 리소스를 사용하여 파드와 외부의 통신을 추상화한다.

(1) 컨테이너 간의 통신

  • 파드는 하나 이상의 컨테이너로 구성된 논리적 단위로, 같은 파드 내의 컨테이너는 동일한 IP 주소와 포트를 공유하고 localhost를 통해 통신할 수 있다.

(2) 파드 간의 통신 (CNI)

  • CNI 플러그인은 컨테이너 런타임에 의해 호출되어 파드가 생성/삭제될 때 네트워크 인터페이스를 추가/제거하고, 라우팅 규칙과 IP 주소 할당, 네트워크 정책을 적용하는 역할을 한다.

  • 모든 파드는 고유한 IP 주소를 가지며, 다른 파드와 NAT 없이 통신할 수 있다.

  • 모든 노드는 모든 파드와 NAT 없이 통신할 수 있다.

(3) 파드와 서비스 간 통신 (서비스, kube-proxy)

  • 서비스 리소스는 일정한 IP 주소와 포트를 가지고, 동일한 기능을 제공하는 파드들의 집합을 추상화한 개념

  • 서비스 리소스는 파드의 IP 주소가 변경되더라도 동일한 IP 주소와 포트를 유지하기 때문에 클라이언트가 파드의 위치를 신경쓰지 않고 통신할 수 있다.

  • kube-proxy는 각 노드에서 실행되는 구성요소로, 서비스 IP 주소와 포트에 대한 요청을 파드의 IP 주소와 포트로 프록시하는 역할을 수행한다.

(4) 외부와 서비스 간 통신 (인그레스)

  • 인그레스 리소스는 외부에서 클러스터 내부의 서비스로 접근하기 위한 규칙들을 정의한 개념

  • 인그레스 컨트롤러는 인그레스 리소스에 정의된 규칙들을 실제로 구현하는 구성요소로, 로드밸런서나 리버스 프록시 역할을 수행한다.

  • 인그레스 컨트롤러가 파드에 접근하기 위해서는 CNI 플러그인과 호환되어야 한다.


📜 파드에 트래픽 로드밸런싱

서비스는 수신한 트래픽을 여러 파드에 로드밸런싱하는 기능을 제공한다.

12121

cat <<EOF > sample-deployment.yaml
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-containers
        image: amsy810/echo-nginx:v2.0
EOF
$ kubectl get pod
NAME                                 READY   STATUS    RESTARTS   AGE
sample-deployment-79448fcf48-2kc5g   1/1     Running   0          111s
sample-deployment-79448fcf48-54g6x   1/1     Running   0          111s
sample-deployment-79448fcf48-6lzsb   1/1     Running   0          111s


# 특정 JSON Path 값만 출력
$ kubectl get pod sample-deployment-79448fcf48-2kc5g -o jsonpath='{.metadata.labels}'
{"app":"sample-app","pod-template-hash":"79448fcf48"}


# 지정한 레이블을 가진 파드 정보 중 특정 JSON Path를 컴럼으로 출력
$ kubectl get pod -l app=sample-app -o custom-columns="NAME:{metadata.name},IP:{status.podIP}"
NAME                                 IP
sample-deployment-79448fcf48-2kc5g   10.38.0.3
sample-deployment-79448fcf48-54g6x   10.38.0.2
sample-deployment-79448fcf48-6lzsb   10.32.0.3
cat <<EOF > sample-clusterip.yaml
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
EOF
root@k8s-master:~/test# kubectl apply -f sample-clusterip.yaml
service/sample-clusterip created


$ kubectl get service sample-clusterip
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
sample-clusterip   ClusterIP   10.96.249.28   <none>        8080/TCP   21s


$ kubectl describe service sample-clusterip
Name:              sample-clusterip
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=sample-app
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.249.28
IPs:               10.96.249.28
Port:              http-port  8080/TCP
TargetPort:        80/TCP
Endpoints:         10.32.0.3:80,10.38.0.2:80,10.38.0.3:80
Session Affinity:  None
Events:            <none>
# 일시적으로 파드를 시작하여 서비스 엔드포인트로 요청

$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.96.249.28:8080
Host=10.96.249.28  Path=/  From=sample-deployment-79448fcf48-2kc5g  ClientIP=10.32.0.1  XFF=
pod "testpod" deleted

$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.96.249.28:8080
Host=10.96.249.28  Path=/  From=sample-deployment-79448fcf48-54g6x  ClientIP=10.32.0.1  XFF=
pod "testpod" deleted


$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s http://10.96.249.28:8080
Host=10.96.249.28  Path=/  From=sample-deployment-79448fcf48-6lzsb  ClientIP=10.32.0.1  XFF=
pod "testpod" deleted


📜 여러 포트 할당

하나의 서비스에 여러 포트를 할당할 수도 있다, 예를 들어 ClusterIP의 8080/TCP 포트로의 요청은 파드 80/TCP로 로드밸런싱하고, ClusterIP의 8443/TCP 포트로의 요청은 파드 443/TCP로 로드밸런싱한다.

2131312

cat <<EOF > sample-clusterip-multi.yaml
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
EOF 


📜 이름을 사용한 포트 참조

파드의 포트 정의에 이름을 지정해 놓으면 이름을 사용하여 참조할 수있다.

cat <<EOF > sample-named-port-pods.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sample-named-port-pod-80
  labels:
    app: sample-app
spec:
  containers:
  - name: nginx-container
    image: amsy810/echo-nginx:v2.0
    ports:
    - name: http 
      containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: sample-named-port-pod-81
  labels:
    app: sample-app
spec:
  containers:
  - name: nginx-container
    image: amsy810/echo-nginx:v2.0
    env:
    - name: NGINX_PORT
      value: "81"
    ports:
    - name: http 
      containerPort: 81
EOF
cat <<EOF > sample-named-port-service.yaml
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
EOF
# 서비스 목적지 엔드포인드 확인
$ kubectl describe service sample-named-port-service
Name:              sample-named-port-service
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=sample-app
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.106.179.157
IPs:               10.106.179.157
Port:              http-port  8080/TCP
TargetPort:        http/TCP
Endpoints:         10.38.0.2:80,10.32.0.3:81
Session Affinity:  None
Events:            <none>


# 파드 IP 주소 확인
$ kubectl get pod -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP          NODE         NOMINATED NODE   READINESS GATES
sample-named-port-pod-80   1/1     Running   0          88s   10.38.0.2   k8s-node01   <none>           <none>
sample-named-port-pod-81   1/1     Running   0          88s   10.32.0.3   k8s-node02   <none>           <none>


$ curl 10.106.179.157:8080
Host=10.106.179.157  Path=/  From=sample-named-port-pod-81  ClientIP=10.40.0.0  XFF=


$ curl 10.106.179.157:8080
Host=10.106.179.157  Path=/  From=sample-named-port-pod-80  ClientIP=10.40.0.0  XFF=


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

쿠버네티스는 서비스 디스커버리 기능을 서비스가 제공하고 있다.

  • 쿠버네티스에서 서비스 디스커버리란 서비스에 속해 있는 파드를 보여주거나 서비스명에서 엔드포인트 정보를 반환하는 것을 말한다.

  • 서비스 디스커버리 방법은 크게 세 가지가 있으며, 그 중 DNS를 사용한 서비스 디스커버리는 클러스터안에 있는 DNS 서버(클러스터 내부 DNS)에 자동으로 등록되는 서비스 엔드포인트 정보를 사용한다.

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

  • 파드 내부에서는 환경 변수에서도 같은 네임스페이스 서비스를 확인할 수 있다.(“-“이 포함된 서비스명은 “_“로 변경된 후 대문자로 변환됨)

  • 그러나 파드가 생성된 서비스 생성이나 삭제에 따라 변경된 환경 변수가 먼저 기동한 파드 환경에는 자동으로 다시 등록되지 않기 때문에 예기치 못한 장애로 이어질 수 있기 때문에 먼저 생성한 파드를 다시 생성하자.

# 환경 변수에 등록된 서비스 정보 확인
$ kubectl exec -it sample-deployment-79448fcf48-2bgkv -- env | grep -i kubernetes
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
  • 아래 예제와 같이 파드의 spec.enableServiceLinks를 false(기본값 true)로 설정하면 환경 변수 추가를 비활성화할 수 있다.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment-servicelinks
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      enableServiceLinks: false
      containers:
      - name: nginx-container
        image: amsy810/echo-nginx:v2.0

(2) DNS A 레코드를 사용한 서버스 디스커버리

  • 다른 파드에서 서비스로 할당된 엔드포인트에 접속하려면 IP 주소를 사용하는 방법 외에도 자동 등록된 DNS 레코드를 사용할 수 있다.

  • 할당된 IP 주소는 서비스를 생성할 때마다 변경되기에 IP 주소를 송신 측 컨테이너에 명시적으로 설정하면 변경될 때마다 변경해야 하기 떄문에 컨테이너를 변경 불가능(immutable) 상태로 유지할 수 없다.

  • 그런 점에서 DNS명을 사용하면, 서비스 재생성에 따른 IP 주소 변경도 신경 쓸 필요가 없어진다.

$ kubectl get all
NAME                                     READY   STATUS    RESTARTS   AGE
pod/sample-deployment-79448fcf48-2bgkv   1/1     Running   0          17m
pod/sample-deployment-79448fcf48-bxj9t   1/1     Running   0          17m
pod/sample-deployment-79448fcf48-qzwgv   1/1     Running   0          17m

NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/sample-clusterip   ClusterIP   10.103.230.182   <none>        8080/TCP   78s

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/sample-deployment   3/3     3            3           17m

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/sample-deployment-79448fcf48   3         3         3       17m


# 일시적을 파드를 기동하여 컨테이너 내부에서 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-79448fcf48-2bgkv  ClientIP=10.32.0.1  XFF=
pod "testpod" deleted

$ 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-79448fcf48-bxj9t  ClientIP=10.32.0.1  XFF=
pod "testpod" deleted
  • 이는 sample-clusterip의 이름 해석이 수행되고 해당 주소로 요청이 발송되도록 되어 있기 때문이며, 실제로 등록된 정식 FQDN는 “서비스명.네임스페이스명.svc.cluster.local”로 되어 있다.
# 일시적으로 파드를 기동하여 컨테이너 내부에서 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

(생략)

;sample-clusterip.default.svc.cluster.local. IN A

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


$ kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig -x  10.103.230.182

(생략)

;182.230.103.10.in-addr.arpa.   IN      PTR

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

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

  • SRV 레코드는 포트명과 프로토콜을 사용하여 서비스를 제공하는 포트 번호를 포함한 엔드포인트를 DNS로 해석하는 구조다.

  • 레코드 형식은 다음과 같다. (서비스의 포트명과 프로토콜에는 접두사에 “_“가 포함되므로 주의)

    • “_서비스의 포트명._프로토콜.서비스명.네임스페이스명.svc.cluster.local
cat <<EOF > sample-clusterip.yaml
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
EOF
# 일시적으로 파드를 기동하여 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 0 100 8080 sample-clusterip.default.svc.cluster.local.

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


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

  • 클러스터 내부 DNS에는 서비스 엔드포인트에 대한 레코드가 저장되어 있고, 클러스터 내부 DNS에 질의하여 서비스 디스커버리에서 사용된다.

  • 물론 클러스터 내부 DNS에는 내부용 레코드만 등록되어 있으므로 이외의 레코드는 외부 DNS에 재귀 질의를 해야 한다.

1


📜 노드 로컬 DNS 캐시

  • 클러스터 내부 DNS에 이름 해석을 보냈지만, 대규모 클러스터에서 성능 향상을 위해 각 노드의 로컬 DNS 캐시 서버를 포함하는 구조가 있다.

  • 이 기능을 활성화한 환경에서 파드의 질의 대상은 같은 노드에 로컬 DNS 캐시 서버가 된다.

2


KUBERNETES 카테고리 내 다른 글 보러가기

댓글 남기기