K8s in Action 読書メモ - [10] StatefulSets: 複製されたステートフル アプリケーションのデプロイ

K8s in Action 読書メモ - [10] StatefulSets: 複製されたステートフル アプリケーションのデプロイ

10.1 ステートフル ポッドのレプリケーション

ReplicaSet は、単一の Pod テンプレートから複数の Pod レプリカを作成します。これらのコピーには、名前と IP アドレスを除いて違いはありません。Pod テンプレートに特定の ReplicaSet を参照するボリュームが含まれている場合PersistentVolumeClaim、そのすべてのレプリカは同じものを使用するため、その P によってバインドされたPersistentVolumeClaim同じものを使用します(図 10.1 を参照)。ersistentVolumeClaimPersistentVolume

宣言への参照は、Pod の複数のコピーを作成するために使用される Pod テンプレート内にあるため、各コピーに独自の独立した を使用させる方法はありませんPersistentVolumeClaim各インスタンスには独自の独立したストレージが必要なため、少なくとも単一の ReplicaSet では、ReplicaSet を使用して分散データ ストアを実行することはできません他の解決策が必要です。

画像-20230604145800327

10.1.1 それぞれに個別のストレージを使用して複数のレプリカを実行する

ポッドの複数のコピーを実行し、各ポッドが独自のストレージ ボリュームを使用できるようにするにはどうすればよいですか? ReplicaSet は Pod の正確なコピーを作成するため、そのような Pod には使用できません。それで、何が使えるでしょうか?

ポッドを手動で作成する

Pod を手動で作成し、各 Pod に独自の を使用させることは可能ですPersistentVolumeClaimが、それらを管理する ReplicaSet がないため、手動で管理し、消滅した場合 (ノード障害など) に再作成する必要があります。したがって、これは実行可能な選択肢ではありません。

Pod インスタンスごとに 1 つの ReplicaSet を使用する

複数の ReplicaSet (Pod ごとに 1 つの ReplicaSet) を作成できます。各 ReplicaSet の必要なレプリカ数は 1 に設定され、各 ReplicaSet の Pod テンプレートは専用の Pod テンプレートを参照します (図 10.2 を参照) PersistentVolumeClaim

画像-20230604150210700

これにより、ノードの障害や Pod の誤った削除が発生した場合に自動的に再スケジュールできますが、単一の ReplicaSet を使用するよりも面倒です。たとえば、この場合に Pod をスケーリングする方法を考えてみましょう。必要なレプリカの数は変更できません。代わりに、追加の ReplicaSet を作成する必要があります。

したがって、複数の ReplicaSet を使用することは最善の解決策ではありません。

同じストレージボリューム上の複数のディレクトリを使用する

同じ を使用しますPersistentVolumeが、ボリューム内の Pod ごとに個別のファイル ディレクトリを作成するというテクニックが使用できます (図 10.3 を参照)。

レプリカは単一の Pod テンプレートとは異なるように構成できないため、各インスタンスにどのディレクトリを使用するかを指示する方法はありません。ただし、他のインスタンスが現在使用していないデータ ディレクトリを各インスタンスに自動的に選択させることができます。このソリューションにはインスタンス間の調整が必要であり、正しく実装するのは簡単ではありません

画像-20230604150438224

10.1.2 各ポッドに安定した ID を提供する

一部のアプリケーションでは、ストレージに加えて、各インスタンスの安定したネットワーク ID が必要です。場合によっては、ポッドが終了され、新しいポッドに置き換えられることがあります。ReplicaSet が Pod を置き換えると、そのストレージ ボリューム内のデータは終了した Pod のデータである可能性がありますが、新しい Pod は新しいホスト名と IP アドレスを持つ完全に新しい Pod になります。一部のアプリケーションでは、古いインスタンスのデータを使用して完全に新しいネットワーク ID を使用して開始すると、問題が発生する可能性があります。

一部のアプリケーションが安定したネットワーク ID を必要とするのはなぜですか? この要件は、分散ステートフル アプリケーションでは非常に一般的です。一部のアプリケーションでは、管理者が他のすべてのクラスター メンバーとその IP アドレス (またはホスト名) を各メンバーの構成ファイルにリストする必要があります。ただし、Kubernetes では、ポッドが再スケジュールされるたびに、新しいポッドは新しいホスト名と新しい IP アドレスを取得するため、メンバーが再スケジュールされるたびにアプリケーション クラスター全体を再構成する必要があります。

ポッドインスタンスごとに専用のサービスを使用する

この問題を解決するには、個々のメンバーに専用の Kubernetes Service を作成することで、クラスター メンバーに安定したネットワーク アドレスを提供するトリックを使用できます。サービス IP は安定しているため、(ポッド IP ではなく) サービス IP によって構成内の各メンバーを指定できます。

これは、前述したように、各メンバーに独立したストレージを提供するために ReplicaSet を作成するのと似ています。これら 2 つの手法を組み合わせると、図 10.4 に示すセットアップが得られます (通常、クラスターのクライアントが必要であるため、クラスター メンバーシップ全体をカバーする追加サービスも示されています)。

画像-20230604150837185

この解決策は見苦しいだけでなく、すべての問題を解決するわけではありません。個々の Pod は、どの Service を通じて公開されているか (つまり、安定した IP) を知る方法がないため、その IP を使用して他の Pod に自己登録することはできません。

