🥞 BE
home

Ch7. 서비스

목차

7.1 서비스의 개념

쿠버네티스 클러스터 안에 컨트롤러를 이용해서 파드를 실행했다면, 해당 파드에 접근하는 방법을 알아봐야 한다. 만들어진 파드끼리 통신을 하기 위해서는 “서비스”가 필요하다.
쿠버네티스에서 파드는 컨트롤러가 관리하므로 한군데에 고정해서 실행되지 않고, 클러스터 안을 옮겨다닌다. 이 과정에서 노드를 옮기면서 실행되기도 하고, 클러스터 안 파드의 IP가 변경되기도 한다.
이렇게 동적으로 변하는 파드들에 고정적으로 접근할 때 사용되는 방법이 쿠버네티스의 서비스이다.
비슷한 개념으로 인그레스로도 접근할 수 있는데, 서비스는 주로 L4 영역에서 통신할 때 사용되고, 인그레스는 L7 영역에서 통신할 때 사용된다는 차이점이 있다.
아래의 그림은 Nodeport 타입을 활용한 Service이다. Node마다 30080 port가 열려 있고, 외부로부터 30080으로 요청을 받고 서비스가 다시 파드들에 로드밸런싱(분산)해주는 식으로 사용자는 서비스를 이용해 파드의 위치에 상관없이 사용할 수 있다.
한마디로 서비스란, 파드를 통해 실행되고 있는 애플리케이션을 네트워크상에 노출시키는 가상의 컴포넌트이다.

7.2 서비스 타입

서비스 타입에는 크게 4가지가 있다.
Cluster IP(기본 형태)
파드들이 클러스터 내부의 다른 리소스들과 통신할 수 있도록 해주는 가상의 클러스터 전용 IP이다.
이 유형의 서비스는 <Cluster IP>로 들어온 클러스터 내부 트래픽을 해당 파드의 <파드IP>:<targetPort> 로 넘겨주도록 동작하므로, 오직 클러스터 내부에서만 접근 가능하게 된다.
NodePort
NodePort는 외부에서 노드 IP의 특정 포트(<NodeIP>:<NodePort>)로 들어오는 요청을 감지하여, 해당 포트와 연결된 지정된 파드로 트래픽을 전달하는 유형의 서비스.
노드의 포트를 사용하므로 클러스터 안 뿐만 아니라 클러스터 외부에서도 접근할 수 있다.
LoadBalancer
AWS, GCP 같은 퍼블릭 클라우드 서비스, 오픈스택 같은 프라이빗 클라우드, 쿠버네티스를 지원하는 로드밸런서 장비에서 사용한다.
위와 같은 환경을 고려하여, 해당 로드밸런서를 클러스터의 서비스로 프로비저닝할 수 있는 유형이다.
ExternalName
서비스에 selector 대신 DNS name을 직접 명시하고자 할 때에 쓰인다.
클러스터 안에서 외부에 접근할 때 주로 사용한다.
spec.externalName 필드에 필요한 DNS 주소를 기입하면, 클러스터의 DNS 서비스가 해당 주소에 대한 CNAME 레코드를 반환한다.

7.3 서비스 사용하기

서비스 템플릿의 기본 구조
apiVersion: v1 kind: Service metadata: name: my-sevice spec: type: ClusterIP clusterIP: 10.0.10.10 selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 9376
YAML
복사
service/service.yaml
.spec.type 필드에서 서비스 타입을 설정할 수 있다. 필드값을 설정하지 않으면 기본 타입은 ClusterIP.
.spec.clusterIP 필드에서 클러스터 IP를 직접 설정할 수 있다. 설정하지 않으면 자동으로 IP값이 할당된다.
.spec.selector 필드에는 서비스와 연결할 파드에서 설정한 .labels 필드 값을 설정한다.
.spec.ports[] 필드는 배열 형태이다. 서비스에서 한꺼번에 포트 여러 개를 외부에 제공할 때는 .spec.ports[] 하위에 필드 값을 설정하면 된다.
다음 명령어로 디플로이먼트로 생성하는 서비스에 연결할 파드를 실행.
kubectl create deployment nginx-for-svc --image=nginx --replicas=2 --port=80
Shell
복사

7.3.1 ClusterIP 타입 서비스 사용하기

