Kubernetes를 배워보자 5일차 - Service, NodePort, ClusterIP

0

kubernetes

목록 보기
5/13

Services

pod를 end user에게 접근 가능하게하고, 다른 pod들끼리 통신하도록 만들고 싶을 것이다. 이러한 것들을 가능하게 해주는 것이 바로 kubernetes의 Services이다.

pod ip 할당에 대한 이해

kubernetes는 pod가 배포되면 자동적으로 cluster에 사용할 수 있는 private IP를 할당한다. 즉, cluster의 모든 pod들은 제 각기의 private IP가 있다는 것이다.

nginx pod를 간단히 만들어보고 해당 pod에 private IP가 있는 지 확인하도록 하자.

  • new-nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: new-nginx-pod
spec:
  containers:
    - image: nginx:latest
      name: new-nginx-container
      ports:
        - containerPort: 80

이전에 우리가 배포했던 pod들과 특별히 다를 바가 없다. 오직 ports로 외부에 노출된 containerPort80이라는 것 밖에 없다.

이제 해당 pod를 실행시켜보도록 하자.

kubectl create -f ./new-nginx-pod.yaml 
pod/new-nginx-pod created

command가 실행되고, pod가 만들어졌다면 kubernetes cluster내부에 사용되는 private IP가 pod에 할당될 것이다.

kubectl get pods -o wide로 살펴보도록 하자.

kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP               NODE                     NOMINATED NODE   READINESS GATES
new-nginx-pod   1/1     Running   0          98s   192.168.33.214   gy95park-400tca-400sca   <none>           <none>

IP부분을 보면 뭔가가 할당된 것을 볼 수 있다. 해당 IP는 cluster 내부에만 존재하며 public internet에는 존재하지 않는다.

이 네트워크는 AWS가 될 수도, GCP가 될 수도, VPC가 될 수도, kubernetes에서 사용하는 기본 flat network가 될 수도 있다. 이는 어떤 클라우드 플랫폼을 쓰냐 또는 CNI(container network interface)에 따라 다르다.

중요한 것은 pod가 만들어지자마자 private ip를 할당받는다는 것이다. 재밌는 것은 pod를 다시 내렸다가 올리면 새로운 IP를 할당받는다는 것이다.

kubectl delete pod new-nginx-pod 
pod "new-nginx-pod" deleted

kubectl create -f ./new-nginx-pod.yaml 
pod/new-nginx-pod created

IP를 확인해보면 다르다는 것을 볼 수 있다.

kubectl get pod -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP               NODE                     NOMINATED NODE   READINESS GATES
new-nginx-pod   1/1     Running   0          19s   192.168.33.195   gy95park-400tca-400sca   <none>           <none>

사실 엄밀하게 이전에 내려간 pod와 이번에 새로 올라간 pod는 완전히 다른 pod이다. 따라서, kubernetes는 pod의 IP를 다르게 설정한 것이다. 따라서, kubernetes에서 할당한 private IP는 고정된 IP가 아니면 이를 기반으로 어떠한 스크립트를 쓸 수가 없다.

MSA서비스들은 대부분ㅇ HTTP protocol을 하는데 이는 TCP/IP에 의존하는 protocol이다. 그러나, pod의 IP에 직접 의존하면 안되는데, pod는 쉽게 삭제되고 다시 생성되며 rescheduled되기 때문이다. 따라서, IP를 고정된 IP로 하드코딩하는 스크립트를 만드는 것은 좋지 않다.

그렇다면 pod에 HTTP와 같은 TCP/IP protocol로 접근하려면 어떻게해야할까? 이 때 사용되는 것이 바로 Service이다.

Service는 cluster의 중간 매개자 역할을 하는데, Service는 자주 삭제되고 재생성되지 않는다. 사실상 계속해서 다운되지 않고 존재해있는데, 마치 proxy같은 역할을 한다. kubernetes에서는 Service를 통해서 네트워킹도 가능하며 로드밸런스도 가능하다.

pod에 Service가 어떻게 트래픽을 라우팅하는 지에 대한 이해

Service는 pod나 container같은 것이 아니라, resource로 kuberntes cluster 자체에서 사용한다. Service는 적절한 IPTABLES를 만들어 트래픽이 적절하게 backend pod로 리다이렉팅하기위해 사용된다.

