1 - 디플로이먼트(Deployment)로 스테이트리스 애플리케이션 실행하기

이 페이지에서는 쿠버네티스 디플로이먼트 오브젝트를 사용하여 애플리케이션을 실행하는 방법을 설명한다.

목적

  • nginx 디플로이먼트 생성하기
  • kubectl을 사용하여 디플로이먼트 정보 나열하기
  • 디플로이먼트 업데이트하기

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

쿠버네티스 서버의 버전은 다음과 같거나 더 높아야 함. 버전: v1.9. 버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.

nginx 디플로이먼트 생성하고 탐색하기

쿠버네티스 디플로이먼트 오브젝트를 생성하여 애플리케이션을 실행할 수 있으며, 디플로이먼트에 대한 명세를 YAML 파일에 기술할 수 있다. 예를 들어 이 YAML 파일은 nginx:1.14.2 도커 이미지를 실행하는 디플로이먼트에 대한 명세를 담고 있다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
  1. YAML 파일을 기반으로 디플로이먼트를 생성한다.

     kubectl apply -f https://k8s.io/examples/application/deployment.yaml
    
  2. 디플로이먼트에 대한 정보를 살펴본다.

     kubectl describe deployment nginx-deployment
    

    출력은 다음과 유사하다.

     Name:     nginx-deployment
     Namespace:    default
     CreationTimestamp:  Tue, 30 Aug 2016 18:11:37 -0700
     Labels:     app=nginx
     Annotations:    deployment.kubernetes.io/revision=1
     Selector:   app=nginx
     Replicas:   2 desired | 2 updated | 2 total | 2 available | 0 unavailable
     StrategyType:   RollingUpdate
     MinReadySeconds:  0
     RollingUpdateStrategy:  1 max unavailable, 1 max surge
     Pod Template:
       Labels:       app=nginx
       Containers:
        nginx:
         Image:              nginx:1.14.2
         Port:               80/TCP
         Environment:        <none>
         Mounts:             <none>
       Volumes:              <none>
     Conditions:
       Type          Status  Reason
       ----          ------  ------
       Available     True    MinimumReplicasAvailable
       Progressing   True    NewReplicaSetAvailable
     OldReplicaSets:   <none>
     NewReplicaSet:    nginx-deployment-1771418926 (2/2 replicas created)
     No events.
    
  3. 디플로이먼트에 의해 생성된 파드를 나열한다.

     kubectl get pods -l app=nginx
    

    출력은 다음과 유사하다.

     NAME                                READY     STATUS    RESTARTS   AGE
     nginx-deployment-1771418926-7o5ns   1/1       Running   0          16h
     nginx-deployment-1771418926-r18az   1/1       Running   0          16h
    
  4. 파드에 대한 정보를 살펴본다.

     kubectl describe pod <pod-name>
    

    <pod-name>은 파드 중 하나의 이름이다.

디플로이먼트 업데이트하기

새 YAML 파일을 적용하여 디플로이먼트를 업데이트할 수 있다. 이 YAML 파일은 nginx 1.16.1을 사용하도록 디플로이먼트를 업데이트해야 함을 명시하고 있다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.16.1 # Update the version of nginx from 1.14.2 to 1.16.1
        ports:
        - containerPort: 80
  1. 새 YAML 파일을 적용한다.

      kubectl apply -f https://k8s.io/examples/application/deployment-update.yaml
    
  2. 디플로이먼트가 새 이름으로 파드를 생성하고 이전 파드를 삭제하는 것을 확인한다.

      kubectl get pods -l app=nginx
    

레플리카 수를 늘려 애플리케이션 확장하기

새 YAML 파일을 적용하여 디플로이먼트의 파드 수를 늘릴 수 있다. 이 YAML 파일은 replicas를 4로 설정하여 디플로이먼트에 4개의 파드가 있어야 함을 명시하고 있다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 4 # Update the replicas from 2 to 4
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.16.1
        ports:
        - containerPort: 80
  1. 새 YAML 파일을 적용한다.

     kubectl apply -f https://k8s.io/examples/application/deployment-scale.yaml
    
  2. 디플로이먼트에 4개의 파드가 있는지 확인한다.

     kubectl get pods -l app=nginx
    

    출력은 다음과 유사하다.

     NAME                               READY     STATUS    RESTARTS   AGE
     nginx-deployment-148880595-4zdqq   1/1       Running   0          25s
     nginx-deployment-148880595-6zgi1   1/1       Running   0          25s
     nginx-deployment-148880595-fxcez   1/1       Running   0          2m
     nginx-deployment-148880595-rwovn   1/1       Running   0          2m
    

디플로이먼트 삭제하기

이름으로 디플로이먼트를 삭제한다.

kubectl delete deployment nginx-deployment

ReplicationControllers -- 예전 방식

애플리케이션을 복제하여 생성하는 기본적인 방법은 내부적으로 레플리카셋(ReplicaSet)을 활용하는 디플로이먼트를 사용하는 것이다. 쿠버네티스에 디플로이먼트 및 레플리카셋이 도입되기 전에는 레플리케이션컨트롤러(ReplicationController)를 사용하여 복제 애플리케이션을 구성했었다.

다음 내용

2 - 단일 인스턴스 스테이트풀 애플리케이션 실행하기

이 페이지에서는 쿠버네티스 클러스터에서 퍼시스턴트볼륨(PersistentVolume)과 디플로이먼트(Deployment)를 사용하여, 단일 인스턴스 스테이트풀 애플리케이션을 실행하는 방법을 보인다. 해당 애플리케이션은 MySQL이다.

목적

  • 사용자 환경의 디스크를 참조하는 퍼시스턴트볼륨 생성하기
  • MySQL 디플로이먼트 생성하기
  • 알려진 DNS 이름으로 클러스터의 다른 파드에 MySQL 서비스 노출하기

시작하기 전에

MySQL 배포하기

쿠버네티스 디플로이먼트를 생성하고 퍼시스턴트볼륨클레임(PersistentVolumeClaim)을 사용하는 기존 퍼시스턴트볼륨에 연결하여 스테이트풀 애플리케이션을 실행할 수 있다. 예를 들어, 다음 YAML 파일은 MySQL을 실행하고 퍼시스턴트볼륨클레임을 참조하는 디플로이먼트를 기술한다. 이 파일은 /var/lib/mysql에 대한 볼륨 마운트를 정의한 후에, 20G의 볼륨을 요청하는 퍼시트턴트볼륨클레임을 생성한다. 이 클레임은 요구 사항에 적합한 기존 볼륨이나 동적 프로비저너에 의해서 충족된다.

참고: config yaml 파일에 정의된 비밀번호는 안전하지 않다. 더 안전한 해결방법을 위해 쿠버네티스 시크릿 을 보자

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
  clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
          # Use secret in real usage
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

  1. YAML 파일의 PV와 PVC를 배포한다.

     kubectl apply -f https://k8s.io/examples/application/mysql/mysql-pv.yaml
    
  2. YAML 파일의 다른 오브젝트들을 배포한다.

     kubectl apply -f https://k8s.io/examples/application/mysql/mysql-deployment.yaml
    
  3. 디플로이먼트에 관한 정보를 확인한다.

     kubectl describe deployment mysql
    

    출력은 다음과 유사하다.

     Name:                 mysql
     Namespace:            default
     CreationTimestamp:    Tue, 01 Nov 2016 11:18:45 -0700
     Labels:               app=mysql
     Annotations:          deployment.kubernetes.io/revision=1
     Selector:             app=mysql
     Replicas:             1 desired | 1 updated | 1 total | 0 available | 1 unavailable
     StrategyType:         Recreate
     MinReadySeconds:      0
     Pod Template:
       Labels:       app=mysql
       Containers:
        mysql:
         Image:      mysql:5.6
         Port:       3306/TCP
         Environment:
           MYSQL_ROOT_PASSWORD:      password
         Mounts:
           /var/lib/mysql from mysql-persistent-storage (rw)
       Volumes:
        mysql-persistent-storage:
         Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
         ClaimName:  mysql-pv-claim
         ReadOnly:   false
     Conditions:
       Type          Status  Reason
       ----          ------  ------
       Available     False   MinimumReplicasUnavailable
       Progressing   True    ReplicaSetUpdated
     OldReplicaSets:       <none>
     NewReplicaSet:        mysql-63082529 (1/1 replicas created)
     Events:
       FirstSeen    LastSeen    Count    From                SubobjectPath    Type        Reason            Message
       ---------    --------    -----    ----                -------------    --------    ------            -------
       33s          33s         1        {deployment-controller }             Normal      ScalingReplicaSet Scaled up replica set mysql-63082529 to 1
    
  4. 디플로이먼트로 생성된 파드를 나열한다.

     kubectl get pods -l app=mysql
    

    출력은 다음과 유사하다.

     NAME                   READY     STATUS    RESTARTS   AGE
     mysql-63082529-2z3ki   1/1       Running   0          3m
    
  5. 퍼시스턴트볼륨클레임을 살펴본다.

     kubectl describe pvc mysql-pv-claim
    

    출력은 다음과 유사하다.

     Name:         mysql-pv-claim
     Namespace:    default
     StorageClass:
     Status:       Bound
     Volume:       mysql-pv-volume
     Labels:       <none>
     Annotations:    pv.kubernetes.io/bind-completed=yes
                     pv.kubernetes.io/bound-by-controller=yes
     Capacity:     20Gi
     Access Modes: RWO
     Events:       <none>
    

MySQL 인스턴스 접근하기

이전의 YAML 파일은 클러스터의 다른 파드가 데이터베이스에 접근할 수 있는 서비스를 생성한다. clusterIP: None 서비스 옵션을 사용하면 서비스의 DNS 이름을 직접 파드의 IP 주소로 해석하도록 처리한다. 이 방법은 서비스에서 연결되는 파드가 오직 하나 뿐이고, 파드의 수를 더 늘릴 필요가 없는 경우에 가장 적합하다.

서버에 접속하기 위하여 MySQL 클라이언트를 실행한다.

kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword

이 명령어는 MySQL 클라이언트를 실행하는 파드를 클러스터에 생성하고, 서비스를 통하여 서버에 연결한다. 연결된다면, 스테이트풀 MySQL 데이터베이스가 실행 중임을 알 수 있다.

Waiting for pod default/mysql-client-274442439-zyp6i to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.

mysql>

업데이트하기

kubectl apply 명령을 사용하여 기존과 동일한 방식으로 디플로이먼트의 이미지나 다른 부분을 변경할 수 있다. 스테이트풀 애플리케이션과 관련하여 몇 가지 주의 사항이 있다.

  • 애플리케이션을 스케일링하지 않는다. 이 설정은 단일 인스턴스 애플리케이션 전용이다. 기본적인 퍼시스턴트볼륨은 하나의 파드에서만 마운트할 수 있다. 클러스터 형태의 스테이트풀 애플리케이션에 대해서는 스테이트풀셋을 보자.
  • 디플로이먼트 구성 YAML 파일에서 strategy: type: Recreate 를 사용한다. 이는 쿠버네티스가 롤링 업데이트를 사용하지 않도록 지시한다. 동시에 두 개 이상의 파드를 생성할 수 없으므로, 롤링 업데이트는 일어나지 않게 된다. Recreate 전략을 사용하면 변경된 구성으로 새로운 파드를 생성하기에 앞서 기존의 파드를 중단한다.

디플로이먼트 삭제하기

이름으로 배포된 오브젝트를 삭제한다.

kubectl delete deployment,svc mysql
kubectl delete pvc mysql-pv-claim
kubectl delete pv mysql-pv-volume

퍼시스턴트볼륨을 수동으로 프로비저닝한 경우라면, 동일하게 수동으로 삭제하고 기본 리소스도 해제해야 한다. 동적 프로비저너를 사용한 경우, 퍼시스턴트볼륨클레임이 삭제되었을 때에 퍼시스턴트볼륨 또한 자동으로 삭제된다. 일부 동적 프로비저너(EBS 와 PD와 같은)는 퍼시스턴트볼륨을 삭제할 때에 기본 리소스도 해제한다.

다음 내용

3 - 복제 스테이트풀 애플리케이션 실행하기

