Gom3rye

Kubernetes Service의 type 본문

현대 오토에버 클라우드 스쿨

Kubernetes Service의 type

Gom3rye 2025. 7. 7. 17:47
728x90
반응형

cluster 안에는 노드들이 있고

노드는 2가지 종류로 나뉜다.

  • control plane (master)
    • API Server: 사용자가 kubectl 명령을 하면 여기로 와서 이 명령이 유효한 명령인지 유효성 검증을 한다. 검증 통과하면 created가 뜬다. → 실제로 만들어진게 아니라 이 명령이 유효성 검증을 통과했어. 이제 나는 일을 할 거야. 라는 의미.
    • etcd: 저장하는 곳, API Server에서 한 일을 저장하고 (모든 정보들은 etcd에 다 있다.)
    • Scheduler: API Server가 일을 어디에 배치할 지 여기로 보내주면 scheduler가 임의의 노드에 일을 배치 (일반적으로 현재 자원을 제일 적게 쓰고 있는 노드에게 보내준다.) 스케쥴러가 배치할 노드에 배치하면 그 내용을 또 etcd는 저장
    • kubectl이 pod를 만들어서 api server에 내용 보내고 해당 내용은 etcd에 또 저장
  • worker node

실제적으로 배포를 할 때는

  • Deployment(Replicaset과 비슷한데 업데이트 전략을 수행할 수 있다),
  • Daemonset(모든 워커노드에 무조건 하나씩 배치되는 것, 로그 수집/모니터링 용),
  • StatefuleSet(이어서 하기 위해 상태를 저장, 데이터베이스/로그인이 있는 경우(세션사용)),
    • 정보를 서버에 저장하면 세션, 클라이언트에 저장하면 쿠키
    • 로그인 정보는 빠르게 가져와야 하니까 인메모리에 저장하자(Redis)
    • 웬만하면 아이디와 비밀번호를 저장하지 말하라 → 토큰 써라(토큰 값은 비교는 할 수 있지만 복원은 할 수 없다.)
  • Job(한 번 모아서 처리하는 것(batch))
    • 실시간 처리 ≠ 배치처리
    • 작업이 너무 많이 발생하는 것은 실시간 처리가 어렵다.
  • CronJob(주기적으로 모아서 처리하는 것(batch), 데이터 백업)
  • 같은 pod내 컨테이너들의 통신은 localhost로 하면 돼서 내가 건들일 필요가 없다. (여기서 도커와 쿠버네티스의 차이를 알아야 한다.)
    • 도커는 컨테이너 단위로 ip가 부여되고 쿠버네티스는 pod 단위로 ip가 부여된다.
  • 서로 다른 Pod끼리는 각각 가상의 ip를 가진다. → 얘네들이 서로 통신을 하려면 가상의 네트워크를 만들어줘야 한다. (같은 네트워크 안에 있으면 통신할 수 있다.)
    • NAT는 독립된 네트워크를 만든다. → a, b 네트워크 → a,b는 서로 통신 못한다. 인터넷을 나가서 통신하면 되긴 하는데 인터넷을 나가서 통신하면 돈이 든다. ⇒ 불필요하게 네트워크 자원을 소비하는 것
    • 파드 만들 때마다 서로 통신할 수 있도록 네트워크 같게 만들어 주는 것 → 너무 일이 많아져서 직접 할 수 없음 ⇒ 이걸 대신 해주는 것이 CNI Plugin (Flannel:10.244.0.16)
    • IP는 CNI Plugin이 부여해주는 것
  • ip는 동적으로 할당됨 → 바뀔 수 있기 때문에(클러스터가 생성되거나 재시작될 때마다 변경될 수 있음) ⇒ 이름으로 통신하자. ⇒ 그래서 만들어진 애가 Service (내가 얘를 고정적인 이름으로 부를 수 있도록 해주세요.) → Cluster IP만 쓰면 되고 더 이상 pod의 ip로 통신하지 않아도 된다.
    • container는 immutable이어야 한다. (컨테이너 내부의 내용을 바꾸지 않고도 동작시켜야 한다.)

서비스의 기능

Load Balancing