pod가 생성되자마자 dynaimic IP를 받듯이, 각 Service들은 static DNS를 가진다. 이 static DNS entry는 cluster에 service가 있는 한 절대 변하지 않는다. 각 Service에게 어떤 pod에 트래픽을 흘려보낼 지 결정할 수 있다.

다른 말로, service를 pod에 할당한 static DNS name으로 생각할 수 있다. 이 방식으로 pod에 접근할 static하고 안전한 TCP/IP를 얻은 것이다. 앞에서 말한 것을 그림으로 표현하면 다음과 같다.

[사진1]

Service를 만들 때 name을 주어야하는데, 이 이름은 kubernetes에서 DNS name를 만들기 위해 사용된다. 이 DNS name은 cluster 내의 모든 pod들이 호출할 수 있는 이름이 된다. 이 DNS entry는 Service로 확인되며, cluster에 저장된다. 즉, Service는 DNS name을 갖고 이 이름을 토대로 pod를 연결해주는 것이다. pod를 연결할 때는 이전에 배운 lable을 selector로 사용한다.

모든 설정이 끝나면 server는 request를 받고 이를 pod로 forward하고 응답을 받아 클라이언트에게 전송한다.

kubernetes에서의 라운드 로빈 load balancing

kubernetes service는 일반 적절하게 설정되면 하나 또는 그 이상의 pod들을 외부에 노출시킬 수 있다. 같은 service에 여러 개의 pod들이 노출되면 요청들은 균등하게 로드밸런싱되는데, 이 때 사용하는 알고리즘이 라운드 로빈이다.

[사진2]

application을 스케일링하는 것이 매우 쉬운데, service 뒤의 pod를 하나 더 추가하면 된다.

이렇게 Service는 하나의 proxy 역할을 하여 하나에 몰리는 request의 양을 적절하게 분산할 수 있다. 라운드 로빈이기 때문에, 하나의 service에 4개의 pod가 붙어있다면 한 pod의 request를 25%만 받도록 할 수 있는 것이다.

어떻게 DNS 이름이 만들어지는 지에 대한 이해

Service에 접근하는 방식은 두가지가 있다.

  1. unique한 IP address
  2. statc하고 변하지 않는 DNS name

보통 DNS name으로 하며, 몇 가지 법칙이 있다.

Service에 대한 DNS name은 Service의 이름으로부터 만들어진다. 가령, Service이름이 my-app-service라면 이것의 DNS는 my-app-sercie.default.svc.cluster.local이 된다.

약간 복잡한데, 자세히 정리하면 다음과 같다.
1. my-app-service: service에 부여한 이름으로 DNS name에 가장 먼저 나온다.
2. default: kubernetes namespace으로, service가 선언된 공간을 말한다. 아직 namespace를 사용하지 않았지만, 설정하지 않으면 기본적으로 default로 설정된다.
3. svc: service의 축약형이다.
4. cluster.local: cluster.local은 가장 상위 level의 DNS이다. 이 이름은 kubernetes cluster에서 사용할 수 있으며, 같은 cluster 내의 pod들끼리의 통신에서만 사용된다.

my-app-servicedefault는 각 Service마다 다른 부분이고, svccluster.local은 고정된 부분이다. 이렇게 만들어진 DNS 이름으로 cluster에서 curl이나 wget 요청을 보내보면 연결된 pod에 요청이 전달되는 것을 볼 수 있다.

Service는 어떻게 트래픽을 전달할 pod의 list를 가지는가?

service는 exposing pod를 하는 것이다. 다음의 그림을 보도록 하자.

[사진3]

위의 그림과 같이 exposing pod를 하는 방법은 여러 방법이 있는데, kubectl 명령어를 통해 imperative하게 설정해줄 수도 있다. kubectl--expose 파라미터를 추가하는 것이다.

kubectl run nginx --image nginx:latest --expose --port 80
service/nginx created
pod/nginx created

service/nginx가 만들어진 것을 볼 수 있다. kubectl 명령어로 svc의 리스트를 보도록 하자.

kubectl get svc
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
nginx           ClusterIP      10.98.88.160    <none>        80/TCP           50s

nginx라는 service가 만들어진 것을 볼 수 있다. Servicepodlabel을 통해서 연결이 될 수 있는데, ServiceSelector로 무엇이 사용되는 지 확인하기 위해 describe명령어를 사용해보도록 하자.

kubectl describe svc nginx 
Name:              nginx
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          run=nginx
Type:              ClusterIP
IP:                10.98.88.160
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         192.168.33.228:80
Session Affinity:  None
Events:            <none>

