アプリの開発

アプリのワークロードを IBM Cloud® Kubernetes Service にデプロイするための構成を開発します。 Kubernetes は、特定の言語またはアプリに依存しない、拡張可能なコンテナー・オーケストレーション・プラットフォームです。そのため、任意の言語で作成されたステートレス・アプリ、ステートフル・アプリ、データ処理アプリなど、さまざまなワークロードを実行できます。

YAML ファイルでのアプリ要件の指定

Kubernetes では、Kubernetes オブジェクトの構成を宣言する YAML ファイルにアプリを記述します。 その後 Kubernetes API サーバーは、その YAML ファイルを処理し、オブジェクトの構成と必要な状態を etcd データ・ストアに保管します。 Kubernetes スケジューラーがクラスター内のワーカー・ノードにワークロードをスケジュールします。このとき、YAML ファイル内の仕様、管理者が設定するクラスター・ポリシー、使用可能なクラスター・キャパシティーが考慮されます。

YAMLファイル全体 』のコピーを確認してください。 その後、以下のセクションを確認して、どのようにすればアプリ・デプロイメントを拡張できるかを理解してください。

デプロイメントで Kubernetes オブジェクトがどのように連携するかについて詳しく説明します。 アプリのための Kubernetes オブジェクトを理解するを確認してください。

基本デプロイメント・メタデータ

デプロイする Kubernetes オブジェクトの種類に適した API バージョンを使用してください。 Kubernetes オブジェクトに対してサポートされる機能のうち使用できる機能は、API バージョンによって決まります。 メタデータで指定する名前はオブジェクトの名前であり、ラベルではありません。 この名前は、オブジェクトと対話するときに使用します (kubectl get deployment <name>など)。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty

レプリカ・セット

アプリの可用性を高めるために、デプロイメントでレプリカ・セットを指定することができます。 レプリカ・セットで、デプロイするアプリ・インスタンスの数を定義します。 レプリカ・セットは Kubernetes デプロイメントによって管理され、モニターされます。 あるアプリ・インスタンスがダウンすると、Kubernetes は新しいアプリ・インスタンスを自動的に開始して、指定された数のアプリ・インスタンスを維持します。

spec:
  replicas: 3

ラベル

ラベルを使用すると、クラスター内のさまざまなタイプのリソースに同じ key: value ペアのマークを付けることができます。 その後、このラベルに突き合わせるセレクターを指定することによって、これらの他のリソースに基づいて処理を進めることができます。 アプリをパブリックに公開することを計画している場合は、サービスで指定するセレクターと一致するラベルを使用する必要があります。 この例では、デプロイメント仕様がラベルに一致するテンプレートを使用しています。 app: wasliberty.

クラスター内のラベル付きオブジェクトを取得して、staging コンポーネントや production コンポーネントを調べるなどの作業を行うことができます。 例えば、クラスター内のすべての名前空間における env: production ラベルの付いたリソースをすべてリストします。 注: このコマンドを実行するには、すべての名前空間に対するアクセス権限が必要です。

kubectl get all -l env=production --all-namespaces
selector:
  matchLabels:
    app: wasliberty
template:
  metadata:
    labels:
      app: wasliberty

アフィニティー

ポッドがどのワーカーノードにスケジューリングされるかをより細かく制御したい場合は、アフィニティ(コロケーション)を指定してください。 アフィニティーはスケジューリング時にのみポッドに影響を与えます。 例えば、同じノードにポッドがスケジュールされるようにするのではなく、各ワーカー・ノード全体にデプロイメントを分散させるには、標準クラスターで podAntiAffinity オプションを使用します。 「優先」と「必須」という 2 つのタイプのポッド・アンチアフィニティーを定義できます。

詳細については、 Kubernetes のドキュメント「ポッドをノードに割り当てる」を参照してください。