# sample-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:  # ← selector와 같은 레벨
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: amsy810/echo-nginx:v2.0
 
# pod 확인
kubectl get pods -o wide # ip까지 확인
# pod의 IP만 확인(app=sample-app 레이블을 가진 파드만 필터링하여 파드 이름과 IP 주소만 선택적으로 확인)
kubectl get pods -l app=sample-app -o custom-columns="NAME:{metadata.name}, IP:{status.podIP}"
# NAME                                  IP (이 IP 주소들은 클러스터 내부에서만 접근 가능한 파드 IP)
sample-deployment-5d9fcfcbb6-k4lvs   10.244.1.55
sample-deployment-5d9fcfcbb6-pl8gg   10.244.2.54
sample-deployment-5d9fcfcbb6-w9txk   10.244.2.53

# 하나의 클러스터 내부에서 다른 파드에 접근할 수 있는 ip를 부여해서 로드밸런싱을 해주는 Cluster IP를 부여하기 위한 매니페스트 작성
# sample-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-clusterip
spec:
  type: ClusterIP # 클러스터 내부에서만 접근 가능한 가상 IP를 할당해 서비스에 대한 접근을 제공. 외부에서는 직접 접근할 수 없다.
  ports:
  - name: "http-port" # 포트포워딩 하는 부분
    protocol: "TCP"
    port: 8080 # 클러스터 내부의 다른 파드나 서비스가 sample-clusterip에 접근하려면 8080 포트를 사용해야 함
    targetPort: 80 # 실제 파드의 컨테이너 포트(targetPort: 80: port: 8080으로 요청이 들어오면, 실제 파드 컨테이너의 80번 포트로 전달된다. Nginx의 기본 HTTP 포트는 80번)
  selector:
    app: sample-app # app: sample-app레이블을 가진 모든 파드를 이 서비스의 엔드포인트(Endpoints)로 포함
    
# 클러스터ip 서비스 확인
kubectl apply -f sample-clusterip.yaml
kubectl get service
#NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
#kubernetes         ClusterIP   10.96.0.1      <none>        443/TCP    5d17h
#sample-clusterip   ClusterIP   10.103.98.14   <none>        8080/TCP   2d17h
kubectl describe service sample-clusterip # 더 자세히 보기
# 여기에 아까 pod들의 endpoint가 있다. -> 자기가 가야할 곳을 알고 있다.
# Endpoints:  10.244.1.55:80, 10.244.2.53:80, 10.244.2.54:80 (pod의 ip:서비스되는 port)
# EndPoints에 아무런 IP도 보이지 않는다면 selector 부분 오류일 가능성이 높다.

# Load Balancing 기능 확인: 
# 생성된 ClusterIP 서비스를 통해 sample-app 파드에 정상적으로 접근하고 로드 밸런싱이 동작하는지 테스트
# curl 명령을 10.103.98.14에 보내보기
# cluster로 요청을 전송하는 pod를 생성 (매니페스트 사용하지 않고 만드는 것 --rm : 끝나면 지워줘)
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s <http://10.103.98.14:8080> # (clusterip:port)
# from의 이름이 파드 별로 번갈아가면서 출력된다.

클러스터 내부 DNS와 서비스 디스커버리

  • 쿠버네티스에서는 서비스 디스커버리 기능을 서비스가 제공한다.
  • 서비스 디스커버리: IP 주소를 할당하거나 앤드포인트를 구성하지 않아도 서비스가 동적으로 서로 검색할 수 있게 하는 것 (ip가 바뀌더라도 내가 바꿔야 할 것은 없음)
    • 최근에 개발되고 있는 애플리케이션은 확장성의 문제 때문에 MSA를 도입한 경우가 많은데 서비스끼리 통신할 때 어떻게 하면 효율적으로 할 수 있을까를 고민하여 나온 것이 서비스 디스커버리이다.
    • 마이크로서비스가 종료되거나 IP가 바뀌어도 자동으로 이를 검색할 수 있는 매커니즘을 도입한 것이다.
    • 이름에서 앤드포인트를 판별해주는 기능 (서비스 디스커버리를 통해 DNS 이름만 알면 자동으로 올바른 Pod로 라우팅 가능)
  • 쿠버네티스에서 서비스 디스커버리랑 서비스에 있는 파드를 보여주거나 서비스명에서 앤드포인트 정보를 반환하는 것
  • 방법
    • 환경 변수를 사용한 디스커버리
    • DNS A 레코드를 사용한 서비스 디스커버리
    • DNS SRV 레코드를 사용한 서비스 디스커버리
      • A 레코드는 "이 이름의 IP 주소가 뭐야?"만 알려주는 반면, SRV 레코드는 거기에 더해서:
      🔸 "이 서비스는 어떤 호스트(IP)에서, 어떤 포트에서, 어떤 우선순위로 실행돼?"
    • 까지 알려준다.