Selector: run=nginx을 집중하자 run=nginx라는 label을 가진 pod와 연결되었다는 것인데, kubectl get 명령어로 찾아보도록 하자.

kubectl get pods nginx --show-labels
NAME    READY   STATUS    RESTARTS   AGE     LABELS
nginx   1/1     Running   0          4m44s   run=nginx

nginx라는 pod가 run-nginx label을 사용하고 이를 통해 Service와 연결된 것을 볼 수 있다. 이제 Service의 cluster-ip로 curl을 전송하면 nginx pod의 80포트에 트래픽이 전달되어 응답이 전송될 것이다. 왜냐하면, 80 포트로 Service가 pod를 exposing시켰기 때문이다.

curl 10.98.88.160:80

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
root@gy95park-400TCA-400SCA:/p4ws/kubernetes/chapter7# curl 10.98.88.160:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

service를 디버깅하기 위해서 dnsutils docker image 사용하기

dnsutils image를 사용하면 service를 디버깅하기 굉장히 쉬워진다.

사실 service가 만들어지고난 뒤에는 이에 접근하기가 굉장히 어렵다. 특히 연결된 pod가 오직 cluster에서만 접근이 가능하거나, cluster가 어떠한 internet 연결을 갖지 않은 경우가 그렇다.

이러한 경우를 위해 pod에 wget이나 nslookup과 같은 기본적인 네트워크 커맨드를 설치하는 것이 좋다. 그러나 pod를 만들다보면 이러한 바이너리를 넣지못하거나, 없이 만든 것들이 있을 수 있다. dnsutils이 바로 이러한 needs를 잘 맞춰준다. dnsutils를 사용하여 service를 쉽게 test할 수 있다.

kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml
pod/dnsutils created

잘 설치되었다면 이를 활용해 이전에 설치한 pod인 nginx의 service를 통해 nginx 홈페이지에 접근할 수 있는 지 확인하도록 하자. pod이름이 nginx이고 namespacedefault이기 때문에 다음의 dns이름을 갖는다. nginx.default.svc.cluster.local가 바로 DNS이름이 된다.

kubectl exec -it dnsutils -- nslookup nginx.default.svc.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10#53 # kubernetes에서만 유효한 주소

Name:   nginx.default.svc.cluster.local # DNS
Address: 10.98.88.160 # service IP

확인결과 DNS가 잘 설정되었음을 알 수 있다. 이렇게 dnsutils를 통해서 nslookup을 사용할 수 있는 것이다.

왜 --expose flag를 사용하지 않아야할까?

--expose flag를 사용해서 service를 만들지 않아야하는데, 이는 만들어지는 Service를 충분히 제어하지 못하기 때문이다. 가령 기본적으로 --expose flag는 ClusterIP service를 만드는데, 개발자가 NodePort service를 만들고 싶으면 변경이 불가능하다.

물론, imperative syntax를 사용해서 NodePort service를 만들 수 있긴하지만, 너무 복잡하고 어려운 command를 만들어야 할 것이다. 때문에 --expose를 사용하기 보다는 declarative syntax를 사용하는 것이 훨씬 더 좋다.

service가 어떻게 만들어지는 지에 대한 정리

이전에 살펴본 것을 되새겨보면 pod와 service는 label과 selector로 연결된다. 즉, pod의 lable과 service의 selector가 서로 연결되면 이들이 이어진다는 것이다.

workflow는 다음과 같다.

  1. pod를 만들고 임의의 label들을 붙인다.
  2. service를 만들고 pod의 label에 맞게 selector를 설정한다.
  3. service가 시작되고 selector에 맞는 label을 가진 pod를 탐색한다.
  4. DNS 또는 IP를 통해서 service를 호출할 수 있다.
  5. service는 해당하는 label에 맞는 pod 중 하나에 트래픽을 흘려보낸다.

여러가지 Service의 종류

kubernetes의 service에는 여러가지 종류가 있다.

이들은 모두 Service라고 불리지만, 이들은 서로 다른 목적들을 가지고 있고 다르게 설정된다.

단, 이들이 모두 Service라고 불리는 것은 이들 모두 exposing pod라는 가장 큰 대전제를 따른다는 것이다. 즉, 우리의 pod를 static한 인터페이스로 expose하는 것이 주된 목적이라는 것이다.