幸いなことに、Kubernetes はStatefulSetを提供します。

10.2 StatefulSet について

10.2.1 StatefulSetとReplicaSetの比較

「ペット」と「牛」の例えを使用してステートフル Pod を理解する

「ペット」と「牛」の例えを聞いたことがあるかもしれません。そうでない場合は、説明させてください。アプリケーションの例をペットや牛として考えることができます。

注: StatefulSet は元々 PetSet と呼ばれていました。名前の由来は、ここで説明した「ペット」と「牛」の類似性に由来しています。

私たちはアプリケーション インスタンスをペットとして考える傾向があり、各インスタンスに名前を付け、各インスタンスに個別の注意を払います。ただし、多くの場合、インスタンスを群れとして扱い、個々のインスタンスに特別な注意を払わないほうがよいでしょう。これにより、農家が不健康な牛を置き換えるように、不健康なインスタンスをあまり考えずに簡単に置き換えることができます。

ステートレス アプリケーションのインスタンスは、群れの中の牛のように動作します。インスタンスが死んだ場合は、新しいインスタンスを作成できますが、人々は違いに気づきません。

ただし、ステートフル アプリケーションでは、アプリケーション インスタンスはペットのようなものです。ペットが死んでも、人々が気付かないことを期待して新しいペットを買うことはできません。いなくなったペットの代わりになるには、古いペットと見た目も動作もまったく同じ新しいペットを見つける必要がありますアプリケーションにとって、これは、新しいインスタンスが古いインスタンスと同じ状態と ID を持つ必要があることを意味します。

StatefulSet と ReplicaSet または ReplicationController の比較

ReplicaSet または ReplicationController によって管理されるポッド レプリカは牛の群れのようなものです。これらはほとんどステートレスであるため、いつでもポッドの新しいコピーと置き換えることができます。ステートフル ポッドには別のアプローチが必要です。ステートフル Pod インスタンスが停止した場合 (または、そのインスタンスが存在するノードに障害が発生した場合)、Pod インスタンスは別のノードで再起動する必要がありますが、新しいインスタンスは、置き換えられた古いインスタンスと同じ名前、ネットワーク ID、およびステータスを取得する必要がありますこれはStatefulSetPod を管理するときに起こることです。

StatefulSetポッドの ID と状態を維持する方法でポッドが再スケジュールされていることを確認します。ペットの数を簡単に増やすこともできます。ReplicaSet のような StatefulSet には、その時点で実行するペットの数を決定するための予想レプリカ数フィールドがあります。ReplicaSet と同様に、Pod は StatefulSet の一部として指定された Pod テンプレートから作成されます。ただし、ReplicaSet によって作成された Pod とは異なり、StatefulSet によって作成された Pod は同一のコピーではありません。各ポッドは独自のボリュームのセットを持つことができ、他のポッドとは一意になります。ペット ポッドは、新しいポッド インスタンスごとに完全にランダムな ID を取得するのではなく、予測可能な (そして安定した) ID も持ちます。

10.2.2 安定したネットワーク ID の提供

統治サービス

StatefulSet によって作成された各 Pod には、シリアル番号インデックス (ゼロベース) が割り当てられます。これは、Pod の名前とホスト名を生成し、安定したストレージを Pod に接続するために使用されます。したがって、各 Pod の名前は StatefulSet の名前とインスタンスの序数インデックスから派生するため、Pod 名は予測可能です。ランダムな名前が付いたポッドとは異なり、以下の図に示すように、非常に体系化された方法で名前が付けられます。

画像-20230604151501047

通常の Pod とは異なり、ステートフル Pod はホスト名でアドレス指定する必要がある場合がありますが、ステートレス Pod は通常その必要がありません。結局のところ、ステートレスなすべての Pod は他の Pod とまったく同じです。必要なときにいずれかを選択できます。ただし、ステートフルな Pod では、通常、特定の Pod 上で操作する必要があります。

したがって、StatefulSet では、各ポッドに実際のネットワーク ID を提供する、対応するコントロール ヘッドレス サービス ( governing headless Service) を作成する必要があります。このサービスを通じて、各 Pod は独自の DNS エントリを取得するため、そのピアおよび場合によってはクラスター内の他のクライアントがそのホスト名で Pod にアドレス指定できます。たとえば、コントロール サービスがデフォルトの名前空間に属し、名前が foo で、ポッドの 1 つの名前が A-0 である場合、完全修飾ドメイン名 ( ) を介してそのポッドにアクセスできますa-0.foo.default.svc.cluster.localこれは、ReplicaSet を使用して管理される Pod では不可能です。

さらに、DNS を使用して、StatefulSet 内のすべての Pod の名前を検索できます。

ポッドを交換する

StatefulSet によって管理されている Pod インスタンスが消滅すると (Pod が配置されているノードに障害が発生したり、Pod がノードから削除されたり、誰かが手動で Pod オブジェクトを削除したりしたため)、StatefulSet はそれを新しいインスタンスに置き換えることを保証します。 ReplicaSet が行うことと同様です。ただし、ReplicaSet とは異なり、新しい Pod は消滅した Pod と同じ名前とホスト名を取得します (ReplicaSet と StatefulSet の違いは図 10.6 に示されています)。

画像-20230604152740019

新しいポッドは必ずしも同じノードにスケジュールされるわけではありませんが、前に学んだように、ポッドがどのノードで実行されるかは問題ではありません。ポッドが別のノードでスケジュールされている場合でも、以前のホスト名で引き続き利用可能でアクセス可能です。