必須アンチアフィニティー
ワーカー・ノードと同数のレプリカのみをデプロイできます。 例えば、クラスターに 3 つのワーカー・ノードがある場合は、YAML ファイルに 5 つのレプリカを定義しても、3 つのレプリカしかデプロイされません。 各レプリカは異なるワーカー・ノード上に存在します。 残りの 2 つのレプリカは保留中のままです。 別のワーカー・ノードをクラスターに追加すると、残りのレプリカのうち 1 つが新しいワーカー・ノードに自動的にデプロイされます。 ワーカー・ノードに障害が発生しても、アフィニティー・ポリシーが必要になるため、ポッドはスケジュール変更されません。 「required」が指定されたYAMLの例については、「 required」ポッドのアンチアフィニティを設定したLibertyアプリを参照してください。
優先アンチアフィニティー
使用可能なキャパシティーを備えたノードにポッドをデプロイできるので、ワークロードの柔軟性が向上します。 ポッドはそれぞれ、できるだけ異なるワーカー・ノードにスケジュールされます。 例えば、十分なキャパシティーを備えたワーカー・ノードがクラスターに 3 つある場合は、これらのノード全体に 5 つのレプリカ・ポッドをスケジュールできます。 ただし、クラスタにワーカーノードをさらに2つ追加した場合でも、アフィニティルールによって、既存のノード上で実行中の2つの追加ポッドが、利用可能なノードへ再スケジューリングされることはありません。
ワーカー・ノード・アフィニティー
ベアメタルなどの特定のワーカー・ノードでのみ実行されるようにデプロイメントを構成できます。 詳しくは、ラベルを使用した特定のワーカー・ノードへのアプリのデプロイを参照してください。

優先アンチアフィニティーの例

spec:
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - wasliberty
          topologyKey: kubernetes.io/hostname

コンテナー・イメージ

コンテナーに使用するイメージ、イメージのロケーション、イメージ・プル・ポリシーを指定します。 イメージ・タグを指定しない場合、デフォルトではlatestというタグが付いたイメージがプルされます。

実動ワークロードに latest タグを使用するのは避けてください。 Docker Hub や IBM Cloud Container Registry などのパブリック・リポジトリーまたは共有リポジトリーを使用している場合は、ワークロードを最新イメージでテストしていない可能性があります。

例えば、パブリック IBM イメージのタグをリストするには、以下のようにします。

  1. グローバル・レジストリー・リージョンに切り替えます。
    ibmcloud cr region-set global
    
  2. IBM イメージをリストします。
    ibmcloud cr images --include-ibm
    

デフォルトの imagePullPolicyIfNotPresent に設定され、ローカルに存在しない場合のみイメージがプルされるようになります。 コンテナーが開始されるたびにイメージがプルされるようにするには、imagePullPolicy: Always を指定します。

containers:
- name: wasliberty
  image: icr.io/ibm/liberty:webProfile8
  imagePullPolicy: Always

アプリのサービス用のポート

アプリのサービスを開くために使用するコンテナー・ポートを選択します。 どのポートを開く必要があるかを確認するには、アプリ仕様または Dockerfile を参照してください。 プライベート・ネットワークからポートにアクセスできますが、パブリック・ネットワーク接続からはアクセスできません。 アプリをパブリックに公開するには、NodePort サービス、ロード・バランサー・サービス、または Ingress サービスを作成する必要があります。 Service オブジェクトを作成するときは、これと同じポート番号を使用します。

IBM Cloud のすべてのサービスで、ポート 25 はブロックされています。

ports:
- containerPort: 9080

リソースの要求と制限

クラスタ管理者は、クラスタ内の各 Kubernetes ネームスペースに対して ResourceQuota オブジェクトを作成することで、クラスタを共有する各チームが、計算リソース(メモリとCPU)を公平な割り当て分を超えて使用しないようにします。 クラスター管理者がコンピュート・リソース割り当て量を設定した場合は、デプロイメント・テンプレート内のコンテナーごとに、メモリーと CPU に関するリソースの要求と制限を指定する必要があります。そうしないと、ポッド作成は失敗します。

  1. 名前空間にリソース割り当て量が設定されているかどうかを確認します。
    kubectl get quota --namespace=<namespace>
    
  2. 割り当て量制限を調べます。
    kubectl describe quota <quota_name> --namespace=<namespace>
    

リソース割り当て量が設定されていなくても、デプロイメントにリソースの要求と制限を含めることによって、ワーカー・ノード・リソースの管理を改善することができます。

