- 이전에 Kubernetes Cluster안에 Controller들을 이용해서 POD를 정의했습니다.
POD 특성상 생성 및 정의 될때 지정되는 IP가 랜덤하고 또한 리스타트 때마다 IP가 변동됩니다. 두개의 이유로 POD는 고정된 EndPoint
로 호출이 어렵습니다.
또한 여러 POD에 같은 Application을 운용할 경우 이 POD 간의 LoadBalancing을 지원해줘야 하는데 이러한 기능들을 수행하는게 Service(Service)
입니다.
간략한 Service들의 기능을 요약해보면 아래 4가지 정도입니다.
- Service를 사용하게 되면 고정된 주소를 이용해서 접근이 가능해 집니다.
- Service를 통해 Cluster 외부에서 POD에 접근하는것도 가능합니다.
- 여러 POD를 묶어 로드 밸런싱이 가능합니다.
- 고유한 DNS 이름을 가질 수 있습니다.
Service는 get service
명령을 통해 목록을 받아 올 수 있습니다
[root@nasa-master nasa]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24d
- 명령을 입력하면 default NameSpace에 항상 존재하는 Service가 보이네요, Master Node의 API로 접근하기 위한 Service입니다!!
- Service는 다음과 같이 구성이 가능하며, 라벨 셀렉터(label selector)를 이용하여 관리하고자 하는 Pod 들을 정의할 수 있습니다.
Service 템플릿 기본 구조는 다음과 같습니다.
apiVersion: v1
kind: Service
metadata:
name: hello-nasa-svc
spec:
type: ClusterIP (LoadBalancer)
clusterIP: 10.0.10.10
selector:
app: hello-nasa
ports:
- port: 80
protocol: TCP
targetPort: 8080
다른 부분은 일반적인 형태입니다
spec.type
: Service 타입을 지정할수 있습니다. spec.type을 지정하지 않으면 기본 타입은 ClusterIP입니다. spec.clusterIP
: 사용하려는 ClusterIP를 직접 지정하는것도 가능합니다. spec.selector
: Service와 연결할 POD에 지정된 라벨을 지정합니다. spec.ports
: 배열 형태의 값입니다. Service가 포트를 외부에 제공 시 하나가 아니라 여러 개를 한번에 제공 할 수 있는데 spec.ports 하위에 값을 넣어주면 됩니다.
이런 형태의 멀티 포트 Service가 가능합니다
- 예를 들어 웹서버의 HTTP와 HTTPS 포트가 대표적인 예인데 아래와 같이 ports 부분에 두개의 포트 정보를 정의해주면 됩니다.
apiVersion: v1
kind: Service
metadata:
name: nasa-node-svc
spec:
selector:
app: nasa-node
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
- name: https
port: 443
protocol: TCP
targetPort: 8082
위의 템플릿으로 생성을 해보면 아래와 같이 멀티 포트로 생성이 됩니다
[root@nasa-master nasa]# kubectl apply -f nasa-svcm.yml
service/nasa-node-svc created
[root@nasa-master nasa]#
[root@nasa-master nasa]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 24d
nasa-node-svc ClusterIP 10.96.147.197 <none> 80/TCP,443/TCP 8s
- EndPoint란 Service의
Label Selector
에 의해 연결된 POD의 IP 목록입니다. kube get endpoints
명령어로 확인 할 수 있습니다
[root@nasa-master nasa]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 10.146.0.6:6443 24d
nasa-node-svc <none> 7m30s
- 방금 만든 Service의 경우 연결되어있는 POD가 없기에
none
으로 정의 되어있습니다.
label
을 맞춰준 POD를 하나 생성해봅시다!
apiVersion: v1
kind: Pod
metadata:
name: nasa
labels:
app: nasa-node
spec:
containers:
- name: nasa
image: nginx:latest
ports:
- containerPort: 8080
protocol: TCP
[root@nasa-master nasa]# kubectl apply -f nasa.pod-s.yml
pod/nasa created
[root@nasa-master nasa]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS G
ATES
nasa 1/1 Running 0 97s 10.32.0.2 nasa-node3 <none> <none>
이렇게 label
을 연결해준 POD가 생성되면 ENDPOINT
가 생성됩니다.
[root@nasa-master nasa]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 10.146.0.6:6443 24d
nasa-node-svc 10.32.0.2:8082,10.32.0.2:8080 17m
Test POD를 하나 돌려서 Cluster끼리의 통신을 확인해봅시다!
[root@nasa-master nasa]# kubectl run nasatest -it --image=c1t1d0s7/network-multitool --generator=run-pod/v1 --rm=true bash
If you don't see a command prompt, try pressing enter.
bash-5.0#
bash-5.0# curl http://10.32.0.2
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
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>
- 위의 테스트처럼 1개의 POD가 아닌 [RS,RC,DS]처럼 여러개의 POD가 생성되면 당연히 LB로 POD를 묶어 여러개의 ENDPOINT를 가지고 있게 될 것이다
이 경우 Client에서 요청을 보내면 요청은 LoadBalancing되어 매번 다른 파드로 연결된다.
그러나 만약 특정 Client에서 요청이 들어오면 매번 특정 파드로 연결하고 싶은 경우 사용하는 것이 세션 어피니티입니다
apiVersion: v1
kind: Service
metadata:
name: mynapp-svc-ses-aff
spec:
sessionAffinity: ClientIP
ports:
- port: 80
targetPort: 8080
selector:
app: mynapp-rs
- 세션 어피니티 구성은 None과 ClientIP가 있으며 디폴트는 None입니다.
ClientIP
를 설정하면 Kubernetes Cluster의 프록시(kube-proxy)는 Client의 IP를 보고 매번 같은 파드로 연결해줍니다
sessionAffinity
로 정의 할 수 있다
- none : (기본) 세션 어피니티 없음
- ClientIP : Client의 IP를 확인해 같은 파드로 연결됨
- Service는 IP 주소 할당 방식과 연동 Service등에 따라 크게 4가지로 구별할 수 있습니다.
Cluster IP
- Default 설정으로, Service에 Cluster IP (내부 IP)를 할당합니다.
Kubernetes Cluster 내에서는 이 Service에 접근이 가능하지만 Cluster 외부에서는 외부 IP를 할당 받지 못했기 때문에, 접근이 불가능합니다.
Load Balancer
- 보통 클라우드 벤더에서 제공하는 설정 방식으로 외부 IP 를 가지고 있는 Loadbalancer 를 할당합니다.
외부 IP를 가지고 있기 때문에, Cluster 외부에서 접근이 가능합니다.
Node IP
- Cluster IP로만 접근이 가능한것이 아니라 모든 노드의 IP와 포트를 통해서도 접근이 가능하게 됩니다.
예를 들어 아래와 같이 hello-node-svc
라는 Service를 NodePort 타입으로 선언을 하고 nodePort를 30036으로 설정하면
아래 설정에 따라 Cluster IP의 80포트로도 접근이 가능하지만 모든 노드의 30036 포트로도 Service를 접근할 수 있습니다.
hello-node-svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: hello-node-svc
spec:
selector:
app: hello-node
type: NodePort
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
nodePort: 30036
그림의 로직을 보면 이해가 쉽습니다.
- ExternalName은 외부 Service를 Kubernetes 내부에서 호출할 때 사용 할 수 있습니다.
Cluster내의 Pod들은 Cluster IP를 가지고 있기 때문에 IP 대역 밖의 Service를 호출하려면 NAT 설정 등 복잡한 설정이 필요합니다.
특히 클라우드 환경을 사용할 경우 DB 또는 Cloud 제공 SaaS Service (RDS, CloudSQL)등을 사용 할 경우 Kubernetes Cluster 밖이기 때문에
호출이 어려운 경우가 있는데 이를 쉽게 해결할 수 있는 방법이 ExternalName
타입입니다.
아래와 같이 Service를 ExternalName 타입으로 설정하고 주소를 DNS로 my.database.example.com으로 설정합니다.
- 이 my-service는 들어오는 모든 요청을 my.database.example.com 으로 포워딩 해주게 됩니다. (일종의 Proxy와 같은 역할)
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
다음과 같은 구조로 Service가 배포됩니다.
- (DNS가 아닌 직접 IP를 이용하는 방식) 위의 경우 DNS를 이용하였는데, DNS가 아니라 직접 IP 주소를 이용하는 방법도 있습니다.
Service ClusterIP Service로 생성을 한 후 Service Name만 정의하고 Service에 속해있는 Pod를 지정하지 않습니다.
apiVersion: v1
kind: Service
metadata:
name: nasa-svc-ext
spec:
ports:
- port: 80
다음으로, 아래와 같이 Service의 EndPoint를 별도로 지정해주면 됩니다.
apiVersion: v1
kind: Endpoints
metadata:
name: nasa-svc-ext
subsets:
- addresses:
- ip: 35.225.75.124
ports:
- port: 80
- 이 때
Service명
과 Service EndPoints의 이름
이 동일해야 합니다.
위의 경우에는 nasa-svc-ext
로 같은 Service명을 사용하였고 이 Service는 35.225.75.124:80 Service를 가르키도록 되어 있습니다.
- Service는 접근을 위해서 Cluster IP 또는 External IP 를 지정받습니다.
즉 Service를 통해서 제공되는 기능들에 대한 EndPoint를 Kubernetes Service를 통해서 통제하는 개념인데 MSA 에서는 기능 컴포넌트에 대한 EndPoint (IP 주소)를 찾는 기능을 Service 디스커버리 (Service Discovery) 라고 하고 Service의 위치를 등록해놓는 Service 디스커버리 솔루션을 제공합니다.
Etcd
나 hashcorp
의 consul (https://www.consul.io/)과 같은 솔루션이 해당합니다. Kubernetes Service를 통해서 Service Componant를 관리하는 것이 아니라 Service 디스커버리 솔루션을 이용하기 때문에 Service에 대한 IP 주소가 필요 없습니다.
이런 시나리오를 지원하기 위한 Kubernetes의 Service를 Headless라고 하는데 이러한 Headless Service는 Cluster IP 등의 주소를 가지지 않습니다. 단 DNS이름을 가지게 되는데 이 DNS 이름을 lookup 해보면 Service (Loadbalancer )의 IP 를 리턴하지 않고 이 Service에 연결된 Pod 들의 IP 주소들을 리턴하게 됩니다.
간단하게 테스트를 해보겠습니다.
- RS로 여러개의 POD를 정의해놓은 상태에서 테스트 진행하겠습니다.
[root@nasa-master nasa]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nasatest-5bdd7d57f-s8b7d 1/1 Running 0 27m 10.32.0.4 nasa-node3 <none> <none>
replicaset-nasa-47skg 1/1 Running 0 5m6s 10.46.0.2 nasa-node1 <none> <none>
replicaset-nasa-7j58x 1/1 Running 0 5m6s 10.32.0.2 nasa-node3 <none> <none>
replicaset-nasa-8ncc5 1/1 Running 0 5m6s 10.42.0.3 nasa-node2 <none> <none>
replicaset-nasa-ktzpq 1/1 Running 0 5m6s 10.32.0.3 nasa-node3 <none> <none>
여기에 다음과 같은 Headless Service를 하나 가동 시켜보겠습니다.
apiVersion: v1
kind: Service
metadata:
name: nasa-node-svc-headless
spec:
clusterIP: None
selector:
app: nasa-nginx-pods-label
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
- POD들의 레이블을 묶어준 뒤 Service를 정의하게되면
아래와 같이 ClusterIP가 할당되지 않는 것을 확인 할 수 있습니다.
[root@nasa-master nasa]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 31m
nasa-node-svc-headless ClusterIP None <none> 80/TCP 6m12s
그러나 다른 POD를 생성해서 NSLOOKUP
을 날려 DNS를 조회해보면
[root@nasa-master nasa]# kubectl run nasatest -it --image=c1t1d0s7/network-multitool --generator=run-pod/v1 --rm=true bash
If you don't see a command prompt, try pressing enter.
bash-5.0# nslookup nasa-node-svc-headless
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nasa-node-svc-headless.default.svc.cluster.local
Address: 10.46.0.2
Name: nasa-node-svc-headless.default.svc.cluster.local
Address: 10.32.0.2
Name: nasa-node-svc-headless.default.svc.cluster.local
Address: 10.32.0.3
Name: nasa-node-svc-headless.default.svc.cluster.local
Address: 10.42.0.3
- 위과 같이 Service에 의해 제공되는 pod 들의 IP 주소 목록이 나오는 것을 확인할 수 있
- 현재 Cluster 환경은 GCP의 Instance에 KUBEADM으로 구성한 상태입니다. 현재 환경에서 GCP의 외부 IP로 LB를 이용해 URL을 받아와 보겠습니다.
외부 IP를 가지고 있기 때문에, Cluster 외부에서 접근이 가능합니다. 방화벽 문제만 없다면요!
우선 다음과 같은 RS를 하나 정의합니다.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-nasa
spec:
replicas: 4
selector:
matchLabels:
app: nasa-rs-pod
template:
metadata:
name: nasa-rs
labels:
app: nasa-rs-pod
spec:
containers:
- name: rs-nasa
image: nginx:latest
ports:
- containerPort: 80
그리고 아래와 같은 LB Service를 하나 정의합니다.
apiVersion: v1
kind: Service
metadata:
name: nasa-node-lb
spec:
selector:
app: nasa-rs-pod
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
type: LoadBalancer
externalIPs:
- 34.84.172.31
externalIPs
의 경우 GCP 인스턴스에서 고정으로 할당한 IP입니다
위의 정의된 템플릿들을 생성하면 아래와 같이 정상적으로 생성됩니다!!
[root@nasa-master nasa]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATE
D NODE READINESS GATES
rs-nasa-fvzm6 1/1 Running 0 64m 10.46.0.3 nasa-node1 <none>
<none>
rs-nasa-hqhjs 1/1 Running 0 64m 10.42.0.3 nasa-node2 <none>
<none>
rs-nasa-jn6jz 1/1 Running 0 64m 10.32.0.2 nasa-node3 <none>
<none>
rs-nasa-zfh2n 1/1 Running 0 64m 10.46.0.2 nasa-node1 <none>
<none>
[root@nasa-master nasa]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SEL
ECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 119m <no
ne>
nasa-node-lb LoadBalancer 10.101.13.59 34.84.172.31 80:30850/TCP 36m app
=nasa-rs-pod
[root@nasa-master nasa]#
[root@nasa-master nasa]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 10.146.0.6:6443 120m
nasa-node-lb 10.32.0.2:80,10.42.0.3:80,10.46.0.2:80 + 1 more... 36m
- POS 정상기동, Service 정상기동, EndPoint에 정상적으로 Pod가 동기화 됨을 확인합니다.
자 그럼 이제 Service를 위한 정의는 모두 끝났습니다!!
- 테스트를 하기전 GCP 방화벽에서 HTTP에 대한 PORT를 허용해줍니다!
모두 확인이 완료 되었으면 외부 ubuntu os에서 curl로 요청해봅시다!
curl 34.84.172.31:30850
LB SVC에서 외부 PORT가 30850으로 설정되어있어 해당 포트로 요청해야합니다!!
- 정상적으로 LB SVC의 외부IP로 URL을 받아오네요!! 성공!!