StatefulSetの拡張と縮小

StatefulSet のサイズを変更すると、次の未使用の序数インデックスを持つ新しい Pod インスタンスが作成されます。インスタンスの数を 2 から 3 に拡張すると、新しいインスタンスにはインデックス 2 が付けられます (既存のインスタンスのインデックスは明らかに 0 と 1 です)。

StatefulSet のサイズ変更 下方スケールの利点は、どのポッドが削除されるかを常に把握できることです。繰り返しますが、これは、どのインスタンスが削除されるかわからない、または最初に削除するインスタンスを指定することさえできない ReplicaSet のダウンサイジングとは対照的です (ただし、この機能は将来導入される可能性があります)。StatefulSet を縮小すると、常に、順序インデックスが最も高いインスタンスが最初に削除されます(図 10.7 を参照)。これにより、ダウンサイジングの効果が予測可能になります。

画像-20230604152855919

一部のステートフル アプリケーションはスケールダウンを適切に処理できないため、StatefulSet は一度に 1 つの Pod インスタンスのみをスケールダウンしますたとえば、複数のノードが同時にオフラインになると、分散データ ストアでデータが失われる可能性があります。たとえば、レプリケートされたデータ ストアが各データ エントリのコピーを 2 つ保存するように構成されている場合、両方のノードが同時にシャットダウンされると、データ エントリが両方のノードに保存されていた場合、データ エントリは失われます。ダウンスケーリングが順次に行われる場合、分散データ ストアは、失われた (1 つの) コピーを置き換えるために、データ エントリの追加コピーを別の場所に作成する時間があります。

したがって、まさにこの理由により、StatefulSet では、インスタンスが異常な場合に操作をスケールダウンすることはできませんインスタンスに異常があり、同時にインスタンスをスケールダウンすると、事実上一度に 2 つのクラスター メンバーが失われます。

10.2.3 各ステートフル インスタンスに安定した専用ストレージを提供する

StatefulSet がステートフル Pod に安定した ID を保証する方法については見てきましたが、ストレージはどのように処理されるのでしょうか? 各ステートフル Pod インスタンスには独自のストレージが必要です。ステートフル Pod が再スケジュールされた場合 (以前と同じ ID を持つ新しいインスタンスに置き換えられた場合)、新しいインスタンスには同じストレージが付属する必要があります。StatefulSet はどのようにしてこれを実現するのでしょうか?

明らかに、ステートフルな Pod のストレージは永続的であり、 Pod から分離されている必要があります第 6 章では、 Pod を参照することで永続ストレージを接続できるようにするPersistentVolumesについて学びました。と は1 対 1 の関係であるため、StatefulSet 内の各 Pod インスタンスは、独自の独立した Pod インスタンスを持つために別の Pod インスタンスを参照する必要がありますすべての Pod インスタンスは同じ Pod テンプレートからコピーされているため、異なるものをどのように参照するのでしょうか? では、誰がこれらのステートメントを作成するのでしょうか?PersistentVolumeClaimsPersistentVolumeClaimPersistentVolumeClaimsPersistentVolumesPersistentVolumeClaimPersistentVolumePersistentVolumeClaim

Pod テンプレートと Volume Claim テンプレートを連携する

StatefulSet は Pod を作成するだけでなく、Pod の作成と同様に Persistent VolumeClaim も作成します。したがって、StatefulSet には、各 Pod インスタンスで Persistent VolumeClaim を生成する 1 つ以上の Volume Claim テンプレートを含めることもできます (図 10.8 を参照)。

画像-20230604153528158

PersistentVolumeClaim の Persistent Volume は、管理者が事前に割り当てることも、Persistent Volume を動的に割り当てることでオンザフライで作成することもできます。

PersistentVolumeClaims の作成と削除

Pod インスタンスを追加することにより、StatefulSet は 2 つ以上の API オブジェクト (1 つは Pod、もう 1 つ以上は Pod によって参照される Persistent VolumeClaims) を作成します。ただし、インスタンスをスケールダウンすると、Pod のみが削除され、PersistentVolumeClaims は変更されません。これは、PersistentVolumeClaim を削除した場合の結果が明らかであるためです。PersistentVolumeClaim が削除されると、それがバインドされている Persistent Volume はリサイクルまたは削除され、その中のデータは失われます。

基盤となる PersistentVolume を解放するには、PersistentVolumeClaim を手動で削除する必要があります。

スケールダウン操作後の PersistentVolumeClaim の永続性は、後続のスケールアップ操作で、バインドされた Persistent Volume とその内容を含む同じ PersistentVolumeClaim を新しい Pod インスタンスに再アタッチできることを意味します (図 10.9 を参照)。StatefulSet が誤って縮小された場合、操作を再度スケーリングすることでこのエラーを元に戻すことができ、新しい Pod インスタンスは同じ永続状態 (および同じ名前) を再び取得します。

画像-20230604153751716

10.2.4 StatefulSet の保証について

通常のステートレス Pod は交換可能ですが、ステートフル Pod は交換できません。ステートフルな Pod は常に同一の Pod (同じ名前とホスト名、同じ永続ストアの使用など) に置き換えられることがわかりました。Kubernetes は、古いポッドがもう存在しないことを検出すると (ポッドを手動で削除するなど)、それを置き換えます。

