[K8s]5-2. 서비스(Service)

[K8s]5-2. 서비스(Service)


이 글은 쿠버네티스 인 액션 이라는 책과 검색을 통해서 공부한 것을 정리합니다.

서비스

서비스 검색

서비스를 생성해서 포드에게 액세스할 수 있는 단일 IP 주소와 고정된 포트를 가질 수 있다. 서비스의 라이프사이클 동안 변경되지 않는다. 결국 서비스의 단일 IP 주소 및 고정 IP 주소를 통해서 포드에 항상 액세스할 수 있음.

쿠버네티스는 클라이언트의 포드가 서비스의 IP/포트 를 알아낼 수 있는 방법을 제공함.

(1) 환경 변수를 이용한 서비스 검색

  • 쿠버네티스는 포드가 시작되면, 그 순간에 존재하는 각 서비스를 가리키는 환경 변수 세트를 초기화 함.
  • 포드는 환경 변수를 조사해서 서비스의 IP 주소와 포트 번호를 알아낼 수 있음.
  • 환경변수 조사
➜ kubectl exec kubia-svc-client env

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-svc-client
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBIA_PORT_80_TCP_PROTO=tcp
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBIA_SERVICE_HOST=10.98.199.212 # '서비스' 의 클러스 IP
KUBIA_PORT_80_TCP_PORT=80 # 사용가능한 포트
(생략)

(2) DNS 를 통해서 IP를 찾는 방법

FQDN(정규화된 도메인 이름)을 통해서 액세스 가능하다

  • ex) backend-database.default.svc.cluster.local
  • backend-database 는 서비스 이름
  • default 는 서비스가 정의된 네임스페이스
  • svc.cluster.local 는 모든 클러스터의 로컬 서비스 이름에 해당하는 클러스터의 도메인 접두사
➜ kubectl get pods

NAME                   READY   STATUS    RESTARTS   AGE
kubia-svc-client       1/1     Running   0          150m
kubia-svc-not-client   1/1     Running   0          143m

➜ kubectl exec -it kubia-svc-client bash

root@kubia-svc-client:/# curl http://kubia
You've hit kubia-svc-not-client
root@kubia-svc-client:/# curl http://kubia.default
You've hit kubia-svc-not-client
root@kubia-svc-client:/# curl http://kubia.default.svc.cluster.local
You've hit kubia-svc-not-client

클러스터 외부 서비스에 연결

수동으로 서비스 엔드포인트 설정

  • external-service.yml
    1 apiVersion: v1
    2 kind: Service
    3 metadata:
    4   name: external-service
    5 spec:
    6   ports:
    7   - port: 80 # 80으로 들어오는 연결을 처리하는 external-service 이라는 서비스
    
  • external-service-endpoint.yml
  1 apiVersion: v1
  2 kund: Endpoints
  3 metadata:
  4   name: external-service # 엔드포인트 객체 이름은 서비스 이름과 매칭돼야
  5 subsets:
  6   - addresses:
  7     - ip: 11.11.11.11
  8     - ip: 22.22.22.22 # 엔드포인트 IP들
  9     ports:
 10       port: 80 # endPoint의 대상 포트 80 함
  • Endpoint의 객체 즉 name 은 서비스와 동일한 이름이어햐 함.

FQDN(정규화된 도메인 이름)으로 외부 서비스를 참조

  • exrernal-service-externalname.yml
  1 apiVersion: v1
  2 kind: Service
  3 metadata:
  4   name: external-service
  5 spec:
  6   type: ExternalName # 반드시 서비스 타입은 ExternalName으로 설정
  7   externalName: someapi.somecompany.com # 실제 서비스의 전체 도메인 주소
  8   ports:
  9   - port: 80
  • ExternalName 서비스는 DNS 레벨에서만 구현됨.
  • 서비스로 연결하는 클라이언트는 서비스 프록시를 통하지 않고, 직접 외부 서비스로 연결할 것이다.

외부 서비스에서 외부 클라이언트로

설명

  • 예를 들어서 외부 서비스(웹서버)를 외부에 노출해서 외부 클라이언트가 접근할 수 있도록 함.

NodePort

  • 쿠버네티스가 모든 노드를 대상으로 포트를 예약
  • 그리고 들어오는 접속을 서비스 각 부분의 포트로 전송함.
  • ClusterIP(오직 클러스터 내부에서만 접근)와 큰 차이는 내부 클러스터 IP를 통해 액세스도 되고, 노드의 IP와 예약된 포트를 통해서도 액세스 된다.

  • kubia-svc-nodeport.yml
  1 apiVersion: v1
  2 kind: Service
  3 metadata:
  4   name: kubia-nodeport
  5 spec:
  6   type: NodePort
  7   ports:
  8   - port: 80 # 서비스의 내부 클러스터 IP 포트
  9     targetPort: 8080 # 이 서비스를 지원하는 포드의 대상 포트
 10     nodePort: 30123 # 이 서비스는 각 클러스터 노드를 포트 30123을 통해 액세스함.
 11   selector:
 12     app: kubia
  • $ get svc kubia-nodeport
    NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
    kubia-nodeport   NodePort   10.98.18.230   <none>        80:30123/TCP   26s
    