コンテナーがその制限を超えた場合、コンテナーは再始動するか障害が起こる可能性があります。 コンテナーが要求を超えた場合、ワーカー・ノードが、制限を超えているそのリソースを使い尽くすと、コンテナーのポッドが強制除去される可能性があります。 トラブルシューティングについて詳しくは、ポッドの再始動が繰り返し失敗するまたはポッドが予期せず除去されるを参照してください。

要求
スケジューラーがコンテナー用に予約する最小リソース量。 この量が制限に等しい場合、要求は保証されます。 この量が制限より少ない場合、やはり要求は保証されますが、スケジューラーは要求と制限の差を使用して、他のコンテナーのリソースを充足させることができます。
制限
コンテナーが消費できる最大リソース量。 各コンテナー全体で使用される合計リソース量がワーカー・ノード上の使用可能量を超えると、スペースを解放するためにコンテナーが強制除去されることがあります。 強制除去が行われないようにするには、リソース要求をコンテナーの制限と同じに設定します。 制限が指定されない場合のデフォルトは、ワーカー・ノードのキャパシティーです。

詳細については、『 Kubernetes 』のドキュメントを参照してください。

resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "1024Mi"
    cpu: "1000m"

Liveness Probe と Readiness Probe

デフォルトでは、Kubernetes はポッド内のすべてのコンテナーの開始後にトラフィックをアプリ・ポッドに送信し、コンテナーが異常終了するとコンテナーを再始動します。 ただし、サービス・トラフィック・ルーティングの頑強性を向上させるために、ヘルス・チェックを設定できます。

例えば、アプリの起動が遅い場合があります。 アプリ全体の準備が完全に整う前にアプリ・プロセスが開始する場合があります。これは、特に多数のインスタンスにわたるスケールアップ時に、応答に影響を与えることがあります。 ヘルス・チェックを使用すれば、アプリが実行中で要求を受け取る準備ができているかどうかをシステムに知らせることができます。 これらのプローブを設定すると、アプリのローリング更新を実行するときにダウン時間を回避するのにも役立ちます。 Liveness Probe と Readiness Probe の 2 つのタイプのヘルス・チェックを設定できます。

Liveness Probe
コンテナーが実行中であるかどうかを検査するには、Liveness Probe をセットアップします。 プローブが失敗した場合、コンテナーは再始動します。 コンテナーで Liveness Probe が指定されていなくても、プローブは成功します。コンテナーは Running 状況のときは活動状態であると見なされるからです。
Readiness Probe
コンテナーが要求と外部トラフィックを受け取る準備ができているかどうかを検査するには、Readiness Probe をセットアップします。 プローブが失敗した場合、ポッドに対応するサービスの使用可能 IP アドレスであるポッドの IP アドレスが削除されますが、コンテナーは再始動しません。 アプリの起動に時間がかかる場合は、初期遅延を設定したレディネスプローブを設定することが特に重要です。 初期遅延より前にプローブが開始することはないので、コンテナーに起動時間が与えられます。 コンテナーで Readiness Probe が指定されていなくても、プローブは成功します。コンテナーは Running 状況のときは活動状態であると見なされるからです。

コマンド、HTTP 要求、または TCP ソケットとしてプローブをセットアップできます。 例では、HTTP 要求を使用しています。 Liveness Probe の時間設定は Readiness Probe より長くしてください。 詳細については、『 Kubernetes 』のドキュメントを参照してください。

livenessProbe:
  httpGet:
    path: /
    port: 9080
  initialDelaySeconds: 300
  periodSeconds: 15
readinessProbe:
  httpGet:
    path: /
    port: 9080
  initialDelaySeconds: 45
  periodSeconds: 5

ポッド中断バジェット

アプリの可用性を高めるには、 PodDisruptionBudget オブジェクトを使用して、希望する可用性の種類に応じて、 障害発生時のアプリの動作を制御することができます。