しかし、Kubernetes がポッドの状態を判断できない場合はどうなるでしょうか? 同じ ID を持つ置換ポッドを作成する場合、システム内で同じ ID を持つアプリケーションのインスタンスが 2 つ実行される可能性があります。また、これらは同じストレージにバインドされるため、同じ ID を持つ 2 つのプロセスが同時に同じファイルに書き込みます。明らかにアプリケーションは同じファイル上で動作するため、ReplicaSet によって管理される Pod ではこれは問題になりません。さらに、ReplicaSet はランダムに生成された ID を使用して Pod を作成するため、同じ ID を持つ 2 つのプロセスが同時に実行される可能性はありません。

したがって、Kubernetes は、同じ ID を持ち、同じ Persistent VolumeClaim にバインドされている 2 つのステートフル Pod インスタンスが同時に実行されないように細心の注意を払う必要がありますStatefulSet は、ステートフル Pod インスタンスに対して少なくとも 1 回のセマンティック保証を提供する必要があります。

これは、StatefulSet は、代替ポッドを作成する前に、古いポッドが実行を停止していることを確認する必要があることを意味します。これはノード障害の処理に重大な影響を及ぼしますが、これについてはこの章で後ほど説明します。

10.3 ステートフルセットの使用

10.3.1 アプリとコンテナイメージの作成

以前の kubia プログラムを拡張して、各 Pod インスタンスの個々のデータ エントリの保存と取得を可能にします。次のように:

const http = require('http');
const os = require('os');
const fs = require('fs');

const dataFile = "/var/data/kubia.txt";

function fileExists(file) {
    
    
  try {
    
    
    fs.statSync(file);
    return true;
  } catch (e) {
    
    
    return false;
  }
}

var handler = function(request, response) {
    
    
  if (request.method == 'POST') {
    
    
    var file = fs.createWriteStream(dataFile);
    file.on('open', function (fd) {
    
    
      request.pipe(file);
      console.log("New data has been received and stored.");
      response.writeHead(200);
      response.end("Data stored on pod " + os.hostname() + "\n");
    });
  } else {
    
    
    var data = fileExists(dataFile) ? fs.readFileSync(dataFile, 'utf8') : "No data posted yet";
    response.writeHead(200);
    response.write("You've hit " + os.hostname() + "\n");
    response.end("Data stored on this pod: " + data + "\n");
  }
};

var www = http.createServer(handler);
www.listen(8080);

アプリケーションは POST リクエストを受信するたびに、リクエスト本文で受信したデータをファイルに書き込みます/var/data/kubia.txtGET リクエストを受信すると、ホスト名と格納されたデータ (ファイルの内容) を返します。

Dockerfile ファイルは次のとおりです。

FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]

10.3.2 StatefulSet を介したアプリのデプロイ

アプリケーションをデプロイするには、2 つ (または 3 つ) の異なるタイプのオブジェクトを作成する必要があります。

  1. データ ファイルの保存に使用される Persistent Volumes (クラスターが Persistent Volume の動的なプロビジョニングをサポートしていない場合は、手動で作成する必要があります)。
  2. StatefulSet に必要な制御サービス。
  3. StatefulSet 自体。

各 Pod インスタンスに対して、StatefulSet は Persistent Volume にバインドされる Persistent VolumeClaim を作成します。クラスターが動的プロビジョニングをサポートしている場合は、Persistent Volume を手動で作成する必要はありません (次のセクションはスキップできます)。サポートされていない場合は、次のセクションの説明に従って作成する必要があります。

永続ボリュームを作成する

StatefulSet を 3 つのコピーに拡張するため、3 つの Persistent Volume が必要になります。StatefulSet をさらに多くのレプリカに拡張する予定がある場合は、さらに Persistent Volume を作成する必要があります。

次のファイルを使用して Persistent Volume を作成します。

# 1. 创建持久卷PV
apiVersion: v1
kind: PersistentVolume
metadata:
  # PV卷名称
  name: mongodb-pv1
spec:
  # 容量
  capacity: 
    # 存储大小: 100MB
    storage: 100Mi
  # 该卷支持的访问模式
  accessModes:
    - ReadWriteOnce # RWO, 该卷可以被一个节点以读写方式挂载
    - ReadOnlyMany  # ROX, 该卷可以被多个节点以只读方式挂载
  # 回收策略: 保留
  persistentVolumeReclaimPolicy: Retain
  # 该持久卷的实际存储类型: 此处使用HostPath类型卷
  hostPath:
    path: /tmp/mongodb1
---
# 1. 创建持久卷PV
apiVersion: v1
kind: PersistentVolume
metadata:
  # PV卷名称
  name: mongodb-pv2
spec:
  # 容量
  capacity: 
    # 存储大小: 100MB
    storage: 100Mi
  # 该卷支持的访问模式
  accessModes:
    - ReadWriteOnce # RWO, 该卷可以被一个节点以读写方式挂载
    - ReadOnlyMany  # ROX, 该卷可以被多个节点以只读方式挂载
  # 回收策略: 保留
  persistentVolumeReclaimPolicy: Retain
  # 该持久卷的实际存储类型: 此处使用HostPath类型卷
  hostPath:
    path: /tmp/mongodb2
---
# 1. 创建持久卷PV
apiVersion: v1
kind: PersistentVolume
metadata:
  # PV卷名称
  name: mongodb-pv3