실습

  • 노드의 IP를 알아내고 > $ curl http://알아낸노드IP:30123

LoadBalancer

  • NodePort에 로드밸런서 기능을 넣은 서비스로 본다.
  • 대게 많은 쿠버네티스 클러스터는 로드 밸런서를 자동으로 프로비저닝하는 기능을 지원함.
  • 그래서 쿠버네티스 클러스터 기능을 지원한다면 NodePort 서비스보다 LoadBalancer 서비스를 생성하여 자동으로 프로비전 할 수 있음.

  • kubia-svc-loadbalancer.yml
  1 apiVersion: v1
  2 kind: Service
  3 metadata:
  4   name: kubia-loadbalancer
  5 spec:
  6   type: LoadBalancer
  7   ports:
  8   - port: 80
  9     targetPort: 8080
 10   selector:
 11     app: kubia
  • NodePort와 다르게 특이점은 노드를 위한 포트를 설정하지 않음 쿠버네티스가 알아서 선택하게 함.

실습

  • $ kubectl get svc kubia-loadbalancer > EXTERNAL-IP
  • $ curl http://{EXTERNAL-IP}

인그레스(Ingress)

  • 여러개의 서비스를 하나의 인그레스로 외부에 노출 할 수 있음.

  • 실습을 위해서 minikube를 사용하고 있으면,
➜ minikube addons enable ingress
✅  ingress was successfully enabled

➜ minikube addons list
- addon-manager: enabled
- dashboard: enabled
- default-storageclass: enabled
- efk: disabled
- freshpod: disabled
- gvisor: disabled
- heapster: disabled
- ingress: enabled
- logviewer: disabled
- metrics-server: disabled
- nvidia-driver-installer: disabled
- nvidia-gpu-device-plugin: disabled
- registry: disabled
- registry-creds: disabled
- storage-provisioner: enabled
- storage-provisioner-gluster: disabled
  • kubia-ingress.yml
    1 apiVersion: extension/v1beta1
    2 kind: Ingress
    3 metadata:
    4   name: kubia
    5 spec:
    6   rules:
    7   - host: kubia.example.com
    8     http:
    9       paths:
     10       - path: / # kubia.example.com/ 서비스로 라우팅됨
     11         backend:
     12           serviceName: kubia-nodeport
     13           servicePort: 80
     14      #- path: /foo # kubia.example.com/foo 서비스로 라우팅됨
     15      #  backend:
     16      #    serviceName: kubia-foo
     17      #    servicePort: 80
    
  • kubia.example.com 으로 전달되는 요청은 80포드의 kubia-nodeport 라는 서비스로 보내짐.
  • HTTP 요청이 인그레스 컨트롤러를 통해 수신되는 규칙임.

  • $ kubectl get ingress
NAME    HOSTS               ADDRESS     PORTS   AGE
kubia   kubia.example.com   10.0.2.15   80      174m
  • etc/hosts 파일 수정. 위에서 얻은 ADDRESS
    10.0.2.15 kubia.example.com

readiness Probe

  • 포드를 서비스의 엔드포인트에 포함시킬지의 여부를 결정하기 위해서 사용한다.
  • 레디네스 프로브는 주기적으로 호출이 되어서, 포드가 클라이언트의 요청의 수락을 받아드리는 여부를 결정함.
  • 결국 클라이언트의 요청에 대한 체크리스트로 이해하는 것이 좋음.
  • 각 컨테이너에 국한된 것.

liveness vs readiness

  • 라이브니스 프로브는 주기적으로 호출하여 상태가 좋지 않은 컨테이너를 종료하고 새로운 것으로 교체하여 포드의 상태를 좋게 유지함.
  • 레디니스 프로브는 오직 포드가 요청을 수신할 수 있는 환경인지를 파악하여, 포드가 요청을 수신할 수 있는 환경이 됐을 때 수신한다.

  • 아래는 readiness의 종류인데 liveness 와 동일하므로 실습은 생략

(1) Exec Probe

(2) HTTP Get Probe

(3) TCP Socket Probe

주의 할 점

  • 항상 레디네스(readiness Probe)를 정의 할 것.
    • 정의 하지 않으면 거의 즉시 서비스의 엔드포인트가 되게 된다.
    • 간단한 HTTP 요청을 보내더라도 레디네스 프로브를 항상 정의해야 함.