환경 변수를 사용한 디스커버리

환경 변수 확인

kubectl exec -it 파드이름 -- env | grep -i kubernetes

kyla@masternode:~/kube$ kubectl exec -it sample-deployment-5d9fcfcbb6-j4pvd -- env | grep -i kubernetes
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_SERVICE_PORT=443

DNS A 레코드를 사용한 서비스 디스커버리

  • 다른 파드에서 서비스로 할당되는 앤드포인트에 접속하려면 당연히 목적지가 필요한데 할당된 IP 주소를 사용하는 방법 외에도 자동 등록된 DNS 레코드를 이용할 수 있다.
  • 쿠버네티스에서 IP 주소를 편하게 관리하려면 기본적으로 자동 할당된 IP 주소에 연결된 DNS 이름을 사용하는 것이 바람직하다.
    • 할당되는 IP 주소는 파드를 생성할 때마다 변경되기 때문이다.
  • IP 주소를 송신 측 컨테이너 설정 파일 등에서 명시적으로 설정하면 변경될 때마다 설정 파일 등을 변경해야 하기 때문에 컨테이너는 변경 불가능한(immutable) 상태로 유지할 수 없어 추천하지 않는다.
kubectl delete service sample-clusterip
kubectl apply -f sample-clusterip.yaml
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s <http://10.103.98.14:8080> # (clusterip:port)
# ip가 바뀌었기 때문에 실행되지 않는다.
# 서비스 이름을 이용한 요청
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s sample-clusterip:8080 # (서비스이름:port)
# ip가 바뀌어도 이름은 똑같으니 실행 된다.
**# 또 지우고 새로 해봐도 ip로 연결하는게 아니라 이름으로 연결한 거기 때문에 된다.**
kubectl delete service sample-clusterip
kubectl apply -f sample-clusterip.yaml
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s sample-clusterip:8080 # (서비스이름:port)

→ 실제 등록된 FQDN(Fully Qualified Domain Name)은 서비스명.네임스페이스명.svc.cluster.local 로 되어 있다. (FQDN: 어디에서도 중복되지 않는 유일한 이름으로 DNS 이름을 전체 경로로 지정하면 FQDN이 된다.)

kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig **sample-clusterip.default.svc.cluster.local** # (default:네임스페이스)
  • 서비스가 충돌하지 않도록 네임스페이스를 포함한다.
    • 원래 FQDN을 다 써야 하는데 어떻게 sample-clusterip만 썼는데 잘 찾아가지?
    kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- cat /etc/resolv.conf
    #
    search default.svc.cluster.local svc.cluster.local cluster.local
    nameserver 10.96.0.10
    options ndots:5
    pod "testpod" deleted
    
    • cat /etc/resolv.conf 를 보면 search default.svc.cluster.local svc.cluster.local cluster.local로 각각을 붙여주면서 찾기 때문에 sample-clusterip만 써도 찾아갈 수 있다. (like www.naver.comnaver.com 만 쳐도 네이버 잘 가는 것처럼)
  • 자신과 동일한 namespace 일 때는 서비스 이름만 작성하면 되고 만약에 다른 네임스페이스라면 서비스이름.namespace로 요청해야 한다.
    • DNS 설정 파일은 /etc/resolv.conf, /etc/host.conf 이고 유사한 역할을 하는 게 /etc/hosts 파일이다.
  • 역방향도 가능하다. (-x: 리버스 옵션)
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig -x 10.111.144.43
# 10.111.144.43 -> sample-clusterip의 CLUSTER-IP
# 실제 ip가 나오고 dns 이름도 나온다.
;; ANSWER SECTION:
43.144.111.10.in-addr.arpa. 30  IN      PTR     sample-clusterip.default.svc.cluster.local.

