코알못

[kubernetes] Service 소개 및 ClusterIP로 Pod 노출 실습 본문

ETC

[kubernetes] Service 소개 및 ClusterIP로 Pod 노출 실습

코린이s 2023. 5. 27. 21:05
728x90

클러스터에 구축한 파드에 접근하기 위해서는 어떻게 해야 할까 ?

Pod IP는 클러스터 내부에서만 접근 가능하므로 외부에서 접근이 불가능하다. 우리는 이를 위해 Service 오브젝트를 사용하면 된다!

서비스 오브젝트를 이용하면 파드 집합에 대해 단일 엔드 포인트로 접근 가능하며 로드 밸런서 기능을 이용할 수 있다!

주의 사항은 pod보다 서비스가 먼저 생성 되어야하고 같은 네임스페이스에 있는 파드만 관리한다.

Service 오브젝트의 타입은 세가지로 아래와 같으며 목적에 맞게 사용하면 된다.

서비스 타입 설명
ClusterIP 외부에서 접근 불가능
Internal IP만 할당, External IP 할당 받지 않음
내부 통신 목적시에 사용
internal IP를 클러스터 내부에서 호출하면 적합한 파드로 포워딩 해줌
NodePort 외부에서 접근 가능
Internal IP 할당, External IP 할당 받지 않으나 NodePort를 할당 받음
외부 통신 목적시에 사용
노드 IP, NodePort 호출시 서비스 internal IP 로 전달되며 해당 서비스에서 적합한 파드로 포워딩 해줌
노드 IP를 직접 호출 하므로 해당 노드가 죽었을때 서비스 불가능 > LoadBalancer 이용!
LoadBalancer 외부에서 접근 가능
Internal IP 할당, External IP 할당 받음, NodePort 할당 받음
외부 통신 목적시에 사용
서비스 External IP 호출시 적합한 노드(노드 IP를 호출 하지 않으며, 정상 노드로 호출한다.)의 NodePort를 통해 서비스 internal IP 로 전달되며 해당 서비스에서 적합한 파드로 포워딩 해줌

서비스 오브젝트는 관련된 파드 항목에 대한 Endpoints(매핑된 파드 정보) 리소스를 구성한다. 이를 이용하여 서비스 구성을 정상적으로 했는지 확인 가능하다.

실습을 위해 네임스페이스 snackbar를 생성한다.

$ kubectl create namespace snackbar
namespace/snackbar created

order 서비스 오브젝트, 해당 오브젝트에 매핑되는 order Deployment 생성하고

payment  서비스 오브젝트, 해당 오브젝트에 매핑되는 payment Deployment 생성하며

모든 오브젝트는 snackbar 네임스페이스에 생성하도록 한다.

서비스 오브젝트의 경우 아래와 같이 yaml 파일을 작성하며 spec type 을 ClusterIP 로 지정한다.

# service.yaml

apiVersion: v1
kind: Service
metadata: 
  name: order
  namespace: snackbar
  labels:
    service: order
    project: snackbar
spec:
  type: ClusterIP
  selector:
    service: order
    project: snackbar
  ports:
  - port: 80
    targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: payment
  namespace: snackbar
  labels:
    service: payment
    project: snackbar
spec:
  type: ClusterIP
  selector:
    service: payment
    project: snackbar
  ports:
  - port: 80
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order
  namespace: snackbar
  labels:
    service: order
    project: snackbar
spec:
  replicas: 2
  selector:
    matchLabels:
      service: order
      project: snackbar
  template:
    metadata:
      labels:
        service: order
        project: snackbar
    spec:
      containers:
      - name: order
        image: yoonjeong/order:1.0
        ports:
        - containerPort: 8080
        resources:
          limits:
            memory: "64Mi"
            cpu: "50m"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment
  namespace: snackbar
  labels:
    service: payment
    project: snackbar
spec:
  replicas: 2
  selector:
    matchLabels:
      service: payment
      project: snackbar
  template:
    metadata:
      labels:
        service: payment
        project: snackbar
    spec:
      containers:
      - name: payment
        image: yoonjeong/payment:1.0
        ports:
        - containerPort: 8080
        resources:
          limits:
            memory: "64Mi"
            cpu: "50m"

