지금의 목적은 무언가 할 수 있게끔 만들어둔 서버를 실행시키고, 이걸 안정적으로 잘 써먹는데 있습니다.
kubernetes를 쓰는 관점에선 우리가 원하는 상태를 유지하기만 한다면 서버 여럿이 죽었다가 새로이 생겨나는 건 별 일이 아닙니다.
서버 하나 띄우고 애지중지 입김 불어 모셔두는 방식이 아니니까 당연하지만,
한 시간 전에 제 요청을 처리해준 172.18.0.5 pod는 지금 없을 수도 있죠.
그럼 지금 새 요청을 하려면 저는 누굴 뭐라고 불러야 될까요?
K8s에서는 pod들을 논리적으로 묶어서 명찰을 붙여줍니다.
필요시 그 명찰의 이름을 불러주면 됩니다. 3조!
호출되면 그 안에서 누굴 어떻게 부를지는 이 조를 결성할 때 써둔 명세에 있습니다.
여기서의 그룹화와 그 명세가 K8s의 service 중 ClusterIP입니다.
이런게 없으면 매번 어느 pod가 살아있고, 어떤 ip를 갖고 있는지 확인하여 그 ip를 사용해서 불러야겠죠.
그 사이에 그 ip를 가진 pod가 사라졌을 수도 있겠습니다만...
apiVersion: v1
kind: Service
metadata:
name: cluster-svc
spec:
type: ClusterIP
selector:
app: cluster-pod
ports:
- name: cluster
port: 8080
targetPort: 80
위 명세를 통해서 service를 생성하면,
cluster-svc라는 이름으로 app: cluster-pod 라는 label을 가진 pod들을 (관념적으로) 묶습니다.
이 때 통신시에는 service에서는 8080, pod에선 80 port을 사용한다고 약속했습니다.
$ kubectl get svc cluster-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cluster-svc ClusterIP 10.96.43.118 <none> 8080/TCP 94s
cluster 내부에서 이 service는 10.96.43.118를 자동으로 할당 받았습니다. 명세에 직접 지정할 수도 있습니다. (참고)
이 ip는 service에 할당하기로 약속된 ip 범위를 기반으로 합니다.
$ kubectl -n kube-system get pods -l component=kube-controller-manager -o yaml | grep service-cluster-ip-range
- --service-cluster-ip-range=10.96.0.0/16
이 service에 연결된 endpoint를 확인해보면, (참고 : endpoint는 endpointSlice로 대체되어가는 추세)
$ kubectl get endpoints cluster-svc
NAME ENDPOINTS AGE
cluster-svc 10.244.1.2:80,10.244.1.3:80 2m2s
이 ip들은 app: cluster-pod 라는 label을 가진 pod들의 ip이고, 80은 명세에서 지정했던 targetPort 입니다.
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
cluster-pod-1 1/1 Running 0 46m 10.244.1.2 kind-worker <none> <none>
cluster-pod-2 1/1 Running 0 46m 10.244.1.3 kind-worker <none> <none>
netshoot-pod 1/1 Running 0 46m 10.244.1.4 kind-worker <none> <none>
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
cluster-pod-1 1/1 Running 0 71m app=cluster-pod
cluster-pod-2 1/1 Running 0 71m app=cluster-pod
netshoot-pod 1/1 Running 0 71m <none>
이제 ClusterIP 덕분에 pod 하나 하나는 알바 아니게 됐죠.
근데 ClusterIP에 할당된 cluster ip는 cluster 내부의 private ip라 외부에서 호출할 수 없습니다.
외부에서 호출할 수 있는 진입점은 다른 타입의 service인 nodePort를 이용하면 얻을 수 있습니다.
(이 하나의 진입점을 위해 위에서 만들었던 ClusterIP도 생성하고 nodePort도 생성할 필요는 없습니다)
apiVersion: v1
kind: Service
metadata:
name: nodeport-svc
spec:
type: NodePort
selector:
app: nodeport-deploy
ports:
- name: nodeport-svc
port: 80 # 서비스 포트 (Cluster 내부에서 사용)
targetPort: 80 # 실제 컨테이너 포트
nodePort: 31001 # 외부에서 접근할 NodePort
외부에서는 nodePort로 지정된 31001번을 통해 들어오고, 결과적으로는 pod의 80번 (targerPort)로 요청이 들어갑니다.
여기서 지정할 수 있는 nodePort 범위는 기본값이 30000-32767로 설정되어 있습니다. (참고)
위 명세로 생성된 NodePort service는 ClusterIP와 마찬가지로 service-cluster-ip-range를 기반으로 한 cluster ip를 할당 받습니다.
pod들과의 매핑도 동일한 방식으로 확인할 수 있습니다.
$ kubectl get pod,svc
NAME READY STATUS RESTARTS AGE
pod/nodeport-deploy-59b68567d7-cqv64 1/1 Running 0 18m
pod/nodeport-deploy-59b68567d7-ghqk5 1/1 Running 0 18m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nodeport-svc NodePort 10.96.146.167 <none> 80:31001/TCP 18m
$ kubectl get endpoints nodeport-svc
NAME ENDPOINTS AGE
nodeport-svc 10.244.1.5:80,10.244.1.6:80 20m
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nodeport-deploy-59b68567d7-cqv64 1/1 Running 0 20m 10.244.1.6 kind-worker <none> <none>
nodeport-deploy-59b68567d7-ghqk5 1/1 Running 0 20m 10.244.1.5 kind-worker <none> <none>
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nodeport-deploy-59b68567d7-cqv64 1/1 Running 0 20m app=nodeport-deploy,pod-template-hash=59b68567d7
nodeport-deploy-59b68567d7-ghqk5 1/1 Running 0 20m app=nodeport-deploy,pod-template-hash=59b68567d7
service의 부하 분산은 K8s 부하 분산에 의존합니다.
$ curl -s http://localhost:31001 | grep Hostname
Hostname: nodeport-deploy-59b68567d7-cqv64
$ curl -s http://localhost:31001 | grep Hostname
Hostname: nodeport-deploy-59b68567d7-ghqk5
$ http://localhost:31001 | grep Hostname
Hostname: nodeport-deploy-59b68567d7-cqv64
$ for i in {1..100}; do curl -s http://localhost:31001 | grep Hostname; done | sort | uniq -c | sort -nr
52 Hostname: nodeport-deploy-59b68567d7-cqv64
48 Hostname: nodeport-deploy-59b68567d7-ghqk5
kube-proxy mode가 기본값인 iptables면 요청을 랜덤한 pod로 보내는 방식으로 부하를 분산합니다.
kube-proxy mode가 IPVS면 균등 분산합니다. (round-robin)
$ kubectl describe configmap -n kube-system kube-proxy | grep mode
mode: iptables
K8s LoadBalancer 타입의 service를 생성하고 클라우드 서비스사에서 제공하는 LoadBalancing 서비스와 연결하여 관리할 수도 있습니다.
참고
https://medium.com/finda-tech/kubernetes-네트워크-정리-fccd4fd0ae6
https://kubernetes.io/docs/concepts/services-networking/service/
https://kimalarm.tistory.com/100
https://www.reddit.com/r/kubernetes/comments/18bv777/does_a_service_load_balance/?rdt=36384
https://mokpolar.tistory.com/65