이 페이지에서는 스테이트풀셋(StatefulSet) 으로 복제 스테이트풀 애플리케이션을 실행하는 방법에 대해 소개한다. 이 애플리케이션은 복제 MySQL 데이터베이스이다. 이 예제의 토폴로지는 단일 주 서버와 여러 복제 서버로 이루어져있으며, row-based 비동기 복제 방식을 사용한다.

시작하기 전에

목적

  • 스테이트풀셋을 이용한 복제 MySQL 토폴로지를 배포한다.
  • MySQL 클라이언트에게 트래픽을 보낸다.
  • 다운타임에 대한 저항력을 관찰한다.
  • 스테이트풀셋을 확장/축소한다.

MySQL 배포하기

MySQL 디플로이먼트 예시는 컨피그맵과, 2개의 서비스, 그리고 스테이트풀셋으로 구성되어 있다.

컨피그맵 생성하기

다음 YAML 설정 파일로부터 컨피그맵을 생성한다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
data:
  primary.cnf: |
    # Primary에만 이 구성을 적용한다.
    [mysqld]
    log-bin        
  replica.cnf: |
    # 레플리카에만 이 구성을 적용한다.
    [mysqld]
    super-read-only        

kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml

이 컨피그맵은 당신이 독립적으로 주 MySQL 서버와 레플리카들의 설정을 컨트롤할 수 있도록 my.cnf 을 오버라이드한다. 이 경우에는, 주 서버는 복제 로그를 레플리카들에게 제공하고 레플리카들은 복제를 통한 쓰기가 아닌 다른 쓰기들은 거부하도록 할 것이다.

컨피그맵 자체가 다른 파드들에 서로 다른 설정 영역이 적용되도록 하는 것이 아니다. 스테이트풀셋 컨트롤러가 제공해주는 정보에 따라서, 각 파드들은 초기화되면서 설정 영역을 참조할지 결정한다.

서비스 생성하기

다음 YAML 설정 파일로부터 서비스를 생성한다.

# 스테이트풀셋 멤버의 안정적인 DNS 엔트리를 위한 헤드리스 서비스.
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mysql
---
# 읽기용 MySQL 인스턴스에 연결하기 위한 클라이언트 서비스.
# 쓰기용은 Primary인 mysql-0.mysql에 대신 연결해야 한다.
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
    readonly: "true"
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml

헤드리스 서비스는 스테이트풀셋 컨트롤러가 집합의 일부분인 파드들을 위해 생성한 DNS 엔트리들(entries)의 위한 거점이 된다. 헤드리스 서비스의 이름이 mysql이므로, 파드들은 같은 쿠버네티스 클러스터나 네임스페이스에 존재하는 다른 파드들에게 <pod-name>.mysql라는 이름으로 접근될 수 있다.

mysql-read라고 불리우는 클라이언트 서비스는 고유의 클러스터 IP를 가지며, Ready 상태인 모든 MySQL 파드들에게 커넥션을 분배하는 일반적인 서비스이다. 잠재적인 엔드포인트들의 집합은 주 MySQL 서버와 해당 레플리카들을 포함한다.

오직 읽기 쿼리들만 로드-밸런싱된 클라이언트 서비스를 이용할 수 있다는 사실에 주목하자. 하나의 주 MySQL 서버만이 존재하기 떄문에, 클라이언트들은 쓰기 작업을 실행하기 위해서 주 MySQL 파드에 (헤드리스 서비스 안에 존재하는 DNS 엔트리를 통해) 직접 접근해야 한다.

스테이트풀셋 생성하기