DNS SRV 레코드를 사용한 서비스 디스커버리

  • SRV는 포트명과 프로토콜을 사용해 서비스를 제공하는 포트 번호를 포함한 앤드포인트를 DNS로 해석하는 구조이다.
  • 레코드 형식
    • _서비스의포트명_프로토콜.네임스페이스이름.svc.cluster.local
  • SRV를 DNS로 해석이 가능한지 확인 (너무 길어서 실제 이걸로 통신하려 하지는 않을 것)
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig -x **_http_port._tcp.sample-clusterip.default**.svc.cluster.local SRV

클러스터 내부 DNS와 외부 DNS

  • dnsPolicy를 사용하여 파드의 DNS 서버 설정을 명시적으로 하지 않는 이상 기동하는 모든 파드는 이 클러스터 내부 DNS를 사용하여 이름을 해석한다.
  • 클러스터 내부 DNS에 서비스 앤드포인트에 대한 레코드(서비스이름.네임스페이스.cluster.local)가 저장되어 있다.
  • 기본적으로 요청을 전송하면 내부 DNS에 질의하여 서비스 디스커버리를 사용한다.
  • 그래서 만약 내부 DNS에서 요청을 처리하지 못하면 외부 DNS에 재귀 질의를 한다.

ClusterIP

쿠버네티스 서비스의 가장 기본

  • type: ClusterIP로 생성
  • 서비스가 생성되면 쿠버네티스 클러스터 내부에서만 사용 가능한 Internal Network에 생성되는 가상 IP가 할당된다.
  • ClusterIP와 통신은 각 노드 상에서 실행 중인 시스템 구성 요소 kube-proxy가 파드로 전송을 실시한다.
  • 쿠버네티스 클러스터 외부에서 트래픽을 수신할 필요가 없는 환경에서 클러스터 내부의 로드밸런서로 사용한다.
  • 기본적으로는 쿠버네티스 API에 접속하기 위한 Kubernetes 서비스가 생성되어 있고 이미 ClusterIP가 부여되어 있다. (쿠버네티스를 설치하면, 우리가 명시적으로 서비스를 만들지 않아도 쿠버네티스는 자체적으로 내부 통신용 서비스를 자동으로 생성한다. → master와 worker node가 서로 통신하도록 만들어줘야 하니까)
    • 마스터 노드(API 서버)와 워커 노드(Kubelet, kube-proxy 등)가 항상 서로 통신해야 한다.
    • 이를 위해 기본적으로 API 서버를 ClusterIP로 노출시켜 놓고, 내부 DNS에 등록해두는 것!

ClusterIP 생성

작성법

  • type을 ClusterIP로 설정
  • spec.ports[].port 에는 ClusterIP에서 수신할 포트 번호를 지정하고 spec.ports[].targetPort에는 목적지 컨테이너 포트 번호를 지정한다.

ClusterIP 가상 IP 정적 설정

  • 애플리케이션에서 데이터베이스 서버를 지정하는 경우 기본적으로 쿠버네티스 서비스에 등록된 클러스터 내부 DNS 레코드를 이용해서 호스트를 지정하는 것이 바람직하다.
  • IP 주소를 지정하고자 하는 경우에는 ClusterIP를 정적으로 지정해야 한다.
  • spec.clusterIP 항목에 직접 설정하면 된다.
  • 서비스가 생성된 후에는 변경할 수 없다.
  • kubectl apply 를 이용하면 설정 값을 변경할 수 있지만 ClusterIP 같은 일부 필드는 변경할 수 없다. → 이 경우에는 삭제하고 다시 생성해야 한다.