spec:
  # 容量
  capacity: 
    # 存储大小: 100MB
    storage: 100Mi
  # 该卷支持的访问模式
  accessModes:
    - ReadWriteOnce # RWO, 该卷可以被一个节点以读写方式挂载
    - ReadOnlyMany  # ROX, 该卷可以被多个节点以只读方式挂载
  # 回收策略: 保留
  persistentVolumeReclaimPolicy: Retain
  # 该持久卷的实际存储类型: 此处使用HostPath类型卷
  hostPath:
    path: /tmp/mongodb3
$ kubectl apply -f mongodb-pv.yaml 
persistentvolume/mongodb-pv1 created
persistentvolume/mongodb-pv2 created
persistentvolume/mongodb-pv3 created

$ kubectl get pv
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
mongodb-pv1   100Mi      RWO,ROX        Retain    Available         5s
mongodb-pv2   100Mi      RWO,ROX        Retain    Available         5s
mongodb-pv3   100Mi      RWO,ROX        Retain    Available         5s
管理サービスの作成

前に述べたように、StatefulSet をデプロイする前に、ステートフル ポッドにネットワーク ID を提供するヘッドレス サービスを作成する必要があります。次のリストはサービスの定義を示しています。

# kubia-service-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  clusterIP: None
  selector:
    app: kubia
  ports:
  - name: http
    port: 80
StatefulSet 構成ファイルを作成する

設定ファイルは次のとおりです。

# kubia-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kubia
spec:
  serviceName: kubia
  replicas: 2
  selector:
    matchLabels:
      app: kubia # has to match .spec.template.metadata.labels
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia-pet
        ports:
        - name: http
          containerPort: 8080
        volumeMounts:
        - name: data
          mountPath: /var/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      resources:
        requests:
          storage: 1Mi
      accessModes:
      - ReadWriteOnce

StatefulSet マニフェストは、前に作成した ReplicaSet マニフェストまたは Deployment マニフェストと大きな違いはありません。違いは、volumeClaimTemplates リストです。その中で、data という名前のボリューム要求テンプレートを定義します。これは、各ポッドの Persistent VolumeClaim を作成するために使用されます。ポッドは、マニフェストにpersistentVolumeClaimを含めることによってボリュームスペースを適用します。以前のポッド テンプレートでは、そのようなボリュームは見つかりません。

ステートフルセットを作成する
$ kubectl create -f kubia-statefulset.yaml
statefulset.apps/kubia created

$ kubectl get pod
NAME      READY   STATUS    RESTARTS   AGE
kubia-0   1/1     Running   0          57s
kubia-1   1/1     Running   0          39s

ポッドは、前のポッドが作成されるまで待機してから作成します。

生成されたポッドをテストする

StatefulSet がポッド テンプレートと Persistent VolumeClaim テンプレートに基づいてポッドを構築する方法を理解するために、以下のリストの最初のポッドの仕様を詳しく見てみましょう。

$ kubectl get po kubia-0 -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2023-06-04T08:23:10Z"
  generateName: kubia-
  labels:
    app: kubia
    controller-revision-hash: kubia-c94bcb69b
    statefulset.kubernetes.io/pod-name: kubia-0
  name: kubia-0
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: StatefulSet
    name: kubia
    uid: fe22c872-ed23-4497-aa63-489b28eb4ae3
  resourceVersion: "3898989"
  uid: 4d66ebd8-014c-4cb2-ae78-9e94d925b133
spec:
  containers:
  - image: luksa/kubia-pet
    imagePullPolicy: Always
    name: kubia
    ports:
    - containerPort: 8080
      name: http
      protocol: TCP
    resources: {
    
    }
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/data
      name: data
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-zm4s7
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  hostname: kubia-0
  nodeName: yjq-k8s2
  preemptionPolicy: PreemptLowerPriority
  priority: 0
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {
    
    }
  serviceAccount: default
  serviceAccountName: default
  subdomain: kubia
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - name: data
    persistentVolumeClaim: # 由StatefulSet创建的volume
      claimName: data-kubia-0
  - name: kube-api-access-zm4s7
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace

PersistentVolumeClaim テンプレートは、Persistent VolumeClaim と、作成された Persistent VolumeClaim を参照するポッド内のボリュームを作成するために使用されます。

$ kubectl get pvc
NAME           STATUS   VOLUME        CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-kubia-0   Bound    mongodb-pv1   100Mi      RWO,ROX                     6m15s
data-kubia-1   Bound    mongodb-pv2   100Mi      RWO,ROX                     5m57s

PersistentVolumeClaim が実際に作成されていることがわかります。

10.3.3 ポッドを使って遊ぶ

APIサーバーを使用してPodと通信する

API サーバーの便利な機能は、個々のポッドに直接接続をプロキシできることです。kubia-0 ポッドへのリクエストを実行する場合は、次の URL を使用できます。

<apiServerHost>:<port>/api/v1/namespaces/default/pods/kubia-0/proxy/<path>

プロキシを直接開く方が便利です。

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

kubectl プロキシを介して API サーバーと通信するため、実際の API サーバーのホストとポートの代わりに localhost:8001 が使用されます。次のように kubia-0 ポッドにリクエストを送信します。

$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
You've hit kubia-0
Data stored on this pod: No data posted yet

