Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

소개

Kubernetes 서비스

Kubernetes POD는 원리적으로 프로세스의 집합이다. 프로세서들이 라이프사이클을 가지는 것처럼 POD 들도 라이프사이클을 가진다. 만약 워커 노드가 죽으면, 워커노드위에 작동하는 POD들도 종료된다. 서버가 죽으면 서버 위에서 작동하던 프로세스의 생명이 어떻게 될지를 생각하면 된다.

쿠버네티스에서는 POD에 대한 복제본을 설정할 수 있기 때문에, 워커 노드의 문제로 POD가 내려갈 경우 다른 작동 중인 노드에서 POD를 만들어서 복제본의 총합을 유지한다.

쿠버네티스에서 서비스는 논리적인 POD 셋과 그 POD들에 접근할 수 있는 정책을 정의하는 개념이다. 이 개념을 이용해서 POD들을 느슨하게 결합되도록 할 수 있다. 서비스는 다른 쿠버네티스 객체(POD 등)과 마찬가지로 YAML 혹은 JSON을 이용해서 정의 할 수 있다.

POD가 만들어지면 이들은 고유한 IP를 가지게 되는데, 이들은 클러스터 내부 IP로 외부에서 접근 할 수 없다. 쿠버네티스의 서비스 설정을 이용해서 외부에 IP를 노출 시켜줘야 한다. 쉽게 이해하기 위해서 쿠버네티스 네트워크 정책을 살펴보기로 했다.

Kubernetes Deployments vs Service

쿠버네티스를 처음 접하면 여러 용어들 사이에서 혼란을 겪는다. Deployments 와 Service에서도 혼란을 겪을 수 있는데, 두 개의 차이점을 살펴보도록 하자.

쿠버네티스 Deployments의 목표는 단순하다. 그 목표는 "유사한 포드를 관리하는 것"이다. 이들 포드에 대한 정보는 YAML 형식의 deployment 설정파일로 관리 한다. Deployments 컨트롤러(controller)는 복제 세트와 POD를 선언하며, 이를 업데이트하고 관리할 책임을 가진다. Deployments는 선언된 복제 세트와 POD의 정보를 읽어서 실제로 클러스터에 POD(애플리케이션)를 배포를 수행한다.

이제 클러스터에 전개된 POD를 외부에서 접근 할 수 있도록 노출(expose)해야 하는데, 이렇게 외부에 노출될 POD를 명확하게 정의하는 추상객체를 서비스라고 한다. 서비스를 통해서 비로서 외부에서 접근 할 수 있게 된다.

 Deployment와 Service 개념

애플리케이션 준비

테스트에 사용할 Deployment 설정파일을 준비했다. "Hello World"를 출력하는 테스트용 노드 애플리케이션을 실행한다. 파일의 이름은 "hello-application.yaml"로 했다.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  selector:
    matchLabels:
      run: load-balancer-example
  replicas: 2
  template:
    metadata:
      labels:
        run: load-balancer-example
    spec:
      containers:
        - name: hello-world
          image: gcr.io/google-samples/node-hello:1.0
          ports:
            - containerPort: 8080
              protocol: TCP
POD를 전개하고 확인해보자.
# kubectl apply -f ./hello-application.yaml 
deployment.apps/hello-world created

# kubectl get pods   
NAME                           READY   STATUS    RESTARTS   AGE
hello-world-59966754c9-28ccz   1/1     Running   0          88s
hello-world-59966754c9-lvr2q   1/1     Running   0          88s

Deployments에 대한 상세 정보를 확인해 보자.
# kubectl get deployments hello-world
NAME          READY   UP-TO-DATE   AVAILABLE   AGE
hello-world   2/2     2            2           5m54s

# kubectl describe replicasets       
Name:           hello-world-59966754c9
Namespace:      default
Selector:       pod-template-hash=59966754c9,run=load-balancer-example
Labels:         pod-template-hash=59966754c9
                run=load-balancer-example
Annotations:    deployment.kubernetes.io/desired-replicas: 2
                deployment.kubernetes.io/max-replicas: 3
                deployment.kubernetes.io/revision: 1
Controlled By:  Deployment/hello-world
Replicas:       2 current / 2 desired
Pods Status:    2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  pod-template-hash=59966754c9
           run=load-balancer-example
  Containers:
   hello-world:
    Image:        gcr.io/google-samples/node-hello:1.0
    Port:         8080/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  12m   replicaset-controller  Created pod: hello-world-59966754c9-28ccz
  Normal  SuccessfulCreate  12m   replicaset-controller  Created pod: hello-world-59966754c9-lvr2q

