Workload Resource

StatefulSet

Pain point

  1. Deployment 의 Pod 는 “일용직” 과 유사한 측면이 있다.
    • 일용직 노동자는 일을 그만두어도 다른 인력을 쉽게 구해 대체할 수 있듯이,
    • Pod 는 삭제되어도 아무런 영향을 주지 않고 Deployment 에 의해 재생성된다.
    • 하지만 회사의 중요한 업무를 맡는 직책 (예를 들면 대표이사) 라면?
  2. Deployment 의 Pod 는 이름을 예측할 수 없다.
    • Deployment 를 생성하여 Pod 를 생성하면 nginx-76769d88f7-gsmjb 와 같이 deployment 이름 뒤에 replicaset 과 pod 를 구분짓는 임의의 문자열이 생성되어 작명된다.
    • 하지만 이름을 예측해야 할 필요성이 있다면?

StatefulSet 이란?

  • Pain point 1 번의 설명처럼, Deployment 의 Pod 는 언제든지 삭제되고 재생성되어도 문제가 없는 애플리케이션을 위한 것이다.
    • 이것을 Stateless 라고 표현한다.
  • 하지만 어떤 애플리케이션의 경우 삭제되거나 재생성되는 것이 영향력을 가질 수 있고, 이러한 애플리케이션을 위한 것이 StatefulSet 이다.
    • StatefulSet 의 파드를 이전에는 “Pet” 이라고 표현했다. 다른 것으로 대체될 수 없는 소중한 것이라는 의미를 전달하기 위해서이다.
  • StatefulSet 은 대표적으로 다음과 같은 특징을 갖는다.
    • StatefulSet 이 생성한 파드에는 임의의 문자열이 아닌 0부터 시작하는 숫자가 부여된다. 즉, foo 라는 이름의 StatefulSet 을 생성하면 foo-0, foo-1 … 등의 이름이 부여된다.
      • 이것은 StatefulSet 이 생성하는 Pod 가 소중한 것이라는 개념을 뒷받침해준다. 자신이 기르는 애완동물 이름을 랜덤하게 지어주는 사람은 없다.
    • StatefulSet 의 Pod 들은 소중하기 때문에 일반적으로 데이터가 안전하게 보관되어야 할 필요가 있다. 따라서 StatefulSet 은 PVC (쿠버네티스의 볼륨 요청 방식, 뒤에서 다룬다) 를 자동으로 생성해주는 기능을 제공한다.

Usecase

  • Database
    • 대다수의 DBMS 들은 고가용성 설계를 할 때 단순히 데이터 복제를 하진 않는다.
    • 다양한 방식으로 고가용성 DBMS 가 운영되지만 하나의 사례는 여러개의 DB 인스턴스 중 하나를 Master (혹은 Active), 나머지를 Slave (혹은 Standby) 로 선정하고 Master 가 모든 요청을 처리하되, 데이터 저장 / 변경의 경우 일정 부분의 Slave 에도 반영이 되어야 요청이 처리된 것으로 간주하는 방식이다.
      • 이렇게 하면 모든 DB 인스턴스에 정보를 복제하는 것보다 빠르게 요청을 처리할 수 있고, 데이터 이중화라는 본래의 목적도 달성할 수 있기 때문이다.
    • 하지만 이때에는 DB 인스턴스가 삭제되는 것이 가볍게 지나칠 일이 아니기에 함부로 삭제되거나 재생성되어서는 안된다.
    • 또한, DB 의 사용자(클라이언트) 입장에서는 Master 에게 요청을 보내야 하기 때문에 어떤 인스턴스가 Master 인지 알아야 한다. 즉, DB 인스턴스의 이름을 사전에 알고 있어야 된다는 이야기이다.

DaemonSet

Pain point

  1. 어떤 애플리케이션은 모든 노드에서 작동해야 할 필요가 있다.
    • 만일 Deployment 를 이용해 파드를 모든 노드에서 작동하게 하려면, 추가적인 설정이 필요할 뿐더러 클러스터에 노드가 추가되었을 경우 파드의 개수를 수동으로 증가시켜줘야 한다.

DaemonSet 이란?

  • DaemonSet 은 모든 노드 각각에 반드시 한개의 Pod 가 작동하도록 보장하는 리소스이다.

Usecase

  • 노드를 관리하는 애플리케이션
    • 노드의 Networking 을 담당하는 파드
      • 쿠버네티스 시스템 컴포넌트 중 노드 내외부의 통신을 담당하는 Kube-proxy 도 DaemonSet 으로 생성된다.
    • 노드의 로그를 수집하는 파드
      • 노드의 시스템 로그 및 파드들이 생산하는 로그를 수집하는 파드의 경우 모든 노드에 배포되게 하기 위해 DaemonSet 으로 생성된다.