Service는 하나에 여러가지 타입을 가질 수 없다. 한 개의 타입이 정해지면 바꿀 수 없다는 것이다. 따라서, pod에 연결된 Service가 여러 개일 수 있다.

종류를 정리하면 다음과 같다.

  1. NodePort: host machine의 임시 port range로부터 하나의 port를 pod와 bind한다. 이를 통해 해당 host IP의 port로 pod에 접근이 가능하다. 이를 사용하여 cluster의 외부로부터 오는 트래픽에 대해서 Host의 port를 통해 pod로 전달이 가능하다.
  2. ClusterIP: ClusterIP service는 kubernetes cluster 간의 pod들끼리의 통신을 위해 존재하는 IP이다. --expose 플래그로 만들어지는 기본적인 Service로 static한 IP를 갖는데, 이 IP는 kubernetes cluster 외부에서 접근할 수는 없다. 해당 static IP에 접근하게되면 Service에서 요청을 해당하는 pod로 포워딩해준다. 만약 ClusterIP service에 하나 이상의 Pod가 연결되어있다면 이는 라운드 로빈 알고리즘을 따르며 load balancing을 제공해준다. 또한, 이름은 ClusterIP이지만, 보통 DNS이름을 통해 접근한다.
  3. LoadBalancer: LoadBalancer type은 꽤 복잡한 service이다. 이는 kubernetes cluster가 동작 중인 cloud computing platform을 찾아내고 cloud에 load balancer를 만들어낸다. 만약 AWS에서 kubernetes cluster가 동작하면 Elastic Load Balacner(ELB)라는 것을 만들어낸다. 만약 vanilla kubernetes를 동작중이라면 LoadBalancer는 아무것도 안만들어낸다. 이 service는 cloud platform 외에서는 아무짝에도 쓸모없다. 일반적으로 사람들은 Terraform과 같은 서비를 사용하여 자신들의 cloud infrastructure를 provision을 하기 때문에, 해당 service는 다른 service들보다 덜 쓰인다.

이제 하나씩 만들어보면서 알아보도록 하자. 먼저 NodePort service를 만들어보도록 하자.

NodePort Service

NodePort service를 사용하면 pod를 해당 host의 port에 맵핑시켜 pod에 접근하는 것을 가능하게 해준다.

NodePort service를 사용하면 host의 port를 통해 pod에 접근할 수 있다고 했다. 이 때 host는 해당 pod가 있는 worker node이다. 즉, 해당 worker node의 IP:PORT로 접근이 가능하다는 것이다.

두 개의 동일한 container를 사용하는 pod를 만들고 이를 NodePort service를 사용하여 외부에 노출시키도록 해보자.

간단하게 containous/whoami라는 image로 whoami1 ,whoami2라는 pod를 만들어보도록 하자. 해당 컨테이너는 golang으로 만들어진 간단한 web server로 80 포트로 접근 시에 받은 요청을 응답으로 전달한다.

kubectl run whoami1 --image=containous/whoami:latest  --port=80 --labels="app=whoami"
pod/whoami1 created
kubectl run whoami2 --image=containous/whoami:latest  --port=80 --labels="app=whoami" 
pod/whoami2 created

이제 pod가 잘 만들어졌고, lable이 잘 붙었는 지 확인해보도록 하자.

kubectl get pod --show-labels
NAME            READY   STATUS    RESTARTS   AGE    LABELS
whoami1         1/1     Running   0          71s    app=whoami
whoami2         1/1     Running   0          44s    app=whoami

이제 NodePort Service를 만들어보도록 하자.

NodePort Service를 만들어 whoami1whoami2의 port를 노출시키고, 접근할 수 있도록 해주자

  • nodeport-whoami.yaml
apiVersion: v1
kind: Service
metadata:
  name: nodeport-whoami
spec:
  type: NodePort
  selector:
    app: whoami
  ports:
    - nodePort: 30001
      port: 9596
      targetPort: 80

manifest가 약간 어려운데, kindService이고, spec.typeNodePort로 하여 type를 지정한다. spec.selectorlabel을 지정하는데, 우리의 경우 whoami1, whoami2 pod가 여기에 해당하기 때문에 하나의 서비스에 두 개의 pod가 연결되게 된다.

spec.ports부분은 NodePort service를 만들고 나서 실행시켜보도록 하자.

kubectl create -f nodeport-whoami.yaml 
service/nodeport-whoami created