# sample-clusterip-vip.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-clusterip-vip
spec:
  type: ClusterIP
  clusterIP: 10.105.177.14
  ports:
  - name: "http-port" # 포트포워딩 하는 부분
    protocol: "TCP"
    port: 8080 
    targetPort: 80
  selector:
    app: sample-app # app: sample-app레이블을 가진 모든 파드를 이 서비스의 엔드포인트(Endpoints)로 포함
    
# 제대로 배포되었는지 확인
kubectl apply -f sample-clusterip-vip.yaml
kubectl describe service sample-cluster-vip
# IP:         10.105.177.14
# Endpoints:  10.244.1.57:80,10.244.1.58:80,10.244.1.59:80 로 잘 들어가 있다.
# 통신도 해보기
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s <http://10.105.177.14:8080>
# 이제 clusterIP를 바꾼 후 apply 하면 may not change once set 이라고 에러 나온다.

ExternalIP 서비스

  • 특정 쿠버네티스 노드 IP 주소:포트 에서 수신한 트래픽을 컨테이너로 전송하는 형태로 외부와 통신할 수 있도록 하는 서비스이다.
  • 최근에는 이런 경우 NodePort 서비스를 사용하는 것을 권장한다.
# sample-externalip.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-externalip
spec:
  type: ClusterIP
  externalIPs:
  - 192.168.202.169 # 노드의 IP
  ports:
  - name: "http-port" 
    protocol: "TCP"
    port: 8080
    targetPort: 80
  selector:
    app: sample-app
    
# 제대로 배포되었는지 확인
kubectl apply -f sample-externalip.yaml
# 서비스 확인 
kubectl get services
# 내부 로드밸런싱을 수행하기 위해 ClusterIP가 자동으로 생성되었다.
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP       PORT(S)    AGE
kubernetes             ClusterIP   10.96.0.1       <none>            443/TCP    5d22h
sample-clusterip       ClusterIP   10.111.144.43   <none>            8080/TCP   3h23m
sample-clusterip-vip   ClusterIP   10.105.177.14   <none>            8080/TCP   35m
sample-externalip      ClusterIP   10.102.42.51    192.168.202.169   8080/TCP   12m
# 실제 서비스 디스커버리는 ClusterIP (외부 사용자는 ExternalIP를 통해 접근하지만, 클러스터 내부에서는 DNS 이름 → ClusterIP → Pod으로 연결)
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig sample-externalip.default.svc.cluster.local
	;; ANSWER SECTION:
	sample-externalip.default.svc.cluster.local. 30 IN A 10.102.42.51
# ExternalIPs 타입을 쓰면 노드IP+포트를 통해 외부에서 접근 가능 (But, 이렇게는 잘 사용 안 하고 NodePort나 LoadBalancer 방식 사용 권장)
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- curl -s <http://192.168.202.169:8080>

✅ ExternalIP는 "외부에서 접근할 수 있도록 노출된 IP"일 뿐

  • 내부적으로는 여전히 ClusterIP를 가지고 있다.

NodePort

ExternalIP Service와 유사

  • ExternalIP는 지정한 쿠버네티스 노드의 IP주소:포트 에서 수신한 트래픽을 컨테이너로 전송하는 형태로 외부와 통신한다.
  • NodePort는 모든 쿠버네티스노드의IP:포트 에서 수신한 트래픽을 컨테이너로 전송하는 형태
  • ExternalIP 서비스에서 전체 노드의 트래픽을 수신할 수 있도록 설정하는 기능과 비슷하지만 엄밀히 말하면 Listen 할 때 0.0.0.0:포트 를 사용하여 모든 IP 주소를 바인드하는 형태이다.

작성법

  • type을 NodePort로 설정
  • 포트 매핑을 할 때 nodePort:포트번호 를 설정하는데 이때 포트 번호는 3000~32767까지이다.
# sample-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-nodeport
spec:
  type: NodePort
  ports:
  - name: "http-port" 
    protocol: "TCP"
    port: 8080
    targetPort: 80
    nodePort: 30080 # 이를 설정하지 않으면 자동으로 빈 포트가 할당된다.
  selector:
    app: sample-app
    
# 리소스 생성
kubectl apply -f sample-nodeport.yaml
# 서비스 확인 
kubectl get services
# sample-nodeport   NodePort  10.97.20.109    <none>   8080:30080/TCP   15s