Job

Pain point

  1. 파드가 생성된 후 일정한 작업을 수행하고 종료되었으면 좋겠다.
    • 하지만, Deployment 는 파드가 삭제되어도 재생성되기에 이러한 니즈를 충족시킬 수 없다.

Job 이란?

  • 파드를 생성한 후, 작업이 정상적으로 종료될때까지 파드를 유지하다가 정상 종료 이후 삭제하는 리소스이다.

Usecase

  • 일회성 작업
    • 예를 들면, AI/ML 모델 학습용 파드나
    • 시스템 성능 테스트 (벤치마크) 파드 등

CronJob

Pain point

  1. 주기적으로 어떤 작업을 수행해주기 위해, Job 이 특정한 시간 주기로 생성되었으면 좋겠다.

CronJob 이란?

  • 지정한 시간 간격 (혹은 규칙) 에 따라 Job 을 생성하여 특정한 작업을 하게 해주는 리소스이다.

Usecase

  • 주기적인 작업을 요하는 애플리케이션
    • 예를 들면, 매월 1일자에 저장된 로그를 압축하여 디스크 사용량을 줄이는 파드

Networking Resource

네트워크 관련 리소스들은 아래의 Deployment 를 활용한 실습을 통해 소개드리겠습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:stable

Service

Pain point

  1. Deployment 를 통해 파드를 여러개 생성했을 때, 단일 진입점이 있었으면 좋겠다.
  2. Pod IP 와 같이 언제든 바뀔 수 있는 진입 주소가 아닌, 고정된 진입 주소가 있으면 좋겠다.

Service?

  • Service 는 Pod 로 접근하는 엔드포인트를 제공하는 리소스이다.
  • 위의 Pain Point 는 이 Service 리소스를 통해 다음과 같이 해결된다:
    1. Service 로 들어온 트래픽은 Service 와 연결된 Pod 들로 부하 분산된다.
    2. Service 는 생성시에 Virtual IP 가 할당되기에 접근할 수 있는 고정주소로 사용할 수 있다.
      • 추가설명) Pod 는 재생성이 빈번하고 그때마다 IP 를 새로 할당받지만, Service 의 경우에는 재생성될 일이 적고, 생성시에 IP 를 할당받기는 하지만 YAML 파일에 IP 를 직접 명시해 줄 수도 있다. 또한, 쿠버네티스에는 클러스터 내부에서 사용할 용도의 DNS 가 존재하기에, Service 의 IP 주소가 아닌 도메인으로 접속하는 것도 가능하다.
      • 이것에 관해서는 아래의 실습에서도 확인할 수 있다.

ClusterIP Service

ClusterIP 서비스란?
  • 클러스터 내부 통신용 엔드포인트를 생성하기 위한 서비스 리소스이다.
  • Service 리소스를 생성할 때, type 을 지정해주지 않거나 type: ClusterIP 로 지정해주면 해당 리소스가 생성된다.
Cluster IP 실습
  • 다음의 명령어 혹은 YAML 파일을 이용해 nginx Deployment 에 ClusterIP 서비스를 연결해준다.
kubectl expose deployment nginx --name nginx --port 80
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: ClusterIP
  • 생성된 ClusterIP 를 확인한다.
kubectl get service nginx
NAME	TYPE		CLUSTER-IP	   EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   10.105.103.161   <none>		80/TCP	4m2s
  • ClusterIP 를 통해 접속이 이루어지는지 확인하기 위해 간단히 curl 이 설치되어있는 파드를 생성한다.
apiVersion: v1
kind: Pod
metadata:
  name: curlpod
spec:
  containers:
  - name: curl
    image: docker.io/curlimages/curl:7.86.0
    command:
      - sleep
      - "infinity"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always
  • 다음의 명령어를 통해 curl 파드 내에서 통신을 시도해보고, NGINX 홈페이지 HTML 이 조회되는지 확인한다. (사용된 IP 는 다를 수 있다.)