헤드리스(headless) 서비스

  • 클라이언트가 모든 포드에 연결해야 한다면? = 대개 클러스터링 구성의 경우..ex)카프카, ignite 등..
  • 이전까지는 서비스 연결은 임의로 선택된 하나의 포드로 전달되고, 서비스는 안정적인 IP 주소를 제공하는 방법을 사용했다.
  • 즉, 일반적으로 쿠버네티스는 클라이언트가 DNS 룩업을 통해서 포드의 IP를 찾는 것을 허용한다. 대개 DNS 룩업을 실행하면 서버는 단일 IP(ClusterIP)를 반환한다.
  • 그러나 ClusterIP : None 으로 설정하게 되면(이말은 쿠버네티스에게 클러스터 IP가 필요없다고 말하는 것) 포드의 IP 목록을 전부 알려줄 것임. 따라서 클라이언트는 한 개, 혹은 그 이상 혹은 전부를 연결할 수 있다.

헤드리스 서비스 실습

  • kubia-svc-headless.yml
    1 apiVersion: v1
    2 kind: Service
    3 metadata:
    4   name: kubia-headless
    5 spec:
    6   clusterIP: None
    7   ports:
    8   - port: 80
    9     targetPort: 8080
     10   selector:
     11     app: kubia
    
➜  kubectl create -f kubia-svc-headless.yml
service/kubia-headless created
➜ kubectl describe svc kubia-headless
Name:              kubia-headless
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=kubia
Type:              ClusterIP
IP:                None
Port:              <unset>  80/TCP
TargetPort:        8080/TCP
Endpoints:         172.17.0.2:8080
Session Affinity:  None
Events:            <none>
  • dns 룩업을 위한 파드 생성
    ➜ kubectl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command -- sleep infinity
    
➜ kubectl exec dnsutils nslookup kubia-headless
(생략)

Name:	kubia-headless.default.svc.cluster.local
Address: 172.17.0.2
  • 여기서 주목해야하는 것은 Address: 172.17.0.2 이다. 이는 해당 IP가 준비되었다고 하는 것이며, 나는 어떤 pod 가 이 ip에 해당하는지 살펴보려고 한다.
  • 그리고 따로 서비스에 아래와 같이 설정하지 않으면 현재 running 상태의 준비된 pod만 노출 된다는 것을 명심하자.
(구 버전)
kind: Service
metadata:
  annotations:
    service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"

(신 버전)
spec 정의의 publishNotReadyAddresses 의 서비스의 스펙 필드를 공식문서에서 살펴본다.

pod 상태 자세히보기

➜ get po -o wide
NAME                   READY   STATUS    RESTARTS   AGE     IP           NODE       NOMINATED NODE   READINESS GATES
dnsutils               1/1     Running   0          6m32s   172.17.0.9   minikube   <none>           <none>
kubia-svc-client       1/1     Running   2          9d      172.17.0.4   minikube   <none>           <none>
kubia-svc-not-client   1/1     Running   2          9d      172.17.0.2   minikube   <none>           <none>
  • kubia-svc-not-client 현재 running 상태의 pod가 해당 ip임을 알 수 있는데…. 즉. DNS 서버는 kubia-headless.default.svc.cluster.local FQDN을 위해서 172.17.0.2kubia-svc-not-client 의 준비된 POD를 리포팅한 것이다.

결론

  • 클라이언트 관점에서 엔드포인트 파드에 연결하는건 다르지 않음.
  • 논헤드리스 서비스나 헤드리스 서비스나 결국에는 서비스의 DNS 이름으로 연결하고 최종적으로 포드에 접속하게 된다.
  • 그러나 헤드리스 서비스 에서 DNS는 포드의 IP를 직접 반환. 위처럼 172.17.0.2 라는 IP를 반환하기 때문에 클라이언트는 서비스 프록시 대신 포드에 직접 연결한다.

번외. 논헤드리스 서비스(서비스의 ClusterIP)가 반환하는 IP를 알아보자.

➜  minikube kubectl exec dnsutils nslookup kubia
(생략)

Name:	kubia.default.svc.cluster.local
Address: 10.98.199.212

10.98.199.212 이 녀석에 대해서 알아보자

➜ kubectl get po -o wide
NAME                   READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
dnsutils               1/1     Running   0          12m   172.17.0.9   minikube   <none>           <none>
kubia-svc-client       1/1     Running   2          9d    172.17.0.4   minikube   <none>           <none>
kubia-svc-not-client   1/1     Running   2          9d    172.17.0.2   minikube   <none>           <none>

10.98.199.212 이 IP 녀석이 안보이는데?

  • 반환된 IP는 아래와 같이 kubia name의 서비스클러스터 IP 였다.
➜  kubectl get svc -o wide
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE    SELECTOR
kubernetes       ClusterIP   10.96.0.1       <none>        443/TCP        11d    <none>
kubia            ClusterIP   10.98.199.212   <none>        80/TCP         9d     app=kubia
kubia-headless   ClusterIP   None            <none>        80/TCP         28m    app=kubia
kubia-nodeport   NodePort    10.98.18.230    <none>        80:30123/TCP   5d1h   app=kubia

서비스의 헤드리스와 논헤드리스 일 때 pod의 ip 직접노출과 서비스 clusterIp 차이를 알아보았다!