클러스터에 적용한다.

$ kubectl apply -f service.yaml
service/order created
service/payment created
deployment.apps/order created
deployment.apps/payment created

-n 은 네임스페이스내에서 확인하는 명령어로 snackbar 네임스페이스에 해당하는 모든 오브젝트를 불러온다.

모두 정상적으로 기동 되었다.

$ kubectl get all -n snackbar
NAME                           READY   STATUS    RESTARTS   AGE
pod/order-55468df7b9-bxxfk     1/1     Running   0          3m46s
pod/order-55468df7b9-r86z4     1/1     Running   0          3m46s
pod/payment-5cb8974b5f-gq287   1/1     Running   0          2m34s
pod/payment-5cb8974b5f-kgwcj   1/1     Running   0          2m21s

NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/order     ClusterIP   10.104.11.12   <none>        80/TCP    8m37s
service/payment   ClusterIP   10.104.5.184   <none>        80/TCP    8m36s

NAME                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/order     2/2     2            2           3m48s
deployment.apps/payment   2/2     2            2           3m47s

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/order-55468df7b9     2         2         2       3m49s
replicaset.apps/payment-5cb8974b5f   2         2         2       2m37s

svc 라는 명령어로 서비스 오브젝트를 조회하며 External IP 가 없고 설정한 80포트로 활성화 되었다.

$ kubectl get svc order -o wide -n snackbar
NAME    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE     SELECTOR
order   ClusterIP   10.104.11.12   <none>        80/TCP    9m31s   project=snackbar,service=order
$ kubectl get svc payment -o wide -n snackbar
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE     SELECTOR
payment   ClusterIP   10.104.5.184   <none>        80/TCP    9m54s   project=snackbar,service=payment

엔드포인트의 경우 아래와 같이 확인하며 해당 서비스에 연결된 파드 IP, PORT 정보가 나온다.

파드 IP 를 확인하여 정상적으로 매핑 되었는지 확인하면 정상적으로 매핑 되었다.

$ kubectl get endpoints order -n snackbar
NAME    ENDPOINTS                           AGE
order   10.100.0.31:8080,10.100.3.41:8080   10m
$ kubectl get endpoints payment -n snackbar
NAME      ENDPOINTS                           AGE
payment   10.100.2.36:8080,10.100.3.43:8080   10m

$ kubectl get pod -o wide -n snackbar
NAME                       READY   STATUS    RESTARTS   AGE     IP            NODE                                           NOMINATED NODE   READINESS GATES
order-55468df7b9-bxxfk     1/1     Running   0          6m16s   10.100.3.41   gke-corin-cluster-default-pool-d2d86113-xq03   <none>           <none>
order-55468df7b9-r86z4     1/1     Running   0          6m16s   10.100.0.31   gke-corin-cluster-default-pool-d2d86113-w610   <none>           <none>
payment-5cb8974b5f-gq287   1/1     Running   0          5m4s    10.100.3.43   gke-corin-cluster-default-pool-d2d86113-xq03   <none>           <none>
payment-5cb8974b5f-kgwcj   1/1     Running   0          4m51s   10.100.2.36   gke-corin-cluster-default-pool-d2d86113-zy02   <none>           <none>

아래와 같이 IP 만 결과를 받아볼 수 있다. 

$ kubectl get svc order -o jsonpath="{.spec.clusterIP}" -n snackbar
10.104.11.12

해당 클러스터 IP로 호출하면 Cluster IP 이기에 외부에서 바로 통신 되지 않는다.

$ curl 10.104.11.12

아래와 같이 포트 포워딩을 이용하여 파드에 접근하여 서비스 오브젝트 문제가 아닌것을 확인해보자!

$ kubectl port-forward service/order 8080:80 -n snackbar
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

정상적으로 데이터 나오는 부분 확인 할 수 있다. (서비스 오브젝트에 연결 했음에도 같은 파드 IP만 접근하는것을 볼 수있다.)

$ curl localhost:8080
Welcome to Snackbar!
Order what you want!