応答は、kubia-0 ポッドで実行されているアプリケーションによってリクエストが実際に受信され、処理されたことを示しています。

API サーバー経由でポッドと通信し、kubectl プロキシ経由で API サーバーに接続しているため、リクエストは 2 つの異なるプロキシを経由します (1 つ目は kubectl プロキシで、もう 1 つは API サーバーです。ポッドへのプロキシ)。状況をより明確に理解するには、図 10.10 を参照してください。

画像-20230604163639118

ポッドに送信するリクエストは GET リクエストですが、API サーバー経由で POST リクエストを送信することもできます。これは、POST リクエストを GET リクエストと同じプロキシ URL に送信することで実現されます。

アプリケーションは POST リクエストを受信すると、リクエスト本文の内容をローカル ファイルに保存します。POST リクエストを kubia-0 ポッドに送信します。

$ curl -X POST -d "Hey there! This greeting was submitted to kubia-0." localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
Data stored on pod kubia-0

送信したデータはポッドに保存されるはずです。もう一度 GET リクエストを実行して、保存されたデータが返されるかどうかを確認してみましょう。

$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
You've hit kubia-0
Data stored on this pod: Hey there! This greeting was submitted to kubia-0.

さて、ここまでは順調です。次に、他のクラスター ノード (kubia-1 ポッド) が何を返すかを見てみましょう。

$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-1/proxy/
You've hit kubia-1
Data stored on this pod: No data posted yet

予想どおり、各ノードには独自の状態があります。しかし、この状態は持続するのでしょうか?検証してみましょう。

ポッドの状態が永続的かどうかを確認する

ポッドを削除して以下を確認します。

$ kubectl delete pod kubia-0
pod "kubia-0" deleted

$ kubectl get po
NAME      READY   STATUS        RESTARTS   AGE
kubia-0   1/1     Terminating   0          20m
kubia-1   1/1     Running       0          19m

$ kubectl get pod
NAME      READY   STATUS              RESTARTS   AGE
kubia-0   0/1     ContainerCreating   0          14s
kubia-1   1/1     Running             0          20m

同じ名前の Pod が作成されていることがわかります。

この新しいポッドは、必ずしも前のポッドが配置されているノードではなく、クラスター内の任意のノードにスケジュールできます。古いポッドのアイデンティティ全体 (名前、ホスト名、およびストレージ) は、新しいノードに効果的に移動されます (図 10.11 を参照)。

画像-20230604164523118

新しいポッドが実行されたので、以前と同じ ID があるかどうかを確認してみましょう。

$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
You've hit kubia-0
Data stored on this pod: Hey there! This greeting was submitted to kubia-0.

以前に保存されたデータも永続化されていることがわかります。

StatefulSet をスケールする

StatefulSet をスケーリングする方法は、Pod を削除してすぐに StatefulSet から再作成することとあまり変わりません。StatefulSet のスケーリングは Pod を削除するだけであり、PersistentVolumeClaims には影響しないことに注意してください。

StatefulSet を作成するときに Pod を 1 つずつ作成するのと同じように、スケーリング (ダウンまたはアップ) は段階的に行われることを覚えておくことが重要です。複数のインスタンスをスケールダウンする場合、シーケンス番号が最も大きいポッドが最初に削除されます。次に大きいシーケンス番号を持つポッドは、ポッドが完全に終了した後にのみ削除されます。

クラスター内のサービスにアクセスする

Piggyback Pod を使用してクラスター内から Service にアクセスする場合と比較して、API サーバーが提供するプロキシ機能を使用して、同様の方法で Service にアクセスできます。

まず次のサービスを作成します。

# kubia-service-public.yaml
apiVersion: v1
kind: Service
metadata:
  name: kubia-public
spec:
  selector:
    app: kubia
  ports:
  - port: 80
    targetPort: 8080

プロキシ リクエストからサービスへの URI パスの形式は次のとおりです。

/api/v1/namespaces/<namespace>/services/<service name>/proxy/<path>

したがって、次のようにローカル マシンでcurlを実行し、kubectlプロキシ経由でサービスにアクセスできます(以前にkubectlプロキシを実行しており、まだ実行中のはずです)。

$ curl localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
You've hit kubia-0
Data stored on this pod: Hey there! This greeting was submitted to kubia-0.

同様に、(クラスター内の) クライアントは、kubia-public サービスを使用して、クラスター化されたデータ ストアにデータを保存したり、クラスター化されたデータ ストアからデータを読み取ることができます。もちろん、各リクエストはランダムなクラスター ノードに到達するため、毎回ランダムなノードからデータを取得することになります。次のステップで改善を加えます。

10.4 StatefulSet 内のピアの検出

グループ アプリケーションの重要な要件はピア検出機能です。つまり、各 StatefulSet メンバーは他のすべてのメンバーを簡単に見つけられる必要がありますもちろん、API サーバーと通信することでこれを行うことができますが、Kubernetes の目標の 1 つは、アプリケーションが完全に Kubernetes に依存しないようにする機能を提供することです。したがって、アプリケーションが Kubernetes API と通信することはお勧めできません。

では、Pod は API と通信せずにどのようにしてピアを検出するのでしょうか? これを実現できる既存のよく知られたテクノロジーはありますか? ドメイン ネーム システム (DNS) についてはどうですか? DNS についてどれだけ知っているかに応じて、A、CNAME、または MX レコードの目的を知っているかもしれません。さらに、あまり知られていないタイプの DNS レコードが他にもあり、その 1 つが SRV レコードです。

