🌟

コンテナワークロードの可用性向上

2024/05/21に公開

参考

https://www.redhat.com/ja/blog/8-application-design-principles-to-cope-with-openshift-maintenance-operations

https://www.redhat.com/en/blog/9-best-practices-for-deploying-highly-available-applications-to-openshift

https://www.redhat.com/en/blog/deploying-highly-available-applications-openshift-kubernetes

本記事の記載対象と免責

  • 本資料ではノード停止などコンテナプラットフォームメンテナンス・障害発生時にPod(ワークロード)に極力影響を与えないよう可用性を高めることを目的とした考慮的を記載しており、OCP全般の設計については記載の対象外となる。
  • 公開されている参考情報を元に設計の考慮点、考慮漏れによるデメリット、実装手段について記載する。
  • (免責事項)本記事は参考情報を記載しており、いかなる責任も負わないものとし読者は同意したものと見なします。

従来の仮想サーバとコンテナのコンセプトの違い

  • Conceptの違いを理解した上で設計を行わないと認識齟齬の発生や本来の効果を享受できない可能性がある。
  • Pod(container)はスクラップ&ビルドな設計思想であり、何かしらの理由によりノードが停止した際にはPodも停止する。なお、vSphereのvMotionに相当する機能はない。

目的・ゴール

目的

  • 基盤メンテナンス・障害発生時にワークロード(コンテナ)に影響を与えない環境を目指す。とは言っても全ての要因を事前に潰すことは現実的に不可能であるため、影響が発生した際に範囲の局所化と早期復旧を目指す。

ゴール

  • 予防の観点
    • ワークロードの可用性向上に繋がる設計内容の整理。
    • 設計内容が適切に反映されたことを確認するためクライテリアの整理と実機テストによる評価を行う。
  • 発見の観点
    • 被疑箇所と根本原因を短時間で特定する。
    • コンテナ上で稼働するワークロードの影響有無を短時間で特定する。

コンテナ上で稼働するワークロードの可用性向上に向けた設計

  • 対策はOCPの機能による実装できるものとアプリ・ミドルウェアの対応が必要なものが混在する。特にPodで稼働するアプリ・ミドルウェアにて対応が必要な項目については、実装難易度が高い対策が含まれる可能性がある。
  • 対処策は一般的に効果があるとされるものを記載するが、ワークロードの仕様や実装によってはデメリットとなり得る場合があるため、最終的にはテストによる評価が必要となる。
  • 後述する対処方法は局所的に実施しても全体の可用性向上には繋がらないため、網羅的に対処方法と効果を検討する必要がある。
  • 元も子もないが結局はPod上で稼働するワークロードに依存する部分が大きい。

Podの冗長性確保

  • リスクシナリオ①

    • 特定ワークロードのPodが1台で稼働しており、そのPod停止時にシステム全体停止に繋がる。
  • 対処法

    • deploymentによりPodのreplica数を2以上にする。
    spec:
      replicas: 3
    
  • リスクシナリオ②

    • Podが複数起動しているが、特定ノード上で全てのPodが起動しておりノード停止時にシステム停止となる
  • 対処法

    • pod anti-affinity ruleによりPodを複数ノードに分散する。
    affinity:
      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
            - key: app # 制御対象PodのラベルのKey
              operator: In # ラベルの値が含まれている場合。他のオプションはNotIn/Exists/DoesNotExist
              values:
              - web # 制御対象PodのラベルのValue
          topologyKey: "kubernetes.io/hostname" # Podが同じノードに配置されないようにする。
    
  • リスクシナリオ③

    • Podは複数起動しておりPodも各ノードに分散配置しているが多重障害・メンテナンス等で複数ノードが同時に停止したことにより、Podが複数停止しリソース不足等による処理遅延が発生する。
  • 対処法

    • Pod Disruption Budgetによりノードメンテナンスにおいて同時の停止可能なPod数を指定することで、最低限稼働させたいPod数を維持できる。(基盤担当がノードを複数停止しいようとした際にPod Disruption Budgetのルールに抵触すると警告が表示される)
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: web-pdb
    spec:
      minAvailable: 1
      selector:
        matchLabels:
          app: web
    
    • (参考)Deschedulerにより自動的にPodの配置が複数ノードになるよう再配置可能だが、Operatorの導入が必要となるため、Operatorの管理とバージョンアップパスについての検討が追加で必要となる。
  • Pod冗長化の阻害要因

    • 本項で記載しているPod冗長化は最も基本的な観点だが、以下の要因により冗長化が実現出来ない場合がある。
    • ワークロードにステートフル(Pod再起動前後のデータを引き続き継続利用する)な要件や実装が必要な場合。例)シングル構成のDBMSのためPod冗長化が不可能となる。
    • ライセンス制約・コスト要件により冗長化が出来ない。
    • キャッシュクラスタ等でPodの冗長化はされているものの、特定Pod停止時にシステム全体に影響を及ぼす場合。