ClusterIP 타입 서비스 설정 예시
apiVersion: v1 kind: Service metadata: name: clusterip-sevice spec: type: ClusterIP selector: app: nginx-for-svc ports: - protocol: TCP port: 80 targetPort: 80
YAML
복사
service/clusterip.yaml
clusterip.yaml 파일을 저장하고 kubectl apply -f clusterip.yaml 명령으로 클러스터에 적용한다.
kubectl get svc 명령어로 서비스 생성을 확인.
TYPE 항목이 ClusterIP이고 CLUSTER_IP 항목에 10.100.50.226로 클러스터 IP가 생성되었다. EXTERNAL-IP 항목은 외부 IP가 없으므로 <none>이다. PORT(s)에는 80 포트가 연결되었다는 정보가 보인다.
좀 더 자세한 정보를 보려면 kubectl describe service 서비스이름 명령을 실행한다.
kubectl get pods -o wide 항목으로 현재 실행중인 파드들의 IP를 확인.
앞서 실행했던 nginx-for-service 디플로이먼트의 파드가 2개 실행중이고, IP가 각각 10.1.0.63, 10.1.0.64이므로 clusterip-service 서비스의 Endpoints 항목과 값이 같다.
이제 10.100.50.226 이라는 IP로 파드에 접근할 수 있는지 확인해보자.
클러스터 IP는 쿠버네티스 클러스터 안에서만 사용할 수 있는 IP이다. 따라서 쿠버네티스 클러스터 안에 파드를 하나 실행하고 해당 파드 안에서 앞서 만든 클러스터 IP로 접속한다.
앞에서 만든 netshoot 컨테이너 이미지를 사용한다.
kubectl run -it --image nicolaka/netshoot testnet -- bash 명령으로 파드 안 컨테이너의 배시 셸에 접속할 수 있다. 이 상태로 curl 파드IP 명령을 실행하면 nginx 접속 페이지의 HTML 마크업을 출력한다. ClusterIP 타입 서비스가 잘 설정되었고 정상 작동함을 알 수 있다.

7.3.2 NodePort 타입 서비스 사용하기

NodePort 타입 서비스 설정 예시
apiVersion: v1 kind: Service metadata: name: nodeport-service spec: type: NodePort selector: app: nginx-for-svc ports: - protocol: TCP port: 80 targetPort: 80 nodePort: 30080
YAML
복사
service/nodeport.yaml
kubectl apply -f nodeport.yaml 로 서비스 생성, kubectl get svc 로 서비스 생성을 확인한다.
NodePort 타입이지만 CLUSTER-IP 항목이 10.107.82.34 로 설정된 것을 알 수 있다.
PORT(s) 항목의 80:30080/TCP는 노드의 30080 포트가 ClusterIP 타입 서비스의 80 포트와 연결되었다는 뜻이다.
웹브라우저 실행 후 localhost:30080 으로 접속한다. 서비스 정상 작동.

7.3.3 LoadBalancer 타입 서비스 사용하기

LoadBalancer 타입 서비스 설정 예시
apiVersion: v1 kind: Service metadata: name: loadbalancer-service spec: type: LoadBalancer selector: app: nginx-for-svc ports: - protocol: TCP port: 80 targetPort: 80
YAML
복사
service/loadbalancer.yaml
kubectl apply -f loadbalancer.yaml 로 서비스 생성, kubectl get svc 로 서비스 생성을 확인한다.
현재 실습환경은 도커 데스크톱이므로 외부 로드밸런서가 없어 EXTERNAL-IP 항목이 localhost로 나타난다. 만약 외부 로드밸런서와 연계되어 쿠버네티스 클러스터가 설정된 상태라면 localhost가 아니라 실제 외부에서 접근 가능한 IP가 나타날 것이다.

7.3.4 ExternalName 타입 서비스 사용하기

ExternalName 타입 서비스 설정 예시
apiVersion: v1 kind: Service metadata: name: externalname-service spec: type: ExternalName externalName: google.com
YAML
복사
service/externalname.yaml
.spec.externalName 필드 값으로 google.com 을 설정했다. 연결하려는 외부 도메인 값을 설정한 것.
kubectl apply -f externalname.yaml 로 서비스 생성, kubectl get svc 로 서비스 생성을 확인한다.
ExternalName 타입 서비스 설정이 잘 동작하는지 테스트한다. 다시 kubectl run -it --image nicolaka/netshoot testnet -- bash 명령으로 nicolaka/netshoot 컨테이너 이미지를 사용하는 파드를 만든다.
ExternalName 타입 서비스의 클러스터 내부 도메인은 curl externalname-service.default.svc.cluster.local 이다. curl 명령어로 해당 도메인을 이용해 접속해보면 실제 구글의 HTML 마크업이 출력된다.
DNS 설정이 올바른지 확인하는 dig 도메인이름 명령을 실행하면, externalname-service.default.svc.cluster.local 의 DNS 레코드가 CNAME 타입의 google.com 으로 설정되었음을 볼 수 있다.

7.4 헤드리스 서비스