# kubectl exec -it curlpod -- curl ${kubectl get service nginx 명령어를 통해 확인한 Internal IP 주소}
kubectl exec -it curlpod -- curl 10.105.103.161
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
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>
  • 하지만 ClusterIP 의 Internal IP 는 변경 가능성이 높기 때문에 파드간 통신에서는 Internal IP 를 직접적으로 사용하기 보다는 Domain 를 활용한다.
    • 쿠버네티스에는 기본적으로 “CoreDNS” 라는 네임서버가 파드로 배포되어 있고, 클러스터 내부에서 사용되는 Domain 에 대한 처리를 담당한다.
    • Domiain 은 다음과 같이 구성된다: {서비스 이름}.{네임스페이스 이름}.svc.{클러스터 이름}.local
    • 따라서 위에서 생성한 ClusterIP 에 대한 Domain 은 다음과 같다: nginx.default.svc.cluster.local
      • 참고) {클러스터 이름} 은 기본값이 cluster 이다. 하지만 클러스터 생성시에 다른 이름을 지정해줄 수도 있으니 정상적으로 작동하지 않는다면, kubectl exec -it 파드 -- cat /etc/resolv.conf 로 DNS 설정을 확인해보자.
  • 다음 명령어를 통해 Internal IP 대신 Domain 으로 NGINX 에 접속해본다.
# kubectl exec -it curlpod -- curl ${ClusterIP 의 domain}
kubectl exec -it curlpod -- curl nginx.default.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
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>

NodePort Service

NodePort 서비스란?
  • NodePort 타입의 서비스는 모든 노드의 특정 30000-32767 사이의 포트를 열고, 해당 포트로 들어오는 트래픽을 파드로 전달해줄 수 있게 하는 리소스이다.
  • 보통 클러스터 외부에서 파드에 접근하기 위해 사용한다.
NodePort 실습
  • 다음의 명령어 혹은 YAML 파일을 통해 NodePort 서비스를 생성한다.
kubectl expose deployment nginx --name nginx-np --type NodePort --port 80
apiVersion: v1
kind: Service
metadata:
  name: nginx-np
spec:
  ports:
  - name: http
    nodePort: 31045
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
  • 생성한 NodePort 서비스를 확인한다.
kubectl get service nginx-np
NAME	   TYPE	   CLUSTER-IP	   EXTERNAL-IP   PORT(S)		AGE
nginx-np   NodePort   10.110.201.171   <none>		80:31045/TCP   118s
  • 참고) YAML 파일을 이용하면 개방할 30000 번대 포트를 직접 지정할 수 있지만, kubectl cli 를 이용하면 30000번대의 임의의 포트가 개방된다.
  • PORT(S) 항목에 바인드되어있는 30000 번대 포트를 통해 브라우저로 접속해본다.

LB Service

LB(LoadBalancer) 서비스란?
  • 클러스터 외부의 로드밸런서와 연동하여 외부의 트래픽을 클러스터 내부의 파드로 전달하는 리소스
LB 서비스 vs NodePort 서비스
  • LB 서비스와 NodePort 서비스 모두 외부의 트래픽을 파드에 전달한다는 점에서는 동일하다.
  • 하지만, 두 서비스가 사용되는 Usecase 는 많이 다르다.
    • LB 서비스의 경우에는 클러스터 외부의 로드밸런서 (SW 혹은 HW) 와 연동되어야 하기 때문에 온프레미스 클러스터에서는 사용하기 힘들다.
      • 쿠버네티스 클러스터가 클라우드에서 작동하는 경우에는 Cloud controller 를 이용해 AWS 의 ALB 와 같은 리소스를 자동으로 생성하고,
      • Docker desktop 혹은 minikube 의 경우에는 플러그인이 있어 자동적으로 처리해주지만 온프레미스 클러스터에서 안정적으로 사용할 수 있는 솔루션은 부족하다.
  • 따라서 온프레미스 클러스터에서는 LB 서비스보다는 NodePort 를 이용해 외부의 트래픽을 전달받는다.
    • 참고) NodePort 의 포트 범위가 30000-32767 이기에, 만일 80 이나 443 와 같은 Known port 로 트래픽을 받을 때에는 아래와 같이 앞단에 HAProxy 같은 프록시를 구성하기도 한다.
                                             +-----------------------------+
                                 +----> 32000/TCP(NodePort BE)    K8s Node |
                                 |           +-----------------------------+
                                 |
        +--------------------+   |           +-----------------------------+
--> 80/TCP(FE)       HAPROXY |---+----> 32000/TCP(NodePort BE)    K8s Node |
        +--------------------+   |           +-----------------------------+
                                 |
                                 |           +-----------------------------+
                                 +----> 32000/TCP(NodePort BE)    K8s Node |
                                             +-----------------------------+

Ingress

Ingress 란?

  • ingress 는 L7 라우팅 규칙을 정의하는 리소스이다.