A. CNAME レコードと MX レコードは DNS の一般的なレコード タイプであり、ドメイン名を対応する IP アドレスまたは他のドメイン名に解決するために使用されます。A レコードはドメイン名を IPv4 アドレスに解決するために使用され、CNAME レコードはドメイン名のエイリアスを作成するか他のドメイン名を指すために使用され、MX レコードはこのドメイン名から電子メールを受信するメール サーバーを指定するために使用されます。

SRV レコードは、特定のサービスを提供するサーバーのホスト名とポートを指すために使用されます。Kubernetes は、ヘッドレス サービスの背後にあるポッドのホスト名を指す SRV レコードを作成します。

ステートフル ポッドの SRV レコードは、一時的な新しいポッドで dig DNS ルックアップ ツールを実行するとリストされます。次のコマンドを使用します。

$ kubectl run -it srvlookup --image=tutum/dnsutils --rm --restart=Never -- dig SRV kubia.default.svc.cluster.local

このコマンドは、srvlookup という名前のワンタイム ポッド (--restart=Never) を実行し、コンソール (-it) に接続され、終了後すぐに削除されます (-rm)。このポッドは tutum/dnsutils イメージからコンテナを実行し、次のコマンドを実行しますdig SRV kubia.default.svc.cluster.local

$ kubectl run -it srvlookup --image=tutum/dnsutils --rm --restart=Never -- dig SRV kubia.default.svc.cluster.local
......

;; ANSWER SECTION:
kubia.default.svc.cluster.local. 30 IN  SRV     0 50 80 kubia-1.kubia.default.svc.cluster.local.
kubia.default.svc.cluster.local. 30 IN  SRV     0 50 80 kubia-0.kubia.default.svc.cluster.local.

;; ADDITIONAL SECTION:
kubia-0.kubia.default.svc.cluster.local. 30 IN A 10.244.1.126
kubia-1.kubia.default.svc.cluster.local. 30 IN A 10.244.1.125

;; Query time: 2 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sun Jun 04 12:50:25 UTC 2023
;; MSG SIZE  rcvd: 350

pod "srvlookup" deleted

「ANSWER SECTION」セクションには、ヘッドレス サービスをサポートする 2 つの Pod のホスト名を指す 2 つの SRV レコードが表示されます。追加セクションに示すように、各ポッドには独自の A レコードもあります。

StatefulSet の他のすべての Pod のリストを取得するには、SRV DNS ルックアップを実行するだけです。たとえば、Node.js でこの検索を実行する方法は次のとおりです。

dns.resolveSrv("kubia.default.svc.cluster.local", callBackFunction);

アプリケーションでこのコマンドを使用して、各ポッドがそのピアを検出できるようにします。

返される SRV レコードの順序は、すべて同じ優先度を持つためランダムです。kubia-0 が常に kubia-1 の前にリストされるとは期待しないでください。

10.4.1 DNS を介したピア検出の実装

古いデータ ストアはまだクラスタ化されていません。各データ ストレージ ノードは他のノードから完全に独立して動作し、ノード間で通信は行われません。次に、それらを相互に通信させます。

kubia-public サービスを通じてデータ ストレージ クラスターに接続されているクライアントによって送信されたデータは、ランダムなクラスター ノードに送信されます。クラスターには複数のデータ エントリを保存できますが、現時点ではクライアントはそれらすべてを適切に表示できません。サービスはリクエストをランダムに Pod に転送するため、クライアントがすべての Pod からデータを取得したい場合は、すべての Pod がヒットするまで多くのリクエストを実行する必要があります。

この問題は、ノードがすべてのクラスター ノードのデータを返すようにすることで改善できます。これを行うには、ノードはすべてのピアを見つける必要があります。この目的を達成するには、StatefulSet と SRV レコードについての学習を活用します。

次の例に示すように、アプリケーションのソース コードを変更します。

const http = require('http');
const os = require('os');
const fs = require('fs');
const dns = require('dns');

const dataFile = "/var/data/kubia.txt";
const serviceName = "kubia.default.svc.cluster.local";
const port = 8080;


function fileExists(file) {
    
    
  try {
    
    
    fs.statSync(file);
    return true;
  } catch (e) {
    
    
    return false;
  }
}

function httpGet(reqOptions, callback) {
    
    
  return http.get(reqOptions, function(response) {
    
    
    var body = '';
    response.on('data', function(d) {
    
     body += d; });
    response.on('end', function() {
    
     callback(body); });
  }).on('error', function(e) {
    
    
    callback("Error: " + e.message);
  });
}

var handler = function(request, response) {
    
    
  if (request.method == 'POST') {
    
    
    var file = fs.createWriteStream(dataFile);
    file.on('open', function (fd) {
    
    
      request.pipe(file);
      response.writeHead(200);
      response.end("Data stored on pod " + os.hostname() + "\n");
    });
  } else {
    
    
    response.writeHead(200);
    if (request.url == '/data') {
    
    
      var data = fileExists(dataFile) ? fs.readFileSync(dataFile, 'utf8') : "No data posted yet";
      response.end(data);
    } else {
    
    
      response.write("You've hit " + os.hostname() + "\n");
      response.write("Data stored in the cluster:\n");
      dns.resolveSrv(serviceName, function (err, addresses) {
    
    
        if (err) {
    
    
          response.end("Could not look up DNS SRV records: " + err);
          return;
        }
        var numResponses = 0;
        if (addresses.length == 0) {
    
    
          response.end("No peers discovered.");
        } else {
    
    
          addresses.forEach(function (item) {
    
    
            var requestOptions = {
    
    
              host: item.name,
              port: port,
              path: '/data'
            };
            httpGet(requestOptions, function (returnedData) {
    
    
              numResponses++;
              response.write("- " + item.name + ": " + returnedData + "\n");
              if (numResponses == addresses.length) {
    
    
                response.end();
              }
            });
          });
        }
      });
    }
  }
};