# 내부 DNS에서 반환하는 서비스 디스커버리 주소 확인
kubectl run --image=amsy810/tools:v2.0 --restart=Never --rm -i testpod --command -- dig sample-nod.default.svc.cluster.local
# ;; ANSWER SECTION:
# sample-nodeport.default.svc.cluster.local. 30 IN A 10.97.20.109
# 외부에서 접속하려면 public ip가 있어야 해서 실습 힘듦. (노드ip:포트로 접근)
# 당연한 얘기지만 이미 쓰고 있는 포트를 준다면 에러

LoadBalancer

  • 지금까지 할당한 가상 ip는 클러스터 내부
  • 클러스터 외부로부터 트래픽을 수신할 때 가장 실용적이고 사용하기 편리한 서비스
  • 쿠버네티스 외부의 로드밸런서에 외부 통신이 가능한 가상 IP를 할당할 수 있다.
  • 외부 로드밸런서를 사용하려면 쿠버네티스 클러스터가 구축된 인프라가 이 구조에 맞도록 설계되어 있어야 한다.
  • AWS/GCP/Azure/OpenStack을 비롯한 Cloud 공급자가 LoadBalancer 서비스를 사용할 수 있는 환경을 제공하며 최근에는 MetalLB 등도 출시되었다.
  • NodePort나 ExternalIP에서는 결국 하나의 쿠버네티스 노드에 할당된 IP를 사용하기 때문에 그 노드가 단일 장애점이 되어 버린다.(Single Point of Failure(SPoF) 문제가 있을 수 있다.)
    • NodePort는 기본적으로 클러스터 내 모든 노드에서 포트를 열고 있기 때문에 SPOF 문제가 적지만 운영 환경에서 클라이언트가 특정 노드 IP로만 접속하도록 강제하거나 로드밸런서 없이 ExternalIP나 NodePort 중 한 노드 IP만 알려주는 경우 SPOF 문제가 발생할 수 있다.
      • NodePort는 기본적으로 클러스터 내 모든 노드에서 포트를 열고 있기 때문에 SPOF 문제가 적음
      • ExternalIP는 보통 특정 노드에만 IP가 할당되므로 SPOF가 발생하기 쉬움
      • 운영 환경에서는 로드밸런서(LoadBalancer) 또는 Ingress 같은 상위 레이어를 통해 SPOF 문제를 해결함
  • 하지만 LoadBalancer는 외부 로드밸런서를 사용하기 때문에 노드 장애가 발생해도 문제가 되지 않는다.
  • NodePort 서비스를 생성하고 클러스터 외부 로드밸런서에서 쿠버네티스 노드로 밸런싱을 하는 구조
  • 쿠버네티스 노드에 장애가 발생한 경우에는 그 노드에 트래픽을 전송하지 않음으로써 자동으로 복구하게 된다.
  • 로드밸런서가 노드 장애를 감지하고 목적지에서 제외 처리하기까지의 시간이 필요하므로 그동안 일시적으로 서비스 중단 현상이 발생한다.
  • Docker-Desktop for Mac/Windows 에서는 LoadBalancer 서비스를 생성하면 localhost(127.0.0.1)의 IP 주소를 사용해서 접속할 수 있게 해준다.
    • 따라서 같은 포트 번호를 이용한 여러 LoadBalancer 서비스를 생성할 수 없다. (로컬 컴퓨터 입장에선 127.0.0.1:80 을 두 개가 동시에 쓸 수 없으니까)

작성법

  • NodePort 설정하는 방법과 동일하고 type만 LoadBalancer로 설정하면 된다.
  • 포트 설정
    • port: LoadBalancer 나 NodePort에 할당되는 가상 IP와 ClusterIP에서 수신할 포트 번호(요청)
    • targetPort: 목적지 컨테이너 포트 번호(컨테이너가 서비스를 제공하기 위해 사용하는 포트 번호)
    • nodePort: 모든 쿠버네티스 노드 IP 주소에서 수신할 포트 번호
