본문 바로가기

Container/Kubernetes

[K8S] Service

Kubernetes의 Service는 클러스터 내에서 실행 중인 Pod 간의 네트워크 통신을 안정적으로 관리하고, 외부 클라이언트가 파드에 접근할 수 있도록 하는 추상화된 네트워크 리소스이다.

Service는 파드의 동적인 IP 주소 문제를 해결하고, 여러 Pod에 트래픽을 분산(로드 밸런싱)시키는 역할을 한다.

 

Service의 주요 역할

  1. 파드 간 통신 안정화: Service는 고정된 이름과 IP 주소를 제공하여 파드의 네트워크 위치 변화를 추상화한다.
    • 파드의 IP 주소는 재시작 시 변경될 수 있다.
  2. 로드 밸런싱: 여러 복제본(replica) 파드에 트래픽을 균등하게 분산
  3. 외부 트래픽 연결: 외부 클라이언트(예: 브라우저)에서 클러스터 내 파드에 접근할 수 있는 네트워크 엔드포인트를 제공합니다.
  4. DNS 통합: 쿠버네티스 클러스터에서 Service는 DNS 이름으로 등록되어 클러스터 내부에서 쉽게 접근할 수 있습니다.

Service의 동작 원리

  1. Selector로 파드 선택: Service는 Selector에 지정된 라벨을 가진 파드를 감지.
    - Service는 특정 Selector 없이도 동작할 수 있지만, 이 경우 수동으로 Endpoints를 설정해야 한다.
  2. Endpoints 생성: Service는 연결된 파드의 IP 주소와 포트를 Endpoints 리소스에 저장.
  3. 트래픽 라우팅: Service는 로드 밸런싱을 통해 트래픽을 적절한 파드로 전달.
  4. DNS를 통한 접근: 클러스터 내부에서는 Service 이름을 사용해 접근 가능.

 

서비스 = 쿠버네티스 네트워크
즉, Pod를 묶어주는 Virtual IP

[master ~/controller]$cat > nodeport-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: nodeport-service
spec:
  type: NodePort
  clusterIP: 10.8.0.200
  selector:
    app: webui
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 80
    nodePort: 30200

[master ~/controller]$kubectl create -f nodeport-nginx.yaml 
service/nodeport-service created
[master ~/controller]$kubectl get svc
NAME               TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
kubernetes         ClusterIP   10.8.0.1     <none>        443/TCP          4m43s
nodeport-service   NodePort    10.8.0.200   <none>        8080:30200/TCP   10s


# 워커노드에서 확인
[root@node1 ~]$netstat -anpt | grep 30200

# Kubernetes Service란

  • 동일한 서비스를 제공하는 Pod 그룹의 단일 진입점을 제공
  • Service는 Selector를 사용하여 특정 라벨(Label)을 가진 파드와 연결된다.

https://www.youtube.com/watch?v=5sKkIg7k8nw&list=PLApuRlvrZKohaBHvXAOhUD-RxD0uQ3z0c&index=25

  • Selector: app: webui 라벨을 가진 파드와 연결
  • Port: 클라이언트가 접근할 Service의 포트(80)
  • TargetPort: 파드 내부 컨테이너의 포트(80)
  • clusterIP는 생략 가능= clusterIP(=LB IP, virtual IP)로 접속하면 App로 접속하게 된다.

 

Kubernetes Service Type

  • ClusterIP(default)
    • 클러스터 내부에서만 접근 가능한 가상 IP 제공 - pod 그룹의 단일 진입점(Virtual IP) 생성
    • 즉, Selector의 label가 동일한 파드들의 그룹으로 묶어 단일 진입점 생성 (=service API)
    • 클러스터 내부에서만 사용 가능
      • ex) 백엔드 파드가 프런트엔드 파드와 통신할 때
    • type 생략 시 default값으로 10.96.0.0/12 범위에서 랜덤 할당
      • 고정시킬 수는 있으나, IP 충돌을 예방하기 위해 보통 고정하지 않는다.
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  selector:
    app: backend  # backend 라벨 가진 파드와 연결
  ports:
    - protocol: TCP
      port: 8080  # 클러스터 내부에서 8080 포트로 접근하면
      targetPort: 80  # 파드의 80 포트로 트래픽 전달한다.
  • Node Port
    • Cluster IP가 생성된 후, 모든 Worker Noder에 외부에서 접속 가능한 포트가 예약
    • 클러스터 외부에서 'Cluster IP:Node Port'로 접근
    • 각 노드의 고정 포트를 열어 모든 노드를 대상으로 외부에서 접근할 수 있도록 설정
      • 클러스터 외부에서 접근 가능하므로 보안에 주의해야 한다.
    • Default Nodeport 범위: 30000-32767
    • ClusterIP 생성 후 Nodeport 예약
    • 디버깅이나 간단한 테스트용으로 사용