serivce가 만들어졌다면, 확인해보도록 하자.

kubectl get svc 
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
nodeport-whoami   NodePort       10.102.53.34    <none>        80:30001/TCP     5s

nodeport-whoamiTYPE으로 NodePort를 가지며, CLUSTER-IP10.102.53.34이다. PORTS로는 80:30001/TCP를 가지는데, 80이 pod내의 port이며 30001은 현재 pod를 호스팅하고 있는 worker node의 30001 port를 말한다. 현재 필자는 control plane와 worker node가 동일하므로, localhost:30001에 대한 요청을 보내면 pod와 연결되게 된다.

참고로, NodePort service를 만들 때 내부적으로 ClusterIP를 만들어낸다. 이는 내부적으로 pod들끼리 쓰이는 IP를 만들어낸다는 것이고, pod들끼리 CLUSTER-IP로 통신이 가능하다는 것이다.

curl localhost:30001

Hostname: whoami1
IP: x.x.x.x
IP: x.x.x.x
RemoteAddr: x.x.x.x:xxxx
GET / HTTP/1.1
Host: localhost:30001
User-Agent: curl/7.68.0
Accept: */*

다음과 같은 응답을 받게 될 것이다. 이는 whoami 컨테이너에서 80 port로 요청했을 시의 응답과 동일하므로, pod가 host port와 잘 연결되었음을 알 수 있다.

이제 manifest의 spec.ports부분을 보도록하자. 여기에는 3가지 port가 있는데, 다음과 같다.

  1. nodePort
  2. port
  3. targetPort

nodePort는 host machine(worker node)의 port를 의미한다. 즉, pod와 연결하려는 host machine의 port인 것이다. 지금은 30001으로 설정하였기 때문에 host에서 30001로 접근이 가능한 것이다.

nodePort부분은 임의로 설정되어서는 안된다. 기본적으로 30000 ~ 32767 range를 사용하며 kubernetes cluster config를 변경하면 더 확장할 수 있다.

port는 조금 헷갈릴 수 있는데, NodePort server 자체의 port이다. 위에서는 9596으로 설정하였고, 이를 통해서 nodePorttargetPort를 잇는 것이다. 참고로 port는 임의로 정해도 된다. 이 port가 다음에 나올 ClusterIP와 관련이 있는데, NodePort service는 pod에 ClusterIP를 만들어낸다. 이때 쓰이는 port가 바로 이 파라미터를 매개로 하는 것이다.

targetPort는 타겟이 되는 pod의 포트이다. 해당 port는 열려있고 application이 동작하고 있어야 한다. 해당 targetPortNodePort service는 트래픽을 전달해줄 것이다.

이를 그림으로 표현하면 다음과 같다.

[사진4]

즉, 순서로 보면 다음과 같다.

client <---> Host(localhost:30001) <---> NodePort Service port(:9596) <---> pod(:80)

3개의 port가 이름 때문에 헷갈릴 수 있는데, 이것만 조심하면 어려울 것이 없다. 컨벤션으로 porttargetPort를 동일하게 두는데, 가령 targetPort가 80이면 port도 80으로 두는 것이다. 이는 대다수의 사람들이 이 두 port를 헷갈려하기 때문이다.

NodePort service 삭제하기

삭제하는 방법은 kubectl delete svc <service-name>을 사용하면되서 그렇게 어려울 것이 없다. 다만, 삭제할 시에 pod와 통신할 수 있는 static한 인터페이스를 잃어버린다는 것에 집중해야한다. 이러한 행동이 어떤 문제를 일으킬 지에 대해서 생각해보고 삭제해야한다는 것이다.

kubectl delete svc nodeport-whoami 
service "nodeport-whoami" deleted

Service를 삭제하였으므로, 이제 더이상 whoami pod에 접근할 수 없다.

NodePort와 kubectl port-forward

이들이 기능적으로 하는 일이 비슷해보여도 헷갈릴 것 없이, 이들은 전혀 관계가 없다.

kubectl port-forward command는 testing tool이다. 반면에 NodePort service는 실제 usecase이며 production에 사용할 수 있는 feature이다.

kubectl port-forward는 동작하기 위해서 terminal이 open되어야한다. command가 kill이 되어 terminal에서 닫히면, port forwarding은 멈추게 되고 pod에 접속할 수 없게 된다. 이는 kubectl을 사용해서 pod에 접근하도록 하는 테스트 툴이기 때문이다.

NodePort는 production 케이스에서 사용되며 long-term production-ready solution이다. 즉, kubectl와 같인 command에 open되어 있지 않아도 되며, kubernetes에서 한 번 동작시키면 계속 동작한다.

정리하자면 테스트를 위해서는 kubectl port-forward를 사용하고 production level에서의 feature가 필요하다면 NodePort를 사용하도록 하자.

Cluster IP Service

이제 service의 다른 타입인 ClusterIP에 대해서 알아보도록 하자.

ClusterIP는 개발자의 pod를 같은 kubernetes 상의 다른 pod와 통신하기 위해서 사용하며, IP 주소 또는 DNS 이름을 통해서 서로 통신이 가능하다.

NodePort service는 pod를 외부로 노출시키기 위한 것이라면, Cluster service는 kubernetes cluster 내부에 있는 pod와 다른 pod끼리의 통신을 위한 것이다.

즉, ClusterIP라는 static한 interface로 kubernetes cluster 내의 pod들끼리 통신하는 것이다.

pod가 죽고, 다시 살고, 다시 스케줄링 된다면 이전의 ip와 다른 ip를 할당받을 것이다. ClusterIP는 label selector로 연결된 pod에 대해서 kubernetes cluster 내부에서만 쓰이는 DNS 이름을 할당하여 static한 interface를 제공한다.

재밌게도 NodePort와 달리 ClusterIP service는 worker node에 대해 한 개의 port만 점유하지 않는다. 따라서, kubernetes cluster 외부에서부터 ClusterIP service에 접근하는 것은 불가능하다.

ClusterIP service를 imperative한 방법으로 생성하는 방법

ClusterIP service는 여러 가지 방법으로 만들어질 수 있는데, 크게 두 가지 방법이 있다.

  1. --expose 파라미터 사용 (imperative way)
  2. YAML manifest file 사용 (declarative way)

imperative way는 --expose 파라미터를 사용하는 방법으로 ClusterIP service를 직접만들어낸다. 가령, nginxClusterIP service를 만들다고 한다면 다음과 같이 만들 수 있다.

kubectl run nginx-clusterip --image nginx --expose --port 80
service/nginx-clusterip created
pod/nginx-clusterip created

podClusterIP service 두 개 모두 만들어진 것을 볼 수 있다.

만들어진 ClusterIP의 정보를 자세히 가져와보도록 하자. kubectl describe 명령어를 사용하면 된다.

kubectl describe svc nginx-clusterip 
Name:              nginx-clusterip
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          run=nginx-clusterip
Type:              ClusterIP
IP:                10.109.42.159
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         192.168.33.221:80
Session Affinity:  None
Events:            <none>

Selectorrun=nginx-clusterip를 사용하고 있는 것을 확인할 수 있다. 해당 pod의 IP: 192.168.33.221:80을 10.109.42.159:80 cluster ip로 연결해놓은 것이다.

그렇다면 kubectl get pod 명령어로 어떤 pod가 run=nginx-clusterip라는 label을 가졌는 지 확인해보도록 하자.

kubectl get pod nginx-clusterip --show-labels
NAME              READY   STATUS    RESTARTS   AGE   LABELS
nginx-clusterip   1/1     Running   0          22m   run=nginx-clusterip

nginx-clusteriprun=nginx-clusterip를 갖고 있으며 ClusterIP와 연결된 것을 알 수 있다.

그렇다면 ClusterIP service이름이 nginx-clusterip이므로 DNS이름은 nginx-clusterip.default.svc.cluster.local이 될 것이다. dnsutils를 사용하여 이를 확인해보도록 하자.

kubectl exec -it dnsutils -- nslookup nginx-clusterip.default.svc.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx-clusterip.default.svc.cluster.local
Address: 10.109.42.159

nginx-clusterip.default.svc.cluster.local DNS가 10.109.42.159라는 clusterIP에 연결된 것을 볼 수 있다.

ClusterIP도 동일한 service에 여러 개의 pod를 연결할 수 있다. 가령, 10개의 pod를 연결하면 1000개의 request가 올 시에 각 100개의 request를 받게 된다.

declarative한 방법으로 ClusterIP service를 생성하기

ClusterIP service는 YAML manifest 파일을 사용하여 생성할 수 있다.

  • clusterip-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-clusterip
spec:
  type: ClusterIP # Indicates that the service is a ClusterIP
  ports:
    - port: 80 # the port exposed by the service
      protocol: TCP
      targetPort: 80 # the destination port on the pods
  selector:
    run: nginx-clusterip

위에서 만든 ClusterIP와 동일한 service이다.

typeClusterIP를 맞추고, spec.ports.portspec.ports.targetPort를 설정해주면 된다. portNodePort에서 보았듯이 ClusterIP service가 가진 port이다. targetPort가 pod의 port로 실제 서비스가 열려있는 port인 것이다.

중요한 것은 이전에 NodePort에서 nodePort가 있었는데, 여기에는 없다는 것이다. 즉, ClusterIP service는 worker node의 port를 사용하지 않는다는 것이다.

물론, 그렇다고 host machine과 완련 관련이 없는 것은 아니다. ClusterIP의 ip로 host machine(worker node)에서 curl을 요청을 보내도 성공한다. 이는 host machine의 network체계를 사용하기 때문이다.

kubectl get svc nginx-clusterip 
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx-clusterip   ClusterIP   10.98.78.239   <none>        80/TCP    2m11s

10.98.78.239로 요청을 보내보도록 하자.

curl 10.98.78.239

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

ClusterIP service를 삭제하기

이전과 별반 다를바 없다.

kubectl delete svc <clusterip-service>

NodePort때와 같이 ClusterIP service가 죽으면 cluster내부에 통신할 static한 interface가 삭제되는 것이므로 이에 대해서는 대책을 갖고 있어야 한다.

headless service에 대한 이해

headless service는 ClusterIP service로부터 나온 것으로 공식적으로 service의 또 다른 type은 아니다. 그러나 ClusterIP의 옵션으로 존재한다고 보면 된다.

headless service는 .spec.clusterIPNone으로 설정해주기만 하면 된다. 다음의 예시를 보도록 하자.

  • clusterip-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-clusterip-headless
spec:
  clusterIP: None
  type: ClusterIP # service type is ClusterIP
  ports:
    - port: 80 # service's port
      protocol: TCP
      targetPort: 80 # port on the pod 
  selector:
    run: nginx-clusterip

clusterIP부분에 None을 적은 것 말고는 딱히 추가된 것이 없다. 이렇게 headless service를 만들어보고 리스트를 확인해보도록 하자.

kubectl create -f clusterip-headless.yaml 
service/nginx-clusterip-headless created

kubectl get svc
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
...
nginx-clusterip-headless   ClusterIP      None            <none>        80/TCP           3s
...

CLUSTER-IP가 할당되지 않았다는 것을 볼 수 있다. headless service는 IP를 가지고 있지 않기 때문에, service를 거치지 않고 pod에 직접 통신할 수 있다. 따라서, headless service는 프록시 기능도 없고, 로드 밸런싱 기능도 없다.

오직 headless service가 제공하는 것은 연결된 pod의 DNS를 제공하고, 이를 통해 pod에 직접 통신할 수 있도록 한다는 것이다. headless service에서 ClusterIP service는 단지 pod의 DNS이름만을 반환할 뿐이고, 클라이언트는 선택한 pod의 DNS에 직접 connection을 맺을 책임이 있다.

사실 headless service는 그렇게 많이 쓰이는 service는 아니다. 이는 kubernetes의 기본 구현에 묶이지 않기 때문에 headless service를 사용하여 다른 service discovery 매커니즘과 인터페이스할 수 있다. 물론, headless service는 cluster ip가 할당되지 않고, kube-proxy가 이러한 서비스를 처리하지 않으며, 플랫폼에 의해 로드 밸런싱 또는 프록시 되지 않으므로, DNS에 의존한다는 특징이 있다.

LoadBalancer service

LoadBalancer service는 cloud platform service로, 일반적인 local machine구동되는 kubernetes cluster에서는 동작하지 않는다.

즉, AWS, GCP, AZURE, OpenStack 등 LoadBalancer service를 가능하는 클라우드 공급자만 가능한 것이다.

사실 많은 이들이 LoadBalancer service를 사용하는 것에 대해서 긍정적이지 않다. 왜냐하면 LoadBalancer가 kubernetes cluster에 종속된 object가 아니라, cloud provider에 종속되었기 때문에 어떤 사업자를 선택하냐에 따라 내용이 다를 수 있고, 설정법이 다를 수 있다.

이에 대해서는 추후에 알아보기로 하자.

0개의 댓글