===== Host Info =====
HostIP: 10.100.0.31
HostName: order-55468df7b9-r86z4

$ curl localhost:8080
Welcome to Snackbar!
Order what you want!

===== Host Info =====
HostIP: 10.100.0.31
HostName: order-55468df7b9-r86z4

$ curl localhost:8080
Welcome to Snackbar!
Order what you want!

===== Host Info =====
HostIP: 10.100.0.31
HostName: order-55468df7b9-r86z4

이제 부터 파드간 통신하는 실습을 진행해보도록 하자 ! 

방법은 세가지가 있다.

  1. 서비스 IP, PORT 직접 호출 
  2. 서비스 오브젝트가 파드에 설정한 환경 설정 (서비스 생성전에 만들어진 파드에는 환경설정이 적용되지 않는다.)
  3. DNS (서비스 이름으로 호출)

1번의 경우 파드 접속하여 서비스 IP,PORT 로 직접 호출하는것으로 아래와 같이 간단하게 가능하다.

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar -- curl [서비스클러스터IP]:[서비스포트]
Welcome to Snackbar!
Order First Before Payment!

===== Host Info =====
HostIP: 10.100.3.43
HostName: payment-5cb8974b5f-gq287

2번인 서비스 오브젝트가 파드에 설정한 환경 설정 값으로 파드 통신 방법을 알아보자!

서비스 생성 시점 이후에 생성된 파드에 해당 서비스에 대한 IP, PORT 가 설정 파일에 존재 한다.

이미 생성 되어있는 파드의 경우 수정하지 않는점을 주의하자!

우선 파드 정보를 보고 order 의 파드명을 복사하다.

$ kubectl get pod -n snackbar
NAME                       READY   STATUS    RESTARTS   AGE
order-55468df7b9-bxxfk     1/1     Running   0          19m
order-55468df7b9-r86z4     1/1     Running   0          19m
payment-5cb8974b5f-gq287   1/1     Running   0          18m
payment-5cb8974b5f-kgwcj   1/1     Running   0          17m

order 파드에서 payment 서비스의 호스트 IP, PORT를 얻을 수 있다.

[서비스명]_SERVICE_HOST, [서비스명]_SERVICE_PORT로 설정 된다.

$ kubectl exec order-55468df7b9-bxxfk -n snackbar -- env | grep PAYMENT
PAYMENT_PORT_80_TCP_ADDR=10.104.5.184
PAYMENT_PORT_80_TCP_PORT=80
PAYMENT_PORT_80_TCP_PROTO=tcp
PAYMENT_SERVICE_HOST=10.104.5.184
PAYMENT_SERVICE_PORT=80
PAYMENT_PORT=tcp://10.104.5.184:80
PAYMENT_PORT_80_TCP=tcp://10.104.5.184:80

payment 파드에서도 동일하게 order 서비스에 대한 HOST, PORT 정보를 확인 할 수 있다.

$ kubectl exec payment-5cb8974b5f-gq287 -n snackbar -- env | grep ORDER
ORDER_PORT_80_TCP=tcp://10.104.11.12:80
ORDER_SERVICE_PORT=80
ORDER_PORT_80_TCP_PROTO=tcp
ORDER_PORT_80_TCP_ADDR=10.104.11.12
ORDER_PORT=tcp://10.104.11.12:80
ORDER_SERVICE_HOST=10.104.11.12
ORDER_PORT_80_TCP_PORT=80

이제 order 파드에 접속하여 sh 를 실행하며 curl 로 서비스 환경 변수 불러와서 통신하면 정상적으로 응답값을 불러 올 수 있다.

서비스의 경우 로드밸런싱이 되므로 아래와 같이 호출마다 파드 IP 가 다르다.

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/usr/src/app # curl $PAYMENT_SERVICE_HOST:$PAYMENT_SERVICE_PORT
Welcome to Snackbar!
Order First Before Payment!

===== Host Info =====
HostIP: 10.100.2.36
/usr/src/app # curl $PAYMENT_SERVICE_HOST:$PAYMENT_SERVICE_PORT
Welcome to Snackbar!
Order First Before Payment!