apiVersion: v1
kind: Service
metadata:
  name: nodeport-service
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
      nodePort: 30007  # 클러스터 외부에서 NodeIP:30007로 접근 가능
      # 노드의 모든 IP 주소에서 접근 가능
  • LoadBalancer
    • Public Cloud: 클라우드 인프라스트럭처(AWS, Azure, GCP 등)나 오픈스택 클라우드 적용
    • LoadBalancer를 자동으로 Provision하는 기능 지원
    • NodePort를 예약 후 해당 nodeport로 외부 접근 허용
# 외부에서 로드 밸런서의 IP 주소를 통해 접근 가능
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer-service   # 클라우드 제공자의 로드 밸런서 생성
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  • External Name
    • 클러스터 안에서 외부에 접속 시 사용할 도메인을 등록해서 사용 (DNS 기능 지원)
    • 클러스터 외부의 특정 DNS 이름을 Service로 매핑하여 클러스터 내부에서 외부로 나갈 수 있게 도와준다.
      • ex) 외부 데이터베이스와 통합

kubectl expose 

  • kubectl expose 명령어는 Kubernetes 리소스를 기반으로 Service를 생성하는 명령어
  • 리소스의 네트워크 노출: kubectl expose는 Pod, Deployment, ReplicaSet, 또는 ReplicationController와 같은 리소스에 접근할 수 있는 Service를 생성한다.
# Deployment 기반의 Service 생성
$ kubectl expose deployment my-deployment --port=80 --target-port=8080 --type=ClusterIP

# --port	Service의 포트를 지정. (클라이언트가 접근하는 포트)
# --target-port	Pod 또는 컨테이너에서 사용하는 포트를 지정.

# 외부에 노출
$ kubectl expose pod my-pod --port=80 --target-port=8080 --type=NodePort
# Service는 자동으로 NodePort 범위(30000~32767) 내 포트를 할당.

# 사용자 정의 라벨 Selector
$ kubectl expose pod my-pod --port=80 --selector="app=my-app,env=prod"

 

# ClusterIP  실습

[master ~]$  kubectl run http --image=httpd:alpine --port=80 --expose=true
service/http created
pod/http created

[master ~]$  kubectl describe svc http
Name:                     http
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 run=http
Type:                     ClusterIP
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.43.114.52
IPs:                      10.43.114.52
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
Endpoints:                10.42.0.19:80
Session Affinity:         None
Internal Traffic Policy:  Cluster
Events:                   <none>
[master ~]$cat > deploy-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webui
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webui
  template:
    metadata:
      name: nginx-pod
      labels:
        app: webui
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.14

[master ~]$kubectl create -f deploy-nginx.yaml 
deployment.apps/webui created

[master ~]$cat > clusterip-nginx.yaml
apiVersion: v1                                 
kind: Service
metadata:
  name: clusterip-service
spec:
  type: ClusterIP
  clusterIP: 10.8.0.100
  selector:
    app: webui # webui pod를 묶어서 단일 진입점으로 관리하겠다.
  ports:
  - protocol: TCP
    port: 80    # 10.100.100.100의 포트
    targetPort: 80 # Pod의 포트

[master ~]$kubectl create -f clusterip-nginx.yaml 
service/clusterip-service created

[master ~]$kubectl get service
NAME                TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
clusterip-service   ClusterIP   10.8.0.100   <none>        80/TCP    22s # 확인
kubernetes          ClusterIP   10.8.0.1     <none>        443/TCP   6d8h

[master ~]$kubectl describe svc clusterip-service
Name:              clusterip-service
Namespace:         default
Labels:            <none>
Annotations:       cloud.google.com/neg: {"ingress":true}
Selector:          app=webui
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.8.0.100
IPs:               10.8.0.100
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.4.0.14:80,10.4.0.15:80,10.4.2.5:80 # 여기 3개중에 하나랑 균등하게 분산(LB)되어 실행된다
Session Affinity:  None
Events:            <none>