Podの自己回復力の強化

  • リスクシナリオ①

    • メンテナンスやリード障害発生時にPodが停止されない。またはPod停止に時間を要しPodおよびシステムの復旧に時間を要する場合がある。
  • 対処法

    • Graceful ShutdownによりOpenShiftからPodに対してSIGTERMシグナルを送信した際に、迅速にPodのクリーンアップと停止を行えるようアプリケーション・ミドルウェアの設計に取り込む。なお、Podに対してSIGTERMが命令された場合もPod上で稼働するミドルウェア、アプリ等が処理の終了を受けつかない場合はPod自体の終了処理が待たされるため、結局はミドルウェア、アプリの仕様と実装と密に関係する。
    spec:
      terminationGracePeriodSeconds: 30
    
  • リスクシナリオ②

    • Podが処理を受け付け不可能な状態(ノード停止に伴うPod起動中、ハングアップ、等)にあるにも関わらず、対象Podにトラフィックが流れてしまいクライアントにエラーを返してしまう。
  • 対処法

    • Probe(Startup/Readness/Liveness)によりPodの状態を監視し、Podが処理不可能状態に陥った際にはServiceからの処理を受け付けないようにする。
    readinessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 10
    
    startupProbe:
      httpGet:
        path: /startup
        port: 8080
      initialDelaySeconds: 60
      periodSeconds: 10
      failureThreshold: 30
    
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 10
      periodSeconds: 5
      failureThreshold: 3
    
  • リスクシナリオ③

    • 分散アプリケーションやRDBMSのクラスタ構成など、複数のPodと複数のPVが存在する構成において、Podの起動・停止がランダムに行われると、データ不整合が発生する可能性がある。
  • 対処法

    • 対処法としてStatefulSetを利用することで以下の対処が可能となる。

    • Podの起動・停止順序のコントロール:Redisのような分散アプリケーションにおいて、クラスタを作成する際にはPodの起動・停止順序をコントロールする必要があるため、StatefulSetを使用することで、Pod redis-0 が起動後にPod redis-1 が起動するといったコントロールが可能となる。ただし、ノード障害等で意図せずPodが停止した際に、Podの起動・停止順序が崩れる可能性があるため、意図しない動作が発生するリスクがあり、仕様の確認やテストによる動作確認が必要となる。

    • Pod名の固定化:Deploymentで作成したPodの名前はランダムに決定されるが、StatefulSetの場合はPod名が固定される。これにより、独自に実装した監視スクリプトでPod名を指定して監視することが可能となり、管理が容易になる。

    • PodとPVのバインドの固定化:複数のPodと複数のPVが存在する構成において、StatefulSetを使用すると、特定のPodと特定のPVのバインドを固定化することが可能となる。これにより、Podが再起動されても同じPVにアクセスし続けることが保証され、データの一貫性と永続性が確保される。

適切なリソースの確保

  • リスクシナリオ①
    • 想定以上のリクエストによるPodリソースの逼迫
  • 対処法
    • Horizontal Pod Autoscaler (HPA)による負荷に応じたPodのオートスケーリング
    apiVersion: autoscaling/v1
    kind: HorizontalPodAutoscaler
    metadata:
      name: my-app-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: my-app
      minReplicas: 1
      maxReplicas: 10
      targetCPUUtilizationPercentage: 80
    
  • リスクシナリオ②
    • QesourceQuotaの上限値に抵触しPodが起動しない。
  • 対処法
    • 余裕を持ったQesourceQuotaの設定。ただし通常はノードのキャパシティプランニングは基盤部隊が担っていると思われるため、設定値についてはコスト踏まえ相談する必要がある。
    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: my-quota
    spec:
      hard:
        pods: "10"
        requests.cpu: "4"
        requests.memory: "8Gi"
        limits.cpu: "8"
        limits.memory: "16Gi"
    

データ持続性の確保

  • 実装手段
    • ローカルストレージの利用を避け、PV(Storageclass)を利用しPod再起動後に異なるノードにおいてもデータを継続利用可能な状態とする。

クライテリアの整理

  • 実装の妥当性をテストにて評価する。想定されるテストケースは以下の通り。
  • ノード(workernode)の停止によるドレインの発生
  • ネットワーク分断
  • ストレージ停止(実際にテストを行うことは困難であるが観点として記載)

Discussion