===== Host Info =====
HostIP: 10.100.3.43

이제 3번 방법인 서비스 이름을 통해 호출하는 방법 (쿠버네티스 DNS)을 알아보자!

order 파드에서 curl 서비스명:서비스포트로 호출해보면 정상적으로 응답을 받을 수 있다.

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar -- curl payment:80
Welcome to Snackbar!
Order First Before Payment!

===== Host Info =====
HostIP: 10.100.3.43
HostName: payment-5cb8974b5f-gq287

어떻게 서비스명으로 통신이 되는지 확인하기 위해 우선 host 파일 설정 되어있는지 보면 안되어 있다.

호스트 설정으로 되는것이 아니였다..! 그럼 어떤것일까?

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar -- cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
fe00::0	ip6-mcastprefix
fe00::1	ip6-allnodes
fe00::2	ip6-allrouters
10.100.3.41	order-55468df7b9-bxxfk

DNS 설정을 보면 아래와 같이 설정 되어 있다.

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar -- cat /etc/resolv.conf
search snackbar.svc.cluster.local svc.cluster.local cluster.local us-central1-a.c.indigo-kiln-385910.internal c.indigo-kiln-385910.internal google.internal
nameserver 10.104.0.10
options ndots:5

nameserver는 컨테이너에서 사용할 DNS 서버 주소이며

search는 클러스터의 기본 도메인으로 DNS 서버에서 호스트 네임을 찾을때 사용할 검색 리스트이다.

예를 들어 payment:80 호출하면 payment.snackbar.svc.cluster.local 검색, payment.svc.cluster.local 검색 등 이런식으로 검색한다.

서비스에 대한 도메인은 [서비스명].[네임스페이스명].svc.cluster.local 이므로 첫번째에서 서비스 IP 를 얻을 수 있다.

만약 호출을 하는 파드와 호출 받는 파드가 네임스페이스가 다르다면 payment.<호출받는파드의네임스페이스>:80 으로 호출하면 2번째인 svc.cluster.local에서 서비스 IP 를 얻을수 있다. (pyment.<호출받는네임스페이스>.svc.cluster.local 이 되니까)

클러스터 구성시 기본으로 생성되는 네임 스페이스인 kube-system의 떠있는 서비스인 kube-dns의 IP 와 DNS 네임서버 IP와 일치한다.

$ kubectl get all -n kube-system | grep kube-dns
pod/kube-dns-5b5dfcd97b-js5rc                                 4/4     Running   30 (17d ago)   17d
pod/kube-dns-5b5dfcd97b-k7wzn                                 4/4     Running   0              17d
pod/kube-dns-autoscaler-5f56f8997c-gsckv                      1/1     Running   0              17d
service/kube-dns               ClusterIP   10.104.0.10     <none>        53/UDP,53/TCP   20d
deployment.apps/kube-dns                        2/2     2            2           20d
deployment.apps/kube-dns-autoscaler             1/1     1            1           20d
replicaset.apps/kube-dns-5b5dfcd97b                       2         2         2       20d
replicaset.apps/kube-dns-autoscaler-5f56f8997c            1         1         1       20d

서비스명으로 호출가능한것은 확인한 바와 같이 기본적으로 구성되는 쿠버네티스 DNS 서버에서 제공 하는것으로 서비스 IP를 확인할 필요 없으니 편하게 호출할 수 있다.

위에서 언급한 다른 네임스페이스의 파드와 통신하는 실습을 진행해보자!

우선 새로운 네임스페이스를 생성한다.

$ kubectl create namespace fancy-snackbar
namespace/fancy-snackbar created

새로운 네임스페이스 fancy-snackbar에 서비스와 deployment 생성하는 yaml 을 만들고

# service-v2.yaml

apiVersion: v1
kind: Service
metadata:
  name: delivery
  namespace: fancy-snackbar
  labels:
    service: deliverry
    project: snackbar
spec:
  type: ClusterIP
  selector:
    service: delivery
    project: snackbar
  ports:
  - port: 80
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: delivery
  namespace: fancy-snackbar
  labels:
    service: delivery
    project: snackbar