.spec.clusterIP 필드 값을 None으로 설정하면 클러스터 IP가 없는 서비스를 만들 수 있다. 이런 서비스를 헤드리스 서비스(headless service)라고 한다.
로드 밸런싱이 필요 없거나 단일 서비스 IP가 필요 없을 때 사용한다.
헤드리스 서비스에 셀렉터(.spec.selector 필드)를 설정하면 쿠버네티스 API로 확인할 수 있는 엔드포인트가 만들어진다. 서비스와 연결된 파드를 직접 가리키는 DNS A 레코드도 만들어진다. 셀렉터가 없으면 엔드포인트가 만들어지지 않는다. 단, 셀렉터가 없더라도 DNS 시스템은 ExternalName 타입의 서비스에서 사용할 CNAME 레코드가 만들어진다.
헤드리스 서비스 설정 예시
apiVersion: v1 kind: Service metadata: name: headless-service spec: type: ClusterIP clusterIP: None selector: app: nginx-for-svc ports: - protocol: TCP port: 80 targetPort: 80
YAML
복사
service/headless.yaml
kubectl apply -f headless.yaml 로 서비스 생성, kubectl get svc 로 서비스 생성을 확인한다.
TYPE 항목은 ClusterIP이지만 CLUSTER-IP 항목과 EXTERNAL-IP 항목이 <none>이다.
kubectl describe svc headless-service 명령을 실행해보면 IP 항목은 None이므로 실제 값이 없지만, Endpoints 항목에는 .spec.selector 필드에서 선택한 조건에 맞는 파드들의 IP와 포트 정보를 확인할 수 있다.
DNS A 레코드가 만들어져 있는지를 확인하려면 kubectl attach testnet -c testnet -i -t 명령으로 nicolaka/netshoot 컨테이너 이미지를 이용해 만든 파드에 접속한 후 dig 도메인이름 명령을 실행한다.
ANSWER SECTION 항목에서 A 레코드 확인 가능

7.5 kube-proxy

kube-proxy는 쿠버네티스에서 서비스를 만들었을 때 클러스터 IP나 노드 포트로 접근할 수 있게 만들어 실제 조작을 하는 컴포넌트이다. 쿠버네티스 클러스터의 노드마다 실행되면서 클러스터 내부 IP로 연결하려는 요청을 적절한 파드로 전달한다.
kube-proxy가 네트워크를 관리하는 방법은 userspace, iptables, IPVS가 있다. 초기에는 userspace가 기본 관리 모드였고 현재는 iptables가 기본 관리 모드이다. 앞으로 IPVS로 기본 관리 모드가 바뀔 것으로 예상.

7.5.1 userspace 모드

클라이언트 요청 → 서비스의 클러스터 IP → iptables → kube-proxy
그리고 서비스의 클러스터 IP는 연결되어야 하는 적절한 파드(애플리케이션)로 연결해준다.
이때, 요청은 RR(라운드로빈)방식으로 분배.
파드 하나로의 연결 요청이 실패하면 자동으로 다른 파드에 연결을 재시도.

7.5.2 iptables 모드

userspace모드와 다른 점은 kube-proxy가 iptables 를 관리하는 역할만 한다는 것이다.
직접 클라이언트에서 트래픽을 받지 않는다.
클라이언트에서 오는 모든 요청은 iptables을 거쳐서 파드(애플리케이션)로 직접 전달된다.
파드 하나로의 연결 요청이 실패하면 재시도하지 않고 그냥 요청이 실패한다.
컨테이너에 readinessProbe가 설정되었고 그에 따른 Health Check가 정상적으로 되어야 연결 요청이 이루어진다.

7.5.3 IPVS 모드

리눅스 커널에 있는 L4 로드밸런싱 기술
리눅스 커널 안 네트워크 관련 프레임워크인 넷필터에 포함되어 있다. 따라서 IPVS 커널 모듈이 노드에 설치되어야 한다.
IPVS 모드는 커널 공간(장치 드라이버 대부분을 실행하는 공간)에서 동작하고 데이터 구조를 해시 테이블로 저장하기 때문에 iptables 모드보다 빠르고 좋은 성능을 낸다.
또한 다른 로드밸런싱 알고리즘도 이용할 수 있다.
주요 로드밸런싱 알고리즘
rr(round-robin): 프로세스 사이에 우선순위를 두지 않고 순서와 시간 단위로 CPU를 할당함.
lc(least connection): 접속 개수가 가장 적은 서버를 선택한다.
dh(destination hashing): 목적지 IP주소로 해시값을 계산해 분산할 실제 서버를 선택한다.
sh(source hashing): 출발지 IP주소로 해시값을 계산해 분산할 실제 서버를 선택한다.
sed(shortest expected delay): 응답 속도가 가장 빠른 서버를 선택한다.
nq(never queue): sed와 비슷하지만 활성 접속 개수가 0인 서버를 가장 먼저 선택한다.

Reference