ポッド中断バジェットは、意図的な中断 (ユーザーがアプリのデプロイメントを更新して直接再起動を開始するなど) におけるアプリの動作の計画に使用することも、カーネル・パニックなどの非意図的な中断におけるアプリの動作の計画に使用することもできます。 minAvailable : 中断が発生した後も使用可能でなければならないポッドの数またはパーセンテージを指定できます。

maxUnavailable
中断が発生した後に使用不可になってもよいポッドの数またはパーセンテージを指定できます。 この例では、maxUnavailable: 1 を使用しています。
selector
PodDisruptionBudget が適用されるポッドのセットを選択するために、ラベルを入力してください。 他のポッドのデプロイメントでこの同じラベルを使用した場合、そのポッドも対象になるので注意してください。

詳細については、『 Kubernetes 』のドキュメントを参照してください。

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: wasliberty
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: wasliberty

アプリ・サービスの公開

アプリを公開するサービスを作成できます。 spec セクションで、port とラベルの値を、デプロイメントで使用した値と一致させておきます。 サービスは、ラベル (次の例では app: wasliberty) と一致するオブジェクトを公開します。

  • デフォルトでは、サービスは ClusterIPを使用します。これにより、サービスはクラスタ内からのみアクセス可能となり、クラスタ外からはアクセスできなくなります。
  • NodePort サービス、ロード・バランサー・サービス、または Ingress サービスを作成することで、アプリをパブリックに公開できます。 これらのサービスには 2 つの IP (1 つは外部 IP、1 つは内部 IP) があります。 トラフィックが外部 IP で受信されると、内部クラスター IP に転送されます。 次に、内部クラスター IP からアプリのコンテナー IP にトラフィックがルーティングされます。
  • この例では、NodePort を使用して、クラスター外にサービスを公開します。 外部アクセスのセットアップ方法について詳しくは、NodePort サービス、ロード・バランサー・サービス、または Ingress サービスの選択を参照してください。
apiVersion: v1
kind: Service
metadata:
  name: wasliberty
  labels:
    app: wasliberty
spec:
  ports:
  - port: 9080
  selector:
    app: wasliberty
    type: NodePort

特定のポートで listen するように hostNetwork ポッドをデプロイする必要がある場合、または hostPort を使用してワーカー・ノード上の特定ポートでアプリ・ポッドを公開する必要がある場合は、11000-11200 範囲にあるポートを使用します。IBM Cloud Kubernetes Service は、この目的でワーカー・ノード上の 11000-11200 ポート範囲を指定し、IBM Cloud Kubernetes Service が使用するローカル・ポートや他のポートとの競合を回避します。 hostNetwork ポッドと hostPorts は特定のワーカー・ノードの IP アドレスを参照するので、ポッドはそのワーカー・ノード上に限り実行されるように制限されます。 ワーカー・ノードの削除やリソース不足など、予期しないことが発生した場合、ポッドのスケジュールを変更できません。 ワーカー・ノード上でポッドのポートを公開しようとしている場合は、代わりに NodePort サービスを使用することを検討してください。 詳細については、 ベストプラクティ Kubernetes スのドキュメントを参照してください。

コンテナー環境変数のための構成マップ

構成マップは、デプロイメント・ワークロードの非機密構成情報を提供します。

次の例は、デプロイメント YAML のコンテナー仕様セクションで、構成マップの値を環境変数として参照する方法を示しています。 構成マップの値を参照することにより、その構成情報をデプロイメントから分離して、コンテナー化アプリをポータブルにしておくことができます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  template:
    ...
    spec:
      ...
      containers:
      - name: wasliberty
        ...
        env:
          - name: VERSION
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: VERSION
          - name: LANGUAGE
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: LANGUAGE
        ...
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: wasliberty
  labels:
    app: wasliberty
data:
  VERSION: "1.0"
  LANGUAGE: en

コンテナー環境変数のためのシークレット

シークレットは、デプロイメント・ワークロードのパスワードなどの機密構成情報を提供します。

次の例は、デプロイメント YAML のコンテナー仕様セクションで、シークレットの値を環境変数として参照する方法を示しています。 ボリュームとしてシークレットをマウントすることもできます。 シークレットの値を参照することにより、その構成情報をデプロイメントから分離して、コンテナー化アプリをポータブルにしておくことができます。