spec:
  replicas: 2
  selector:
    matchLabels:
      service: delivery
      project: snackbar
  template:
    metadata:
      labels:
        service: delivery
        project: snackbar
    spec:
      containers:
      - name: delivery
        image: yoonjeong/my-app:2.0
        ports:
        - containerPort: 8080
        resources:
          limits:
            memory: "64Mi"
            cpu: "50m"

클러스터에 적용한다.

$ kubectl apply -f service-v2.yaml
service/delivery created
deployment.apps/delivery created

모든 네임스페이스에서 검색하기 위해 --all-namespaces 옵션을 붙여 오브젝트를 확인한다.

$ kubectl get all -l project=snackbar --all-namespaces
NAMESPACE        NAME                           READY   STATUS    RESTARTS   AGE
fancy-snackbar   pod/delivery-6c8c4bd4-88dgq    1/1     Running   0          78s
fancy-snackbar   pod/delivery-6c8c4bd4-v4v6n    1/1     Running   0          78s
snackbar         pod/order-55468df7b9-bxxfk     1/1     Running   0          75m
snackbar         pod/order-55468df7b9-r86z4     1/1     Running   0          75m
snackbar         pod/payment-5cb8974b5f-gq287   1/1     Running   0          74m
snackbar         pod/payment-5cb8974b5f-kgwcj   1/1     Running   0          74m

NAMESPACE        NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
fancy-snackbar   service/delivery   ClusterIP   10.104.6.51    <none>        80/TCP    80s
snackbar         service/order      ClusterIP   10.104.11.12   <none>        80/TCP    80m
snackbar         service/payment    ClusterIP   10.104.5.184   <none>        80/TCP    80m

NAMESPACE        NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
fancy-snackbar   deployment.apps/delivery   2/2     2            2           80s
snackbar         deployment.apps/order      2/2     2            2           75m
snackbar         deployment.apps/payment    2/2     2            2           75m

NAMESPACE        NAME                                 DESIRED   CURRENT   READY   AGE
fancy-snackbar   replicaset.apps/delivery-6c8c4bd4    2         2         2       81s
snackbar         replicaset.apps/order-55468df7b9     2         2         2       75m
snackbar         replicaset.apps/payment-58c97cc4cd   0         0         0       75m
snackbar         replicaset.apps/payment-5cb8974b5f   2         2         2       74m

새로 생성한 delivery 엔드 포인트를 확인하여 파드가 정상 매핑 됐는지 확인한다.

$ kubectl get endpoints delivery -n fancy-snackbar
NAME       ENDPOINTS                           AGE
delivery   10.100.2.37:8080,10.100.3.44:8080   2m7s

order 파드에서 다른 네임스페이스인 delivery 서비스 클러스터 IP를 호출하면 정상적으로 호출 된다.

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar -- curl 10.104.6.51
Welcome to Version 2!

===== Host Info =====
HostIP: 10.100.2.37
HostName: delivery-6c8c4bd4-88dgq

이제 DNS 를 이용해보자!

위에서 설명한 바와 같이 네임스페이스가 다르면 search 항목에 해당 네임스페이스 관련 주소를 찾지 못하니 실패 하고

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar -- curl delivery
curl: (6) Could not resolve host: delivery
command terminated with exit code 6

아래와 같이 네임스페이스명을 정의하면 가능하다!

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar -- curl delivery.fancy-snackbar
Welcome to Version 2!
===== Host Info =====
HostIP: 10.100.3.44
HostName: delivery-6c8c4bd4-v4v6n%

추가적으로 order 파드는 delivery 서비스가 생성 되기전에 만들어졌기에 delivery 관련 서비스 환경 변수는 없다.

$ kubectl exec -it order-55468df7b9-bxxfk -n snackbar -- env | grep DELIVERY

환경 변수는 생성 시점에 따라서 없는 경우도 있기 때문에 dns 로 접근하거나 IP 로 통신하기로 하자!

이제 실습이 종료 되었으니 모두 삭제한다.

$ kubectl delete all -l project=snackbar --all-namespaces

끝!

728x90
Comments