hello-world 애플리케이션 POD가 클러스터에 전개되었다. expose 명령을 이용해서 hello-world POD를 외부에 노출 할 수 있다. 이렇게 서비스 형태로 노출된 서비스를 마이크로 서비스라고 부른다.
# kubectl expose deployment hello-world --type=NodePort --name=example-service
service/example-service exposed

서비스 정보를 확인해보자.
# kubectl describe services example-service
Name:                     example-service
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 run=load-balancer-example
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.109.161.198
IPs:                      10.109.161.198
Port:                     <unset>  8080/TCP
TargetPort:               8080/TCP
NodePort:                 <unset>  31259/TCP
Endpoints:                172.17.0.6:8080,172.17.0.7:8080
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
  • Name : 서비스 이름
  • Namespace : 단일 쿠보네티스 클러스터를 여러 개의 가상 클러스터로 분할하기 위해서 사용한다. 하나의 쿠버네티스 클러스터에서 여러 개의 프로젝트를 실행 할 수 있다.
  • Type : NodePort는 클러스터를 구성하는 Node에 열린 포트다. 보통은 이 위에 로드밸런서가 놓인다.
  • NodePort : 이 서비스를 위해서 열려있는 포트번호, 이 포트로 연결해야 한다.
서비스에 접근해보자. NodePort를 이용했으니, 노드의 IP를 확인하고 NodePort 31259로 접근하면 된다.
# kubectl cluster-info   
Kubernetes control plane is running at https://192.168.49.2:8443
CoreDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

# curl 192.168.49.2:31259
Hello Kubernetes!

정리

테스트가 끝났으니 서비스와 deployment를 삭제한다.
# kubectl delete services example-service
service "example-service" deleted
# kubectl delete deployment hello-world    
deployment.apps "hello-world" deleted

서비스를 사용하여 프론트엔드를 백엔드에 연결

이렇게 해서 쿠버네티스 서비스를 만들었다. 이제 이 서비스를 프론트엔드와 연결해서 완전한 서버/클라이언트 애플리케이션을 만드는 것에 대해서 알아보도록 하겠다.

테스트 애플리케이션 구성

테스트 애플리케이션은 아래와 같이 구성 할 것이다.

 구성

Nginx를 Loadbalancer로 해서 웹 애플리케이션을 배치하는 간단한 구성이다.
  1. Go 언어로 개발한 간단한 웹 애플리케이션으로 Backend POD를 구성한다.
  2. hello Service를 만들고 Backend POD를 선택(select)한다.
  3. NginX로 된 Frontend POD를 구성한다.
  4. front Service를 만들고 Front POD를 선택한다.

Backend

여기에서는 구글에서 제공하는 예제 웹 애플리케이션인 hello-go-gke:1.0 을 사용 한다. 아래와 같이 deployment 설정을 만들었다.
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  selector:
    matchLabels:
      app: hello
      tier: backend
      track: stable
  replicas: 3
  template:
    metadata:
      labels:
        app: hello
        tier: backend
        track: stable
    spec:
      containers:
        - name: hello
          image: "gcr.io/google-samples/hello-go-gke:1.0"
          ports:
            - name: http
              containerPort: 80
...

Deployment를 생성한다.
# kubectl apply -f ./backend-deployment.yaml 
deployment.apps/hello created

