pod를 end user에게 접근 가능하게하고, 다른 pod들끼리 통신하도록 만들고 싶을 것이다. 이러한 것들을 가능하게 해주는 것이 바로 kubernetes의 Services
이다.
kubernetes는 pod가 배포되면 자동적으로 cluster에 사용할 수 있는 private IP를 할당한다. 즉, cluster의 모든 pod들은 제 각기의 private IP가 있다는 것이다.
nginx
pod를 간단히 만들어보고 해당 pod에 private IP가 있는 지 확인하도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: new-nginx-pod
spec:
containers:
- image: nginx:latest
name: new-nginx-container
ports:
- containerPort: 80
이전에 우리가 배포했던 pod들과 특별히 다를 바가 없다. 오직 ports
로 외부에 노출된 containerPort
가 80
이라는 것 밖에 없다.
이제 해당 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
를 통해서 네트워킹도 가능하며 로드밸런스도 가능하다.
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 service는 일반 적절하게 설정되면 하나 또는 그 이상의 pod들을 외부에 노출시킬 수 있다. 같은 service에 여러 개의 pod들이 노출되면 요청들은 균등하게 로드밸런싱되는데, 이 때 사용하는 알고리즘이 라운드 로빈이다.
[사진2]
application을 스케일링하는 것이 매우 쉬운데, service 뒤의 pod를 하나 더 추가하면 된다.
이렇게 Service
는 하나의 proxy 역할을 하여 하나에 몰리는 request의 양을 적절하게 분산할 수 있다. 라운드 로빈이기 때문에, 하나의 service에 4개의 pod가 붙어있다면 한 pod의 request를 25%만 받도록 할 수 있는 것이다.
Service
에 접근하는 방식은 두가지가 있다.
보통 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-service
와 default
는 각 Service
마다 다른 부분이고, svc
와 cluster.local
은 고정된 부분이다. 이렇게 만들어진 DNS 이름으로 cluster에서 curl
이나 wget
요청을 보내보면 연결된 pod에 요청이 전달되는 것을 볼 수 있다.
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가 만들어진 것을 볼 수 있다. Service
와 pod
는 label
을 통해서 연결이 될 수 있는데, Service
의 Selector
로 무엇이 사용되는 지 확인하기 위해 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>
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
이고 namespace
가 default
이기 때문에 다음의 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를 사용해서 service를 만들지 않아야하는데, 이는 만들어지는 Service
를 충분히 제어하지 못하기 때문이다. 가령 기본적으로 --expose
flag는 ClusterIP
service를 만드는데, 개발자가 NodePort
service를 만들고 싶으면 변경이 불가능하다.
물론, imperative syntax를 사용해서 NodePort
service를 만들 수 있긴하지만, 너무 복잡하고 어려운 command를 만들어야 할 것이다. 때문에 --expose
를 사용하기 보다는 declarative syntax를 사용하는 것이 훨씬 더 좋다.
이전에 살펴본 것을 되새겨보면 pod와 service는 label과 selector로 연결된다. 즉, pod의 lable
과 service의 selector
가 서로 연결되면 이들이 이어진다는 것이다.
workflow는 다음과 같다.
kubernetes의 service에는 여러가지 종류가 있다.
이들은 모두 Service
라고 불리지만, 이들은 서로 다른 목적들을 가지고 있고 다르게 설정된다.
단, 이들이 모두 Service
라고 불리는 것은 이들 모두 exposing pod
라는 가장 큰 대전제를 따른다는 것이다. 즉, 우리의 pod를 static한 인터페이스로 expose하는 것이 주된 목적이라는 것이다.
Service
는 하나에 여러가지 타입을 가질 수 없다. 한 개의 타입이 정해지면 바꿀 수 없다는 것이다. 따라서, pod에 연결된 Service
가 여러 개일 수 있다.
종류를 정리하면 다음과 같다.
NodePort
: host machine의 임시 port range로부터 하나의 port를 pod와 bind한다. 이를 통해 해당 host IP의 port로 pod에 접근이 가능하다. 이를 사용하여 cluster의 외부로부터 오는 트래픽에 대해서 Host의 port를 통해 pod로 전달이 가능하다.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이름을 통해 접근한다.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를 사용하면 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를 만들어 whoami1
과 whoami2
의 port를 노출시키고, 접근할 수 있도록 해주자
apiVersion: v1
kind: Service
metadata:
name: nodeport-whoami
spec:
type: NodePort
selector:
app: whoami
ports:
- nodePort: 30001
port: 9596
targetPort: 80
manifest
가 약간 어려운데, kind
는 Service
이고, spec.type
은 NodePort
로 하여 type를 지정한다. spec.selector
로 label
을 지정하는데, 우리의 경우 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-whoami
는 TYPE
으로 NodePort
를 가지며, CLUSTER-IP
는 10.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가 있는데, 다음과 같다.
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
으로 설정하였고, 이를 통해서 nodePort
와 targetPort
를 잇는 것이다. 참고로 port
는 임의로 정해도 된다. 이 port
가 다음에 나올 ClusterIP
와 관련이 있는데, NodePort
service는 pod에 ClusterIP
를 만들어낸다. 이때 쓰이는 port
가 바로 이 파라미터를 매개로 하는 것이다.
targetPort
는 타겟이 되는 pod의 포트이다. 해당 port는 열려있고 application이 동작하고 있어야 한다. 해당 targetPort
로 NodePort
service는 트래픽을 전달해줄 것이다.
이를 그림으로 표현하면 다음과 같다.
[사진4]
즉, 순서로 보면 다음과 같다.
client <---> Host(localhost:30001) <---> NodePort Service port(:9596) <---> pod(:80)
3개의 port가 이름 때문에 헷갈릴 수 있는데, 이것만 조심하면 어려울 것이 없다. 컨벤션으로 port
와 targetPort
를 동일하게 두는데, 가령 targetPort
가 80이면 port
도 80으로 두는 것이다. 이는 대다수의 사람들이 이 두 port를 헷갈려하기 때문이다.
삭제하는 방법은 kubectl delete svc <service-name>
을 사용하면되서 그렇게 어려울 것이 없다. 다만, 삭제할 시에 pod와 통신할 수 있는 static한 인터페이스를 잃어버린다는 것에 집중해야한다. 이러한 행동이 어떤 문제를 일으킬 지에 대해서 생각해보고 삭제해야한다는 것이다.
kubectl delete svc nodeport-whoami
service "nodeport-whoami" deleted
Service
를 삭제하였으므로, 이제 더이상 whoami
pod에 접근할 수 없다.
이들이 기능적으로 하는 일이 비슷해보여도 헷갈릴 것 없이, 이들은 전혀 관계가 없다.
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
를 사용하도록 하자.
이제 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는 여러 가지 방법으로 만들어질 수 있는데, 크게 두 가지 방법이 있다.
--expose
파라미터 사용 (imperative way)imperative way는 --expose
파라미터를 사용하는 방법으로 ClusterIP
service를 직접만들어낸다. 가령, nginx
의 ClusterIP
service를 만들다고 한다면 다음과 같이 만들 수 있다.
kubectl run nginx-clusterip --image nginx --expose --port 80
service/nginx-clusterip created
pod/nginx-clusterip created
pod
와 ClusterIP 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>
Selector
로 run=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-clusterip
이 run=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를 받게 된다.
ClusterIP
service는 YAML manifest 파일을 사용하여 생성할 수 있다.
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이다.
type
을 ClusterIP
를 맞추고, spec.ports.port
와 spec.ports.targetPort
를 설정해주면 된다. port
는 NodePort
에서 보았듯이 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>
이전과 별반 다를바 없다.
kubectl delete svc <clusterip-service>
NodePort
때와 같이 ClusterIP
service가 죽으면 cluster내부에 통신할 static한 interface가 삭제되는 것이므로 이에 대해서는 대책을 갖고 있어야 한다.
headless service는 ClusterIP
service로부터 나온 것으로 공식적으로 service의 또 다른 type은 아니다. 그러나 ClusterIP
의 옵션으로 존재한다고 보면 된다.
headless service는 .spec.clusterIP
을 None
으로 설정해주기만 하면 된다. 다음의 예시를 보도록 하자.
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는 cloud platform service로, 일반적인 local machine구동되는 kubernetes cluster에서는 동작하지 않는다.
즉, AWS, GCP, AZURE, OpenStack 등 LoadBalancer
service를 가능하는 클라우드 공급자만 가능한 것이다.
사실 많은 이들이 LoadBalancer
service를 사용하는 것에 대해서 긍정적이지 않다. 왜냐하면 LoadBalancer
가 kubernetes cluster에 종속된 object가 아니라, cloud provider에 종속되었기 때문에 어떤 사업자를 선택하냐에 따라 내용이 다를 수 있고, 설정법이 다를 수 있다.
이에 대해서는 추후에 알아보기로 하자.