1 - Deploymentを使用してステートレスアプリケーションを実行する

このページでは、Kubernetes Deploymentオブジェクトを使用してアプリケーションを実行する方法を説明します。

目標

  • nginx deploymentを作成します。
  • kubectlを使ってdeploymentに関する情報を一覧表示します。
  • deploymentを更新します。

始める前に

Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

作業するKubernetesサーバーは次のバージョン以降のものである必要があります: v1.9. バージョンを確認するには次のコマンドを実行してください: kubectl version.

nginx deploymentの作成と探検

Kubernetes Deploymentオブジェクトを作成することでアプリケーションを実行できます。また、YAMLファイルでDeploymentを記述できます。例えば、このYAMLファイルはnginx:1.14.2 Dockerイメージを実行するデプロイメントを記述しています:

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
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ファイルに基づいてDeploymentを作成します:

     kubectl apply -f https://k8s.io/examples/application/deployment.yaml
    
  2. Deploymentに関する情報を表示します:

     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. Deploymentによって作成されたPodを一覧表示します:

     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. Podに関する情報を表示します:

     kubectl describe pod <pod-name>
    

    ここで<pod-name>はPodの1つの名前を指定します。

Deploymentの更新

新しいYAMLファイルを適用してDeploymentを更新できます。このYAMLファイルは、Deploymentを更新してnginx 1.16.1を使用するように指定しています。

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
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. Deploymentが新しい名前でPodを作成し、古いPodを削除するのを監視します:

      kubectl get pods -l app=nginx
    

レプリカ数を増やすことによるアプリケーションのスケール

新しいYAMLファイルを適用することで、Deployment内のPodの数を増やすことができます。このYAMLファイルはreplicasを4に設定します。これはDeploymentが4つのPodを持つべきであることを指定します:

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
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. Deploymentに4つのPodがあることを確認します:

     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
    

Deploymentの削除

Deploymentを名前を指定して削除します:

kubectl delete deployment nginx-deployment

ReplicationControllers -- 昔のやり方

複製アプリケーションを作成するための好ましい方法はDeploymentを使用することです。そして、DeploymentはReplicaSetを使用します。 DeploymentとReplicaSetがKubernetesに追加される前は、ReplicationControllerを使用して複製アプリケーションを構成していました。

次の項目

2 - 単一レプリカのステートフルアプリケーションを実行する

このページでは、PersistentVolumeとDeploymentを使用して、Kubernetesで単一レプリカのステートフルアプリケーションを実行する方法を説明します。アプリケーションはMySQLです。

目標

  • 自身の環境のディスクを参照するPersistentVolumeを作成します。
  • MySQLのDeploymentを作成します。
  • MySQLをDNS名でクラスター内の他のPodに公開します。

始める前に

  • Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

    バージョンを確認するには次のコマンドを実行してください: kubectl version.

  • ここで使用されているPersistentVolumeClaimsの要件を満たすには、デフォルトのStorageClassを使用して動的PersistentVolumeプロビジョナーを作成するか、PersistentVolumesを静的にプロビジョニングする必要があります。

MySQLをデプロイする

Kubernetes Deploymentを作成し、PersistentVolumeClaimを使用して既存のPersistentVolumeに接続することで、ステートフルアプリケーションを実行できます。 たとえば、以下のYAMLファイルはMySQLを実行し、PersistentVolumeClaimを参照するDeploymentを記述しています。 このファイルは/var/lib/mysqlのボリュームマウントを定義してから、20Gのボリュームを要求するPersistentVolumeClaimを作成します。 この要求は、要件を満たす既存のボリューム、または動的プロビジョナーによって満たされます。

注:パスワードはYAMLファイル内に定義されており、これは安全ではありません。安全な解決策についてはKubernetes Secretを参照してください 。

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
  clusterIP: None
---
apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
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
kind: PersistentVolume
apiVersion: v1
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 create -f https://k8s.io/examples/application/mysql/mysql-pv.yaml
    
  2. YAMLファイルの内容をデプロイします。

     kubectl create -f https://k8s.io/examples/application/mysql/mysql-deployment.yaml
    
  3. 作成したDeploymentの情報を表示します。

     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. Deploymentによって作成されたPodを一覧表示します。

     kubectl get pods -l app=mysql
    
     NAME                   READY     STATUS    RESTARTS   AGE
     mysql-63082529-2z3ki   1/1       Running   0          3m
    
  5. PersistentVolumeClaimを確認します。

     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ファイルは、クラスター内の他のPodがデータベースにアクセスできるようにするServiceを作成します。 ServiceのオプションでclusterIP: Noneを指定すると、ServiceのDNS名がPodのIPアドレスに直接解決されます。 このオプションは、ServiceのバックエンドのPodが1つのみであり、Podの数を増やす予定がない場合に適しています。

MySQLクライアントを実行してサーバーに接続します。

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

このコマンドは、クラスター内にMySQLクライアントを実行する新しいPodを作成し、Serviceを通じて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>

アップデート

イメージまたはDeploymentの他の部分は、kubectl applyコマンドを使用して通常どおりに更新できます。 ステートフルアプリケーションに固有のいくつかの注意事項を以下に記載します。

  • アプリケーションをスケールしないでください。このセットアップは単一レプリカのアプリケーション専用です。 下層にあるPersistentVolumeは1つのPodにしかマウントできません。 クラスター化されたステートフルアプリケーションについては、StatefulSetのドキュメントを参照してください。
  • Deploymentを定義するYAMLファイルではstrategy: type: Recreateを使用して下さい。 この設定はKubernetesにローリングアップデートを使用 しない ように指示します。 同時に複数のPodを実行することはできないため、ローリングアップデートは使用できません。 Recreate戦略は、更新された設定で新しいPodを作成する前に、最初のPodを停止します。

Deploymentの削除

名前を指定してデプロイしたオブジェクトを削除します。

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

PersistentVolumeを手動でプロビジョニングした場合は、PersistentVolumeを手動で削除し、また、下層にあるリソースも解放する必要があります。 動的プロビジョニング機能を使用した場合は、PersistentVolumeClaimを削除すれば、自動的にPersistentVolumeも削除されます。 一部の動的プロビジョナー(EBSやPDなど)は、PersistentVolumeを削除すると同時に下層にあるリソースも解放します。

次の項目

3 - レプリカを持つステートフルアプリケーションを実行する

このページでは、StatefulSet コントローラーを使用して、レプリカを持つステートフルアプリケーションを実行する方法を説明します。 ここでの例は、非同期レプリケーションを行う複数のスレーブを持つ、単一マスターのMySQLです。

この例は本番環境向けの構成ではないことに注意してください。 具体的には、MySQLの設定が安全ではないデフォルトのままとなっています。 これはKubernetesでステートフルアプリケーションを実行するための一般的なパターンに焦点を当てるためです。

始める前に

  • Kubernetesクラスターが必要、かつそのクラスターと通信するためにkubectlコマンドラインツールが設定されている必要があります。 このチュートリアルは、コントロールプレーンのホストとして動作していない少なくとも2つのノードを持つクラスターで実行することをおすすめします。 まだクラスターがない場合、minikubeを使って作成するか、 以下のいずれかのKubernetesプレイグラウンドも使用できます:

    バージョンを確認するには次のコマンドを実行してください: kubectl version.
  • ここで使用されているPersistentVolumeClaimsの要件を満たすには、デフォルトのStorageClassを使用して動的PersistentVolumeプロビジョナーを作成するか、PersistentVolumesを静的にプロビジョニングする必要があります。

  • このチュートリアルは、あなたがPersistentVolumeStatefulSet、 さらにはPodServiceConfigMapなどの 他のコアな概念に精通していることを前提としています。
  • MySQLに関する知識は記事の理解に役立ちますが、 このチュートリアルは他のシステムにも役立つ一般的なパターンを提示することを目的としています。

目標

  • StatefulSetコントローラーを使用して、レプリカを持つMySQLトポロジーをデプロイします。
  • MySQLクライアントトラフィックを送信します。
  • ダウンタイムに対する耐性を観察します。
  • StatefulSetをスケールアップおよびスケールダウンします。

MySQLをデプロイする

このMySQLのデプロイの例は、1つのConfigMap、2つのService、および1つのStatefulSetから構成されます。

ConfigMap

次のYAML設定ファイルからConfigMapを作成します。

kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
data:
  primary.cnf: |
    # Apply this config only on the primary.
    [mysqld]
    log-bin    
  replica.cnf: |
    # Apply this config only on replicas.
    [mysqld]
    super-read-only    

このConfigMapは、MySQLマスターとスレーブの設定を独立して制御するために、 それぞれのmy.cnfを上書きする内容を提供します。 この場合、マスターはスレーブにレプリケーションログを提供するようにし、 スレーブはレプリケーション以外の書き込みを拒否するようにします。

ConfigMap自体に特別なことはありませんが、ConfigMapの各部分は異なるPodに適用されます。 各Podは、StatefulSetコントローラーから提供される情報に基づいて、 初期化時にConfigMapのどの部分を見るかを決定します。

Services

以下のYAML設定ファイルからServiceを作成します。

kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the master: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql

ヘッドレスサービスは、StatefulSetコントローラーが StatefulSetの一部であるPodごとに作成するDNSエントリーのベースエントリーを提供します。 この例ではヘッドレスサービスの名前はmysqlなので、同じKubernetesクラスターの 同じ名前空間内の他のPodは、<pod-name>.mysqlを名前解決することでPodにアクセスできます。

mysql-readと呼ばれるクライアントサービスは、独自のクラスターIPを持つ通常のServiceであり、 Ready状態のすべてのMySQL Podに接続を分散します。 Serviceのエンドポイントには、MySQLマスターとすべてのスレーブが含まれる可能性があります。

読み込みクエリーのみが、負荷分散されるクライアントサービスを使用できることに注意してください。 MySQLマスターは1つしかいないため、クライアントが書き込みを実行するためには、 (ヘッドレスサービス内のDNSエントリーを介して)MySQLのマスターPodに直接接続する必要があります。

StatefulSet

最後に、次のYAML設定ファイルからStatefulSetを作成します。

kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Generate mysql server-id from pod ordinal index.
          [[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          # Add an offset to avoid reserved server-id=0 value.
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # Copy appropriate conf.d files from config-map to emptyDir.
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/master.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/slave.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
          # Skip the clone if data already exists.
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Skip the clone on master (ordinal index 0).
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          # Clone data from previous peer.
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # Prepare the backup.
          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:
            # Check we can execute queries over TCP (skip-networking is 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

          # Determine binlog position of cloned data, if any.
          if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
            # XtraBackup already generated a partial "CHANGE MASTER TO" query
            # because we're cloning from an existing slave. (Need to remove the tailing semicolon!)
            cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f xtrabackup_slave_info xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # We're cloning directly from master. Parse binlog position.
            [[ `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

          # Check if we need to complete a clone by starting 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
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
          fi

          # Start a server to send backups when requested by peers.
          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 get pods -l app=mysql --watch

しばらくすると、3つのPodすべてが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を押してウォッチをキャンセルします。 起動が進行しない場合は、始める前にで説明されているように、 PersistentVolumeの動的プロビジョニング機能が有効になっていることを確認してください。

このマニフェストでは、StatefulSetの一部としてステートフルなPodを管理するためにさまざまな手法を使用しています。 次のセクションでは、これらの手法のいくつかに焦点を当て、StatefulSetがPodを作成するときに何が起こるかを説明します。

ステートフルなPodの初期化を理解する

StatefulSetコントローラーは、序数インデックスの順にPodを一度に1つずつ起動します。 各PodがReady状態を報告するまで待機してから、その次のPodの起動が開始されます。

さらに、コントローラーは各Podに <statefulset-name>-<ordinal-index>という形式の一意で不変の名前を割り当てます。 この例の場合、Podの名前はmysql-0mysql-1、そしてmysql-2となります。

上記のStatefulSetマニフェスト内のPodテンプレートは、これらのプロパティーを利用して、 MySQLレプリケーションの起動を順序正しく実行します。

構成を生成する

Podスペック内のコンテナを起動する前に、Podは最初に 初期化コンテナを定義された順序で実行します。

最初の初期化コンテナはinit-mysqlという名前で、序数インデックスに基づいて特別なMySQL設定ファイルを生成します。

スクリプトは、hostnameコマンドによって返されるPod名の末尾から抽出することによって、自身の序数インデックスを特定します。 それから、序数を(予約された値を避けるために数値オフセット付きで)MySQLのconf.dディレクトリーのserver-id.cnfというファイルに保存します。 これは、StatefulSetコントローラーによって提供される一意で不変のIDを、同じ特性を必要とするMySQLサーバーIDの領域に変換します。

さらに、init-mysqlコンテナ内のスクリプトは、master.cnfまたはslave.cnfのいずれかを、 ConfigMapから内容をconf.dにコピーすることによって適用します。 このトポロジー例は単一のMySQLマスターと任意の数のスレーブで構成されているため、 スクリプトは単に序数の0がマスターになるように、それ以外のすべてがスレーブになるように割り当てます。 StatefulSetコントローラーによる デプロイ順序の保証と組み合わせると、 スレーブが作成される前にMySQLマスターがReady状態になるため、スレーブはレプリケーションを開始できます。

既存データをクローンする

一般に、新しいPodがセットにスレーブとして参加するときは、 MySQLマスターにはすでにデータがあるかもしれないと想定する必要があります。 また、レプリケーションログが期間の先頭まで全て揃っていない場合も想定する必要があります。 これらの控えめな仮定は、実行中のStatefulSetのサイズを初期サイズに固定するのではなく、 時間の経過とともにスケールアップまたはスケールダウンできるようにするために重要です。

2番目の初期化コンテナはclone-mysqlという名前で、スレーブPodが空のPersistentVolumeで最初に起動したときに、 クローン操作を実行します。 つまり、実行中の別のPodから既存のデータをすべてコピーするので、 そのローカル状態はマスターからレプリケーションを開始するのに十分な一貫性があります。

MySQL自体はこれを行うためのメカニズムを提供していないため、この例ではPercona XtraBackupという人気のあるオープンソースツールを使用しています。 クローンの実行中は、ソースとなるMySQLサーバーのパフォーマンスが低下する可能性があります。 MySQLマスターへの影響を最小限に抑えるために、スクリプトは各Podに序数インデックスが自分より1低いPodからクローンするように指示します。 StatefulSetコントローラーは、N+1のPodを開始する前には必ずNのPodがReady状態であることを保証するので、この方法が機能します。

レプリケーションを開始する

初期化コンテナが正常に完了すると、通常のコンテナが実行されます。 MySQLのPodは実際にmysqldサーバーを実行するmysqlコンテナと、 サイドカー として機能するxtrabackupコンテナから成ります。

xtrabackupサイドカーはクローンされたデータファイルを見て、 スレーブ上でMySQLレプリケーションを初期化する必要があるかどうかを決定します。 もし必要がある場合、mysqldが準備できるのを待ってから、 XtraBackupクローンファイルから抽出されたレプリケーションパラメーターでCHANGE MASTER TOSTART SLAVEコマンドを実行します。

スレーブがレプリケーションを開始すると、スレーブはMySQLマスターを記憶し、 サーバーが再起動した場合または接続が停止した場合に、自動的に再接続します。 また、スレーブはその不変のDNS名(mysql-0.mysql)でマスターを探すため、 再スケジュールされたために新しいPod IPを取得したとしても、自動的にマスターを見つけます。

最後に、レプリケーションを開始した後、xtrabackupコンテナはデータのクローンを要求する他のPodからの接続を待ち受けます。 StatefulSetがスケールアップした場合や、次のPodがPersistentVolumeClaimを失ってクローンをやり直す必要がある場合に備えて、 このサーバーは無期限に起動したままになります。

クライアントトラフィックを送信する

テストクエリーをMySQLマスター(ホスト名 mysql-0.mysql)に送信するには、 mysql:5.7イメージを使って一時的なコンテナを実行し、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

Ready状態を報告したいずれかのサーバーにテストクエリーを送信するには、ホスト名mysql-readを使用します。

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を押すことができますが、別のウィンドウで実行したままにしておくことで、 次の手順の効果を確認できます。

PodとNodeのダウンタイムをシミュレーションする

単一のサーバーではなくスレーブのプールから読み取りを行うことによって可用性が高まっていることを実証するため、 Podを強制的にReadyではない状態にする間、上記のSELECT @@server_idループを実行したままにしてください。

Readiness Probeを壊す

mysqlコンテナに対する readiness probe は、mysql -h 127.0.0.1 -e 'SELECT 1'コマンドを実行することで、サーバーが起動していてクエリーが実行できることを確認します。

このreadiness probeを失敗させる1つの方法は、そのコマンドを壊すことです。

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

ここでは、mysql-2 Podの実際のコンテナのファイルシステムにアクセスし、 mysqlコマンドの名前を変更してreadiness probeがコマンドを見つけられないようにしています。 数秒後、Podはそのコンテナの1つがReadyではないと報告するはずです。以下を実行して確認できます。

kubectl get pod mysql-2

READY列の1/2を見てください。

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

この時点で、SELECT @@server_idループは実行され続け、しかしもう102が報告されないことが確認できるはずです。 init-mysqlスクリプトがserver-id100+$ordinalとして定義したことを思い出して下さい。 そのため、サーバーID102はPodのmysql-2に対応します。

それではPodを修復しましょう。すると数秒後にループ出力に再び現れるはずです。

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

Podを削除する

StatefulSetは、Podが削除された場合にPodを再作成します。 これはReplicaSetがステートレスなPodに対して行うのと同様です。

kubectl delete pod mysql-2

StatefulSetコントローラーはmysql-2 Podがもう存在しないことに気付き、 同じ名前で同じPersistentVolumeClaimにリンクされた新しいPodを作成します。 サーバーID102がしばらくの間ループ出力から消えて、また元に戻るのが確認できるはずです。

ノードをdrainする

Kubernetesクラスターに複数のノードがある場合は、 drainを発行して ノードのダウンタイム(例えばノードのアップグレード時など)をシミュレートできます。

まず、あるMySQL Podがどのノード上にいるかを確認します。

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-minion-group-9l2t

その後、次のコマンドを実行してノードをdrainします。 これにより、新しいPodがそのノードにスケジュールされないようにcordonされ、そして既存のPodは強制退去されます。 <node-name>は前のステップで確認したノードの名前に置き換えてください。

この操作はノード上の他のアプリケーションに影響を与える可能性があるため、 テストクラスターでのみこの操作を実行するのが最善です。

kubectl drain <node-name> --force --delete-local-data --ignore-daemonsets

Podが別のノードに再スケジュールされる様子を確認しましょう。

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-minion-group-9l2t
[...]
mysql-2   0/2     Pending         0          0s        <none>        kubernetes-minion-group-fjlm
mysql-2   0/2     Init:0/2        0          0s        <none>        kubernetes-minion-group-fjlm
mysql-2   0/2     Init:1/2        0          20s       10.244.5.32   kubernetes-minion-group-fjlm
mysql-2   0/2     PodInitializing 0          21s       10.244.5.32   kubernetes-minion-group-fjlm
mysql-2   1/2     Running         0          22s       10.244.5.32   kubernetes-minion-group-fjlm
mysql-2   2/2     Running         0          30s       10.244.5.32   kubernetes-minion-group-fjlm

また、サーバーID102SELECT @@server_idループの出力からしばらくの消えて、 そして戻ることが確認できるはずです。

それでは、ノードをuncordonして正常な状態に戻しましょう。

kubectl uncordon <node-name>

スレーブの数をスケーリングする

MySQLレプリケーションでは、スレーブを追加することで読み取りクエリーのキャパシティーをスケールできます。 StatefulSetを使用している場合、単一のコマンドでこれを実行できます。

kubectl scale statefulset mysql  --replicas=5

次のコマンドを実行して、新しいPodが起動してくるのを確認します。

kubectl get pods -l app=mysql --watch

新しいPodが起動すると、サーバーID103104SELECT @@server_idループの出力に現れます。

また、これらの新しいサーバーが、これらのサーバーが存在する前に追加したデータを持っていることを確認することもできます。

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

ただし、スケールアップすると新しいPersistentVolumeClaimが自動的に作成されますが、 スケールダウンしてもこれらのPVCは自動的には削除されないことに注意して下さい。 このため、初期化されたPVCをそのまま置いておいくことで再スケールアップを速くしたり、 PVを削除する前にデータを抽出するといった選択が可能になります。

次のコマンドを実行してこのことを確認できます。

kubectl get pvc -l app=mysql

StatefulSetを3にスケールダウンしたにもかかわらず、5つのPVCすべてがまだ存在しています。

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. StatefulSetを削除します。これによってPodの終了も開始されます。

    kubectl delete statefulset mysql
    
  3. Podが消えたことを確認します。 Podが終了処理が完了するのには少し時間がかかるかもしれません。

    kubectl get pods -l app=mysql
    

    上記のコマンドから以下の出力が戻れば、Podが終了したことがわかります。

    No resources found.
    
  4. ConfigMap、Services、およびPersistentVolumeClaimを削除します。

    kubectl delete configmap,service,pvc -l app=mysql
    
  5. PersistentVolumeを手動でプロビジョニングした場合は、それらを手動で削除し、 また、下層にあるリソースも解放する必要があります。 動的プロビジョニング機能を使用した場合は、PersistentVolumeClaimを削除すれば、自動的にPersistentVolumeも削除されます。 一部の動的プロビジョナー(EBSやPDなど)は、PersistentVolumeを削除すると同時に下層にあるリソースも解放します。

次の項目

  • その他のステートフルアプリケーションの例は、Helm Charts repositoryを見てください。

4 - StatefulSetのスケール

このタスクは、StatefulSetをスケールする方法を示します。StatefulSetをスケーリングするとは、レプリカの数を増減することです。

始める前に

  • StatefulSetはKubernetesバージョン1.5以降でのみ利用可能です。 Kubernetesのバージョンを確認するには、kubectl versionを実行してください。

  • すべてのステートフルアプリケーションがうまくスケールできるわけではありません。StatefulSetがスケールするかどうかわからない場合は、StatefulSetの概念またはStatefulSetのチュートリアルを参照してください。

  • ステートフルアプリケーションクラスターが完全に健全であると確信できる場合にのみ、スケーリングを実行してください。

StatefulSetのスケール

kubectlを使用したStatefulSetのスケール

まず、スケールしたいStatefulSetを見つけます。

kubectl get statefulsets <stateful-set-name>

StatefulSetのレプリカ数を変更します:

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

StatefulSetのインプレースアップデート

コマンドライン上でレプリカ数を変更する代わりに、StatefulSetにインプレースアップデートが可能です。

StatefulSetが最初に kubectl applyで作成されたのなら、StatefulSetマニフェストの.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>}}'

トラブルシューティング

スケールダウンがうまくいかない

管理するステートフルPodのいずれかが異常である場合、StatefulSetをスケールダウンすることはできません。それらのステートフルPodが実行され準備ができた後にのみ、スケールダウンが行われます。

spec.replicas > 1の場合、Kubernetesは不健康なPodの理由を判断できません。それは、永続的な障害または一時的な障害の結果である可能性があります。一時的な障害は、アップグレードまたはメンテナンスに必要な再起動によって発生する可能性があります。

永続的な障害が原因でPodが正常でない場合、障害を修正せずにスケーリングすると、StatefulSetメンバーシップが正しく機能するために必要な特定の最小レプリカ数を下回る状態になる可能性があります。これにより、StatefulSetが利用できなくなる可能性があります。

一時的な障害によってPodが正常でなくなり、Podが再び使用可能になる可能性がある場合は、一時的なエラーがスケールアップまたはスケールダウン操作の妨げになる可能性があります。一部の分散データベースでは、ノードが同時に参加および脱退するときに問題があります。このような場合は、アプリケーションレベルでスケーリング操作を考えることをお勧めします。また、ステートフルアプリケーションクラスターが完全に健全であることが確実な場合にのみスケーリングを実行してください。

次の項目

5 - StatefulSetの削除

このタスクでは、StatefulSetを削除する方法を説明します。

始める前に

  • このタスクは、クラスター上で、StatefulSetで表現されるアプリケーションが実行されていることを前提としています。

StatefulSetの削除

Kubernetesで他のリソースを削除するのと同じ方法でStatefulSetを削除することができます。つまり、kubectl deleteコマンドを使い、StatefulSetをファイルまたは名前で指定します。

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

StatefulSet自体が削除された後で、関連するヘッドレスサービスを個別に削除する必要があるかもしれません。

kubectl delete service <service-name>

kubectlを使ってStatefulSetを削除すると0にスケールダウンされ、すべてのPodが削除されます。PodではなくStatefulSetだけを削除したい場合は、--cascade=orphanを使用してください。

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

--cascade=orphankubectl deleteに渡すことで、StatefulSetオブジェクト自身が削除された後でも、StatefulSetによって管理されていたPodは残ります。Podにapp=myappというラベルが付いている場合は、次のようにして削除できます:

kubectl delete pods -l app=myapp

永続ボリューム

StatefulSet内のPodを削除しても、関連付けられているボリュームは削除されません。これは、削除する前にボリュームからデータをコピーする機会があることを保証するためです。Podが終了した後にPVCを削除すると、ストレージクラスと再利用ポリシーによっては、背後にある永続ボリュームの削除がトリガーされることがあります。決してクレーム削除後にボリュームにアクセスできると想定しないでください。

StatefulSetの完全削除

関連付けられたPodを含むStatefulSet内のすべてのものを単純に削除するには、次のような一連のコマンドを実行します:

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

上の例では、Podはapp=myappというラベルを持っています。必要に応じてご利用のラベルに置き換えてください。

StatefulSet Podの強制削除

StatefulSet内の一部のPodが長期間TerminatingまたはUnknown状態のままになっていることが判明した場合は、手動でapiserverからPodを強制的に削除する必要があります。これは潜在的に危険な作業です。詳細はStatefulSet Podの強制削除を参照してください。

次の項目

StatefulSet Podの強制削除の詳細

6 - StatefulSet Podの強制削除

このページでは、StatefulSetの一部であるPodを削除する方法と、削除する際に考慮すべき事項について説明します。

始める前に

  • これはかなり高度なタスクであり、StatefulSetに固有のいくつかの特性に反する可能性があります。
  • 先に進む前に、以下に列挙されている考慮事項をよく理解してください。

StatefulSetに関する考慮事項

StatefulSetの通常の操作では、StatefulSet Podを強制的に削除する必要はまったくありません。StatefulSetコントローラーは、StatefulSetのメンバーの作成、スケール、削除を行います。それは序数0からN-1までの指定された数のPodが生きていて準備ができていることを保証しようとします。StatefulSetは、クラスター内で実行されている特定のIDを持つ最大1つのPodがいつでも存在することを保証します。これは、StatefulSetによって提供される最大1つのセマンティクスと呼ばれます。

手動による強制削除は、StatefulSetに固有の最大1つのセマンティクスに違反する可能性があるため、慎重に行う必要があります。StatefulSetを使用して、安定したネットワークIDと安定した記憶域を必要とする分散型およびクラスター型アプリケーションを実行できます。これらのアプリケーションは、固定IDを持つ固定数のメンバーのアンサンブルに依存する構成を持つことがよくあります。同じIDを持つ複数のメンバーを持つことは悲惨なことになり、データの損失につながる可能性があります(例:定足数ベースのシステムでのスプリットブレインシナリオ)。

Podの削除

次のコマンドで正常なPod削除を実行できます:

kubectl delete pods <pod>

上記がグレースフルターミネーションにつながるためには、pod.Spec.TerminationGracePeriodSecondsに0を指定してはいけませんpod.Spec.TerminationGracePeriodSecondsを0秒に設定することは安全ではなく、StatefulSet Podには強くお勧めできません。グレースフル削除は安全で、kubeletがapiserverから名前を削除する前にPodが適切にシャットダウンすることを保証します。

Kubernetes(バージョン1.5以降)は、Nodeにアクセスできないという理由だけでPodを削除しません。到達不能なNodeで実行されているPodは、タイムアウトの後にTerminatingまたはUnknown状態になります。到達不能なNode上のPodをユーザーが適切に削除しようとすると、Podはこれらの状態に入ることもあります。そのような状態のPodをapiserverから削除することができる唯一の方法は以下の通りです:

  • (ユーザーまたはNode Controllerによって)Nodeオブジェクトが削除されます。
  • 応答していないNodeのkubeletが応答を開始し、Podを終了してapiserverからエントリーを削除します。
  • ユーザーによりPodを強制削除します。

推奨されるベストプラクティスは、1番目または2番目のアプローチを使用することです。Nodeが死んでいることが確認された(例えば、ネットワークから恒久的に切断された、電源が切られたなど)場合、Nodeオブジェクトを削除します。Nodeがネットワークパーティションに苦しんでいる場合は、これを解決するか、解決するのを待ちます。パーティションが回復すると、kubeletはPodの削除を完了し、apiserverでその名前を解放します。

通常、PodがNode上で実行されなくなるか、管理者によってそのNodeが削除されると、システムは削除を完了します。あなたはPodを強制的に削除することでこれを無効にすることができます。

強制削除

強制削除はPodが終了したことをkubeletから確認するまで待ちません。強制削除がPodの削除に成功したかどうかに関係なく、apiserverから名前をすぐに解放します。これにより、StatefulSetコントローラーは、同じIDを持つ交換Podを作成できます。これは、まだ実行中のPodの複製につながる可能性があり、そのPodがまだStatefulSetの他のメンバーと通信できる場合、StatefulSetが保証するように設計されている最大1つのセマンティクスに違反します。

StatefulSetのPodを強制的に削除するということは、問題のPodがStatefulSet内の他のPodと再び接触することはなく、代わりのPodを作成するために名前が安全に解放されることを意味します。

バージョン1.5以上のkubectlを使用してPodを強制的に削除する場合は、次の手順を実行します:

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

バージョン1.4以下のkubectlを使用している場合、--forceオプションを省略する必要があります:

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

これらのコマンドを実行した後でもPodがUnknown状態のままになっている場合は、次のコマンドを使用してPodをクラスターから削除します:

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

StatefulSet Podの強制削除は、常に慎重に、関連するリスクを完全に把握して実行してください。

次の項目

StatefulSetのデバッグの詳細

7 - 水平Pod自動スケーリング

Kubernetesでは、 HorizontalPodAutoscaler は自動的にワークロードリソース(DeploymentStatefulSetなど)を更新し、ワークロードを自動的にスケーリングして需要に合わせることを目指します。

水平スケーリングとは、負荷の増加に対応するために、より多くのPodをデプロイすることを意味します。これは、Kubernetesの場合、既に稼働しているワークロードのPodに対して、より多くのリソース(例:メモリーやCPU)を割り当てることを意味する 垂直 スケーリングとは異なります。

負荷が減少し、Podの数が設定された最小値より多い場合、HorizontalPodAutoscalerはワークロードリソース(Deployment、StatefulSet、または他の類似のリソース)に対してスケールダウンするよう指示します。

水平Pod自動スケーリングは、スケーリングできないオブジェクト(例:DaemonSet)には適用されません。

HorizontalPodAutoscalerは、Kubernetes APIリソースとコントローラーとして実装されています。リソースはコントローラーの動作を決定します。Kubernetesコントロールプレーン内で稼働している水平Pod自動スケーリングコントローラーは、平均CPU利用率、平均メモリー利用率、または指定した任意のカスタムメトリクスなどの観測メトリクスに合わせて、ターゲット(例:Deployment)の理想的なスケールを定期的に調整します。

水平Pod自動スケーリングの使用例のウォークスルーがあります。

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はDeploymentとそのReplicaSetのスケールを制御します。

Kubernetesは水平Pod自動スケーリングを断続的に動作する制御ループとして実装しています(これは連続的なプロセスではありません)。その間隔はkube-controller-manager--horizontal-pod-autoscaler-sync-periodパラメーターで設定します(デフォルトの間隔は15秒です)。

各期間中に1回、コントローラーマネージャーはHorizontalPodAutoscalerの定義のそれぞれに指定されたメトリクスに対するリソース使用率を照会します。コントローラーマネージャーはscaleTargetRefによって定義されたターゲットリソースを見つけ、ターゲットリソースの.spec.selectorラベルに基づいてPodを選択し、リソースメトリクスAPI(Podごとのリソースメトリクスの場合)またはカスタムメトリクスAPI(他のすべてのメトリクスの場合)からメトリクスを取得します。

  • Podごとのリソースメトリクス(CPUなど)の場合、コントローラーはHorizontalPodAutoscalerによってターゲットとされた各PodのリソースメトリクスAPIからメトリクスを取得します。その後、使用率の目標値が設定されている場合、コントローラーは各Pod内のコンテナの同等のリソース要求に対する割合として使用率を算出します。生の値の目標値が設定されている場合、生のメトリクス値が直接使用されます。次に、コントローラーはすべてのターゲットとなるPod間で使用率または生の値(指定されたターゲットのタイプによります)の平均を取り、理想のレプリカ数でスケールするために使用される比率を生成します。

    Podのコンテナの一部に関連するリソース要求が設定されていない場合、PodのCPU利用率は定義されず、オートスケーラーはそのメトリクスに対して何も行動を起こしません。オートスケーリングアルゴリズムの動作についての詳細は、以下のアルゴリズムの詳細をご覧ください。

  • Podごとのカスタムメトリクスについては、コントローラーはPodごとのリソースメトリクスと同様に機能しますが、使用率の値ではなく生の値で動作します。

  • オブジェクトメトリクスと外部メトリクスについては、問題となるオブジェクトを表す単一のメトリクスが取得されます。このメトリクスは目標値と比較され、上記のような比率を生成します。autoscaling/v2 APIバージョンでは、比較を行う前にこの値をPodの数で割ることもできます。

HorizontalPodAutoscalerを使用する一般的な目的は、集約API(metrics.k8s.iocustom.metrics.k8s.io、またはexternal.metrics.k8s.io)からメトリクスを取得するように設定することです。metrics.k8s.io APIは通常、別途起動する必要があるMetrics Serverというアドオンによって提供されます。リソースメトリクスについての詳細は、Metrics Serverをご覧ください。

メトリクスAPIのサポートは、これらの異なるAPIの安定性の保証とサポート状況を説明します。

HorizontalPodAutoscalerコントローラーは、スケーリングをサポートするワークロードリソース(DeploymentやStatefulSetなど)にアクセスします。これらのリソースはそれぞれscaleというサブリソースを持っており、これはレプリカの数を動的に設定し、各々の現在の状態を調べることができるインターフェースを提供します。Kubernetes APIのサブリソースに関する一般的な情報については、Kubernetes API Conceptsをご覧ください。

アルゴリズムの詳細

最も基本的な観点から言えば、HorizontalPodAutoscalerコントローラーは、理想のメトリクス値と現在のメトリクス値との間の比率で動作します:

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

たとえば、現在のメトリクス値が200mで、理想の値が100mの場合、レプリカの数は倍増します。なぜなら、200.0 / 100.0 == 2.0だからです。現在の値が50mの場合、レプリカの数は半分になります。なぜなら、50.0 / 100.0 == 0.5だからです。コントロールプレーンは、比率が十分に1.0に近い場合(全体的に設定可能な許容範囲内、デフォルトでは0.1)には、任意のスケーリング操作をスキップします。

targetAverageValueまたはtargetAverageUtilizationが指定されている場合、currentMetricValueは、HorizontalPodAutoscalerのスケールターゲット内のすべてのPodで指定されたメトリクスの平均を取ることで計算されます。

許容範囲を確認し、最終的な値を決定する前に、コントロールプレーンは、メトリクスが欠けていないか、また何個のPodがReady状態であるかを考慮します。削除タイムスタンプが設定されているすべてのPod(削除タイムスタンプがあるオブジェクトはシャットダウンまたは削除の途中です)は無視され、失敗したPodはすべて破棄されます。

特定のPodがメトリクスを欠いている場合、それは後で検討するために取っておかれます。メトリクスが欠けているPodは、最終的なスケーリング量の調整に使用されます。

CPUに基づいてスケーリングする場合、任意のPodがまだReadyになっていない(まだ初期化中か、おそらくunhealthy)、またはPodがReadyになる前の最新のメトリクスポイントがある場合、そのPodも取り置かれます。

技術的な制約により、HorizontalPodAutoscalerコントローラーは特定のCPUメトリクスを取り置くかどうかを判断する際に、Podが初めてReadyになる時間を正確に決定することができません。その代わり、Podが起動してから設定可能な短い時間内にReadyに遷移した場合、それを「まだReadyになっていない」とみなします。この値は、--horizontal-pod-autoscaler-initial-readiness-delayフラグで設定し、デフォルトは30秒です。Podが一度Readyになると、起動してから設定可能な長い時間内に発生した場合、それが最初のReadyへの遷移だとみなします。この値は、--horizontal-pod-autoscaler-cpu-initialization-periodフラグで設定し、デフォルトは5分です。

次に、上記で取り置かれたり破棄されたりしていない残りのPodを使用して、currentMetricValue / desiredMetricValueの基本スケール比率が計算されます。

メトリクスが欠けていた場合、コントロールプレーンは平均値をより保守的に再計算し、スケールダウンの場合はそのPodが理想の値の100%を消費していたと仮定し、スケールアップの場合は0%を消費していたと仮定します。これにより、潜在的なスケールの大きさが抑制されます。

さらに、まだReadyになっていないPodが存在し、欠けているメトリクスやまだReadyになっていないPodを考慮せずにワークロードがスケールアップした場合、コントローラーは保守的にまだReadyになっていないPodが理想のメトリクスの0%を消費していると仮定し、スケールアップの大きさをさらに抑制します。

まだReadyになっていないPodと欠けているメトリクスを考慮に入れた後、コントローラーは使用率の比率を再計算します。新しい比率がスケールの方向を逆転させるか、許容範囲内である場合、コントローラーはスケーリング操作を行いません。その他の場合、新しい比率がPodの数の変更を決定するために使用されます。

新しい使用率の比率が使用されたときであっても、平均使用率の元の値は、まだReadyになっていないPodや欠けているメトリクスを考慮せずに、HorizontalPodAutoscalerのステータスを通じて報告されることに注意してください。

HorizontalPodAutoscalerに複数のメトリクスが指定されている場合、この計算は各メトリクスに対して行われ、その後、理想のレプリカ数の最大値が選択されます。これらのメトリクスのいずれかを理想のレプリカ数に変換できない場合(例えば、メトリクスAPIからのメトリクスの取得エラーが原因)、そして取得可能なメトリクスがスケールダウンを提案する場合、スケーリングはスキップされます。これは、1つ以上のメトリクスが現在の値よりも大きなdesiredReplicasを示す場合でも、HPAはまだスケーリングアップ可能であることを意味します。

最後に、HPAがターゲットを減らす直前に、減らす台数の推奨値が記録されます。コントローラーは、設定可能な時間内のすべての推奨値を考慮し、その時間内で最も高い推奨値を選択します。この値は、--horizontal-pod-autoscaler-downscale-stabilizationフラグを使用して設定でき、デフォルトは5分です。これは、スケールダウンが徐々に行われ、急速に変動するメトリクス値の影響を滑らかにすることを意味します。

APIオブジェクト

Horizontal Pod Autoscalerは、Kubernetesのautoscaling APIグループのAPIリソースです。現行の安定バージョンは、メモリーおよびカスタムメトリクスに対するスケーリングのサポートを含むautoscaling/v2 APIバージョンに見つけることができます。autoscaling/v2で導入された新たなフィールドは、autoscaling/v1で作業する際にアノテーションとして保持されます。

HorizontalPodAutoscaler APIオブジェクトを作成するときは、指定された名前が有効なDNSサブドメイン名であることを確認してください。APIオブジェクトについての詳細は、HorizontalPodAutoscaler Objectで見つけることができます。

ワークロードスケールの安定性

HorizontalPodAutoscalerを使用してレプリカ群のスケールを管理する際、評価されるメトリクスの動的な性質により、レプリカの数が頻繁に変動する可能性があります。これは、スラッシング または フラッピング と呼ばれることがあります。これは、サイバネティクス における ヒステリシス の概念に似ています。

ローリングアップデート中の自動スケーリング

Kubernetesでは、Deploymentに対してローリングアップデートを行うことができます。その場合、Deploymentが基礎となるReplicaSetを管理します。Deploymentに自動スケーリングを設定すると、HorizontalPodAutoscalerを単一のDeploymentに結びつけます。HorizontalPodAutoscalerはDeploymentのreplicasフィールドを管理します。Deploymentコントローラーは、ロールアウト時およびその後も適切な数になるように、基礎となるReplicaSetのreplicasを設定する責任があります。

自動スケールされたレプリカ数を持つStatefulSetのローリングアップデートを実行する場合、StatefulSetは直接そのPodのセットを管理します(ReplicaSetのような中間リソースは存在しません)。

リソースメトリクスのサポート

HPAの任意のターゲットは、スケーリングターゲット内のPodのリソース使用状況に基づいてスケールすることができます。Podの仕様を定義する際には、cpumemoryなどのリソース要求を指定する必要があります。これはリソースの使用状況を決定するために使用され、HPAコントローラーがターゲットをスケールアップまたはスケールダウンするために使用されます。リソース使用状況に基づくスケーリングを使用するには、以下のようなメトリクスソースを指定します:

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

このメトリクスを使用すると、HPAコントローラーはスケーリングターゲット内のPodの平均使用率を60%に保ちます。使用率は、Podの要求したリソースに対する現在のリソース使用量の比率です。使用率がどのように計算され、平均化されるかの詳細については、アルゴリズムを参照してください。

コンテナリソースメトリクス

FEATURE STATE: Kubernetes v1.27 [beta]

HorizontalPodAutoscaler APIは、コンテナメトリクスソースもサポートしています。これは、ターゲットリソースをスケールするために、HPAが一連のPod内の個々のコンテナのリソース使用状況を追跡できるようにするものです。これにより、特定のPodで最も重要なコンテナのスケーリング閾値を設定することができます。例えば、Webアプリケーションとロギングサイドカーがある場合、サイドカーのコンテナとそのリソース使用を無視して、Webアプリケーションのリソース使用に基づいてスケーリングすることができます。

ターゲットリソースを新しいPodの仕様に修正し、異なるコンテナのセットを持つようにした場合、新たに追加されたコンテナもスケーリングに使用されるべきであれば、HPAの仕様も修正すべきです。メトリクスソースで指定されたコンテナが存在しないか、または一部のPodのみに存在する場合、それらのPodは無視され、推奨が再計算されます。計算に関する詳細は、アルゴリズムを参照してください。コンテナリソースを自動スケーリングに使用するためには、以下のようにメトリクスソースを定義します:

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

上記の例では、HPAコントローラーはターゲットをスケールし、すべてのPodのapplicationコンテナ内のCPUの平均使用率が60%になるようにします。

カスタムメトリクスでのスケーリング

FEATURE STATE: Kubernetes v1.23 [stable]

(以前のautoscaling/v2beta2 APIバージョンでは、これをベータ機能として提供していました)

autoscaling/v2 APIバージョンを使用することで、HorizontalPodAutoscalerをカスタムメトリクス(KubernetesまたはKubernetesのコンポーネントに組み込まれていない)に基づいてスケールするように設定することができます。その後、HorizontalPodAutoscalerコントローラーはこれらのカスタムメトリクスをKubernetes APIからクエリします。

要件については、メトリクスAPIのサポートを参照してください。

複数メトリクスでのスケーリング

FEATURE STATE: Kubernetes v1.23 [stable]

(以前のautoscaling/v2beta2 APIバージョンでは、これをベータ機能として提供していました)

autoscaling/v2 APIバージョンを使用することで、HorizontalPodAutoscalerがスケールするための複数のメトリクスを指定することができます。その後、HorizontalPodAutoscalerコントローラーは各メトリクスを評価し、そのメトリクスに基づいた新しいスケールを提案します。HorizontalPodAutoscalerは、各メトリクスで推奨される最大のスケールを取得し、そのサイズにワークロードを設定します(ただし、これが設定した全体の最大値を超えていないことが前提です)。

メトリクスAPIのサポート

デフォルトでは、HorizontalPodAutoscalerコントローラーは一連のAPIからメトリクスを取得します。これらのAPIにアクセスするためには、クラスター管理者が以下を確認する必要があります:

  • API集約レイヤーが有効になっていること。

  • 対応するAPIが登録されていること:

    • リソースメトリクスの場合、これは一般的にmetrics-serverによって提供されるmetrics.k8s.io APIです。クラスターの追加機能として起動することができます。

    • カスタムメトリクスの場合、これはcustom.metrics.k8s.io APIです。これはメトリクスソリューションベンダーが提供する「アダプター」APIサーバーによって提供されます。利用可能なKubernetesメトリクスアダプターがあるかどうかは、メトリクスパイプラインで確認してください。

    • 外部メトリクスの場合、これはexternal.metrics.k8s.io APIです。これは上記のカスタムメトリクスアダプターによって提供される可能性があります。

これらの異なるメトリクスパスとその違いについての詳細は、HPA V2custom.metrics.k8s.io、およびexternal.metrics.k8s.ioの関連デザイン提案をご覧ください。

これらの使用方法の例については、カスタムメトリクスの使用方法外部メトリクスの使用方法をご覧ください。

設定可能なスケーリング動作

FEATURE STATE: Kubernetes v1.23 [stable]

(以前のautoscaling/v2beta2 APIバージョンでは、これをベータ機能として提供していました)

v2 HorizontalPodAutoscaler APIを使用する場合、behaviorフィールド(APIリファレンスを参照)を使用して、スケールアップとスケールダウンの振る舞いを個別に設定することができます。これらの振る舞いは、behaviorフィールドの下でscaleUpおよび/またはscaleDownを設定することにより指定します。

スケーリングターゲットのレプリカ数のフラッピングを防ぐための 安定化ウィンドウ を指定することができます。また、スケーリングポリシーにより、スケーリング中のレプリカの変化率を制御することもできます。

スケーリングポリシー

1つ以上のスケーリングポリシーをspecのbehaviorセクションで指定することができます。複数のポリシーが指定された場合、デフォルトで最も多くの変更を許可するポリシーが選択されます。次の例は、スケールダウンする際のこの振る舞いを示しています:

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

periodSecondsは、ポリシーが真でなければならない過去の時間を示します。最初のポリシー(Pods)では、1分間で最大4つのレプリカをスケールダウンできます。2つ目のポリシー(Percent)では、1分間で現在のレプリカの最大10%をスケールダウンできます。

デフォルトでは、最も多くの変更を許可するポリシーが選択されるため、2つ目のポリシーはPodのレプリカの数が40を超える場合にのみ使用されます。40レプリカ以下の場合、最初のポリシーが適用されます。例えば、レプリカが80あり、ターゲットを10レプリカにスケールダウンしなければならない場合、最初のステップでは8レプリカが減少します。次のイテレーションでは、レプリカの数が72で、ポッドの10%は7.2ですが、数値は8に切り上げられます。オートスケーラーコントローラーの各ループで、変更するべきPodの数は現在のレプリカの数に基づいて再計算されます。レプリカの数が40以下になると、最初のポリシー(Pods)が適用され、一度に4つのレプリカが減少します。

ポリシーの選択は、スケーリング方向のselectPolicyフィールドを指定することで変更できます。この値をMinに設定すると、レプリカ数の最小変化を許可するポリシーが選択されます。この値をDisabledに設定すると、その方向へのスケーリングが完全に無効になります。

安定化ウィンドウ

安定化ウィンドウは、スケーリングに使用されるメトリクスが常に変動する場合のレプリカ数のフラッピングを制限するために使用されます。自動スケーリングアルゴリズムは、このウィンドウを使用して以前の望ましい状態を推測し、ワークロードスケールへの望ましくない変更を避けます。

例えば、次の例のスニペットでは、scaleDownに対して安定化ウィンドウが指定されています。

behavior:
  scaleDown:
    stabilizationWindowSeconds: 300

メトリクスがターゲットをスケールダウンすべきであることを示すと、アルゴリズムは以前に計算された望ましい状態を探し、指定された間隔から最高値を使用します。上記の例では、過去5分間のすべての望ましい状態が考慮されます。

これは移動最大値を近似し、スケーリングアルゴリズムが頻繁にPodを削除して、わずかな時間後に同等のPodの再作成をトリガーするのを防ぎます。

デフォルトの動作

カスタムスケーリングを使用するためには、全てのフィールドを指定する必要はありません。カスタマイズが必要な値のみを指定することができます。これらのカスタム値はデフォルト値とマージされます。デフォルト値は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%を削除することが許可されています。これは、スケーリングターゲットが最小許容レプリカ数まで縮小されることを意味します。スケールアップの場合、安定化ウィンドウはありません。メトリクスがターゲットをスケールアップするべきであることを示すと、ターゲットはすぐにスケールアップされます。2つのポリシーがあり、HPAが安定状態に達するまで、最大で15秒ごとに4つのポッドまたは現在稼働しているレプリカの100%が追加されます。

例: ダウンスケール安定化ウィンドウの変更

1分間のカスタムダウンスケール安定化ウィンドウを提供するには、HPAに以下の動作を追加します:

behavior:
  scaleDown:
    stabilizationWindowSeconds: 60

例: スケールダウン率の制限

HPAによるPodの除去率を毎分10%に制限するには、HPAに以下の動作を追加します:

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

1分あたりに削除されるPodが5つを超えないようにするために、固定サイズ5の2番目のスケールダウンポリシーを追加し、selectPolicyを最小に設定することができます。selectPolicyMinに設定すると、オートスケーラーは最少数のPodに影響を与えるポリシーを選択します:

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

例: スケールダウンの無効化

selectPolicyの値がDisabledの場合、指定された方向のスケーリングをオフにします。したがって、スケールダウンを防ぐには、次のようなポリシーが使われます:

behavior:
  scaleDown:
    selectPolicy: Disabled

kubectlにおけるHorizontalPodAutoscalerのサポート

HorizontalPodAutoscalerは、他のすべてのAPIリソースと同様にkubectlによって標準的にサポートされています。kubectl createコマンドを使用して新しいオートスケーラーを作成することができます。kubectl get hpaを使用してオートスケーラーを一覧表示したり、kubectl describe hpaを使用して詳細な説明を取得したりできます。最後に、kubectl delete hpaを使用してオートスケーラーを削除することができます。

さらに、HorizontalPodAutoscalerオブジェクトを作成するための特別なkubectl autoscaleコマンドがあります。例えば、kubectl autoscale rs foo --min=2 --max=5 --cpu-percent=80を実行すると、ReplicaSet fooのオートスケーラーが作成され、ターゲットのCPU使用率が80%に設定され、レプリカ数は2から5の間になります。

暗黙のメンテナンスモードの非活性化

HPAの設定自体を変更することなく、ターゲットのHPAを暗黙的に非活性化することができます。ターゲットの理想のレプリカ数が0に設定され、HPAの最小レプリカ数が0より大きい場合、HPAはターゲットの調整を停止します(そして、自身のScalingActive条件をfalseに設定します)。これは、ターゲットの理想のレプリカ数またはHPAの最小レプリカ数を手動で調整して再活性化するまで続きます。

DeploymentとStatefulSetを水平自動スケーリングへ移行する

HPAが有効になっている場合、Deploymentおよび/またはStatefulSetのspec.replicasの値をそのマニフェストから削除することが推奨されます。これを行わない場合、たとえばkubectl apply -f deployment.yamlを介してそのオブジェクトに変更が適用されるたびに、これはKubernetesに現在のPodの数をspec.replicasキーの値にスケールするよう指示します。これは望ましくない場合があり、HPAがアクティブなときに問題になる可能性があります。

spec.replicasの削除は、このキーのデフォルト値が1であるため(参照: Deploymentのレプリカ数)、一度だけPod数が低下する可能性があることに注意してください。更新時に、1つを除くすべてのPodが終了手順を開始します。その後の任意のDeploymentアプリケーションは通常どおり動作し、望む通りのローリングアップデート設定を尊重します。Deploymentをどのように変更しているかによって、以下の2つの方法から1つを選択することでこの低下を回避することができます:

  1. kubectl apply edit-last-applied deployment/<deployment_name>
  2. エディターでspec.replicasを削除します。保存してエディターを終了すると、kubectlが更新を適用します。このステップではPod数に変更はありません。
  3. これでマニフェストからspec.replicasを削除できます。ソースコード管理を使用している場合は、変更をコミットするか、更新の追跡方法に適したソースコードの改訂に関するその他の手順を行います。
  4. ここからはkubectl apply -f deployment.yamlを実行できます。

サーバーサイド適用を使用する場合は、この具体的なユースケースをカバーしている所有権の移行ガイドラインに従うことができます。

次の項目

クラスターでオートスケーリングを設定する場合、Cluster Autoscalerのようなクラスターレベルのオートスケーラーを実行することも検討してみてください。

HorizontalPodAutoscalerに関する詳細情報:

8 - Horizontal Pod Autoscalerウォークスルー

Horizontal Pod Autoscalerは、Deployment、ReplicaSetまたはStatefulSetといったレプリケーションコントローラー内のPodの数を、観測されたCPU使用率(もしくはベータサポートの、アプリケーションによって提供されるその他のメトリクス)に基づいて自動的にスケールさせます。

このドキュメントはphp-apacheサーバーに対しHorizontal Pod Autoscalerを有効化するという例に沿ってウォークスルーで説明していきます。Horizontal Pod Autoscalerの動作についてのより詳細な情報を知りたい場合は、Horizontal Pod Autoscalerユーザーガイドをご覧ください。

始める前に

この例ではバージョン1.2以上の動作するKubernetesクラスターおよびkubectlが必要です。 Metrics APIを介してメトリクスを提供するために、Metrics serverによるモニタリングがクラスター内にデプロイされている必要があります。 Horizontal Pod Autoscalerはメトリクスを収集するためにこのAPIを利用します。metrics-serverをデプロイする方法を知りたい場合はmetrics-server ドキュメントをご覧ください。

Horizontal Pod Autoscalerで複数のリソースメトリクスを利用するためには、バージョン1.6以上のKubernetesクラスターおよびkubectlが必要です。カスタムメトリクスを使えるようにするためには、あなたのクラスターがカスタムメトリクスAPIを提供するAPIサーバーと通信できる必要があります。 最後に、Kubernetesオブジェクトと関係のないメトリクスを使うにはバージョン1.10以上のKubernetesクラスターおよびkubectlが必要で、さらにあなたのクラスターが外部メトリクスAPIを提供するAPIサーバーと通信できる必要があります。 詳細についてはHorizontal Pod Autoscaler user guideをご覧ください。

php-apacheの起動と公開

Horizontal Pod Autoscalerのデモンストレーションのために、php-apacheイメージをもとにしたカスタムのDockerイメージを使います。 このDockerfileは下記のようになっています。

FROM php:5-apache
COPY index.php /var/www/html/index.php
RUN chmod a+rx index.php

これはCPU負荷の高い演算を行うindex.phpを定義しています。

<?php
  $x = 0.0001;
  for ($i = 0; $i <= 1000000; $i++) {
    $x += sqrt($x);
  }
  echo "OK!";
?>

まず最初に、イメージを動かすDeploymentを起動し、Serviceとして公開しましょう。 下記の設定を使います。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  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

Horizontal Pod Autoscalerを作成する

サーバーが起動したら、kubectl autoscaleを使ってautoscalerを作成しましょう。以下のコマンドで、最初のステップで作成したphp-apache deploymentによって制御されるPodレプリカ数を1から10の間に維持するHorizontal Pod Autoscalerを作成します。 簡単に言うと、HPAは(Deploymentを通じて)レプリカ数を増減させ、すべてのPodにおける平均CPU使用率を50%(それぞれのPodはkubectl runで200 milli-coresを要求しているため、平均CPU使用率100 milli-coresを意味します)に保とうとします。 このアルゴリズムについての詳細はこちらをご覧ください。

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

以下を実行して現在のAutoscalerの状況を確認できます。

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

現在はサーバーにリクエストを送っていないため、CPU使用率が0%になっていることに注意してください(TARGETカラムは対応するDeploymentによって制御される全てのPodの平均値を示しています。)。

負荷の増加

Autoscalerがどのように負荷の増加に反応するか見てみましょう。 コンテナを作成し、クエリの無限ループをphp-apacheサーバーに送ってみます(これは別のターミナルで実行してください)。

kubectl run -i --tty load-generator --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"

数分以内に、下記を実行することでCPU負荷が高まっていることを確認できます。

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

ここでは、CPU使用率はrequestの305%にまで高まっています。 結果として、Deploymentはレプリカ数7にリサイズされました。

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

負荷の停止

ユーザー負荷を止めてこの例を終わらせましょう。

私たちがbusyboxイメージを使って作成したコンテナ内のターミナルで、<Ctrl> + Cを入力して負荷生成を終了させます。

そして結果の状態を確認します(数分後)。

kubectl get hpa
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に戻ります。

複数のメトリクスやカスタムメトリクスを基にオートスケーリングする

autoscaling/v2beta2 APIバージョンと使うと、php-apache Deploymentをオートスケーリングする際に使う追加のメトリクスを導入することが出来ます。

まず、autoscaling/v2beta2内のHorizontalPodAutoscalerのYAMLファイルを入手します。

kubectl get hpa.v2beta2.autoscaling -o yaml > /tmp/hpa-v2.yaml

/tmp/hpa-v2.yamlファイルをエディタで開くと、以下のようなYAMLファイルが見えるはずです。

apiVersion: autoscaling/v2beta2
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使用率メトリクスは、Podコンテナで定められたリソースの割合として表されるため、リソースメトリクスです。CPU以外のリソースメトリクスを指定することもできます。デフォルトでは、他にメモリだけがリソースメトリクスとしてサポートされています。これらのリソースはクラスター間で名前が変わることはなく、そしてmetrics.k8s.io APIが利用可能である限り常に利用可能です。

さらにtarget.typeにおいてUtilizationの代わりにAverageValueを使い、target.averageUtilizationフィールドの代わりに対応するtarget.averageValueフィールドを設定することで、リソースメトリクスをrequest値に対する割合に代わり、直接的な値に設定することも可能です。

PodメトリクスとObjectメトリクスという2つの異なる種類のメトリクスが存在し、どちらもカスタムメトリクスとみなされます。これらのメトリクスはクラスター特有の名前を持ち、利用するにはより発展的なクラスター監視設定が必要となります。

これらの代替メトリクスタイプのうち、最初のものがPodメトリクスです。これらのメトリクスはPodを説明し、Podを渡って平均され、レプリカ数を決定するためにターゲット値と比較されます。 これらはほとんどリソースメトリクス同様に機能しますが、targetの種類としてはAverageValueのみをサポートしている点が異なります。

Podメトリクスはmetricブロックを使って以下のように指定されます。

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

2つ目のメトリクスタイプはObjectメトリクスです。これらのメトリクスはPodを説明するかわりに、同一Namespace内の異なったオブジェクトを説明します。このメトリクスはオブジェクトから取得される必要はありません。単に説明するだけです。Objectメトリクスはtargetの種類としてValueAverageValueをサポートします。Valueでは、ターゲットはAPIから返ってきたメトリクスと直接比較されます。AverageValueでは、カスタムメトリクスAPIから返ってきた値はターゲットと比較される前にPodの数で除算されます。以下の例はrequests-per-secondメトリクスのYAML表現です。

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

もしこのようなmetricブロックを複数提供した場合、HorizontalPodAutoscalerはこれらのメトリクスを順番に処理します。 HorizontalPodAutoscalerはそれぞれのメトリクスについて推奨レプリカ数を算出し、その中で最も多いレプリカ数を採用します。

例えば、もしあなたがネットワークトラフィックについてのメトリクスを収集する監視システムを持っているなら、kubectl editを使って指定を次のように更新することができます。

apiVersion: autoscaling/v2beta2
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/v1beta1
        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/v1beta1
        kind: Ingress
        name: main-route
      current:
        value: 10k

この時、HorizontalPodAutoscalerはそれぞれのPodがCPU requestの50%を使い、1秒当たり1000パケットを送信し、そしてmain-route Ingressの裏にあるすべてのPodが合計で1秒当たり10000パケットを送信する状態を保持しようとします。

より詳細なメトリクスをもとにオートスケーリングする

多くのメトリクスパイプラインは、名前もしくは labels と呼ばれる追加の記述子の組み合わせによって説明することができます。全てのリソースメトリクス以外のメトリクスタイプ(Pod、Object、そして下で説明されている外部メトリクス)において、メトリクスパイプラインに渡す追加のラベルセレクターを指定することができます。例えば、もしあなたがhttp_requestsメトリクスをverbラベルとともに収集しているなら、下記のmetricブロックを指定してGETリクエストにのみ基づいてスケールさせることができます。

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

このセレクターは完全なKubernetesラベルセレクターと同じ文法を利用します。もし名前とセレクターが複数の系列に一致した場合、この監視パイプラインはどのようにして複数の系列を一つの値にまとめるかを決定します。このセレクターは付加的なもので、ターゲットオブジェクト(Podsタイプの場合は対象Pod、Objectタイプの場合は説明されるオブジェクト)ではないオブジェクトを説明するメトリクスを選択することは出来ません。

Kubernetesオブジェクトと関係ないメトリクスに基づいたオートスケーリング

Kubernetes上で動いているアプリケーションを、Kubernetes Namespaceと直接的な関係がないサービスを説明するメトリクスのような、Kubernetesクラスター内のオブジェクトと明確な関係が無いメトリクスを基にオートスケールする必要があるかもしれません。Kubernetes 1.10以降では、このようなユースケースを外部メトリクスによって解決できます。

外部メトリクスを使うにはあなたの監視システムについての知識が必要となります。この設定はカスタムメトリクスを使うときのものに似ています。外部メトリクスを使うとあなたの監視システムのあらゆる利用可能なメトリクスに基づいてクラスターをオートスケールできるようになります。上記のようにmetricブロックでnameselectorを設定し、ObjectのかわりにExternalメトリクスタイプを使います。 もし複数の時系列がmetricSelectorにより一致した場合は、それらの値の合計がHorizontalPodAutoscalerに使われます。 外部メトリクスはValueAverageValueの両方のターゲットタイプをサポートしています。これらの機能はObjectタイプを利用するときとまったく同じです。

例えばもしあなたのアプリケーションがホストされたキューサービスからのタスクを処理している場合、あなたは下記のセクションをHorizontalPodAutoscalerマニフェストに追記し、未処理のタスク30個あたり1つのワーカーを必要とすることを指定します。

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

可能なら、クラスター管理者がカスタムメトリクスAPIを保護することを簡単にするため、外部メトリクスのかわりにカスタムメトリクスを用いることが望ましいです。外部メトリクスAPIは潜在的に全てのメトリクスへのアクセスを許可するため、クラスター管理者はこれを公開する際には注意が必要です。

付録: Horizontal Pod Autoscaler status conditions

autoscaling/v2beta2形式のHorizontalPodAutoscalerを使っている場合は、KubernetesによるHorizontalPodAutoscaler上のstatus conditionsセットを見ることができます。status conditionsはHorizontalPodAutoscalerがスケール可能かどうか、そして現時点でそれが何らかの方法で制限されているかどうかを示しています。

このconditionsはstatus.conditionsフィールドに現れます。HorizontalPodAutoscalerに影響しているconditionsを確認するために、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において、いくつかの正常な状態のconditionsを見ることができます。まず最初に、AbleToScaleは、HPAがスケール状況を取得し、更新させることが出来るかどうかだけでなく、何らかのbackoffに関連した状況がスケーリングを妨げていないかを示しています。2番目に、ScalingActiveは、HPAが有効化されているかどうか(例えば、レプリカ数のターゲットがゼロでないこと)や、望ましいスケールを算出できるかどうかを示します。もしこれがFalseの場合、大体はメトリクスの取得において問題があることを示しています。最後に、一番最後の状況であるScalingLimitedは、HorizontalPodAutoscalerの最大値や最小値によって望ましいスケールがキャップされていることを示しています。この指標を見てHorizontalPodAutoscaler上の最大・最小レプリカ数制限を増やす、もしくは減らす検討ができます。

付録: 数量

全てのHorizontalPodAutoscalerおよびメトリクスAPIにおけるメトリクスはquantityとして知られる特殊な整数表記によって指定されます。例えば、10500mという数量は10進数表記で10.5と書くことができます。メトリクスAPIは可能であれば接尾辞を用いない整数を返し、そうでない場合は基本的にミリ単位での数量を返します。これはメトリクス値が11500mの間で、もしくは10進法表記で書かれた場合は11.5の間で変動するということを意味します。

付録: その他の起きうるシナリオ

Autoscalerを宣言的に作成する

kubectl autoscaleコマンドを使って命令的にHorizontalPodAutoscalerを作るかわりに、下記のファイルを使って宣言的に作成することができます。

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

下記のコマンドを実行してAutoscalerを作成します。

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