クラスター全体におけるすべてのシークレットの集中管理、およびアプリケーション・ランタイムでの注入には、IBM Cloud Secrets Manager を試してください。

apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: wasliberty
  spec:
    replicas: 3
    template:
      ...
      spec:
        ...
        containers:
        - name: wasliberty
          ...
          env:
          - name: username
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: username
          - name: password
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: password
          ...
  ---
  apiVersion: v1
  kind: Secret
  metadata:
    name: wasliberty
    labels:
      app: wasliberty
  type: Opaque
  data:
    username: dXNlcm5hbWU=
    password: cGFzc3dvcmQ=

コンテナー・ストレージ用永続ボリューム

永続ボリューム (PV) は、物理ストレージとインターフェースをとり、コンテナー・ワークロード用の永続データ・ストレージを提供します。

次の例は、アプリに永続ストレージを追加する方法を示しています。 永続ストレージをプロビジョンするには、永続ボリューム請求 (PVC) を作成して、用意するファイル・ストレージのタイプとサイズを記述します。 PVC を作成すると、ダイナミックプロビジョニングによって、パーシステントボリュームと物理ストレージが自動的に作成されます。 デプロイメント YAML で PVC を参照することで、アプリ・ポッドにストレージが自動的にマウントされます。 ポッド内のコンテナーが /test マウント・パス・ディレクトリーにデータを書き込むと、データは NFS ファイル・ストレージ・インスタンスに保管されます。 プロビジョンできるその他のタイプのストレージのオプションについては、可用性の高い永続ストレージの計画を参照してください。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  template:
    ...
    spec:
      ...
      containers:
      - name: wasliberty
        ...
        volumeMounts:
        - name: pvmount
          mountPath: /test
      volumes:
      - name: pvmount
        persistentVolumeClaim:
          claimName: wasliberty
        ...
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wasliberty
  annotations:
    volume.beta.kubernetes.io/storage-class: "ibmc-file-bronze"
  labels:
    billingType: "hourly"
    app: wasliberty
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 24Gi

完全なデプロイメント YAML の例

以下の例は、既にセクションごとに解説されているデプロイメント YAML のコピーです。 また、 GitHub から YAML ファイルをダウンロードすることもできます。

YAML を適用するには、以下のようにします。

kubectl apply -f file.yaml [-n <namespace>]

YAML の例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasliberty
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wasliberty
  template:
    metadata:
      labels:
        app: wasliberty
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - wasliberty
              topologyKey: kubernetes.io/hostname
      containers:
      - name: wasliberty
        image: icr.io/ibm/liberty:latest
        env:
          - name: VERSION
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: VERSION
          - name: LANGUAGE
            valueFrom:
              configMapKeyRef:
                name: wasliberty
                key: LANGUAGE
          - name: username
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: username
          - name: password
            valueFrom:
              secretKeyRef:
                name: wasliberty
                key: password
        ports:
          - containerPort: 9080
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1024Mi"
            cpu: "1000m"
        livenessProbe:
          httpGet:
            path: /
            port: 9080
          initialDelaySeconds: 300
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /
            port: 9080
          initialDelaySeconds: 45
          periodSeconds: 5
        volumeMounts:
        - name: pvmount
          mountPath: /test
      volumes:
      - name: pvmount
        persistentVolumeClaim:
          claimName: wasliberty
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: wasliberty
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: wasliberty
---
apiVersion: v1
kind: Service
metadata:
  name: wasliberty
  labels:
    app: wasliberty
spec:
  ports:
  - port: 9080
  selector:
    app: wasliberty
  type: NodePort
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: wasliberty
  labels:
    app: wasliberty
data:
  VERSION: "1.0"
  LANGUAGE: en
---
apiVersion: v1
kind: Secret
metadata:
  name: wasliberty
  labels:
    app: wasliberty
type: Opaque
data:
  username: dXNlcm5hbWU=
  password: cGFzc3dvcmQ=
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wasliberty
  annotations:
    volume.beta.kubernetes.io/storage-class: "ibmc-file-bronze"
  labels:
    billingType: "hourly"
    app: wasliberty
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 24Gi