# scale up시 Endpoints 갯수가 늘어나느 것 확인
[master ~]$kubectl scale deployment webui --replicas=5
deployment.apps/webui scaled
[master ~]$kubectl describe svc clusterip-service
Name:              clusterip-service
Namespace:         default
Labels:            <none>
Annotations:       cloud.google.com/neg: {"ingress":true}
Selector:          app=webui
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.8.0.100
IPs:               10.8.0.100
Port:              <unset>  8080/TCP
TargetPort:        80/TCP
Endpoints:         10.4.0.14:80,10.4.0.15:80,10.4.1.14:80 + 2 more...
Session Affinity:  None
Events:            <none>

 

# LoadBalance 실습

[master ~/controller]$cat > loadbalancer-nginx.yaml 
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer-service
spec:
  type: LoadBalancer
  selector:
    app: webui
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    
[master ~/controller]$kubectl create -f loadbalancer-nginx.yaml 
service/loadbalancer-service created

# 로드밸런스를 만들면 EXTERNAL-IP에 LB의 IP ADDR(+방화벽)이 들어온다.
[master ~/controller]$kubectl get svc
NAME                   TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)        AGE
kubernetes             ClusterIP      10.8.0.1      <none>          443/TCP        6h30m
loadbalancer-service   LoadBalancer   10.8.11.120   35.225.72.184   80:30924/TCP   43s
nodeport-service       NodePort       10.8.0.15     <none>          80:30200/TCP   84s

 

# External Name 실습

[master ~]$cat external-name.yaml 
apiVersion: v1
kind: Service
metadata:
  name: externalname-svc
spec:
  type: ExternalName
  externalName: google.com

[master ~]$ubectl create -f external-name.yaml 
service/externalname-svc created

[master ~]$kubectl get svc
NAME               TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
externalname-svc   ExternalName   <none>       google.com    <none>    12s
kubernetes         ClusterIP      10.8.0.1     <none>        443/TCP   2m45s

[master ~]$kubectl run testpod -it --image=centos:7 
If you don't see a command prompt, try pressing enter.
[root@testpod /]curl externalname-svc.default.svc.cluster.local
<!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
  <title>Error 404 (Not Found)!!1</title>
  <style>
    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
  </style>
  <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
  <p><b>404.</b> <ins>That’s an error.</ins>
  <p>The requested URL <code>/</code> was not found on this server.  <ins>That’s all we know.</ins>
  # google로 접속

 

# Headless Service

  • ClusterIP가 없는 서비스단일 진입점이 필요 없을 때 사용
  • Service와 연결된 Pod의 endpoint로 DNS 레코드가 생성됨 :: DNS resolving service로 요청 가능
    • 즉, Pod들의 endpoint에 DNS resolving service 지원
  • Pod의 DNS 주소: Pod-id-addr.namespace.pod.cluster.local
[master ~]$cat > headless-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: headless-service  # 이게 설정되어 있으면 Headless service임, 따로 명시하진 않음
spec:
  type: ClusterIP # 단일진입점으로 ClusterIP는 만들되, IP는 없어도 된다.
  clusterIP: None
  selector:
    app: webui
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    
[master ~]$kubectl create -f deploy-nginx.yaml 
deployment.apps/webui created
[master ~]$kubectl get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP          NODE                                       NOMINATED NODE   READINESS GATES
webui-9cc457697-gbbrz   1/1     Running   0          8s    10.4.0.18   gke-cluster-1-default-pool-697aeef7-5nx7   <none>           <none>
webui-9cc457697-hkmrv   1/1     Running   0          8s    10.4.2.7    gke-cluster-1-default-pool-697aeef7-ccmj   <none>           <none>
webui-9cc457697-jgpm4   1/1     Running   0          8s    10.4.0.19   gke-cluster-1-default-pool-697aeef7-5nx7   <none>           <none>

# clusterIP와 비교해볼 것임
[master ~]$kubectl create -f clusterip-nginx.yaml 
service/clusterip-service created
[master ~]$kubectl get svc
NAME                TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
clusterip-service   ClusterIP   10.8.0.100   <none>        8080/TCP   6s
kubernetes          ClusterIP   10.8.0.1     <none>        443/TCP    39m

[master ~]$kubectl create -f headless-nginx.yaml 
service/headless-service created
[master ~]$kubectl get svc
NAME                TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
clusterip-service   ClusterIP   10.8.0.100   <none>        8080/TCP   43s
headless-service    ClusterIP   None         <none>        80/TCP     11s # Cluster-IP None
kubernetes          ClusterIP   10.8.0.1     <none>        443/TCP    40m