var www = http.createServer(handler);
www.listen(port);

図 10.12 は、アプリケーションが GET リクエストを受信したときに何が起こるかを示しています。まず、リクエストを受信したサーバーは、ヘッドレス kubia サービスの SRV レコード ルックアップを実行し、次にサービスをサポートするすべての Pod (またはそれ自体にさえ、明らかに不要ですが、コードを次のようにしたい) に GET リクエストを送信します。シンプルとして可能)。次に、すべてのノードと各ノードに保存されているデータのリストを返します。

画像-20230604205628599

このアプリケーションの新しいバージョンを含むコンテナ イメージは、 にありますdocker.io/luksa/kubia-pet-peers

10.4.2 StatefulSetの更新

StatefulSet はすでに実行されているので、ポッドが新しいイメージを使用するようにポッド テンプレートを更新する方法を見てみましょう。同時に、レプリカの数も 3 に設定します。StatefulSet を更新するには、kubectl edit コマンドを使用します (patch コマンドは別のオプションです)。

これにより、デフォルトのエディターで StatefulSet 定義が開きます。定義で、changespec.replicas を 3 に変更し、新しいイメージ (luksa/kubia-pet ではなく luksa/kubia-pet-peers) を指すように spec.template.spec.containers.image プロパティを変更します。ファイルを保存してエディタを終了し、StatefulSet を更新します。以前は 2 つのレプリカが実行されていましたが、現在は kubia-2 という名前の追加のレプリカが起動しているのが確認できるはずです。ポッドをリストして確認します。

$ kubectl get pod
NAME      READY   STATUS              RESTARTS   AGE
kubia-0   1/1     Running             0          4h16m
kubia-1   1/1     Running             0          4h36m
kubia-2   0/1     ContainerCreating   0          12s

新しい Pod インスタンスは新しいイメージを実行しています。しかし、既存の 2 つのコピーはどうなるのでしょうか? 年代から判断すると、更新されていないようです。これは正常です。最初は、StatefulSet は Deployment よりも ReplicaSet に似た動作をするため、テンプレートが変更されたときにローリング アップデートを実行しません。コピーを手動で削除する必要があります。そうすると、StatefulSet が新しいテンプレートに基づいてコピーを再作成します。

Kubernetes バージョン 1.7 以降、StatefulSet は Deployments および DaemonSet と同じローリング アップデートをサポートします。詳細については、kubectl Explain コマンドを使用した StatefulSet の spec.updateStrategy フィールドのドキュメントを参照してください。

10.4.3 クラスター化されたデータ ストアを試す

両方のポッドが実行されると、新しい Stone Age データ ストアが期待どおりに動作しているかどうかを確認できます。以下の例に従って、クラスターにいくつかのリクエストを送信します。

$ curl -X POST -d "The sun is shining" localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
Data stored on pod kubia-1

$ curl -X POST -d "The weather is sweet" localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
Data stored on pod kubia-2

次に、以下の例に従って、保存されたデータを読み取ります。

$ curl localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
You've hit kubia-0
Data stored in the cluster:
- kubia-0.kubia.default.svc.cluster.local: Hey there! This greeting was submitted to kubia-0.
- kubia-1.kubia.default.svc.cluster.local: The sun is shining
- kubia-2.kubia.default.svc.cluster.local: The weather is sweet

クライアント要求がクラスター ノードに到達すると、すべてのピアを検出し、それらからデータを収集し、すべてのデータをクライアントに送り返します。StatefulSet を拡張または縮小した場合でも、クライアント リクエストを処理するポッドは、その時点で実行されているすべてのピアを常に見つけることができます。

10.5 StatefulSet がノード障害にどのように対処するかを理解する

セクション 10.2.4 で、Kubernetes は、代替ポッドを作成する前にステートフル ポッドの実行が停止していることを確認する必要があると述べました。ノードに突然障害が発生した場合、Kubernetes はノードまたはそのポッドのステータスを知る方法がありません。ポッドが実行を停止しているのか、それともまだ実行中であり、到達可能な可能性さえあるのかを判断する方法はありません。単に、Kubelet がマスターへのノード ステータスの報告を停止しただけです。

StatefulSet は、同じ ID とストレージを持つ 2 つのポッドが同時に実行されないことを保証するため、ノードに障害が発生した場合、前のポッドが実行を停止したと確信できるまで、StatefulSet は代替ポッドを作成できませんし、作成すべきではありません。

StatefulSet は、クラスター管理者から指示された場合にのみポッドの状態を決定できます管理者は、特定のポッドを削除するか、ノード全体を削除することでこれを行うことができます。これにより、ノード上のすべてのポッドが削除されます。

おすすめ

転載: blog.csdn.net/weixin_47692652/article/details/131037143