# sample-lb.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-lb
spec:
  type: LoadBalancer
  ports:
  - name: "http-port" 
    protocol: "TCP"
    port: 8080
    targetPort: 80
    nodePort: 30082
  selector:
    app: sample-app
    
# 리소스 생성
kubectl apply -f sample-lb.yaml
# 서비스 확인 
kubectl get services
# NAME           TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)          AGE
# kubernetes     ClusterIP      10.96.0.1       <none>            443/TCP          6d
# sample-lb      LoadBalancer   10.96.134.251   <pending>         8080:30082/TCP   4m31s
# 로드밸런서 서비스가 생성되고 클러스터 IP 생성되고 포트 매핑도 이루어진다.
# 이 환경에서는 ExternalIP가 할당되지 않아서 외부에서 실제 접근은 되지 않는다.
  • 가상 IP 정적 지정
    • spec.loadBalancerIP 속성에 지정하면 된다.
    • GKE(구글)에서는 지원하지 않는다.
    • GKE에서는 Ingress Resource를 사용해서 전역 타입의 정적 주소를 지정할 수 있다.
  • 로드밸런서의 방화벽 정책 설정
    • LoadBalancer 서비스를 생성하면 기본적으로 전세계로 공개
    • GKE(GCP)와 EKS(AWS)에서는 spec.loadBalancerSourceRanges 에 접속을 허가하는 발신 측 네트워크 주소를 설정하면 클러스터 외부의 외부 로드밸런서의 클라우드 프로바이더가 제공하는 방화벽 기능을 사용해서 접속 제한을 설정할 수 있다.
      • 기본값은 0.0.0.0/0으로 지정되어 있다.
    • 외부 로드밸런서에서 접속 제어가 구현되지 않은 쿠버네티스 환경에서 이 loadBalancerSourceRanges를 설정하면 쿠버네티스 노드 측의 iptables를 사용해 접속 제어가 이루어진다. (쿠버네티스는 노드 OS 레벨에서 iptables(리눅스 방화벽 도구)를 이용해서 이 IP 범위에 맞지 않는 접속을 차단한다.)
    • 네트워크 레벨에서 더 세밀하게 트래픽을 제어하고 싶으면 NetworkPolicy 리소스를 쓰면 된다. (But, 더 좋은건 앞에서 막는 거니까 로드밸런서에서 막는 것이 더 나음)
    • NetworkPolicy를 쓰려면 쿠버네티스 클러스터 내에 네트워크 플러그인(CNI 플러그인) 이 NetworkPolicy를 지원해야 하는 등 사용할 수 있는 환경에 제한이 있고, NetworkPolicy가 동작하면 쿠버네티스 노드의 iptables를 사용하여 접속을 제한한다.
    • 확장성 저하나 레이턴시에 영향을 미치기 쉬우므로 가능하면 로드밸런서 쪽에서 제한하도록 권장한다.
# sample-lb.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-lb
spec:
  type: LoadBalancer
  loadBalancerSourceRanges:
  - 10.0.0.0/8 # 10번대 IP만 들어오도록 하고 싶으면
  ports:
  - name: "http-port" 
    protocol: "TCP"
    port: 8080
    targetPort: 80
    nodePort: 30082
  selector:
    app: sample-app
    
# 리소스 생성
kubectl apply -f sample-lb.yaml
# 서비스 확인 
kubectl get services
# NAME           TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)          AGE
# kubernetes     ClusterIP      10.96.0.1       <none>            443/TCP          6d
# sample-lb      LoadBalancer   10.96.134.251   <pending>         8080:30082/TCP   4m31s
# 로드밸런서 서비스가 생성되고 클러스터 IP 생성되고 포트 매핑도 이루어진다.
# 이 환경에서는 ExternalIP가 할당되지 않아서 외부에서 실제 접근은 되지 않는다.
  • Public Cloud의 Load Balancer 서비스를 사용할 때 주의점!
    • Public Cloud의 Load Balancer는 별도의 서비스이고 별도로 과금된다.
    • 실수로 클러스터를 삭제하고 Load Balancer를 삭제하지 않는 경우 계속해서 과금된다. (서비스를 배치를 못하는 것 뿐 받아는 오니까)
728x90
반응형