[master ~]$kubectl describe svc headless-service
Name:              headless-service
Namespace:         default
Labels:            <none>
Annotations:       cloud.google.com/neg: {"ingress":true}
Selector:          app=webui
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                None                      #Cluster-IP
IPs:               None
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.4.0.18:80,10.4.0.19:80,10.4.2.7:80
Session Affinity:  None
Events:            <none>



[master ~]$kubectl run testpod --image=centos:7 -it /bin/bash
If you don't see a command prompt, try pressing enter.

[root@testpod /]# cat /etc/resolv.conf 
search default.svc.cluster.local svc.cluster.local cluster.local c.caramel-element-415606.internal google.internal
nameserver 10.8.0.10            # core-dns의 IP
options ndots:5

# 10.4.2.7 Pod와 연결시켜주는 resolving service 확인
[root@testpod /]# curl 10-4-2-7.default.pod.cluster.local
<!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>

# kube-proxy

  • kubernetes service의 Backend 구현
  • endpoint 연결을 위한 iptables 구성
  • nodePort로의 접근과 Pod 연결을 구현(iptables rule 구성)
[master ~]$kubectl get pods -n kube-system | grep proxy
kube-proxy-gke-cluster-1-default-pool-697aeef7-5nx7   1/1     Running   0          8d
kube-proxy-gke-cluster-1-default-pool-697aeef7-7ss7   1/1     Running   0          8d
kube-proxy-gke-cluster-1-default-pool-697aeef7-ccmj   1/1     Running   0          8d

# 노드에 접속해서 확인해보면 iptables rule을 확인할 수 있다.
[root@node1 ~]$iptables -t nat -S | grep 80
-A KUBE-SEP-COZRZBQNU5VVKBBP -p tcp -m comment --comment "default/clusterip-service" -m tcp -j DNAT --to-destination 10.4.0.18:80
-A KUBE-SEP-DUNFEYM2OH36MSQP -p tcp -m comment --comment "kube-system/default-http-backend:http" -m tcp -j DNAT --to-destination 10.4.0.8:8080
-A KUBE-SEP-PPZDR4DAUPEHW5MR -p tcp -m comment --comment "default/clusterip-service" -m tcp -j DNAT --to-destination 10.4.2.7:80
-A KUBE-SEP-TRPQWLCKYQVY3LNP -p tcp -m comment --comment "default/clusterip-service" -m tcp -j DNAT --to-destination 10.4.0.19:80
-A KUBE-SVC-KUSV3H3OUX7H3MDQ -m comment --comment "default/clusterip-service -> 10.4.0.18:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-COZRZBQNU5VVKBBP
-A KUBE-SVC-KUSV3H3OUX7H3MDQ -m comment --comment "default/clusterip-service -> 10.4.0.19:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-TRPQWLCKYQVY3LNP
-A KUBE-SVC-KUSV3H3OUX7H3MDQ -m comment --comment "default/clusterip-service -> 10.4.2.7:80" -j KUBE-SEP-PPZDR4DAUPEHW5MR
-A KUBE-SVC-XP4WJ6VSLGWALMW5 -m comment --comment "kube-system/default-http-backend:http -> 10.4.0.8:8080" -j KUBE-SEP-DUNFEYM2OH36MSQP

 

 

⭐kube-proxy mode

  • userspace (K8S 초창기에 사용)
    • 클라이언트의 서비스 요청을 iptables를 거쳐 kube-proxy가 받아서 연결
  • ⭐iptables : default k8s network mode
    • kube-proxy는 service API 요청 시 iptables rule이 생성
    • 클라이언트 연결은 kube-proxy가 받아서 (LISTEN 통신) iptables 룰을 통해 연결
  • IPVS : 리눅스 커널이 지원하는 L4 로드 밸런싱 기술 이용
    • 별도의 ipvs 지원 모듈을 설정한 후 적용 가능 

 

Service와 Ingress의 차이

역할 파드와 연결된 네트워크 엔드포인트를 제공 HTTP/HTTPS 요청을 라우팅
기능 TCP/UDP 트래픽 전달 도메인 및 경로 기반 트래픽 라우팅
사용 사례 파드에 직접 연결하거나 로드 밸런싱 제공 여러 서비스에 트래픽을 라우팅해야 하는 경우

 

반응형

'Container > Kubernetes' 카테고리의 다른 글

[K8S] 쿠버네티스 레이블  (0) 2024.03.17
[K8S] Ingress Controller  (0) 2024.03.07
[K8S] Cronjob Controller  (0) 2024.02.28
[K8S] Job Controller  (0) 2024.02.27
[K8S] Statefulset  (0) 2024.02.26