Ingress? Ingress controller?

  • 쿠버네티스의 다른 리소스들과 마찬가지로, “리소스”, “리소스-컨트롤러” 와의 관계와 같다.
    • “리소스”는 객체이고, 해당 객체에 명시된 내용에 따라 클러스터의 상태를 관리하는 프로세스(컨테이너) 가 “리소스-컨트롤러” 이듯이,
    • Ingress 는 객체이고, 생성된 Ingress 에 따라 L7 라우팅 규칙을 업데이트하고 그에 따라 작동할 수 있게 하는 것이 Ingress controller 이다.
    • NGINX 에 비유하자면, Ingress 는 nginx.conf 파일이고, Ingress controller 는 NGINX 프로세스이다.
  • 쿠버네티스 내부에는 Ingress 리소스가 정의되어 있지만, 이것을 처리하는 컨트롤러 (Ingress controller)는 구현되어있지 않고 별도의 설치가 필요하다.
    • 이것은 쿠버네티스가 직접 소프트웨어 L7 로드밸런서를 구현하기보다는 NGINX, HAProxy 같은 상용 솔루션들을 활용하고 사용자로 하여금 익숙한 솔루션을 선택할 수 있도록 한 것이다.
    • 다만, 컨트롤러도 디플로이먼트의 형태로 구성되기에 이것을 외부에 노출시키기 위해 LB 서비스 혹은 NodePort 서비스의 도움이 필요하다.

Ingress 의 사용 이유

  • NGINX, HAProxy 같은 일반적인 소프트웨어 L7 로드밸런서를 사용하는 이유와 같다.
  • 즉, 클러스터로 진입하는 제일 앞단에서 TLS/SSL 인증서를 처리하거나, Application layer 에서의 라우팅을 이용해 클러스터 내부의 백엔드로 트래픽을 전달하는 용도로 사용한다.
  • 따라서 원하는 파드를 외부에 노출시키고자 할 때 LB 혹은 NodePort 를 이용해 직접 노출시키는 것이 아닌 Ingress controller 만 외부에 노출시켜놓고 이것이 전달해주는 트래픽을 받을 수 있도록 Ingress 리소스를 생성해주면 된다.

Ingress 의 작동 원리

  • Ingress controller 를 설치하고 외부에 노출한 뒤에는, Ingress 를 생성했을 때에 그에 맞는 L7 라우팅 규칙이 생성되어 외부의 트래픽이 파드로 전달되는 방식으로 작동한다.
    • 트래픽을 규칙에 따라 파드로 전달할때에는, Ingress controller pod → target pod 간 통신, 즉, 클러스터 내부의 통신이기에 Cluster IP 타입의 서비스를 이용한다.
    • 즉, L7 로드밸런서로 비유하자면, FE 는 Ingress controller 를 클러스터 외부에 노출시키는 LB type service 이고, BE 는 클러스터 내부의 파드에 연결된 Cluster IP type service 이며, 이 둘을 연결지어주는 규칙을 Ingress 리소스로 정의하게 되는 셈이다.

실습

  • 먼저, Ingress 를 처리할 Ingress controller 를 설치한다.
    • 아래의 명령어는 Kubernetes 에서 공식적으로 지원하는 Ingress controller 인 NGINX ingress controller 를 ingress-nginx 네임스페이스에 설치한다.
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/cloud/deploy.yaml
  • ingress-nginx 네임스페이스에 인그레스 컨트롤러가 정상적으로 설치되었는지 확인한다.
kubectl -n ingress-nginx get all
  • https://localhost 로 접속하여 404: Not Found 화면이 보이는지 확인한다.
    • 404: Not Found 가 보이는 것은 Ingress controller 가 정상적으로 작동하고 있으나, root path 에 대한 ingress 가 생성되지 않았기에 자원을 찾을 수 없다는 문구를 보여주는 것이다.

  • 신규 Ingress 를 생성한다.
    • 본 Ingress 가 작동하기 위해서는 nginx 라는 이름의 ClusterIP 서비스와, 해당 서비스가 노출하고 있는 Deployment 등의 리소스가 필요하다. (앞선 실습에서 이미 생성되었다고 가정)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ing
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: localhost
    http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
  • 생성한 Ingress 에는 host 가 localhost 이고 path 가 /testpath 일때 “nginx” 라는 이름의 서비스 오브젝트로 트래픽을 전달하도록 되어 있다. 따라서 브라우저에서 https://localhost/testpath 로 접속해본다.

생성한 리소스 정리

kubectl delete deployment nginx
kubectl delete service nginx
kubectl delete service nginx-np
kubectl delete pod curlutils
kubectl delete ingress nginx-ing
kubectl delete -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/cloud/deploy.yaml