생성된 deployment 내용을 확인해보자.
# kubectl describe deployment hello  
Name:                   hello
Namespace:              default
CreationTimestamp:      Tue, 07 Sep 2021 14:11:11 +0900
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=hello,tier=backend,track=stable
Replicas:               7 desired | 7 updated | 7 total | 7 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=hello
           tier=backend
           track=stable
  Containers:
   hello:
    Image:        gcr.io/google-samples/hello-go-gke:1.0
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   hello-685445b9db (7/7 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  60s   deployment-controller  Scaled up replica set hello-685445b9db to 7
7개의 replica가 모드 작동하고 있는 걸 확인 할 수 있다.

이제 서비스를 만들어보자. 서비스의 이름은 hello다.
---
apiVersion: v1
kind: Service
metadata:
  name: hello
spec:
  selector:
    app: hello
    tier: backend
  ports:
  - protocol: TCP
    port: 80
    targetPort: http
...
쿠버네티스는 이 서비스 스펙을 읽어서 "hello"라는 service 객체를 생성한다. 모든 pod는 TCP 포트 80 번으로 접근 할 수 있다.

앞서 "pod와 service 는 서로 분리" 된다고 했다. 따라서 이 서비스 스펙을 적용할 pod를 선택(select)해야 한다. 셀렉터(selector)를 이용해서 선택 할 수 있다. "app: hello"로 서비스할 pod를 선택했다. pod 스펙 파일의 "metadata > labels"에 있는 값이다. 서비스를 실행하고 내용을 확인해보자.
# kubectl describe services hello
Name:              hello
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=hello,tier=backend
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.111.238.224
IPs:               10.111.238.224
Port:              <unset>  80/TCP
TargetPort:        http/TCP
Endpoints:         172.17.0.10:80,172.17.0.11:80,172.17.0.12:80 + 4 more...
Session Affinity:  None
Events:            <none>

FrontEnd

백엔드 서비스를 만들었으니 프론트엔드 서비스를 만들 것이다. 프론트 엔드는 NginX를 가지고 있으며, NginX로 백앤드를 로드밸런싱한다. 사용자는 프론트 엔드 서비스를 통해서 hello 서비스를 사용 할 수 있다. frontend-deployment.yaml 파일이다.
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  selector:
    matchLabels:
      app: hello
      tier: frontend
      track: stable
  replicas: 1
  template:
    metadata:
      labels:
        app: hello
        tier: frontend
        track: stable
    spec:
      containers:
        - name: nginx
          image: "gcr.io/google-samples/hello-frontend:1.0"
          lifecycle:
            preStop:
              exec:
                command: ["/usr/sbin/nginx","-s","quit"]
...
구글에서 예제로 제공하는 nginx 이미지로 부터 pod를 생성한다. NginX로 로드밸런싱을 하려면 설정이 중요한데, 구글에서 제공하는 이 NginX 이미지는 hello 서비스 테스트를 위한 설정을 포함하고 있다. 아래는 설정이다.
upstream hello {
    server hello;
}

server {
    listen 80;

    location / {
        proxy_pass http://hello;
    }
}

서비스를 만든다. 서비스 파일의 이름은 frontend-deployment.yaml 이다.
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector:
    app: hello
    tier: frontend
  ports:
  - protocol: "TCP"
    port: 80
    targetPort: 80
  type: LoadBalancer
...
type이 LoadBalancer인 것을 주목하자. 쿠버네티스 Service는 5가지 타입(type)이 있다.
  • ClusterIP : 서비스를 클러스터 내부 IP로 노출한다. 이 값을 선택하면 클러스터내에서만 서비스에 도달할 수 있다. 기본 Type이다.
  • NodePort : 고정포트로 각 노드의 IP/Port로 서비스를 노출한다. NodePort를 생성하면 ClusterIP 서비스가 자동으로 생성된다.
  • LoadBalancer : 클라우드 공급자의 로드 밸런서를 사용하여 서비스를 외부에 노출시킨다. 우리 예제에서는 NginX를 사용한다. 외부 로드 밸런서가 라우팅되는 NodePort와 ClusterIP 서비스가 자동으로 생성된다.
  • ExternalName : 내부 클라이언트가 서비스의 DNS 이름을 외부 DNS 이름의 별칭으로 사용한다.
  • Headless : Pod를 그룹화하지만 고정된 IP가 필요하지 않을 경우 Headless 서비스를 사용 할 수 있다.
프론트엔드 서비스를 실행하자.
# kubectl apply -f ./frontend-deployment.yaml
# kubectl apply -f ./frontend-service.yaml

만약 로컬에서 minikube로 테스트하고 있다면 minikube tunnel를 실행하자. LoadBalancer 타입에서는 로드 밸런서에 IP를 할당해줘야 하는데, 로컬에서는 IP를 할당 할 수 없어서 테스트를 진행 할 수 없다.
# minikube tunnel 
Status:
        machine: minikube
        pid: 247281
        route: 10.96.0.0/12 -> 192.168.49.2
        minikube: Running
        services: [frontend]
    errors: 
                minikube: no errors
                router: no errors
                loadbalancer emulator: no errors

프론트엔드 서비스 상태를 확인해보자.
# kubectl get service frontend --watch

NAME       TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
frontend   LoadBalancer   10.111.87.78   10.111.87.78   80:31616/TCP   100m
EXTERNAL-IP에 IP가 설정되면 성공이다. EXTERNAL-IP가 로드밸런서의 IP다. 테스트를 해보자.
# curl 10.111.87.78
{"message":"Hello"}

정리

테스트가 끝났다면 서비스를 삭제하자.
# kubectl delete services frontend hello  
service "frontend" deleted
service "hello" deleted

# kubectl delete deployment frontend backend
deployment.apps "frontend" deleted
deployment.apps "backend" deleted