마지막으로, 다음 YAML 설정 파일로부터 스테이트풀셋을 생성한다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
      app.kubernetes.io/name: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
        app.kubernetes.io/name: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7
        command:
        - bash
        - "-c"
        - |
          set -ex
          # 파드의 원래 인덱스에서 mysql server-id를 생성.
          [[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          # 예약된 server-id=0 값을 피하기 위해 오프셋 추가.
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # config-map에서 emptyDir로 적당한 conf.d 파일들을 복사.
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/primary.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/replica.cnf /mnt/conf.d/
          fi          
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      - name: clone-mysql
        image: gcr.io/google-samples/xtrabackup:1.0
        command:
        - bash
        - "-c"
        - |
          set -ex
          # 데이터가 이미 존재하면 복제 생략. 
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Primary에 복제 생략(ordinal index 0).
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          # 이전 피어(peer)에서 데이터 복제.
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # 백업 준비.
          xtrabackup --prepare --target-dir=/var/lib/mysql          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            # TCP 상에서 쿼리를 실행할 수 있는지 확인(skip-networking은 off).
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: gcr.io/google-samples/xtrabackup:1.0
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql

          # 복제된 데이터의 binlog 위치를 확인.
          if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
            # XtraBackup은 기존 레플리카에서 복제하기 때문에
            # 일부 "CHANGE MASTER TO" 쿼리는 이미 생성했음. (테일링 세미콜론을 제거해야 한다!)
            cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
            # 이 경우에는 xtrabackup_binlog_info는 무시(필요없음).
            rm -f xtrabackup_slave_info xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # Primary로부터 직접 복제함. binlog 위치를 파싱.
            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm -f xtrabackup_binlog_info xtrabackup_slave_info
            echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
                  MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
          fi

          # Replication을 시작하여 복제를 완료해야 하는지 확인.
          if [[ -f change_master_to.sql.in ]]; then
            echo "Waiting for mysqld to be ready (accepting connections)"
            until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done

            echo "Initializing replication from clone position"
            mysql -h 127.0.0.1 \
                  -e "$(<change_master_to.sql.in), \
                          MASTER_HOST='mysql-0.mysql', \
                          MASTER_USER='root', \
                          MASTER_PASSWORD='', \
                          MASTER_CONNECT_RETRY=10; \
                        START SLAVE;" || exit 1
            # 컨테이너가 다시 시작하는 경우, 이 작업을 한번만 시도한다.
            mv change_master_to.sql.in change_master_to.sql.orig
          fi

          # 피어가 요청할 때 서버를 시작하여 백업을 보냄.
          exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
            "xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml

다음을 실행하여, 초기화되는 프로세스들을 확인할 수 있다.

kubectl get pods -l app=mysql --watch

잠시 뒤에, 3개의 파드들이 Running 상태가 되는 것을 볼 수 있다.

NAME      READY     STATUS    RESTARTS   AGE
mysql-0   2/2       Running   0          2m
mysql-1   2/2       Running   0          1m
mysql-2   2/2       Running   0          1m

Ctrl+C를 입력하여 watch를 종료하자.

해당 메니페스트에는 스테이트풀셋의 일부분인 스테이트풀 파드들을 관리하기 위한 다양한 기법들이 적용되어 있다. 다음 섹션에서는 스테트풀셋이 파드들을 생성할 때 일어나는 일들을 이해할 수 있도록 일부 기법들을 강조하여 설명한다.

스테이트풀 파드 초기화 이해하기

스테이트풀셋 컨트롤러는 파드들의 인덱스에 따라 순차적으로 시작시킨다. 컨트롤러는 다음 파드 생성 이전에 각 파드가 Ready 상태가 되었다고 알려줄 때까지 기다린다.

추가적으로, 컨트롤러는 각 파드들에게 <스테이트풀셋 이름>-<순차적 인덱스> 형태의 고유하고 안정적인 이름을 부여하는데, 결과적으로 파드들은 mysql-0, mysql-1, 그리고 mysql-2 라는 이름을 가지게 된다.

스테이트풀셋 매니페스트의 파드 템플릿은 해당 속성들을 통해 순차적인 MySQL 복제의 시작을 수행한다.

설정 생성하기

파드 스펙의 컨테이너를 시작하기 전에, 파드는 순서가 정의되어 있는 초기화 컨테이너들을 먼저 실행시킨다.

init-mysql라는 이름의 첫 번째 초기화 컨테이너는, 인덱스에 따라 특별한 MySQL 설정 파일을 생성한다.

스크립트는 인덱스를 hostname 명령으로 반환되는 파드 이름의 마지막 부분에서 추출하여 결정한다. 그리고 인덱스(이미 사용된 값들을 피하기 위한 오프셋 숫자와 함께)를 MySQL의 conf.d 디렉토리의 server-id.cnf 파일에 저장한다. 이는 스테이트풀셋에게서 제공된 고유하고, 안정적인 신원을 같은 속성을 필요로 하는 MySQL 서버 ID의 형태로 바꾸어준다.

또한 init-mysql 컨테이너의 스크립트는 컨피그맵을 conf.d로 복사하여, primary.cnf 또는 replica.cnf을 적용한다. 이 예제의 토폴로지가 하나의 주 MySQL 서버와 일정 수의 레플리카들로 이루어져 있기 때문에, 스크립트는 0 인덱스를 주 서버로, 그리고 나머지 값들은 레플리카로 지정한다. 스테이트풀셋 컨트롤러의 디플로이먼트와 스케일링 보증과 합쳐지면, 복제를 위한 레플리카들을 생성하기 전에 주 MySQL 서버가 Ready 상태가 되도록 보장할 수 있다.

기존 데이터 복제(cloning)

일반적으로, 레플리카에 새로운 파드가 추가되는 경우, 주 MySQL 서버가 이미 데이터를 가지고 있다고 가정해야 한다. 또한 복제 로그가 첫 시작점부터의 로그들을 다 가지고 있지는 않을 수 있다고 가정해야 한다. 이러한 보수적인 가정들은 스테이트풀셋이 초기 크기로 고정되어 있는 것보다, 시간에 따라 확장/축소하게 할 수 있도록 하는 중요한 열쇠가 된다.

clone-mysql라는 이름의 두 번째 초기화 컨테이너는, 퍼시스턴트볼륨에서 처음 초기화된 레플리카 파드에 복제 작업을 수행한다. 이 말은 다른 실행 중인 파드로부터 모든 데이터들을 복제하기 때문에, 로컬의 상태가 충분히 일관성을 유지하고 있어서 주 서버에서부터 복제를 시작할 수 있다는 의미이다.

MySQL 자체가 이러한 메커니즘을 제공해주지는 않기 때문에, 이 예제에서는 XtraBackup이라는 유명한 오픈소스 도구를 사용한다. 복제 중에는, 복제 대상 MySQL 서버가 성능 저하를 겪을 수 있다. 주 MySQL 서버의 이러한 충격을 최소화하기 위해, 스크립트는 각 파드가 자신의 인덱스보다 하나 작은 파드로부터 복제하도록 지시한다. 이것이 정상적으로 동작하는 이유는 스테이트풀셋 컨트롤러가 파드 N+1을 실행하기 전에 항상 파드 N이 Ready 상태라는 것을 보장하기 때문이다.

복제(replication) 시작하기

초기화 컨테이너들의 성공적으로 완료되면, 일반적인 컨테이너가 실행된다. MySQL 파드는 mysqld 서버를 구동하는 mysql 컨테이너로 구성되어 있으며, xtrabackup 컨테이너는 사이드카(sidecar)로서 작동한다.

xtrabackup 사이드카는 복제된 데이터 파일들을 보고 레플리카에 MySQL 복제를 시작해야 할 필요가 있는지 결정한다. 만약 그렇다면, mysqld이 준비될 때까지 기다린 후 CHANGE MASTER TO, 그리고 START SLAVE를 XtraBackup 복제(clone) 파일들에서 추출한 복제(replication) 파라미터들과 함께 실행시킨다.

레플리카가 복제를 시작하면, 먼저 주 MySQL 서버를 기억하고, 서버가 재시작되거나 커넥션이 끊어지면 다시 연결한다. 또한 레플리카들은 주 서버를 안정된 DNS 이름 (mysql-0.mysql)으로 찾기 때문에, 주 서버가 리스케쥴링에 의해 새로운 파드 IP를 받아도 주 서버를 자동으로 찾는다.

마지막으로, 복제를 시작한 후에는, xtrabackup 컨테이너는 데이터 복제를 요청하는 다른 파드들의 커넥션을 리스닝한다. 이 서버는 스테이트풀셋이 확장하거나, 다음 파드가 퍼시스턴트볼륨클레임을 잃어서 다시 복제를 수행해햐 할 경우를 대비하여 독립적으로 존재해야 한다.

클라이언트 트래픽 보내기

임시 컨테이너를 mysql:5.7 이미지로 실행하고 mysql 클라이언트 바이너리를 실행하는 것으로 테스트 쿼리를 주 MySQL 서버(mysql-0.mysql 호스트네임)로 보낼 수 있다.

kubectl run mysql-client --image=mysql:5.7 -i --rm --restart=Never --\
  mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello');
EOF

mysql-read 호스트네임을 사용하여 Ready 상태가 되었다고 보고하는 어느 서버에나 시험 쿼리를 보낼 수 있다.

kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
  mysql -h mysql-read -e "SELECT * FROM test.messages"

그러면 다음과 같은 출력를 얻을 것이다.

Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello   |
+---------+
pod "mysql-client" deleted

mysql-read 서비스가 커넥션을 서버들에게 분배하고 있다는 사실을 확인하기 위해서, SELECT @@server_id를 루프로 돌릴 수 있다.

kubectl run mysql-client-loop --image=mysql:5.7 -i -t --rm --restart=Never --\
  bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"

보고된 @@server_id가 무작위로 바뀌는 것을 알 수 있다. 왜냐하면 매 커넥션 시도마다 다른 엔드포인트가 선택될 수 있기 때문이다.

+-------------+---------------------+
| @@server_id | NOW()               |
+-------------+---------------------+
|         100 | 2006-01-02 15:04:05 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW()               |
+-------------+---------------------+
|         102 | 2006-01-02 15:04:06 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW()               |
+-------------+---------------------+
|         101 | 2006-01-02 15:04:07 |
+-------------+---------------------+

루프를 끝내고 싶다면 Ctrl+C를 입력하면 되지만, 다음 단계에서의 영향을 보기 위해서 다른 창에 계속 실행시켜 놓는 것이 좋다.

노드와 파드 실패 시뮬레이션하기

단일 서버 대비 레플리카들의 풀에 의한 읽기의 가용성 향상을 시연하기 위해, 파드를 Ready 상태로 강제하면서 SELECT @@server_id 루프를 돌리자.

준비성 프로브 고장내기

mysql 컨테이너의 준비성 프로브는 쿼리를 실행할 수 있는지를 확인하기 위해 mysql -h 127.0.0.1 -e 'SELECT 1' 명령을 실행한다.

준비성 프로브를 강제로 실패시키는 방법은 해당 명령을 고장내는 것이다.

kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off

이것은 mysql-2 파드의 실제 컨테이너의 파일시스템에 접근해서 mysql 명령의 이름을 변경하여, 준비성 프로브가 찾을 수 없도록 한다. 몇 초 뒤에, 파드는 컨터이너 중 하나가 준비되지 않았다고 보고할 것이다. 다음 명령을 실행시켜서 확인할 수 있다.

kubectl get pod mysql-2

READY 항목의 1/2를 확인하자.

NAME      READY     STATUS    RESTARTS   AGE
mysql-2   1/2       Running   0          3m

이 시점에서, 102를 보고하지 않지만, 계속해서 SELECT @@server_id 루프가 실행되는 것을 확인할 수 있을 것이다. init-mysql 스크립트에서 server-id100 + $ordinal로 정의했기 때문에, 서버 ID 102mysql-2 파드에 대응된다는 사실을 상기하자.

이제 파드를 고치면 몇 초 뒤에 루프 출력에 나타날 것이다.

kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql

파드 삭제하기

레플리카셋이 스테이트리스 파드들에게 하는 것처럼, 스테이트풀셋 또한 파드들이 삭제되면 파드들을 다시 생성한다.

kubectl delete pod mysql-2

스테이트풀셋 컨트롤러는 mysql-2 파드가 존재하지 않는 것을 인지하고, 같은 이름으로 새로운 파드를 생성하고 같은 퍼시스턴트볼륨클레임에 연결한다. 잠시동안 서버 ID 102가 루프 출력에서 사라지고 다시 나타나는 것을 확인할 수 있을 것이다.

노드 드레인하기

만약 당신의 쿠버네티스 클러스터가 여러 노드들을 가지고 있으면, 드레인을 사용하여 노드 다운타임을 시뮬레이션할 수 있다.

먼저 MySQL 파드가 존재하고 있는 노드를 확인하자.

kubectl get pod mysql-2 -o wide

노드 이름은 마지막 열에서 나타날 것이다.

NAME      READY     STATUS    RESTARTS   AGE       IP            NODE
mysql-2   2/2       Running   0          15m       10.244.5.27   kubernetes-node-9l2t

그 후에, 다음 명령을 실행하여 노드를 드레인한다. 그러면 새로운 파드가 스케줄되지 않게 방지하고(cordon), 기존의 파드들을 추방(evict)한다. <node-name>를 이전 단계에서 찾았던 노드의 이름으로 바꾸자.

# 위에 명시된 다른 워크로드들이 받는 영향에 대한 주의사항을 확인한다.
kubectl drain <node-name> --force --delete-emptydir-data --ignore-daemonsets

이제 파드가 다른 노드에 리스케줄링되는 것을 관찰할 수 있다.

kubectl get pod mysql-2 -o wide --watch

출력은 다음과 비슷할 것이다.

NAME      READY   STATUS          RESTARTS   AGE       IP            NODE
mysql-2   2/2     Terminating     0          15m       10.244.1.56   kubernetes-node-9l2t
[...]
mysql-2   0/2     Pending         0          0s        <none>        kubernetes-node-fjlm
mysql-2   0/2     Init:0/2        0          0s        <none>        kubernetes-node-fjlm
mysql-2   0/2     Init:1/2        0          20s       10.244.5.32   kubernetes-node-fjlm
mysql-2   0/2     PodInitializing 0          21s       10.244.5.32   kubernetes-node-fjlm
mysql-2   1/2     Running         0          22s       10.244.5.32   kubernetes-node-fjlm
mysql-2   2/2     Running         0          30s       10.244.5.32   kubernetes-node-fjlm

그리고, 서버 ID 102SELECT @@server_id 루프 출력에서 잠시 사라진 후 다시 보이는 것을 확인할 수 있을 것이다.

이제 노드의 스케줄 방지를 다시 해제(uncordon)해서 정상으로 돌아가도록 조치한다.

kubectl uncordon <node-name>

레플리카 스케일링하기

MySQL 레플리케이션을 사용하면, 레플리카를 추가하는 것으로 읽기 쿼리 용량을 키울 수 있다. 스테이트풀셋을 사용하면, 단 한 줄의 명령으로 달성할 수 있다.

kubectl scale statefulset mysql  --replicas=5

명령을 실행시켜서 새로운 파드들이 올라오는 것을 관찰하자.

kubectl get pods -l app=mysql --watch

파드들이 올라오면, SELECT @@server_id 루프 출력에 서버 ID 103104가 나타나기 시작할 것이다.

그리고 해당 파드들이 존재하기 전에 추가된 데이터들이 해당 새 서버들에게도 존재하는 것을 확인할 수 있다.

kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
  mysql -h mysql-3.mysql -e "SELECT * FROM test.messages"
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello   |
+---------+
pod "mysql-client" deleted

축소하는 것도 간단하게 할 수 있다.

kubectl scale statefulset mysql --replicas=3

다음 명령을 실행하여 확인할 수 있다.

kubectl get pvc -l app=mysql

스테이트풀셋을 3으로 축소했음에도 PVC 5개가 아직 남아있음을 보여준다.

NAME           STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
data-mysql-0   Bound     pvc-8acbf5dc-b103-11e6-93fa-42010a800002   10Gi       RWO           20m
data-mysql-1   Bound     pvc-8ad39820-b103-11e6-93fa-42010a800002   10Gi       RWO           20m
data-mysql-2   Bound     pvc-8ad69a6d-b103-11e6-93fa-42010a800002   10Gi       RWO           20m
data-mysql-3   Bound     pvc-50043c45-b1c5-11e6-93fa-42010a800002   10Gi       RWO           2m
data-mysql-4   Bound     pvc-500a9957-b1c5-11e6-93fa-42010a800002   10Gi       RWO           2m

만약 여분의 PVC들을 재사용하지 않을 것이라면, 이들을 삭제할 수 있다.

kubectl delete pvc data-mysql-3
kubectl delete pvc data-mysql-4

정리하기

  1. SELECT @@server_id 루프를 끝내기 위해, 터미널에 Ctrl+C를 입력하거나, 해당 명령을 다른 터미널에서 실행시키자.

    kubectl delete pod mysql-client-loop --now
    
  2. 스테이트풀셋을 삭제한다. 이것은 파드들 또한 삭제할 것이다.

    kubectl delete statefulset mysql
    
  3. 파드들의 삭제를 확인한다. 삭제가 완료되기까지 시간이 걸릴 수 있다.

    kubectl get pods -l app=mysql
    

    위와 같은 메세지가 나타나면 파드들이 삭제되었다는 것을 알 수 있다.

    No resources found.
    
  4. 컨피그맵, 서비스, 그리고 퍼시스턴트볼륨클레임들을 삭제한다.

    kubectl delete configmap,service,pvc -l app=mysql
    
  5. 만약 수동으로 퍼시스턴스볼륨들을 프로비저닝했다면, 수동으로 삭제하면서, 그 밑에 존재하는 리소스들을 또한 삭제해야 한다. 만약 동적 프로비저너를 사용했다면, 당신이 퍼시스턴트볼륨클레임으로 삭제하면 자동으로 퍼시스턴트볼륨을 삭제한다. 일부 (EBS나 PD와 같은) 동적 프로비저너들은 퍼시스턴트볼륨을 삭제 하면 그 뒤에 존재하는 리소스들도 삭제한다.

다음 내용

4 - 스테이트풀셋(StatefulSet) 확장하기

이 작업은 스테이트풀셋을 확장하는 방법을 보여준다. 스테이트풀셋 확장은 레플리카 수를 늘리거나 줄이는 것을 의미한다.

시작하기 전에

  • 스테이트풀셋은 쿠버네티스 버전 1.5 이상에서만 사용할 수 있다. 쿠버네티스 버전을 확인하려면 kubectl version을 실행한다.

  • 모든 스테이트풀 애플리케이션이 제대로 확장되는 것은 아니다. 스테이트풀셋을 확장할지 여부가 확실하지 않은 경우에 자세한 내용은 스테이트풀셋 또는 스테이트풀셋 튜토리얼을 참조한다.

  • 스테이트풀 애플리케이션 클러스터가 완전히 정상이라고 확신할 때만 확장을 수행해야 한다.

스테이트풀셋 확장하기

kubectl을 사용하여 스테이트풀셋 확장

먼저 확장하려는 스테이트풀셋을 찾는다.

kubectl get statefulsets <stateful-set-name>

스테이트풀셋의 레플리카 수를 변경한다.

kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>

스테이트풀셋 인플레이스(in-place) 업데이트

대안으로 스테이트풀셋에 인플레이스 업데이트를 수행할 수 있다.

스테이트풀셋이 처음에 kubectl apply로 생성된 경우, 스테이트풀셋 매니페스트의 .spec.replicas를 업데이트한 다음 kubectl apply를 수행한다.

kubectl apply -f <stateful-set-file-updated>

그렇지 않으면 kubectl edit로 해당 필드를 편집한다.

kubectl edit statefulsets <stateful-set-name>

또는 kubectl patch를 사용한다.

kubectl patch statefulsets <stateful-set-name> -p '{"spec":{"replicas":<new-replicas>}}'

트러블슈팅

축소가 제대로 작동하지 않음

스테이트풀셋에서 관리하는 스테이트풀 파드가 비정상인 경우에는 스테이트풀셋을 축소할 수 없다. 축소는 스테이트풀 파드가 실행되고 준비된 후에만 발생한다.

spec.replicas > 1인 경우 쿠버네티스는 비정상 파드의 원인을 결정할 수 없다. 영구적인 오류 또는 일시적인 오류의 결과일 수 있다. 일시적인 오류는 업그레이드 또는 유지 관리에 필요한 재시작으로 인해 발생할 수 있다.

영구적인 오류로 인해 파드가 비정상인 경우 오류를 수정하지 않고 확장하면 스테이트풀셋 멤버십이 올바르게 작동하는 데 필요한 특정 최소 레플리카 수 아래로 떨어지는 상태로 이어질 수 있다. 이로 인해 스테이트풀셋을 사용할 수 없게 될 수 있다.

일시적인 오류로 인해 파드가 비정상 상태이고 파드를 다시 사용할 수 있게 되면 일시적인 오류가 확장 또는 축소 작업을 방해할 수 있다. 일부 분산 데이터베이스에는 노드가 동시에 가입 및 탈퇴할 때 문제가 있다. 이러한 경우 애플리케이션 수준에서 확장 작업에 대해 추론하고 스테이트풀 애플리케이션 클러스터가 완전히 정상이라고 확신할 때만 확장을 수행하는 것이 좋다.

다음 내용

5 - 스테이트풀셋(StatefulSet) 삭제하기

이 작업은 스테이트풀셋을 삭제하는 방법을 설명한다.

시작하기 전에

  • 이 작업은 클러스터에 스테이트풀셋으로 표시되는 애플리케이션이 있다고 가정한다.

스테이트풀셋 삭제

쿠버네티스에서 다른 리소스를 삭제하는 것과 같은 방식으로 스테이트풀셋을 삭제할 수 있다. kubectl delete 명령어를 사용하고 파일 또는 이름으로 스테이트풀셋을 지정하자.

kubectl delete -f <file.yaml>
kubectl delete statefulsets <statefulset-name>

스테이트풀셋 자체를 삭제한 후 연결된 헤드리스 서비스는 별도로 삭제해야 할 수도 있다.

kubectl delete service <service-name>

kubectl을 통해 스테이트풀셋을 삭제하면, 스테이트풀셋의 크기가 0으로 설정되고 이로 인해 스테이트풀셋에 포함된 모든 파드가 삭제된다. 파드가 아닌 스테이트풀셋만 삭제하려면, --cascade=orphan 옵션을 사용한다. 예시는 다음과 같다.

kubectl delete -f <file.yaml> --cascade=orphan

kubectl delete--cascade=orphan 를 사용하면 스테이트풀셋 오브젝트가 삭제된 후에도 스테이트풀셋에 의해 관리된 파드는 남게 된다. 만약 파드가 app.kubernetes.io/name=MyApp 레이블을 갖고 있다면, 다음과 같이 파드를 삭제할 수 있다.

kubectl delete pods -l app.kubernetes.io/name=MyApp

퍼시스턴트볼륨(PersistentVolume)

스테이트풀셋의 파드들을 삭제하는 것이 연결된 볼륨을 삭제하는 것은 아니다. 이것은 볼륨을 삭제하기 전에 볼륨에서 데이터를 복사할 수 있는 기회를 준다. 파드가 종료된 후 PVC를 삭제하면 스토리지 클래스와 반환 정책에 따라 백업 퍼시스턴트볼륨 삭제가 트리거될 수 있다. 클레임 삭제 후 볼륨에 접근할 수 있다고 가정하면 안된다.

스테이트풀셋의 완벽한 삭제

연결된 파드를 포함해서 스테이트풀셋의 모든 것을 삭제하기 위해 다음과 같이 일련의 명령을 실행한다.

grace=$(kubectl get pods <stateful-set-pod> --template '{{.spec.terminationGracePeriodSeconds}}')
kubectl delete statefulset -l app.kubernetes.io/name=MyApp
sleep $grace
kubectl delete pvc -l app.kubernetes.io/name=MyApp

위의 예에서 파드에는 app.kubernetes.io/name=MyApp 라는 레이블이 있다. 사용자에게 적절한 레이블로 대체하자.

스테이트풀셋 파드의 강제 삭제

스테이트풀셋의 일부 파드가 오랫동안 'Terminating' 또는 'Unknown' 상태에 있는 경우, apiserver에 수동적으로 개입하여 파드를 강제 삭제할 수도 있다. 이것은 잠재적으로 위험한 작업이다. 자세한 설명은 스테이트풀셋 파드 강제 삭제하기를 참고한다.

다음 내용

스테이트풀셋 파드 강제 삭제하기에 대해 더 알아보기.

6 - 스테이트풀셋(StatefulSet) 파드 강제 삭제하기

이 페이지에서는 스테이트풀셋의 일부인 파드를 삭제하는 방법을 보여주고 이 과정에서 고려해야 할 사항을 설명한다.

시작하기 전에

  • 이것은 상당히 고급 태스크이며 스테이트풀셋 고유의 속성 중 일부를 위반할 가능성이 있다.
  • 계속하기 전에 아래 나열된 고려 사항을 숙지하도록 한다.

스테이트풀셋 고려 사항

정상적인 스테이트풀셋의 작동에서는 스테이트풀셋 파드를 강제로 삭제할 필요가 절대 없다. 스테이트풀셋 컨트롤러는 스테이트풀셋의 멤버 생성, 스케일링, 삭제를 담당한다. 서수 0부터 N-1까지 지정된 수의 파드가 활성 상태이고 준비되었는지 확인한다. 스테이트풀셋은 언제든지 클러스터에서 실행 중인 지정된 신원을 가진 최대 하나의 파드가 있는지 확인한다. 이를 스테이트풀셋에서 제공하는 최대 하나 의미론이라고 한다.

수동 강제 삭제는 스테이트풀셋 고유의 최대 하나 의미론을 위반할 가능성이 있으므로 주의해서 수행해야 한다. 스테이트풀셋은 안정적인 네트워크 신원과 안정적인 스토리지가 필요한 분산 클러스터 애플리케이션을 실행하는 데 사용할 수 있다. 이러한 애플리케이션은 종종 고정된 신원을 가진 고정된 수의 구성원 앙상블에 의존하는 구성을 가진다. 여러 구성원이 동일한 신원을 갖는 것은 재앙이 될 수 있으며 데이터 손실로 이어질 수 있다(예: 쿼럼 기반 시스템의 스플릿 브레인(split-brain) 시나리오).

파드 삭제

다음 명령을 사용하여 파드를 단계적으로 삭제할 수 있다.

kubectl delete pods <pod>

위의 내용이 단계적인 종료로 이어지려면 파드가 pod.Spec.TerminationGracePeriodSeconds를 0으로 지정하지 않아야 한다. pod.Spec.TerminationGracePeriodSeconds를 0초로 설정하는 관행은 안전하지 않으며 스테이트풀셋 파드에서 강력히 권장하지 않는다. 단계적 삭제는 안전하며 kubelet이 apiserver에서 이름을 삭제하기 전에 파드가 정상적으로 종료되도록 한다.

노드에 연결할 수 없는 경우 파드는 자동으로 삭제되지 않는다. 연결할 수 없는 노드에서 실행 중인 파드는 타임아웃 후에 'Terminating'이나 'Unknown' 상태가 된다.

사용자가 연결할 수 없는 노드에서 파드를 단계적으로 삭제하려고 하면 파드가 이러한 상태에 들어갈 수도 있다. 이러한 상태의 파드를 apiserver에서 제거할 수 있는 유일한 방법은 다음과 같다.

  • 노드 오브젝트가 삭제된다(사용자 또는 노드 컨트롤러에 의해).
  • 응답하지 않는 노드의 kubelet이 응답을 시작하고 파드를 종료하여 apiserver에서 항목을 제거한다.
  • 사용자가 파드를 강제로 삭제한다.

권장되는 모범 사례는 첫 번째 또는 두 번째 방법을 사용하는 것이다. 노드가 죽은 것으로 확인되면(예: 네트워크에서 영구적으로 연결이 끊기거나 전원이 꺼진 경우 등) 노드 오브젝트를 삭제한다. 노드에 네트워크 파티션이 있는 경우 이를 해결하거나 해결될 때까지 기다린다. 파티션이 복구되면 kubelet은 파드 삭제를 완료하고 apiserver에서 해당 이름을 해제한다.

일반적으로 시스템은 파드가 노드에서 더 이상 실행되지 않거나 노드가 관리자에 의해 삭제되면 삭제를 완료한다. 파드를 강제로 삭제하여 이를 재정의할 수 있다.

강제 삭제

강제 삭제는 파드가 종료되었다는 kubelet의 확인을 기다리지 않는다. 강제 삭제가 파드를 죽이는 데 성공했는지 여부와 관계없이 즉시 apiserver에서 이름을 해제한다. 이렇게 하면 스테이트풀셋 컨트롤러가 동일한 신원으로 대체 파드를 생성할 수 있다. 이것은 여전히 실행 중인 파드와 중복될 수 있으며, 해당 파드가 여전히 스테이트풀셋의 다른 멤버와 통신할 수 있다면 스테이트풀셋이 보장하도록 설계된 최대 하나 의미론을 위반할 것이다.

스테이트풀셋 파드를 강제로 삭제하는 것은 문제의 파드가 스테이트풀셋의 다른 파드와 다시는 접촉하지 않으며 대체 생성을 위해 해당 이름을 안전하게 해제할 수 있다고 주장하는 것이다.

kubectl 버전 >= 1.5를 사용하여 파드를 강제로 삭제하려면, 다음을 수행한다.

kubectl delete pods <pod> --grace-period=0 --force

kubectl <= 1.4 버전을 사용하는 경우, --force 옵션을 생략하고 다음을 사용해야 한다.

kubectl delete pods <pod> --grace-period=0

이러한 명령 후에도 파드가 Unknown 상태에서 멈추면, 다음 명령을 사용하여 클러스터에서 파드를 제거한다.

kubectl patch pod <pod> -p '{"metadata":{"finalizers":null}}'

항상 관련된 위험에 대해 완전히 이해한 상태에서 주의 깊게 스테이트풀셋 파드의 강제 삭제를 수행한다.

다음 내용

스테이트풀셋 디버깅하기에 대해 더 알아보기.

7 - Horizontal Pod Autoscaling

쿠버네티스에서, HorizontalPodAutoscaler 는 워크로드 리소스(예: 디플로이먼트 또는 스테이트풀셋)를 자동으로 업데이트하며, 워크로드의 크기를 수요에 맞게 자동으로 스케일링하는 것을 목표로 한다.

수평 스케일링은 부하 증가에 대해 파드를 더 배치하는 것을 뜻한다. 이는 수직 스케일링(쿠버네티스에서는, 해당 워크로드를 위해 이미 실행 중인 파드에 더 많은 자원(예: 메모리 또는 CPU)를 할당하는 것)과는 다르다.

부하량이 줄어들고, 파드의 수가 최소 설정값 이상인 경우, HorizontalPodAutoscaler는 워크로드 리소스(디플로이먼트, 스테이트풀셋, 또는 다른 비슷한 리소스)에게 스케일 다운을 지시한다.

Horizontal Pod Autoscaling은 크기 조절이 불가능한 오브젝트(예: 데몬셋)에는 적용할 수 없다.

HorizontalPodAutoscaler는 쿠버네티스 API 자원 및 컨트롤러 형태로 구현되어 있다. HorizontalPodAutoscaler API 자원은 컨트롤러의 행동을 결정한다. 쿠버네티스 컨트롤 플레인 내에서 실행되는 HPA 컨트롤러는 평균 CPU 사용률, 평균 메모리 사용률, 또는 다른 커스텀 메트릭 등의 관측된 메트릭을 목표에 맞추기 위해 목표물(예: 디플로이먼트)의 적정 크기를 주기적으로 조정한다.

Horizontal Pod Autoscaling을 활용하는 연습 예제가 존재한다.

HorizontalPodAutoscaler는 어떻게 작동하는가?

graph BT hpa[Horizontal Pod Autoscaler] --> scale[Scale] subgraph rc[RC / Deployment] scale end scale -.-> pod1[Pod 1] scale -.-> pod2[Pod 2] scale -.-> pod3[Pod N] classDef hpa fill:#D5A6BD,stroke:#1E1E1D,stroke-width:1px,color:#1E1E1D; classDef rc fill:#F9CB9C,stroke:#1E1E1D,stroke-width:1px,color:#1E1E1D; classDef scale fill:#B6D7A8,stroke:#1E1E1D,stroke-width:1px,color:#1E1E1D; classDef pod fill:#9FC5E8,stroke:#1E1E1D,stroke-width:1px,color:#1E1E1D; class hpa hpa; class rc rc; class scale scale; class pod1,pod2,pod3 pod

그림 1. HorizontalPodAutoscaler는 디플로이먼트 및 디플로이먼트의 레플리카셋의 크기를 조정한다.

쿠버네티스는 Horizontal Pod Autoscaling을 간헐적으로(intermittently) 실행되는 컨트롤 루프 형태로 구현했다(지속적인 프로세스가 아니다). 실행 주기는 kube-controller-manager--horizontal-pod-autoscaler-sync-period 파라미터에 의해 설정된다(기본 주기는 15초이다).

각 주기마다, 컨트롤러 매니저는 각 HorizontalPodAutoscaler 정의에 지정된 메트릭에 대해 리소스 사용률을 질의한다. 컨트롤러 매니저는 scaleTargetRef에 의해 정의된 타겟 리소스를 찾고 나서, 타겟 리소스의 .spec.selector 레이블을 보고 파드를 선택하며, 리소스 메트릭 API(파드 단위 리소스 메트릭 용) 또는 커스텀 메트릭 API(그 외 모든 메트릭 용)로부터 메트릭을 수집한다.

  • 파드 단위 리소스 메트릭(예 : CPU)의 경우 컨트롤러는 HorizontalPodAutoscaler가 대상으로하는 각 파드에 대한 리소스 메트릭 API에서 메트릭을 가져온다. 그런 다음, 목표 사용률 값이 설정되면, 컨트롤러는 각 파드의 컨테이너에 대한 동등한 자원 요청을 퍼센트 단위로 하여 사용률 값을 계산한다. 대상 원시 값이 설정된 경우 원시 메트릭 값이 직접 사용된다. 그리고, 컨트롤러는 모든 대상 파드에서 사용된 사용률의 평균 또는 원시 값(지정된 대상 유형에 따라 다름)을 가져와서 원하는 레플리카의 개수를 스케일하는데 사용되는 비율을 생성한다.

    파드의 컨테이너 중 일부에 적절한 리소스 요청이 설정되지 않은 경우, 파드의 CPU 사용률은 정의되지 않으며, 따라서 오토스케일러는 해당 메트릭에 대해 아무런 조치도 취하지 않는다. 오토스케일링 알고리즘의 작동 방식에 대한 자세한 내용은 아래 알고리즘 세부 정보 섹션을 참조하기 바란다.

  • 파드 단위 사용자 정의 메트릭의 경우, 컨트롤러는 사용률 값이 아닌 원시 값을 사용한다는 점을 제외하고는 파드 단위 리소스 메트릭과 유사하게 작동한다.

  • 오브젝트 메트릭 및 외부 메트릭의 경우, 문제의 오브젝트를 표현하는 단일 메트릭을 가져온다. 이 메트릭은 목표 값과 비교되어 위와 같은 비율을 생성한다. autoscaling/v2 API 버전에서는, 비교가 이루어지기 전에 해당 값을 파드의 개수로 선택적으로 나눌 수 있다.

HorizontalPodAutoscaler를 사용하는 일반적인 방법은 집약된 API(metrics.k8s.io, custom.metrics.k8s.io, 또는 external.metrics.k8s.io)로부터 메트릭을 가져오도록 설정하는 것이다. metrics.k8s.io API는 보통 메트릭 서버(Metrics Server)라는 애드온에 의해 제공되며, Metrics Server는 별도로 실행해야 한다. 자원 메트릭에 대한 추가 정보는 Metrics Server를 참고한다.

메트릭 API를 위한 지원에서 위의 API들에 대한 안정성 보장 및 지원 상태를 확인할 수 있다.

HorizontalPodAutoscaler 컨트롤러는 스케일링을 지원하는 상응하는 워크로드 리소스(예: 디플로이먼트 및 스테이트풀셋)에 접근한다. 이들 리소스 각각은 scale이라는 하위 리소스를 갖고 있으며, 이 하위 리소스는 레플리카의 수를 동적으로 설정하고 각각의 현재 상태를 확인할 수 있도록 하는 인터페이스이다. 쿠버네티스 API의 하위 리소스에 대한 일반적인 정보는 쿠버네티스 API 개념에서 확인할 수 있다.

알고리즘 세부 정보

가장 기본적인 관점에서, HorizontalPodAutoscaler 컨트롤러는 원하는(desired) 메트릭 값과 현재(current) 메트릭 값 사이의 비율로 작동한다.

원하는 레플리카 수 = ceil[현재 레플리카 수 * ( 현재 메트릭 값 / 원하는 메트릭 값 )]

예를 들어 현재 메트릭 값이 200m이고 원하는 값이 100m인 경우 200.0 / 100.0 == 2.0이므로 복제본 수가 두 배가 된다. 만약 현재 값이 50m 이면, 50.0 / 100.0 == 0.5 이므로 복제본 수를 반으로 줄일 것이다. 컨트롤 플레인은 비율이 1.0(기본값이 0.1인 -horizontal-pod-autoscaler-tolerance 플래그를 사용하여 전역적으로 구성 가능한 허용 오차 내)에 충분히 가깝다면 스케일링을 건너 뛸 것이다.

targetAverageValue 또는 targetAverageUtilization가 지정되면, currentMetricValue는 HorizontalPodAutoscaler의 스케일 목표 안에 있는 모든 파드에서 주어진 메트릭의 평균을 취하여 계산된다.

허용치를 확인하고 최종 값을 결정하기 전에, 컨트롤 플레인은 누락된 메트릭이 있는지, 그리고 몇 개의 파드가 Ready인지도 고려한다. 삭제 타임스탬프가 설정된 모든 파드(파드에 삭제 타임스탬프가 있으면 셧다운/삭제 중임을 뜻한다)는 무시되며, 모든 실패한 파드는 버려진다.

특정 파드에 메트릭이 누락된 경우, 나중을 위해 처리를 미뤄두는데, 이와 같이 누락된 메트릭이 있는 모든 파드는 최종 스케일 량을 조정하는데 사용된다.

CPU를 스케일할 때, 파드가 아직 Ready되지 않았거나(여전히 초기화중이거나, unhealthy하여서) 또는 파드의 최신 메트릭 포인트가 준비되기 전이라면, 마찬가지로 해당 파드는 나중에 처리된다.

기술적 제약으로 인해, HorizontalPodAutoscaler 컨트롤러는 특정 CPU 메트릭을 나중에 사용할지 말지 결정할 때, 파드가 준비되는 시작 시간을 정확하게 알 수 없다. 대신, 파드가 아직 준비되지 않았고 시작 이후 짧은 시간 내에 파드가 준비 상태로 전환된다면, 해당 파드를 "아직 준비되지 않음(not yet ready)"으로 간주한다. 이 값은 --horizontal-pod-autoscaler-initial-readiness-delay 플래그로 설정되며, 기본값은 30초 이다. 일단 파드가 준비되고 시작된 후 구성 가능한 시간 이내이면, 준비를 위한 어떠한 전환이라도 이를 시작 시간으로 간주한다. 이 값은 --horizontal-pod-autoscaler-cpu-initialization-period 플래그로 설정되며 기본값은 5분이다.

현재 메트릭 값 / 원하는 메트릭 값 기본 스케일 비율은 나중에 사용하기로 되어 있거나 위에서 폐기되지 않은 남아있는 파드를 사용하여 계산된다.

누락된 메트릭이 있는 경우, 컨트롤 플레인은 파드가 스케일 다운의 경우 원하는 값의 100%를 소비하고 스케일 업의 경우 0%를 소비한다고 가정하여 평균을 보다 보수적으로 재계산한다. 이것은 잠재적인 스케일의 크기를 약화시킨다.

또한, 아직-준비되지-않은 파드가 있고, 누락된 메트릭이나 아직-준비되지-않은 파드가 고려되지 않고 워크로드가 스케일업 된 경우, 컨트롤러는 아직-준비되지-않은 파드가 원하는 메트릭의 0%를 소비한다고 보수적으로 가정하고 스케일 확장의 크기를 약화시킨다.

아직-준비되지-않은 파드나 누락된 메트릭을 고려한 후에, 컨트롤러가 사용률을 다시 계산한다. 새로 계산한 사용률이 스케일 방향을 바꾸거나, 허용 오차 내에 있으면, 컨트롤러가 스케일링을 건너뛴다. 그렇지 않으면, 새로 계산한 사용률를 이용하여 파드 수 변경 결정을 내린다.

평균 사용량에 대한 원래 값은 새로운 사용 비율이 사용되는 경우에도 아직-준비되지-않은 파드 또는 누락된 메트릭에 대한 고려없이 HorizontalPodAutoscaler 상태를 통해 다시 보고된다.

HorizontalPodAutoscaler에 여러 메트릭이 지정된 경우, 이 계산은 각 메트릭에 대해 수행된 다음 원하는 레플리카 수 중 가장 큰 값이 선택된다. 이러한 메트릭 중 어떠한 것도 원하는 레플리카 수로 변환할 수 없는 경우(예 : 메트릭 API에서 메트릭을 가져오는 중 오류 발생) 스케일을 건너뛴다. 이는 하나 이상의 메트릭이 현재 값보다 높은 desiredReplicas 을 제공하는 경우 HPA가 여전히 확장할 수 있음을 의미한다.

마지막으로, HPA가 목표를 스케일하기 직전에 스케일 권장 사항이 기록된다. 컨트롤러는 구성 가능한 창(window) 내에서 가장 높은 권장 사항을 선택하도록 해당 창 내의 모든 권장 사항을 고려한다. 이 값은 --horizontal-pod-autoscaler-downscale-stabilization 플래그를 사용하여 설정할 수 있고, 기본값은 5분이다. 즉, 스케일 다운이 점진적으로 발생하여 급격히 변동하는 메트릭 값의 영향을 완만하게 한다.

API 오브젝트

Horizontal Pod Autoscaler는 쿠버네티스 autoscaling API 그룹의 API 리소스이다. 현재의 안정 버전은 autoscaling/v2 API 버전이며, 메모리와 커스텀 메트릭에 대한 스케일링을 지원한다. autoscaling/v2에서 추가된 새로운 필드는 autoscaling/v1를 이용할 때에는 어노테이션으로 보존된다.

HorizontalPodAutoscaler API 오브젝트 생성시 지정된 이름이 유효한 DNS 서브도메인 이름인지 확인해야 한다. API 오브젝트에 대한 자세한 내용은 HorizontalPodAutoscaler 오브젝트에서 찾을 수 있다.

워크로드 스케일링의 안정성

HorizontalPodAutoscaler를 사용하여 레플리카 그룹의 크기를 관리할 때, 측정하는 메트릭의 동적 특성 때문에 레플리카 수가 계속 자주 요동칠 수 있다. 이는 종종 thrashing 또는 flapping이라고 불린다. 이는 사이버네틱스 분야의 이력 현상(hysteresis) 개념과 비슷하다.

롤링 업데이트 중 오토스케일링

쿠버네티스는 디플로이먼트에 대한 롤링 업데이트를 지원한다. 이 경우, 디플로이먼트가 기저 레플리카셋을 알아서 관리한다. 디플로이먼트에 오토스케일링을 설정하려면, 각 디플로이먼트에 대한 HorizontalPodAutoscaler를 생성한다. HorizontalPodAutoscaler는 디플로이먼트의 replicas 필드를 관리한다. 디플로이먼트 컨트롤러는 기저 레플리카셋에 replicas 값을 적용하여 롤아웃 과정 중/이후에 적절한 숫자까지 늘어나도록 한다.

오토스케일된 레플리카가 있는 스테이트풀셋의 롤링 업데이트를 수행하면, 스테이트풀셋이 직접 파드의 숫자를 관리한다(즉, 레플리카셋과 같은 중간 리소스가 없다).

리소스 메트릭 지원

모든 HPA 대상은 스케일링 대상에서 파드의 리소스 사용량을 기준으로 스케일링할 수 있다. 파드의 명세를 정의할 때는 cpumemory 와 같은 리소스 요청을 지정해야 한다. 이것은 리소스 사용률을 결정하는 데 사용되며 HPA 컨트롤러에서 대상을 스케일링하거나 축소하는 데 사용한다. 리소스 사용률 기반 스케일링을 사용하려면 다음과 같은 메트릭 소스를 지정해야 한다.

type: Resource
resource:
  name: cpu
  target:
    type: Utilization
    averageUtilization: 60

이 메트릭을 사용하면 HPA 컨트롤러는 스케일링 대상에서 파드의 평균 사용률을 60%로 유지한다. 사용률은 파드의 요청된 리소스에 대한 현재 리소스 사용량 간의 비율이다. 사용률 계산 및 평균 산출 방법에 대한 자세한 내용은 알고리즘을 참조한다.

컨테이너 리소스 메트릭

기능 상태: Kubernetes v1.20 [alpha]

HorizontalPodAutoscaler API는 대상 리소스를 스케일링하기 위해 HPA가 파드 집합에서 개별 컨테이너의 리소스 사용량을 추적할 수 있는 컨테이너 메트릭 소스도 지원한다. 이를 통해 특정 파드에서 가장 중요한 컨테이너의 스케일링 임계값을 구성할 수 있다. 예를 들어 웹 애플리케이션 프로그램과 로깅 사이드카가 있는 경우 사이드카 컨테이너와 해당 리소스 사용을 무시하고 웹 애플리케이션의 리소스 사용을 기준으로 스케일링할 수 있다.

대상 리소스를 다른 컨테이너 세트를 사용하여 새 파드 명세를 갖도록 수정하는 경우 새로 추가된 컨테이너도 스케일링에 사용해야 한다면 HPA 사양을 수정해야 한다. 메트릭 소스에 지정된 컨테이너가 없거나 파드의 하위 집합에만 있는 경우 해당 파드는 무시되고 권장 사항이 다시 계산된다. 계산에 대한 자세한 내용은 알고리즘을 을 참조한다. 컨테이너 리소스를 오토스케일링에 사용하려면 다음과 같이 메트릭 소스를 정의한다.

type: ContainerResource
containerResource:
  name: cpu
  container: application
  target:
    type: Utilization
    averageUtilization: 60

위의 예에서 HPA 컨트롤러는 모든 파드의 application 컨테이너에 있는 CPU의 평균 사용률이 60%가 되도록 대상을 조정한다.

사용자 정의 메트릭을 이용하는 스케일링

기능 상태: Kubernetes v1.23 [stable]

(이전에는 autoscaling/v2beta2 API 버전이 이 기능을 베타 기능으로 제공했었다.)

autoscaling/v2beta2 API 버전을 사용하는 경우, (쿠버네티스 또는 어느 쿠버네티스 구성 요소에도 포함되어 있지 않은) 커스텀 메트릭을 기반으로 스케일링을 수행하도록 HorizontalPodAutoscaler를 구성할 수 있다. 이 경우 HorizontalPodAutoscaler 컨트롤러가 이러한 커스텀 메트릭을 쿠버네티스 API로부터 조회한다.

요구 사항에 대한 정보는 메트릭 API를 위한 지원을 참조한다.

복수의 메트릭을 이용하는 스케일링

기능 상태: Kubernetes v1.23 [stable]

(이전에는 autoscaling/v2beta2 API 버전이 이 기능을 베타 기능으로 제공했었다.)

autoscaling/v2 API 버전을 사용하는 경우, HorizontalPodAutoscaler는 스케일링에 사용할 복수의 메트릭을 설정할 수 있다. 이 경우 HorizontalPodAutoscaler 컨트롤러가 각 메트릭을 확인하고 해당 단일 메트릭에 대한 새로운 스케일링 크기를 제안한다. HorizontalPodAutoscaler는 새롭게 제안된 스케일링 크기 중 가장 큰 값을 선택하여 워크로드 사이즈를 조정한다(이 값이 이전에 설정한 '총 최대값(overall maximum)'보다는 크지 않을 때에만).

메트릭 API를 위한 지원

기본적으로 HorizontalPodAutoscaler 컨트롤러는 일련의 API에서 메트릭을 검색한다. 이러한 API에 접속하려면 클러스터 관리자는 다음을 확인해야 한다.

  • API 애그리게이션 레이어 활성화

  • 해당 API 등록:

    • 리소스 메트릭의 경우, 일반적으로 이것은 메트릭-서버가 제공하는 metrics.k8s.io API이다. 클러스터 애드온으로 실행할 수 있다.

    • 사용자 정의 메트릭의 경우, 이것은 custom.metrics.k8s.io API이다. 메트릭 솔루션 공급 업체에서 제공하는 "어댑터" API 서버에서 제공한다. 사용 가능한 쿠버네티스 메트릭 어댑터가 있는지 확인하려면 사용하고자 하는 메트릭 파이프라인을 확인한다.

    • 외부 메트릭의 경우, 이것은 external.metrics.k8s.io API이다. 위에 제공된 사용자 정의 메트릭 어댑터에서 제공될 수 있다.

이런 다양한 메트릭 경로와 각각의 다른 점에 대한 상세 내용은 관련 디자인 제안서인 HPA V2, custom.metrics.k8s.io, external.metrics.k8s.io를 참조한다.

어떻게 사용하는지에 대한 예시는 커스텀 메트릭 사용하는 작업 과정외부 메트릭스 사용하는 작업 과정을 참조한다.

구성가능한 스케일링 동작

기능 상태: Kubernetes v1.23 [stable]

(이전에는 autoscaling/v2beta2 API 버전이 이 기능을 베타 기능으로 제공했었다.)

v2 버전의 HorizontalPodAutoscaler API를 사용한다면, behavior 필드(API 레퍼런스 참고)를 사용하여 스케일업 동작과 스케일다운 동작을 별도로 구성할 수 있다. 각 방향에 대한 동작은 behavior 필드 아래의 scaleUp / scaleDown를 설정하여 지정할 수 있다.

안정화 윈도우 를 명시하여 스케일링 목적물의 레플리카 수 흔들림을 방지할 수 있다. 스케일링 정책을 이용하여 스케일링 시 레플리카 수 변화 속도를 조절할 수도 있다.

스케일링 정책

스펙의 behavior 섹션에 하나 이상의 스케일링 폴리시를 지정할 수 있다. 폴리시가 여러 개 지정된 경우 가장 많은 양의 변경을 허용하는 정책이 기본적으로 선택된 폴리시이다. 다음 예시는 스케일 다운 중 이 동작을 보여준다.

behavior:
  scaleDown:
    policies:
    - type: Pods
      value: 4
      periodSeconds: 60
    - type: Percent
      value: 10
      periodSeconds: 60

periodSeconds 는 폴리시가 참(true)으로 유지되어야 하는 기간을 나타낸다. 첫 번째 정책은 (파드들) 이 1분 내에 최대 4개의 레플리카를 스케일 다운할 수 있도록 허용한다. 두 번째 정책은 비율 로 현재 레플리카의 최대 10%를 1분 내에 스케일 다운할 수 있도록 허용한다.

기본적으로 가장 많은 변경을 허용하는 정책이 선택되기에 두 번째 정책은 파드의 레플리카 수가 40개를 초과하는 경우에만 사용된다. 레플리카가 40개 이하인 경우 첫 번째 정책이 적용된다. 예를 들어 80개의 레플리카가 있고 대상을 10개의 레플리카로 축소해야 하는 경우 첫 번째 단계에서 8개의 레플리카가 스케일 다운 된다. 레플리카의 수가 72개일 때 다음 반복에서 파드의 10%는 7.2 이지만, 숫자는 8로 올림된다. 오토스케일러 컨트롤러의 각 루프에서 변경될 파드의 수는 현재 레플리카의 수에 따라 재계산된다. 레플리카의 수가 40 미만으로 떨어지면 첫 번째 폴리시 (파드들) 가 적용되고 한번에 4개의 레플리카가 줄어든다.

확장 방향에 대해 selectPolicy 필드를 확인하여 폴리시 선택을 변경할 수 있다. 레플리카의 수를 최소로 변경할 수 있는 폴리시를 선택하는 최소(Min)로 값을 설정한다. 값을 Disabled 로 설정하면 해당 방향으로 스케일링이 완전히 비활성화된다.

안정화 윈도우

안정화 윈도우는 스케일링에 사용되는 메트릭이 계속 변동할 때 레플리카 수의 흔들림을 제한하기 위해 사용된다. 오토스케일링 알고리즘은 이전의 목표 상태를 추론하고 워크로드 수의 원치 않는 변화를 방지하기 위해 이 안정화 윈도우를 활용한다.

예를 들어, 다음 예제 스니펫에서, scaleDown에 대해 안정화 윈도우가 설정되었다.

behavior:
  scaleDown:
    stabilizationWindowSeconds: 300

메트릭 관측 결과 스케일링 목적물이 스케일 다운 되어야 하는 경우, 알고리즘은 이전에 계산된 목표 상태를 확인하고, 해당 구간에서 계산된 값 중 가장 높은 값을 사용한다. 위의 예시에서, 이전 5분 동안의 모든 목표 상태가 고려 대상이 된다.

이를 통해 동적 최대값(rolling maximum)을 근사화하여, 스케일링 알고리즘이 빠른 시간 간격으로 파드를 제거하고 바로 다시 동일한 파드를 재생성하는 현상을 방지할 수 있다.

기본 동작

사용자 지정 스케일링을 사용하기 위해서 모든 필드를 지정하지 않아도 된다. 사용자 정의가 필요한 값만 지정할 수 있다. 이러한 사용자 지정 값은 기본값과 병합된다. 기본값은 HPA 알고리즘의 기존 동작과 동일하다.

behavior:
  scaleDown:
    stabilizationWindowSeconds: 300
    policies:
    - type: Percent
      value: 100
      periodSeconds: 15
  scaleUp:
    stabilizationWindowSeconds: 0
    policies:
    - type: Percent
      value: 100
      periodSeconds: 15
    - type: Pods
      value: 4
      periodSeconds: 15
    selectPolicy: Max

안정화 윈도우의 스케일링 다운의 경우 300 초 (또는 제공된 경우--horizontal-pod-autoscaler-downscale-stabilization 플래그의 값)이다. 스케일링 다운에서는 현재 실행 중인 레플리카의 100%를 제거할 수 있는 단일 정책만 있으며, 이는 스케일링 대상을 최소 허용 레플리카로 축소할 수 있음을 의미한다. 스케일링 업에는 안정화 윈도우가 없다. 메트릭이 대상을 스케일 업해야 한다고 표시된다면 대상이 즉시 스케일 업된다. 두 가지 폴리시가 있다. HPA가 정상 상태에 도달 할 때까지 15초 마다 4개의 파드 또는 현재 실행 중인 레플리카의 100% 가 추가된다.

예시: 다운스케일 안정화 윈도우 변경

사용자 지정 다운스케일 안정화 윈도우를 1분 동안 제공하기 위해 다음 동작이 HPA에 추가된다.

behavior:
  scaleDown:
    stabilizationWindowSeconds: 60

예시: 스케일 다운 속도 제한

HPA에 의해 파드가 제거되는 속도를 분당 10%로 제한하기 위해 다음 동작이 HPA에 추가된다.

behavior:
  scaleDown:
    policies:
    - type: Percent
      value: 10
      periodSeconds: 60

분당 제거되는 파드 수가 5를 넘지 않도록 하기 위해, 크기가 5로 고정된 두 번째 축소 정책을 추가하고, selectPolicy 를 최소로 설정하면 된다. selectPolicyMin 으로 설정하면 자동 스케일러가 가장 적은 수의 파드에 영향을 주는 정책을 선택함을 의미한다.

behavior:
  scaleDown:
    policies:
    - type: Percent
      value: 10
      periodSeconds: 60
    - type: Pods
      value: 5
      periodSeconds: 60
    selectPolicy: Min

예시: 스케일 다운 비활성화

selectPolicyDisabled 값은 주어진 방향으로의 스케일링을 끈다. 따라서 다운 스케일링을 방지하기 위해 다음 폴리시가 사용된다.

behavior:
  scaleDown:
    selectPolicy: Disabled

kubectl의 HorizontalPodAutoscaler 지원

Horizontal Pod Autoscaler는 모든 API 리소스와 마찬가지로 kubectl에 의해 표준 방식으로 지원된다. kubectl create 커맨드를 사용하여 새로운 오토스케일러를 만들 수 있다. kubectl get hpa로 오토스케일러 목록을 조회할 수 있고, kubectl describe hpa로 세부 사항을 확인할 수 있다. 마지막으로 kubectl delete hpa를 사용하여 오토스케일러를 삭제할 수 있다.

또한 Horizontal Pod Autoscaler를 생성할 수 있는 kubectl autoscale이라는 특별한 명령이 있다. 예를 들어 kubectl autoscale rs foo --min=2 --max=5 --cpu-percent=80을 실행하면 레플리카셋 foo 에 대한 오토스케일러가 생성되고, 목표 CPU 사용률은 80 %, 그리고 2와 5 사이의 레플리카 개수로 설정된다.

암시적 점검 모드(maintenance-mode) 비활성화

HPA 구성 자체를 변경할 필요없이 대상에 대한 HPA를 암시적으로 비활성화할 수 있다. 대상의 의도한 레플리카 수가 0으로 설정되고, HPA의 최소 레플리카 수가 0 보다 크면, 대상의 의도한 레플리카 수 또는 HPA의 최소 레플리카 수를 수동으로 조정하여 다시 활성화할 때까지 HPA는 대상 조정을 중지한다(그리고 ScalingActive 조건 자체를 false로 설정).

디플로이먼트와 스테이트풀셋을 horizontal autoscaling으로 전환하기

HPA가 활성화되어 있으면, 디플로이먼트, 스테이트풀셋 모두 또는 둘 중 하나의 매니페스트에서 spec.replicas의 값을 삭제하는 것이 바람직하다. 이렇게 적용하지 않으면, (예를 들어 kubectl apply -f deployment.yaml 명령으로) 오브젝트에 변경이 생길 때마다 쿠버네티스가 파드의 수를 spec.replicas에 기재된 값으로 조정할 것이다. 이는 바람직하지 않으며 HPA가 활성화된 경우에 문제가 될 수 있다.

spec.replicas 값을 제거하면 1회성으로 파드 숫자가 줄어들 수 있는데, 이는 이 키의 기본값이 1이기 때문이다(레퍼런스: 디플로이먼트 레플리카). 값을 업데이트하면, 파드 1개를 제외하고 나머지 파드가 종료 절차에 들어간다. 이후의 디플로이먼트 애플리케이션은 정상적으로 작동하며 롤링 업데이트 구성도 의도한 대로 동작한다. 이러한 1회성 저하를 방지하는 방법이 존재하며, 디플로이먼트 수정 방법에 따라 다음 중 한 가지 방법을 선택한다.

  1. kubectl apply edit-last-applied deployment/<디플로이먼트_이름>
  2. 에디터에서 spec.replicas를 삭제한다. 저장하고 에디터를 종료하면, kubectl이 업데이트 사항을 적용한다. 이 단계에서 파드 숫자가 변경되지는 않는다.
  3. 이제 매니페스트에서 spec.replicas를 삭제할 수 있다. 소스 코드 관리 도구를 사용하고 있다면, 변경 사항을 추적할 수 있도록 변경 사항을 커밋하고 추가 필요 단계를 수행한다.
  4. 이제 kubectl apply -f deployment.yaml를 실행할 수 있다.

서버 쪽에 적용하기를 수행하려면, 정확히 이러한 사용 사례를 다루고 있는 소유권 이전하기 가이드라인을 참조한다.

다음 내용

클러스터에 오토스케일링을 구성한다면, Cluster Autoscaler와 같은 클러스터 수준의 오토스케일러 사용을 고려해 볼 수 있다.

HorizontalPodAutoscaler에 대한 더 많은 정보는 아래를 참고한다.

8 - HorizontalPodAutoscaler 연습

HorizontalPodAutoscaler(약어: HPA)는 워크로드 리소스(예: 디플로이먼트 또는 스테이트풀셋)를 자동으로 업데이트하며, 워크로드의 크기를 수요에 맞게 자동으로 스케일링하는 것을 목표로 한다.

수평 스케일링은 부하 증가에 대해 파드를 더 배치하는 것을 뜻한다. 이는 수직 스케일링(쿠버네티스에서는, 해당 워크로드를 위해 이미 실행 중인 파드에 더 많은 자원(예: 메모리 또는 CPU)를 할당하는 것)과는 다르다.

부하량이 줄어들고, 파드의 수가 최소 설정값 이상인 경우, HorizontalPodAutoscaler는 워크로드 리소스(디플로이먼트, 스테이트풀셋, 또는 다른 비슷한 리소스)에게 스케일 다운을 지시한다.

이 문서는 예제 웹 앱의 크기를 자동으로 조절하도록 HorizontalPodAutoscaler를 설정하는 예시를 다룬다. 이 예시 워크로드는 PHP 코드를 실행하는 아파치 httpd이다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

쿠버네티스 서버의 버전은 다음과 같거나 더 높아야 함. 버전: 1.23. 버전 확인을 위해서, 다음 커맨드를 실행 kubectl version. 이전 버전의 쿠버네티스를 사용하고 있다면, 해당 버전의 문서를 참고한다(사용 가능한 문서의 버전 참고).

이 예제를 실행하기 위해, 클러스터에 Metrics Server가 배포 및 구성되어 있어야 한다. 쿠버네티스 Metrics Server는 클러스터의 kubelet으로부터 리소스 메트릭을 수집하고, 수집한 메트릭을 쿠버네티스 API를 통해 노출시키며, 메트릭 수치를 나타내는 새로운 종류의 리소스를 추가하기 위해 APIService를 사용할 수 있다.

Metrics Server를 실행하는 방법을 보려면 metrics-server 문서를 참고한다.

php-apache 서버 구동 및 노출

HorizontalPodAutoscaler 시연을 위해, hpa-example 이미지를 사용하여 컨테이너를 실행하는 디플로이먼트를 시작하고, 다음의 매니페스트를 사용하여 디플로이먼트를 서비스로 노출한다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  replicas: 1
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: registry.k8s.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache

이를 위해, 다음의 명령어를 실행한다.

kubectl apply -f https://k8s.io/examples/application/php-apache.yaml
deployment.apps/php-apache created
service/php-apache created

HorizontalPodAutoscaler 생성

이제 서비스가 동작중이므로, kubectl을 사용하여 오토스케일러를 생성한다. 이를 위해 kubectl autoscale 서브커맨드를 사용할 수 있다.

아래에서는 첫 번째 단계에서 만든 php-apache 디플로이먼트 파드의 개수를 1부터 10 사이로 유지하는 Horizontal Pod Autoscaler를 생성하는 명령어를 실행할 것이다.

간단히 이야기하면, HPA 컨트롤러는 평균 CPU 사용량을 50%로 유지하기 위해 (디플로이먼트를 업데이트하여) 레플리카의 개수를 늘리고 줄인다. 그러면 디플로이먼트는 레플리카셋을 업데이트하며(이는 모든 쿠버네티스 디플로이먼트의 동작 방식 중 일부이다), 레플리카셋은 자신의 .spec 필드의 변경 사항에 따라 파드를 추가하거나 제거한다.

kubectl run으로 각 파드는 200 밀리코어를 요청하므로, 평균 CPU 사용은 100 밀리코어이다. 알고리즘에 대한 세부 사항은 알고리즘 세부 정보를 참고한다.

HorizontalPodAutoscaler를 생성한다.

kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
horizontalpodautoscaler.autoscaling/php-apache autoscaled

다음을 실행하여, 새로 만들어진 HorizontalPodAutoscaler의 현재 상태를 확인할 수 있다.

# "hpa" 또는 "horizontalpodautoscaler" 둘 다 사용 가능하다.
kubectl get hpa

출력은 다음과 같다.

NAME         REFERENCE                     TARGET    MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   0% / 50%  1         10        1          18s

(HorizontalPodAutoscalers 이름이 다르다면, 이미 기존에 존재하고 있었다는 뜻이며, 보통은 문제가 되지 않는다.)

아직 서버로 요청을 보내는 클라이언트가 없기 때문에, 현재 CPU 사용량이 0%임을 확인할 수 있다. (TARGET 열은 디플로이먼트에 의해 제어되는 파드들의 평균을 나타낸다)

부하 증가시키기

다음으로, 부하가 증가함에 따라 오토스케일러가 어떻게 반응하는지를 살펴볼 것이다. 이를 위해, 클라이언트 역할을 하는 다른 파드를 실행할 것이다. 클라이언트 파드 안의 컨테이너가 php-apache 서비스에 쿼리를 보내는 무한 루프를 실행한다.

# 부하 생성을 유지하면서 나머지 스텝을 수행할 수 있도록,
# 다음의 명령을 별도의 터미널에서 실행한다.
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"

이제 아래 명령을 실행한다.

# 준비가 되면, 관찰을 마치기 위해 Ctrl+C를 누른다.
kubectl get hpa

1분 쯤 지나면, 다음과 같이 CPU 부하가 올라간 것을 볼 수 있다.

NAME         REFERENCE                     TARGET      MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   305% / 50%  1         10        1          3m

그리고 다음과 같이 레플리카의 수가 증가한 것도 볼 수 있다.

NAME         REFERENCE                     TARGET      MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   305% / 50%  1         10        7          3m

CPU 사용률이 305%까지 증가하였다. 결과적으로, 디플로이먼트의 레플리카 개수가 7개까지 증가하였다.

kubectl get deployment php-apache

HorizontalPodAutoscaler를 조회했을 때와 동일한 레플리카 수를 확인할 수 있다.

NAME         READY   UP-TO-DATE   AVAILABLE   AGE
php-apache   7/7      7           7           19m

부하 발생 중지하기

본 예제를 마무리하기 위해 부하를 중단시킨다.

busybox 파드를 띄운 터미널에서, <Ctrl> + C로 부하 발생을 중단시킨다.

그런 다음 (몇 분 후에) 결과를 확인한다.

# 준비가 되면, 관찰을 마치기 위해 Ctrl+C를 누른다.
kubectl get hpa php-apache --watch

출력은 다음과 같다.

NAME         REFERENCE                     TARGET       MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   0% / 50%     1         10        1          11m

디플로이먼트도 스케일 다운 했음을 볼 수 있다.

kubectl get deployment php-apache
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
php-apache   1/1     1            1           27m

CPU 사용량이 0으로 떨어져서, HPA가 자동으로 레플리카의 개수를 1로 줄였다.

다양한 메트릭 및 사용자 정의 메트릭을 기초로한 오토스케일링

php-apache 디플로이먼트를 오토스케일링할 때, autoscaling/v2 API 버전을 사용하여 추가적인 메트릭을 제공할 수 있다.

첫 번째로, autoscaling/v2 형식으로 HorizontalPodAutoscaler YAML 파일을 생성한다.

kubectl get hpa php-apache -o yaml > /tmp/hpa-v2.yaml

에디터로 /tmp/hpa-v2.yaml 파일을 열면, 다음과 같은 YAML을 확인할 수 있다.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
status:
  observedGeneration: 1
  lastScaleTime: <some-time>
  currentReplicas: 1
  desiredReplicas: 1
  currentMetrics:
  - type: Resource
    resource:
      name: cpu
      current:
        averageUtilization: 0
        averageValue: 0

targetCPUUtilizationPercentage 필드가 metrics 배열로 대체되었다. CPU 사용량 메트릭은 resource metric 으로 파드 컨테이너 자원의 백분율로 표현된다. CPU 외에 다른 메트릭을 지정할 수 있는데, 기본적으로 지원되는 다른 메트릭은 메모리뿐이다. 이 자원들은 한 클러스터에서 다른 클러스터로 이름을 변경할 수 없으며, metrics.k8s.io API가 가용한 경우 언제든지 사용할 수 있어야 한다.

또한, Utilization 대신 AverageValuetarget 타입을, 그리고 target.averageUtilization 대신 target.averageValue로 설정하여 자원 메트릭을 퍼센트 대신 값으로 명시할 수 있다.

파드 메트릭과 오브젝트 메트릭 두 가지의 사용자 정의 메트릭 이 있다. 파드 메트릭과 오브젝트 메트릭. 이 메트릭은 클러스터에 특화된 이름을 가지고 있으며, 더 고급화된 클러스터 모니터링 설정이 필요하다.

이러한 대체 메트릭 타입중 첫 번째는 파드 메트릭 이다. 이 메트릭은 파드들을 설명하고, 파드들 간의 평균을 내며, 대상 값과 비교하여 레플리카 개수를 결정한다. 이것들은 AverageValuetarget만을 지원한다는 것을 제외하면, 자원 메트릭과 매우 유사하게 동작한다.

파드 메트릭은 이처럼 메트릭 블록을 사용하여 정의된다.

type: Pods
pods:
  metric:
    name: packets-per-second
  target:
    type: AverageValue
    averageValue: 1k

두 번째 대체 메트릭 타입은 오브젝트 메트릭 이다. 이 메트릭은 파드를 기술하는 대신에 동일한 네임스페이스 내에 다른 오브젝트를 표현한다. 이 메트릭은 반드시 오브젝트로부터 가져올 필요는 없다. 단지 오브젝트를 기술할 뿐이다. 오브젝트 메트릭은 ValueAverageValuetarget 타입을 지원한다. Value를 사용할 경우 대상은 API로부터 반환되는 메트릭과 직접 비교된다. AverageValue를 사용할 경우, 대상 값과 비교되기 이전에 사용자 정의 메트릭 API로부터 반환된 값은 파드의 개수로 나눠진다. 다음은 requests-per-second 메트릭을 YAML로 기술한 예제이다.

type: Object
object:
  metric:
    name: requests-per-second
  describedObject:
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    name: main-route
  target:
    type: Value
    value: 2k

이러한 메트릭 블록을 여러 개 제공하면, HorizontalPodAutoscaler는 각 메트릭을 차례로 고려한다. HorizontalPodAutoscaler는 각 메트릭에 대해 제안된 레플리카 개수를 계산하고, 그중 가장 높은 레플리카 개수를 선정한다.

예를 들어, 네트워크 트래픽 메트릭을 수집하는 모니터링 시스템이 있는 경우, kubectl edit 명령어를 이용하여 다음과 같이 정의를 업데이트 할 수 있다.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
  - type: Pods
    pods:
      metric:
        name: packets-per-second
      target:
        type: AverageValue
        averageValue: 1k
  - type: Object
    object:
      metric:
        name: requests-per-second
      describedObject:
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        name: main-route
      target:
        type: Value
        value: 10k
status:
  observedGeneration: 1
  lastScaleTime: <some-time>
  currentReplicas: 1
  desiredReplicas: 1
  currentMetrics:
  - type: Resource
    resource:
      name: cpu
    current:
      averageUtilization: 0
      averageValue: 0
  - type: Object
    object:
      metric:
        name: requests-per-second
      describedObject:
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        name: main-route
      current:
        value: 10k

이후, HorizontalPodAutoscaler는 각 파드가 요청 된 약 50%의 CPU 사용률을 소모하는지, 초당 1000 패킷을 처리하는지, 메인-루트 인그레스 뒤의 모든 파드들이 초당 10000 요청을 처리하는지 확인한다.

보다 구체적인 메트릭을 기초로한 오토스케일링

많은 메트릭 파이프라인들을 사용하면 이름 또는 labels 이라 불리는 추가적인 식별자로 메트릭을 설명할 수 있다. 그리고, 모든 비 자원 메트릭 타입(파드, 오브젝트 그리고 아래 기술된 외부 타입)에 대해, 메트릭 파이프라인으로 전달되는 추가 레이블 셀렉터를 지정할 수 있다. 예를 들면, verb 레이블로 http_requests 메트릭을 수집하는 경우, 다음과 같이 메트릭 블록을 지정하여 GET 요청에 대해 크기를 조정할 수 있다.

type: Object
object:
  metric:
    name: http_requests
    selector: {matchLabels: {verb: GET}}

이 셀렉터는 쿠버네티스의 레이블 셀렉터와 동일한 문법이다. 모니터링 파이프라인은 네임과 셀렉터가 여러 시리즈와 일치하는 경우, 해당 여러 시리즈를 단일 값으로 축소하는 방법을 결정한다. 셀렉터는 부가적인 속성이며, 대상 오브젝트(Pods 타입의 대상 파드, Object 타입으로 기술된 오브젝트)가 아닌 메트릭을 선택할 수 없다.

쿠버네티스 오브젝트와 관련이 없는 메트릭을 기초로한 오토스케일링

쿠버네티스 위에서 동작하는 애플리케이션은, 쿠버네티스 클러스터의 어떤 오브젝트와도 관련이 없는 메트릭에 기반하여 오토스케일링을 할 수도 있다. 예로, 쿠버네티스 네임스페이스와 관련이 없는 서비스를 기초로한 메트릭을 들 수 있다. 쿠버네티스 버전 1.10 포함 이후 버전에서, 외부 메트릭 을 사용하여 이러한 유스케이스를 해결할 수 있다.

외부 메트릭 사용시, 먼저 모니터링 시스템에 대한 이해가 있어야 한다. 이 설치는 사용자 정의 메트릭과 유사하다. 외부 메트릭을 사용하면 모니터링 시스템의 사용 가능한 메트릭에 기반하여 클러스터를 오토스케일링 할 수 있다. 위의 예제처럼 nameselector를 갖는 metric 블록을 명시하고, Object 대신에 External 메트릭 타입을 사용한다. 만일 여러 개의 시계열이 metricSelector와 일치하면, HorizontalPodAutoscaler가 값의 합을 사용한다. 외부 메트릭들은 ValueAverageValue 대상 타입을 모두 지원하고, Object 타입을 사용할 때와 똑같이 동작한다.

예를 들면 애플리케이션이 호스팅 된 대기열 서비스에서 작업을 처리하는 경우, 다음과 같이 HorizontalPodAutoscaler 매니퍼스트에 30개의 미해결 태스크 당 한 개의 워커를 지정하도록 추가할 수 있다.

- type: External
  external:
    metric:
      name: queue_messages_ready
      selector:
        matchLabels:
          queue: "worker_tasks"
    target:
      type: AverageValue
      averageValue: 30

가능하다면, 외부 메트릭 대신 사용자 정의 메트릭 대상 타입을 사용하길 권장한다. 왜냐하면, 클러스터 관리자가 사용자 정의 메트릭 API를 보안관점에서 더 쉽게 보호할 수 있기 때문이다. 외부 메트릭 API는 잠재적으로 어떠한 메트릭에도 접근할 수 있기에, 클러스터 관리자는 API를 노출시킬때 신중해야 한다.

부록: Horizontal Pod Autoscaler 상태 조건

HorizontalPodAutoscaler의 autoscaling/v2 형식을 사용하면, HorizontalPodAutoscaler에서 쿠버네티스가 설정한 상태 조건 을 확인할 수 있다. 이 상태 조건들은 HorizontalPodAutoscaler가 스케일을 할 수 있는지, 어떤 방식으로든 제한되어 있는지 여부를 나타낸다.

이 조건은 status.conditions에 나타난다. HorizontalPodAutoscaler에 영향을 주는 조건을 보기 위해 kubectl describe hpa를 사용할 수 있다.

kubectl describe hpa cm-test
Name:                           cm-test
Namespace:                      prom
Labels:                         <none>
Annotations:                    <none>
CreationTimestamp:              Fri, 16 Jun 2017 18:09:22 +0000
Reference:                      ReplicationController/cm-test
Metrics:                        ( current / target )
  "http_requests" on pods:      66m / 500m
Min replicas:                   1
Max replicas:                   4
ReplicationController pods:     1 current / 1 desired
Conditions:
  Type                  Status  Reason                  Message
  ----                  ------  ------                  -------
  AbleToScale           True    ReadyForNewScale        the last scale time was sufficiently old as to warrant a new scale
  ScalingActive         True    ValidMetricFound        the HPA was able to successfully calculate a replica count from pods metric http_requests
  ScalingLimited        False   DesiredWithinRange      the desired replica count is within the acceptable range
Events:

이 HorizontalPodAutoscaler 경우, 건강 상태의 여러 조건들을 볼 수 있다. 첫 번째 AbleToScale는 HPA가 스케일을 가져오고 업데이트할 수 있는지, 백 오프 관련 조건으로 스케일링이 방지되는지 여부를 나타낸다. 두 번째 ScalingActive는 HPA가 활성화되어 있는지(즉 대상 레플리카 개수가 0이 아닌지), 원하는 스케일을 계산할 수 있는지 여부를 나타낸다. 만약 False 인 경우, 일반적으로 메트릭을 가져오는 데 문제가 있다. 마지막으로, 마지막 조건인 ScalingLimited는 원하는 스케일 한도가 HorizontalPodAutoscaler의 최대/최소값으로 제한돼있음을 나타낸다. 이는 HorizontalPodAutoscaler에서 레플리카의 개수 제한을 최대/최소값으로 올리거나 낮추려는 것이다.

수량

HorizontalPodAutoscaler와 메트릭 API에서 모든 메트릭은 쿠버네티스에서 사용하는 수량 숫자 표기법을 사용한다. 예를 들면, 10500m 수량은 10진법 10.5으로 쓰인다. 메트릭 API들은 가능한 경우 접미사 없이 정수를 반환하며, 일반적으로 수량을 밀리단위로 반환한다. 10진수로 표현했을때, 11500m 또는 11.5 로 메트릭 값을 나타낼 수 있다.

다른 가능한 시나리오

명시적으로 오토스케일러 만들기

HorizontalPodAutoscaler를 생성하기 위해 kubectl autoscale 명령어를 사용하지 않고, 명시적으로 다음 매니페스트를 사용하여 만들 수 있다.

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

다음으로, 아래의 명령어를 실행하여 오토스케일러를 생성한다.

kubectl create -f https://k8s.io/examples/application/hpa/php-apache.yaml
horizontalpodautoscaler.autoscaling/php-apache created

9 - 파드 내에서 쿠버네티스 API에 접근

이 페이지는 파드 내에서 쿠버네티스 API에 접근하는 방법을 보여준다.

시작하기 전에

쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

파드 내에서 API에 접근

파드 내에서 API에 접근할 때, API 서버를 찾아 인증하는 것은 위에서 설명한 외부 클라이언트 사례와 약간 다르다.

파드에서 쿠버네티스 API를 사용하는 가장 쉬운 방법은 공식 클라이언트 라이브러리 중 하나를 사용하는 것이다. 이러한 라이브러리는 API 서버를 자동으로 감지하고 인증할 수 있다.

공식 클라이언트 라이브러리 사용

파드 내에서, 쿠버네티스 API에 연결하는 권장 방법은 다음과 같다.

각각의 경우, 파드의 서비스 어카운트 자격 증명은 API 서버와 안전하게 통신하는 데 사용된다.

REST API에 직접 접근

파드에서 실행되는 동안, 쿠버네티스 apiserver는 default 네임스페이스에서 kubernetes라는 서비스를 통해 접근할 수 있다. 따라서, 파드는 kubernetes.default.svc 호스트 이름을 사용하여 API 서버를 쿼리할 수 있다. 공식 클라이언트 라이브러리는 이를 자동으로 수행한다.

API 서버를 인증하는 권장 방법은 서비스 어카운트 자격 증명을 사용하는 것이다. 기본적으로, 파드는 서비스 어카운트와 연결되어 있으며, 해당 서비스 어카운트에 대한 자격 증명(토큰)은 해당 파드에 있는 각 컨테이너의 파일시스템 트리의 /var/run/secrets/kubernetes.io/serviceaccount/token 에 있다.

사용 가능한 경우, 인증서 번들은 각 컨테이너의 파일시스템 트리의 /var/run/secrets/kubernetes.io/serviceaccount/ca.crt 에 배치되며, API 서버의 제공 인증서를 확인하는 데 사용해야 한다.

마지막으로, 네임스페이스가 지정된 API 작업에 사용되는 기본 네임스페이스는 각 컨테이너의 /var/run/secrets/kubernetes.io/serviceaccount/namespace 에 있는 파일에 배치된다.

kubectl 프록시 사용

공식 클라이언트 라이브러리 없이 API를 쿼리하려면, 파드에서 새 사이드카 컨테이너의 명령으로 kubectl proxy 를 실행할 수 있다. 이런 식으로, kubectl proxy 는 API를 인증하고 이를 파드의 localhost 인터페이스에 노출시켜서, 파드의 다른 컨테이너가 직접 사용할 수 있도록 한다.

프록시를 사용하지 않고 접근

인증 토큰을 API 서버에 직접 전달하여 kubectl 프록시 사용을 피할 수 있다. 내부 인증서는 연결을 보호한다.

# 내부 API 서버 호스트 이름을 가리킨다
APISERVER=https://kubernetes.default.svc

# 서비스어카운트(ServiceAccount) 토큰 경로
SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount

# 이 파드의 네임스페이스를 읽는다
NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)

# 서비스어카운트 베어러 토큰을 읽는다
TOKEN=$(cat ${SERVICEACCOUNT}/token)

# 내부 인증 기관(CA)을 참조한다
CACERT=${SERVICEACCOUNT}/ca.crt

# TOKEN으로 API를 탐색한다
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api

출력은 다음과 비슷하다.

{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "10.0.1.149:443"
    }
  ]
}