TECHSTEP

ITインフラ関連の記事を公開してます。

Kubernetesのマルチテナントの現状を整理する

はじめに

本記事では、Kubernetesで実現するマルチテナントについて、2020年9月時点での現状と、将来的に利用できるであろう機能の紹介をいたします。各機能についての詳細は、参考ドキュメント等を参照していただければと思います。

本記事の要点

  • マルチテナントは単一のクラスター上に複数のテナントを共存させることを指す。

  • Kubernetesにはマルチテナントを実現するための機能が備わっている。

    • アクセスコントロール:RBAC
    • セキュリティ:Namespace / Network Policy / Pod Security Policy
    • リソースの隔離:ResourceQuota / LimitRange / Affinity / Taintなど
  • Kubernetesのマルチテナント機能は、SIGを中心として機能開発が進められている。

    • Benchmarks
    • Tenant Controller
    • Hierarchical Namespace Controller
    • Virtual Cluster

マルチテナントとは

まずマルチテナントの概要を説明します。

マルチテナントについて理解するには、まずテナントとは何かを知る必要があります。テナントとは、CLIAPI・UIなどのインターフェイスを通じて、システムへのアクセス権を持つユーザー・グループの集まりを指します。Kubernetesにおいては、kubectlコマンドやIngress/LoadBalancerなどのServiceリソース、Kubernetes Dashboardなどを通じて、Kubernetesクラスターリソースへのアクセス権を持つユーザー・グループのことを指します。

マルチテナントは、1つの共有リソース上に複数のテナントを共存させるような形態を意味しています。Kubernetesでは、複数のテナントが1つのKubernetesクラスターを共有することをマルチテナントと呼びます。

マルチテナント導入のメリット

マルチテナントを導入することでどんなメリットを得たいかといえば、大きく2つのメリットがあります。

コスト削減

各テナント毎に単一のクラスターを利用しようとすると、クラスターの数に応じて利用するためのコストが発生します。クラウドでは利用した個数と時間だけの利用コストが発生し、オンプレでは機器購入や発注コストが発生します。

また、テナント毎にクラスターを用意すると、各クラスターでのリソース使用率にばらつきが生じ、効率的にリソースを利用することができなくなります。

マルチテナントを採用することで、クラスターの利用にかかる金額やリードタイムといったコストを削減することが可能になります。また1つ、あるいは少数のクラスターを利用することで、リソースの使用効率も上昇します。これらの効果は、テナントの数が増加するほど顕著になり、コストメリットは増加します。

運用負担の軽減

テナント毎にクラスターを用意すると、各テナントに対するオペレーション作業を行う際、各テナントに対する操作を実行するため、操作対象のクラスターを切り替える必要があります。

テナント数が増加するほどオペレーションの負担は増加し、特に少人数で運用を行うチームへの影響が大きくなります。

マルチテナントを採用することでクラスターの切り替えコストを軽減し、運用負荷を軽減することが期待できます。

小規模システムの場合

こちらはシステムの規模が小規模な場合のメリットとなります。開発中、あるいはリリース直後のサービスでは、システムを利用するユーザーも少ないと予想されます。1つのクラスターの持つ処理能力でシステムを運用するのに十分であれば、マルチテナントを導入することでコストを削減できます。

また開発・運用チームへのメリットとして、単一のクラスター上でシステムを運用することで、複数テナントに対しての機能追加を反映しやすい、システム上のボトルネックがどこになるかを早めに予測することがメリットとして考えられます。

※参考リンク:

マルチテナントの種類

マルチテナントには大きく2種類のモデルがあり、リソースを共有するテナントをどうとらえるかによってモデルが変化します。

Soft Multi-Tenancy

Soft Multi-Tenancyの場合、各テナントは「信頼できる」ユーザーの集まりととらえます。例としては、社内の複数開発チームがクラスターを共有する場合が考えられます。Soft Multi-Tenancyでは、操作ミスなどの影響が他のテナントに広がらないよう、テナント間を分離することが主な目的となります。

Hard Multi-Tenancy

Hard Multi-Tenancyの場合、各テナントは「信頼できない」ユーザーの集まりととらえます。例としては、複数の企業やユーザーが利用するサービスをマルチテナントで提供する場合が考えられます。各テナントには悪意ある行動をとるユーザーが含まれると考え、悪意ある行動が他のテナントに影響しないように分離をする必要があります。

Hard Multi-Tenancyを紹介する資料の1つに「Coke & Pepsi on the same k8s cluster」というワードを確認することができますが、これはHard Multi-Tenancyを端的に表すキーワードだなと個人的には感じました。

マルチテナント導入に向けて考えること

マルチテナントを実現するためには、あるテナントでの操作が他のテナントに影響を与えることが無いよう、3つの観点から考える必要があります。

アクセスコントロール

あるテナントのユーザーが、故意か否かに関わらず、他のテナントへのログインができないようにしなければなりません。

各テナントには、利用を許可された特定のユーザーしかログインすることはできないようにする必要があります。また各ユーザーには適切な権限が付与されており、必要以上の操作は実行できないようにします。

セキュリティ

アクセスコントロールとも関連しますが、あるユーザーが他のテナントのリソースにアクセスできないようにする必要があります。

異なるテナント間では互いに利用するリソースは見ることができず、Requestの傍受などによる干渉をすることもできないようにします。またクラスターNodeのカーネルにアクセスし、権限の昇格を行うことも防がなければなりません。

リソースの隔離

特定のテナントが大量のリソースを利用し、他のテナントが利用できるリソース量が少なくなるようなことがあってはなりません。各テナントが利用できるリソースは公平になるよう制御を行う必要があります。またあるテナントがKubernetesに独自のAPI・CRDを追加したときに、他のテナントとの間で競合しないようにする必要もあります。

Kubernetesでのマルチテナント

ここからはKubernetesのマルチテナントに焦点を当てます。

Kubernetesでのマルチテナント提供方法

Kubernetesのマルチテナントの実装の前に、マルチテナントをどのような形で提供するかについて確認したいと思います。Kubernetesでは、マルチテナントを実現するために、複数の機能を組み合わせる必要がありますが、テナントの提供方法を念頭に置きながら眺めることで、どの機能を組み合わせて利用するかを考えやすくなると、個人的には考えています。

Kubernetesでのマルチテナント提供方法には、大きく2つの方法があります。提供方法の違いによって、各テナントがKubernetesにアクセスする際のインターフェイスが変化し、マルチテナントを実現するうえで必要になるKubernetesの機能も変化してきます。

  • Kubernetes環境そのものを提供する場合Kubernetes環境そのものを複数のテナントに提供する場合は、主にkubectlコマンドなどを経由してアクセスすることになり、Kuberentes APIを共有する形になります。例としては、社内の複数開発チームが一つのクラスターを共有する場合などが挙げられます。

  • Kubernetes上で動作するアプリケーションを提供する場合Kubernetes上のアプリケーションを提供する場合、各テナントは主にIngress/LBといったServiceリソースを経由して、Podで稼働するアプリケーションへアクセスすることになります。例としては、複数の企業やユーザーに対して提供するサービスなどを運用する場合が挙げられます。

f:id:FY0323:20200905141308p:plain

マルチテナントの実現

Kubernetesは、もともとマルチテナントを考慮した設計がされていませんでした。OpenStackではUser / Tenant / Projectといったリソースを利用することができますが、Kubernetesにはテナント用のリソースというものは用意されていません。

一方でKubernetesには多くの標準機能が備わっており、その中にはマルチテナントを実現することを助けるようなものも含まれます。ここでは、Kubernetesの機能の中から、マルチテナント実現に利用されるものを紹介します。

アクセスコントロール

RBAC

RBAC(Role-Based Access Control)は、ユーザーに対してロールを付与することで、アクセスできるリソースの種別と操作内容を制御する機能です。Kubernetesにはユーザーとロールにそれぞれ2種類のものが用意されています。

  • ユーザー
    • UserAccountKubernetes外部の独立したサービスが提供するアカウント。クラウドプロバイダーが提供するIAMなどがこれにあたり、KubernetesにはUserAccountに該当するリソースは存在しない。
    • ServiceAccountKubernetes内部でのみ利用できるアカウント。各リソースを操作するにはServiceAccountを指定する必要があり、指定がない場合はdefaultのアカウントが利用される。
  • ロール
    • Role:後述するNamespaceに対しての権限を付与するために利用する。
    • ClusterRole:特定のNamespaceだけでなく、Namespaceをまたがるようなもの、クラスタースコープなリソースに対しても権限を付与することができる。

以下の図では、あるユーザーにPodへのアクセスコントロールを設定し、Podの情報取得(kubectl getコマンド)は実行できるが、Podの作成(kubectl createコマンド)は実行できないような場合を例として載せています。

f:id:FY0323:20200905142201p:plain

※参考リンク:

セキュリティ

Namespace

NamespaceはKubernetesの環境を分離するうえで重要となる単位です。Kubernetesで利用できる機能はNamespace単位で適用するものが多く、セキュリティや認証の境界として利用されます。

KubernetesではNamespaceを利用することで仮想的なクラスターの分離を実現します。Namespaceは主に2つの機能があります。

  • リソース名の境界:Namespace内では一意なリソース名しか利用することができません。一方でNamespaceが異なれば同一のリソース名を利用することができます。
  • 認証・ポリシーの境界Kubernetesの提供する認証。ポリシー管理機能の多くはNamespaceを単位として利用します。前述のRBACや後述する各ポリシーの多くは、Namespaceを指定して作成することで有効にできます。

※参考リンク:

Network Policy

Network Policyは、クラスター内のPod間の通信を制限する、FWのような機能を提供します。Network PolicyではPodに付与するLabelや送信元・宛先アドレスやポートを利用し、特定のPod間の通信制御を行うことが可能です。

Network PolicyはNamespaceごとに作成する必要があり、またCNIによってはNetwork Policyを利用できない場合があります。Network Policyを利用する場合は、事前に適用可能なCNI(例:Calico、Cilium、Weave Netなど)を用意する必要があります。

以下の図では、特定のNamespaceに対してNetwork Policyを適用し、app: webLabelの付与されたPodからのInbound通信のみを許可した場合を載せています。

f:id:FY0323:20200905142738p:plain

※参考リンク:

Pod Security Policy

Pod Security Policyは、Podに対するセキュリティポリシーを設定するために利用します。設定できる内容には、以下のようなものがあります。

パラメータ名 機能
privileged 特権コンテナを利用するか否か
hostNetwork ホストネットワークを利用するか否か
readOnlyRootFilesystem ルートファイルシステムをread onlyにする
selinux コンテナ内のSELinuxの設定

Pod Security Policyはクラスター全体に対して適用するポリシーです。RBACと組み合わせることで、ユーザーごとに適用するポリシーを変更することができます。

※画像:sysdig blogより

psp

※参考リンク:

リソースの隔離

ResourceQuota / LimitRange

ResourceQuota/LimitRangeは、Pod/Container/PVCなどのリソースが利用できるリソース使用量の制限を行います。ResourceQuotaはNamespaceに適用し、各Namespace内のリソースの合計値を制限するのに利用します。LimitRangeは各リソース個別のリソース使用量を制限します。

制限可能なリソースとしては、各PodのCPU/メモリ利用量の上限・下限、Deployment/Service等リソース種別ごとの個数の上限などがあります。

f:id:FY0323:20200905143310p:plain

※参考リンク:

Affinity/Anti-Affinity

Affinity/Anti-Affinityは、Podのスケジューリングに関する条件を付与する機能です。AffinityはNode/Podに付与したLabel情報を元に、Podを特定のNode、あるいは特定のPodが存在するNodeにスケジュールする仕組みです。

Affinityが未指定の場合、利用できるすべてのNodeがスケジュール候補となります。また、Podがスケジュールされた後にスケジュールポリシーが変更された場合も、スケジュールされたPodはそのまま同じNodeに残り続けます。

以下の図は、disktype: hddのLabelが付与されたNodeの中から、kubernetes.io/hostname: node2のものを優先してスケジュールするように設定した例になります。

f:id:FY0323:20200905145404p:plain

※参考リンク:

2020/9/6追記:Affinity/Anti-Affinityは、マルチテナントを実現するうえでは必ずしも利用する機能ではありませんが、「Kubernetesが備えているリソースの隔離機能」という括りで紹介いたしました。

「AffinityやTaintはPod を作る側に決定権があるため、マルチテナントのための機能とは毛色が違う」というコメントをいただいたため、こちらに追記いたしました。

Taint/Toleration

前述のAffinity/Anti-Affinityが特定のNodeにPodをスケジュールするための仕組みだったのに対し、Taint/Tolerationは特定のNodeにPodがスケジュールされるのを避ける仕組みになります。NodeにはあらかじめTaint(汚れ)を付与しておき、Taintに対するToleration(耐性)を持つPodだけがそのNodeにスケジュールされ、条件に一致しないPodはスケジュールされません。

利用例としては、本番環境専用のNodeやGPU等を備えた特殊なNodeを利用する場合が挙げられます。

PodにTolerationを指定しない場合、Taintの設定されたNodeはスケジュール対象外となります。また、Taint/Tolerationの場合、後からスケジュールポリシーが変更されると、条件に一致しないPodは別のNodeへ退避されます。

以下の図では、env=prd:NoScheduleというTaintを付与したNodeにスケジュールされるよう設定したPodの例を載せています。

f:id:FY0323:20200906145420p:plain

※参考リンク:

2020/9/6追記:Taint/TolerationもAffinity/Anti-Affinityと同様、マルチテナントを実現するうえでは必ずしも利用する機能ではありませんが、「Kubernetesが備えているリソースの隔離機能」という括りで紹介いたしました。

クラウドベンダーでの対応

マネージドなKubernetesサービスを提供するクラウドプロバイダーは、Kubernetesの標準機能に加え、各プロバイダーの提供するサービスを利用したマルチテナント構成の例を紹介しています。ここでは、代表的なクラウドプロバイダー3社のドキュメントから、特徴的な機能を簡単に紹介します。

Amazon Web Services (EKS)

  • IAMとRBACの連携AWS EKSはAWS IAMとRBACを統合して、IAMユーザーとロールに対してRBACを適用することが可能です。
  • EKS on Fargateの利用:Fargateを利用することで、利用者はData Planeを管理する必要がなくなります。また各Podはそれぞれ別のVM上で動作するため、Pod間でリソースを共有せず、Pod間隔離が強化されます。
  • AWS App Mesh:マネージド型のサービスメッシュであるApp Meshを利用することで、ネットワークトラフィックの制御を行うことができます。

※参考リンク:

Google Cloud (GKE)

  • IAMとRBACの連携
  • Project/FolderGoogle Cloudの管理単位であるプロジェクト、複数のプロジェクトを束ねるフォルダを利用することで、管理上の問題を分割することができます。
  • 共有VPCの利用:共有VPCを利用することで、プロジェクトの境界を超えたネットワークリソースの管理が可能になります。
  • GKE Sandbox:GKE Sandboxを利用することで、GKE上でgVisorを利用し、コンテナランタイムセキュリティを強化することができます。
  • Workload IdentityKubernetesGoogle Cloudサービスへアクセスするのに使用するServiceAccountを、Google Cloud ServiceAccountと紐づけ、セキュリティや管理性を向上します。

※参考リンク:

Microsoft Azure (AKS)

  • Azure ADとRBACの連携
  • Azure PolicyAKS専用のポリシーを利用し、セキュリティを強化します。
  • Azure AD Pod Identity:Azure ADと連携して、PodがAzureサービスにアクセスすることを可能にします。

※参考リンク:

Kubernetesディストリビューションでの対応

Kubernetesディストリビューションには、マルチテナントに対応するための機能を備えたものが存在します。

例えばRancherではProjectという機能があり、複数のNamespaceを束ね、一括でポリシーを適用することが可能です。これにより、マルチテナントで発生するであろう大量のNamespaceの管理とポリシーの適用にかかる負担を軽減することができます。

※画像:Rancherドキュメントより

rancher-project

将来Kubernetesが実現するマルチテナント実現方法

ここまでで、Kubernetesのマルチテナントに関する現状を眺めてきました。ここからは、将来的に利用できるであろうKubernetesのマルチテナント実現機能について見ていきます。

Kubernetesコミュニティについて

Kubernetesのマルチテナントについて触れる前に、Kubernetesとコミュニティのかかわりについて少しだけ触れておきます。

Kubernetesの開発や運用は巨大なコミュニティによって支えられており、その中にはSIG(Special Interest Group)というグループが存在します。SIGはKubernetesの特定のトピックに焦点を当てて議論や実装を行うグループであり、2020年9月時点で100以上のグループが存在します。

SIGにはmulti-tenancyというグループも含まれており、ここではKubernetes上でのマルチテナントに関しての議論や開発が行われています。

※参考リンク:

マルチテナントアーキテクチャのパターン

ここからは、multi-tenancy SIGの活動の中から幾つかのトピックについて紹介します。

上述のmulti-tenancy SIGでは、マルチテナントを実現するアーキテクチャを4つ提言しています。4つのアーキテクチャの概要は、以下の画像の通りです。2019年時点(現在も?)では4つのアーキテクチャのうち、パターンB、Tenant+Namespaceのアーキテクチャを実現することに焦点を当てていたようで、それに関わるプロダクトの開発も行われています。

※画像:KubeCon US 2019スライドより

multi-tenant-architecture

  1. Hypervisor上に複数のKubernetesクラスターを作成:HyperVisor上に複数のKubernetesクラスターを構築し、テナントごとにクラスターを提供する。リソースの利用効率は低いが、テナント間の隔離レベルは高い。
  2. TenantによるNamespaceのグルーピング:テナントごとにTenantというリソースを用意し、複数のNamespaceを管理する。リソースの利用効率やテナント間の隔離レベルは比較的高いが、クラスタースコープなリソースに対する制約がある。
  3. Virtual Kubernetes ClusterKubernetesクラスター上に複数の仮想Kubernetesクラスターを作成し、テナントごとに仮想クラスターを提供する。テナント間の隔離レベルも高く、大きな制約は存在しない(と思われる)が、現時点ではアーキテクチャとしての成熟度が低い。
  4. Kubernetesのコア機能の変更Kubernetesのコアの設計を変更し、Tenantをファーストクラスのリソースとして追加する。まだ構想段階で、デザインも存在しないと思われる。

開発プロダクトの紹介

multi-tenancy SIGでは、現在4つの機能・プロダクトを開発しています。

Benchmarks

Benchmarksは、あるクラスターがマルチテナント向けに設定されているかをテストするベンチマークを提供します。ベンチマークには、クラスターの設定ファイルをチェックするタイプと、テスト用Namespace内での振る舞いを見る振る舞いチェックの2種類が存在し、複数のカテゴリに分かれたチェック項目をテストします。現時点では開発中のフェーズですが、E2Eのバリデーションテストも提供されています。

※参考リンク:

Tenant Controller

Tenant Controllerは、クラスター内でTenantリソースを実現し、複数のNamespaceを束ねる機能を提供するコントローラーです。前述のRancher Projectのような機能だと考えてもらえると、分かりやすいかもしれません。

Tenant Controllerは主に2つのCustom Resourceをコントロールします。

  • Tenant:複数のNamespaceを含むCustom Resource。Tenant AdminというServiceAccountをリソース内で指定することで、RBACを自動的に更新し、Tenantへのアクセスを管理します。

  • TenantNamespace:Tenantで利用するNamespaceを明示するCustom Resource。

またPoCではNamespaceTemplateというCustom Resourceも存在し、Namespaceとそこで利用するポリシーなどを定義することができます。TenantNamespaceNamespaceTemplateを指定することで、Namespaceに対するポリシーを適用したTenantを用意することができます。

※参考リンク:

Hierarchical Namespace Controller

Hierarchical Namespace Controller(HNC)は、名称の通りNamespaceを階層構造に管理する機能を提供します。Namespaceを階層構造にすることで、従来複数のNamespaceを管理するうえで問題となった以下のような点を解消することを目指しています。

複数のNamespaceに対するポリシーの適用

複数のNamespaceに対して共通のポリシーを適用する場合、これまでは全てのNamespaceに対するポリシー適用作業が必要となっていました。これはNamespaceが多くなるほど影響が大きくなり、開発チームや組織、サービスが拡大するにつれて顕著になります。

HNCは親Namespaceに適用したポリシーを子Namespaceに伝播することが可能であり、複数Namespaceに対する共通ポリシーの適用が容易になります。また親Namespaceから伝播したポリシーは、子Namespace側から削除することはできず、ポリシーの強制力を持たせることもできます。

なお、親Namespaceから子Namespaceへ伝播するリソース種別はHNCConfigurationで指定することが可能です。

Namespace作成時の権限

Namespaceを作成するにはクラスターレベルの権限が必要であり、権限を制約されたメンバーがNamespaceを作成するには都度管理者への作業申請が必要でした。

HNCでは、子Namespace(Subnamespace Custom Resource)の作成にはクラスターレベルの権限が不要のため、申請等の作業を挟まずリソースの作成が可能になります。

※画像:KubeCon US 2019スライドより

hnc-example

※参考リンク:

Virtual Cluster

従来のKubernetesマルチテナントにおける課題の一つに、Control Planeの分離能の問題があります。

各テナントで同一のKubernetes APIを共有する場合、例えば1つのテナントから大量のリクエストがAPIに向けて飛んでくると、他のテナントからのリクエストに応答できない可能性があります。これは特定のテナントからのDoS攻撃を受け、それが他のテナントにも影響した状態であるため、マルチテナントの目指す状態(個々のテナント間での操作はほかのテナントに影響しない)とは異なる様子であると言えます。

このようなControl Planeに関する問題を解消するため、各テナント毎に仮想のControl Planeを用意し、Control Planeの分離能を高めることを目指したのがVirtual Clusterです。

Virtual Clusterは前述のTenant Controllerを利用してテナント毎にTenant Custom Resourceを用意し、各テナント毎にkube-apiserver / etcd / controller managerなどを作成します。各テナント内での操作は、仮想クラスターによって実現され、各テナントの情報はSuper Master Kubernetesと同期されます。またテナント間の通信はSyncerというコンポーネントによって制限されるため、クラスタースコープのリソースを、他のテナントに影響することなく作成することができます。

※画像:KubeCon Europe 2020スライドより一部抜粋

f:id:FY0323:20200905170403p:plain

※参考リンク:

参考ドキュメント

CephのBest Practiceを探る④:RHCSの更なるパフォーマンス調査

はじめに

本記事はCephのBest Practiceを探る第4弾になります。今回も先日紹介した記事の続編として、Cephの公式ブログで公開されているこちらの記事の内容を紹介いたします。今回も記事内容への補足や個人的なメモは青文字で加えております。なお、本記事を含めてこれまで4回ほど続けてきたこのシリーズは、本記事執筆時点ではこれで最後となります。

ceph.com

要点

  • Part 4では、Part 1からPart 3まで検証したことに加え、様々な観点から行ったパフォーマンス検証の結果をまとめています。
  • 1デバイスあたりのOSD数を変更した結果、2OSDが理想的な比率であり、4OSDの場合はCPU使用率が増加したにもかかわらずIOPSがそこまで改善されませんでした。
  • バイスあたりのCPU Coreを追加した結果、Random Write、Random Read-WriteのIOPSが改善されました。またBlueStoreではCPU CoreとNVMeとの比率は6:1が適切な比率であるとわかりました。
  • RHCSのPool Replica数を少なくした結果、IOPSとレイテンシの改善が見られました。一方Random Readのパフォーマンスは改善が見られませんでした。
  • BlueStoreキャッシュ数を8GBまで増強した結果、IOPSとレイテンシの改善が見られました。
  • Intel Optane P4800xをWAL/RocksDBデバイスとして利用した結果、IOPSとレイテンシの改善が見られました。

検証環境

今回の検証環境は、これまで利用してきたRHCSの環境を引き続き利用します(詳細はPart-1を参照)。

1. 4 OSDs vs. 2 OSDs per NVMe Device

Ceph FileStore OSDを利用する際、デバイスあたりのOSD数は4と設定されることが一般的でした。BlueStoreが開発されたことでOSDあたりのパフォーマンスが向上し、1つのデバイスに2つ以上のOSDを設定することでの効果が少なくなってきました(※1参照)。このベンチマークでは、(BlueStore利用時)デバイスあたりのOSD数は「2」が最適である、という仮説を立て、それを検証しました。

※1:OSDあたりのパフォーマンスが向上し、利用するデバイスの出力できるパフォーマンスの「上限」に達するまでのOSD数が減少した、ということ?

検証の結果は以下のグラフの通りとなります。2つのグラフは横軸にブロックサイズを、縦軸にはIOPSとCPU使用率・レイテンシの結果をそれぞれプロットしています。

Graph 1:2OSD vs 4OSD(CPU使用率)

graph1

Graph 2:2OSD vs 4OSD(レイテンシ)

graph2

上記グラフから、次のことが言えます。

  • Graph 1より、OSD数を変更してもIOPSにはほとんど変化が見られないことがわかります。また、IOPSはほとんど変わらないにもかかわらず、CPU使用率は2OSDのほうが少ないことがわかります(※2参照)。
  • Graph 2より、2OSDのほうがレイテンシも少ないことが分かります(※3参照)。
  • これらの結果は、本検証時に建てた仮説(BlueStore利用時は、デバイスあたりのOSD数をより少なくしたほうが良い)と一致するものといえます。

※2:Graph 1を見ると、CPU使用率は2OSDのほうが常に少ないですが、ブロックサイズが小さいときはほとんど違いはなく、ブロックサイズが16k ~ 64k(Part 2の区分けで言う”Middleブロックサイズ”に相当)の時に大きな違いが見られます。そのため、OSDのCPU使用率については、利用するブロックサイズも大きく影響していると言えそうです。

※3:Graph 2を見ると、ブロックサイズが64kの時は、2OSDのTailレイテンシは4OSDのものと比べて大きく減少していることが見られますが、それ以外はほとんど違いが見られず、ブロックサイズが16k~32kの時には逆に2OSDのほうがTailレイテンシは増加しています。このため、Graph 1と同様、OSDあたりのレイテンシについても、ブロックサイズが大きく影響していると言えそうです。

2. CPU Core to NVMe Ratio

ここでは、NVMeデバイスあたりの物理的CPUコア数の適切な比率を求めることを目的としています。検証の結果は、以下のグラフの通りです。横軸にデバイスあたりのコア数、縦軸はワークロードごとのIOPSを表しています。なおIO Depthは32で固定です。

Graph 3:Physical Cores per NVMe

graph3

Table 1:デバイスあたりのコア数の変化によるIOPSの改善率

Workload IOPS増加率 (4->6) IOPS増加率 (6->8)
Random Read +31.96% +0.51%
Random Read-Write +48.71% +17.15%
Random Write +51.66% +19.60%

上記グラフより以下のことがわかります。

  • Random Read-Write、Random Writeワークロードでは、デバイスあたりのCPUコア数を増加させるにしたがって、IOPSが向上しました。これは以前Smallブロックサイズで行った検証と一致する結果です(※4参照)。
  • Random Readでは、CPU数を6から8に変更したときにほとんど変化しませんでした。以前の検証結果を思い出してみると、メディア使用率は88%まで上昇していたため、これが影響している可能性もあります(※5参照)。
  • 以上の結果から、BlueStore利用時は、デバイスあたりのコア数を「6」に設定するのが最も適切であると分かりました。6以上の数値にしても効果が少なく、コスト的に見合わなくなる可能性があります。また、FileStore利用時は、デバイスあたりのコア数は「10」がよく使われていたことから、BlueStoreを利用することで、パフォーマンスの改善だけでなく、システムリソースの削減にもつながることが分かります。

※4:Part 2のGraph 2の結果に対して、CPUリソースをより多く与えることでパフォーマンスが改善される可能性がある、という考察が記載されていました。

※5:Part 2のGraph 3の結果を受けての記載となります。

3. Pool Replica 2 vs. Pool Replica 3

Red Hat Storage Performance teamによる検証によると、Pool Replica数が2の時は、Pool Replica 3の時と比べ、Ceph OSDが書き込むデータ量が少なくなり、パフォーマンスが向上する、という結果が出ています。しかし一方で、(今回の検証環境である)All-Flash Cephクラスターにおいて、Pool Replica数が2と3を比べたとき、IOPSやレイテンシがどれほど変化するかを知る必要もあります(※6)。

※6:「Pool Replica数を減少させることでパフォーマンスが向上する」という報告はあるが、具体的にどの値がどの程度向上するのかがわからないために実施する、と解釈しています。

Pool Replica数の比較結果は以下のグラフの通りです。

Graph 4:Pool Replica 2 vs Pool Replica 3

graph4

Table 2:Pool Replica 2・3のパフォーマンス改善率 (4K ブロックサイズ)

Workload IOPS 平均レイテンシ Tailレイテンシ (95%) Tailレイテンシ (99%)
Random Read -2.25% +2.09% +1.68% +3.10%
Random Read-Write -27.43% +60.56% +53.42% +51.39%
Random Write -29.08% +51.45% +52.49% +50.96%

上記グラフから以下のことがわかります。

  • Pool Replica数を3から2に減らすことで、(Random Read-Write・Random Writeワークロードの)IOPSは約30%向上し、平均・Tailレイテンシは約50%減少しました(※7参照)。
  • Pool Replica数を少なくすることでパフォーマンスが改善されることは分かったが、Pool Replicaサイズはそれを利用するメディアによって選択する必要があります。FlashメディアはHDDメディアに比べ、MTBF(Mean Time Between Failure)やMTTR(Mean Time To Recovery)は劇的に低くなります。一般にPool Replica数が2の時はFlashメディアを、Pool Replica数が3の時はHDDメディアを使用するのが安全と考えられていますが、それはストレージシステムの設計によって異なります。

※7:Japan Rook Meetupのこちらの発表でも、Replica数を少なくすることで、特にWriteパフォーマンスが向上する、という内容のものがありました。

4. BlueStore 8GB Cache vs BlueStore 4GB Cache

Ceph BlueStoreのRBDワークロードでは、BlueStore Cacheサイズがパフォーマンスに大きな影響を与えます。Onode Cachingは階層構造であり、Onodeがキャッシュされていない場合はDBディスクから読み取られてKV Cache(RocksDBのブロックキャッシュに利用するキャッシュ領域)へ格納し、最終的にはOnode Cacheへ格納されます(※8参照)。

データセット内のすべてのOnodeがBlueStoreブロックキャッシュに収まる場合、OnodeはDBディスクから読み取られることがなく、従ってKV Cacheへ格納されることもありません。これがRBDワークロードでは最適なシナリオとなります。しかし一方で最悪のシナリオとして考えられるのが、Onodeをディスクから読み取ることでKV Cache・BlueStore Cacheの両方に新しいOnodeデータを格納し、後から読み込まれる可能性のある古いOnodeを強制的に削除してしまう場合です(※9参照)。

※8:BlueStoreでは、すべてのメタデータはKV Database (RocksDB)へ格納されます。BlueStoreのメタデータは複数の種類があり、それらはNamespaceで分けられています。Onodeはオブジェクト毎のメタデータを指しており、Onode Cacheにキャッシュとして保存することで高速なパフォーマンスを実現します。Onodeがキャッシュされていない場合はRocksDBに格納されているデータを読み取ってきます。

※9:BlueStoreブロックキャッシュのサイズが大きければ、全てのOnodeがキャッシュに収まることもありますが、BlueStoreブロックキャッシュのサイズが小さい場合、新しいOnode Cacheが格納できず、DBディスクから読み取ることになります。それによって、もともとキャッシュされていた古いOnodeが強制的に削除されることになります。

※参考ドキュメント:

BlueStore Cacheサイズを変更した場合の検証結果は、以下のグラフのようになります。

Graph 5:BlueStore 8GB Cache vs BlueStore 4GB Cache

graph5

Table 3:BlueStore Cacheサイズによる改善率 (4Kb ブロックサイズ)

Workload IOPS 平均レイテンシ Tailレイテンシ (95%) Tailレイテンシ (99%)
Random Read +14.43% -24.57% -25.43% -61.76%
Random Read-Write +30.52% -32.62% -52.12% -11.60%
Random Write +15.40% -19.10% -24.31% -28.68%

上記グラフから、BlueStore Cacheサイズを増加することで、IOPS・レイテンシともに改善すること、特にRandom Read-Writeワークロードでは、IOPSが約30%向上、Tailレイテンシが約50%軽減されたことがわかります(※10参照)。

※10:グラフ中にはTailレイテンシ (95%)のプロットがないため、実際にはグラフから読み取ることはできないのですが、全てのワークロードにおいて、IOPS、平均・Tailレイテンシ (99%)は8GBのほうが低い値となっています。このため、少なくとも「Cacheサイズを増加することでパフォーマンスが改善される」ことは間違いなく言えそうです。

5. Optane vs No Optane as the WAL/RocksDB device

ここでは、Intel Optane P4800XをBlueStoreのメタデータバイスとして利用すると、パフォーマンスが改善するかを検証しています。 

Part 1にある検証環境では、Ceph Data用デバイスIntel P4500、MetadataデバイスにP4800Xを採用していますが、ここでは、MetadataデバイスをP4500にした場合とP4800Xにした場合とで比較しています。

検証の結果は以下のグラフの通りです。

Graph 6:Optane vs No Optane

graph6

Table 4:Optaneによるパフォーマンス改善率 (4Kbブロックサイズ)

Workload IOPS 平均レイテンシ Tailレイテンシ (95%) Tailレイテンシ (99%)
Random Read +4.06% -1.05% -2.46% -2.29%
Random Read-Write +9.55% -3.83% -4.57% -4.01%
Random Write +7.23% -4.40% -13.08% -13.82%

上記グラフより、Optane P4800Xは全てのワークロードにおいてIOPS、平均・Tailレイテンシの改善が見られること、特にRandom WriteのTailレイテンシで大きな改善が見られることがわかります。

予測可能なパフォーマンスを達成することは本番環境のデータベースにおいては重要で、特にTailレイテンシに対してとてもセンシティブなものです。検証でP4800Xを利用した際、Tailレイテンシ(99%)のパフォーマンスは変動幅が小さいことが確認されました。このため、Optane P4800Xを利用することで、Tailレイテンシが改善されるだけでなく、本番環境のデータベースで重要となるTailレイテンシの予測可能で一貫性のあるパフォーマンスが期待できます。

CephのBest Practiceを探る③:Node数によるパフォーマンスの変化

はじめに

本記事はCephのBest Practiceを探る第三弾になります。今回も先日紹介した記事の続編として、Cephの公式ブログで公開されているこちらの記事の内容を紹介いたします。今回も記事内容への補足や個人的なメモは青文字で加えております

ceph.com

要約

  • Part 1で利用した検証環境において、Red Hat Ceph Storage(RHCS)が3ノードの時と5ノードの時を比べ、パフォーマンスがどの程度向上したかを測定しました。
  • Smallブロックサイズでは、ハードウェアリソースを60%追加することで、Random Read-WriteのワークロードのIOPSを95%向上し、平均レイテンシ・Tailレイテンシをそれぞれ46%、44%削減しました。
  • LargeブロックサイズのRandom Readワークロードでは、クライアントネットワークがボトルネックとなり、9.8GB/sのスループットを示しました。
  • LargeブロックサイズのRandom Writeワークロードは、3ノードの時と比べ、スループットが67%向上し、メディア使用率がボトルネックとなりました。

検証内容

今回の検証環境は、Part 1で利用したRHCSの環境を引き続き利用します。

Part 3で異なる点は、Smallブロックサイズ(4KB)とLargeブロックサイズ(1MB)のワークロードを使用し、1回目のパフォーマンス計測はRHCS 3ノードで、2回目の計測は1回目と全く同じ内容で5ノードで計測を行った点です。各ストレージノードは7つのメディアデバイスを接続し、デバイスあたり2つのOSDを用意しました。

計測では、Random Read / Random Read-Write / Random Writeの3つのワークロードについて行い、各ワークロードでIOPS・平均レイテンシ・Tailレイテンシを計測しました。

1. Small ブロックサイズの場合

まずはSmallブロックサイズの計測結果のグラフを載せます。グラフの縦軸はIOPSと平均レイテンシを、横軸には各ワークロードを置いています。棒グラフ・折れ線グラフは、青色が3ノード、赤色が5ノードの結果を表しています。なおIO Depthは32で固定です。

Graph 1:Small ブロックサイズの結果

Graph1

Table 1:5ノードでのパフォーマンス改善率

Workload IOPS Average Latency Tail Latency
Random Read +55% -29% -30%
Random Read-Write +95% -46% -44%
Random Write +77% -40% -38%

Graph 1・Table 1より、以下のことがわかります。

  • クラスターリソースを60%増加したことで(=3ノードから5ノードにしたことで)、Random Read-Writeのパフォーマンスは大きく向上した。IOPSは95%増加し、平均レイテンシ・Tailレイテンシはそれぞれ46%・44%減少した。またパフォーマンスのボトルネックとなった要因として、最初にメディア使用率が、その次にCPU使用率がボトルネックとなった(※1参照)。
  • 上記結果から、スケールアウト可能なクラスターを利用することで、小規模のクラスターからシステムを開始し、その後パフォーマンスを向上させたい場合は、単純にストレージノードを追加するだけで実現することが明らかになった。

※1:Part 2の中で、IO Depth=32の時の計測結果を比較したときに、メディア使用率よりもCPU使用率のほうがボトルネックとして強く影響していることを示唆する結果となっています。Part 3のここでの記述は、Part 2と比べて矛盾するような結果となったようにも読めますが、詳細については特に記載されていないため、これ以上は分かりませんでした。

また、上記グラフ取得時の計測とは別に、IO Depthを64に設定し、クライアント数を増加したときのパフォーマンスを比較を行った結果が、以下のGraph 2になります。

Graph 2:クライアントの負荷テスト

graph2

上記グラフから分かることとして、以下のことが言えます。

  • どのクライアント数においても5ノードのほうが3ノードに比べて高いパフォーマンスを発揮した。
  • ノード数を増加したことで、IOPSは100%以上の向上を、平均レイテンシ・Tailレイテンシはそれぞれ60%・50%の削減がされた。

※2:Part 1でも同様の計測を行っており、そちらと比較すると、5ノードの場合はほぼ同じような値を推移していることが分かります。

Largeブロックサイズの場合

続いてLargeブロックサイズの計測結果が以下のグラフになります。

Graph 3:Largeブロックサイズの結果

graph3

Graph 3より、以下のことが明らかとなります。

  • Random Readにおいて、スループットは最大9.8GB/sで頭打ちとなった。これはクライアントネットワークの集積したスループットボトルネックとなっていた(※3参照)。そのため、もしクライアントノードを追加したり、Cephクラスターとクライアント間のネットワーク帯域幅を増強すれば、Random Readスループットを向上できると予想された。
  • Random Read-Write、Random Writeのパフォーマンスは、OSDのメディア使用率がボトルネックとなっており(※4参照)、サービス稼働時間が増加したため、パフォーマンスが一定の値に留まった。OSDノードをさらに追加することで、Random Read-Write、Random Writeのパフォーマンスをさらに向上させられる可能性もある。

※3:Part 1でも記載したように、クライアントネットワークは最大スループットが10GB/s程度となります。

※4:Part 2でのLargeブロックサイズの計測結果より、メディア使用率がボトルネックとなっていることが示唆されています。

Tekton Trigger exampleを眺める

はじめに

先日Tektonに入門した記事を公開しましたが、TektonにはTask Pipeline以外にもTriggerという重要な機能が存在します。Triggerを利用することで、git pushなどのイベントを契機にパイプラインを実行することが可能となり、CI/CDパイプラインを構築するうえで重要なものです。

今回はTekton Triggerの第一歩として、git pushなどの実践的なイベントを利用する前に、Tekton Triggerの概要と簡単な例について紹介します。

Tekton Triggerとは

Tekton Triggerは、外部からのイベントを契機にパイプラインの実行を開始する機能です。イベントのペイロードから受け取ったfield値をTektonリソースにマップすることが可能です。

Tekton公式ドキュメントより

Tekton Triggerに関するCustom Resourceには、以下のようなものがあります。

  • TriggerTemplate:Triggerによって実行するリソースを定義します。resourceTemplateというフィールドでTektonリソース(PipelineRunなど)を定義し、イベント発生後に定義したリソースを実行します。
  • TriggerBinding:イベントとトリガーとを紐づけます。具体的には、イベントからfield値を取得し、それをパラメータとして保存します。保存したパラメータはTriggerTemplateなどで利用することができます。
  • EventListener:外部からのイベントに関するデータを受け付けます。利用できるデータはHTTPベースのJSONペイロードです。EventListenerでは、イベントから受け取ったデータを渡すTriggerBindingと、それを適用するTriggerTemplateを指定します。またEventListenerではinterceptorという項目があり、受け取るイベントの種類やデータに含まれる値などを指定することが可能です。

検証内容

今回はTektonのGitHubリポジトリに含まれるこちらのexampleを実行し、そこで利用する定義ファイルやその動きについて調べました。

Triggerの構成

まずは今回の検証内容を簡単に紹介します。大まかな流れは、以下の図の通りです。

f:id:FY0323:20200728182313p:plain

上図の左側から辿っていくと、

  • クライアントが特定のFieldを含むPOSTリクエストを送信
  • EventListenerが通信を受け取り、Field値をTriggerBindingへ渡す
  • TriggerBindingで定義されたパラメータをTriggerTemplateへ渡し、PipelineResourceが開始される
  • PipelineRunにより、指定のPipeline Taskを実行する
  • 上記リソースは、専用のService Accountによって実行される

各定義ファイルの構成

次に、パイプラインで利用する各定義ファイルの内容を見ていきます。今回のパイプラインで利用するリソースとその名前は以下の通りです。

リソース種別 リソース名 用途
EventListener listener イベントを受け取り、Trigger関連のリソースに情報を渡す
TriggerBinding pipeline-binding EventListenerから値を受け取りTriggerTemplateへ渡す
message-binding TriggerTemplateへ値を渡す
TriggerTemplate pipeline-template TriggerBindingからパラメータ値を受け取り、定義したPipelineRunを実行する
Pipeline simple-pipeline PipelineRunで実行するパイプラインを定義する
Task say-hello Pipelineに含むタスクの一つ。Content-Typeを表示する
say-message Pipelineに含むタスクの一つ。messageを表示する
say-bye Pipelineに含むタスクの一つ。指定の文字を表示する

上記定義ファイル間のつながりは、以下のようになります。画像では小さくて読めないと思うので、画像をクリックして見てください。

f:id:FY0323:20200728231721p:plain

こまごまと書いていますが、主なポイントは以下の通りです。

  • curlでのPOSTリクエスト中に設定するContent-Type head_commit repositoryの値をTriggerBindingが受け取る
  • EventListenerTriggerTemplateと2つのTriggerBindingを紐づけている
  • TriggerTemplateには`PipelineRunの定義を記載しており、PipelineRunにはPipelineと3つのTaskが紐づいている

実際の動作

ここからは実際の動作について見ていきます。なお、利用した環境は前回のTekton紹介記事と同じものを利用しています。

Kubernetesクラスターを用意したら、まずはTektonをインストールします。Tektonのインストールはこちらのページに記載されている通り、Pipeline・Triggerに関するリソースを作成します。

# Tekton Pipelineインストール
$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml

namespace/tekton-pipelines created
podsecuritypolicy.policy/tekton-pipelines created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-tenant-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-webhook-cluster-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-leader-election created
role.rbac.authorization.k8s.io/tekton-pipelines-controller created
role.rbac.authorization.k8s.io/tekton-pipelines-webhook created
serviceaccount/tekton-pipelines-controller created
serviceaccount/tekton-pipelines-webhook created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-leaderelection created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-tenant-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook-cluster-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook-leaderelection created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook created
customresourcedefinition.apiextensions.k8s.io/clustertasks.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/conditions.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/images.caching.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/pipelines.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelineruns.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelineresources.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/tasks.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/taskruns.tekton.dev created
secret/webhook-certs created
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.pipeline.tekton.dev created
mutatingwebhookconfiguration.admissionregistration.k8s.io/webhook.pipeline.tekton.dev created
validatingwebhookconfiguration.admissionregistration.k8s.io/config.webhook.pipeline.tekton.dev created
clusterrole.rbac.authorization.k8s.io/tekton-aggregate-edit created
clusterrole.rbac.authorization.k8s.io/tekton-aggregate-view created
configmap/config-artifact-bucket created
configmap/config-artifact-pvc created
configmap/config-defaults created
configmap/feature-flags created
configmap/config-leader-election created
configmap/config-logging created
configmap/config-observability created
deployment.apps/tekton-pipelines-controller created
service/tekton-pipelines-controller created
deployment.apps/tekton-pipelines-webhook created
service/tekton-pipelines-webhook created



# リソースの確認
$ kubectl get ns
NAME               STATUS   AGE
default            Active   14m
kube-node-lease    Active   14m
kube-public        Active   14m
kube-system        Active   14m
tekton-pipelines   Active   48s

$ kubectl get pods -n tekton-pipelines
NAME                                           READY   STATUS    RESTARTS   AGE
tekton-pipelines-controller-559bd4d4df-w5fm6   1/1     Running   0          55s
tekton-pipelines-webhook-7bfd859f8c-2qdd8      1/1     Running   0          55s
# Tekton Triggerインストール
$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml

podsecuritypolicy.policy/tekton-triggers created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-admin created
serviceaccount/tekton-triggers-controller created
clusterrolebinding.rbac.authorization.k8s.io/tekton-triggers-controller-admin created
customresourcedefinition.apiextensions.k8s.io/clustertriggerbindings.triggers.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/eventlisteners.triggers.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/triggerbindings.triggers.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/triggertemplates.triggers.tekton.dev created
secret/triggers-webhook-certs created
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.triggers.tekton.dev created
mutatingwebhookconfiguration.admissionregistration.k8s.io/webhook.triggers.tekton.dev created
validatingwebhookconfiguration.admissionregistration.k8s.io/config.webhook.triggers.tekton.dev created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-aggregate-edit created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-aggregate-view created
configmap/config-logging-triggers created
configmap/config-observability-triggers created
service/tekton-triggers-controller created
deployment.apps/tekton-triggers-controller created
service/tekton-triggers-webhook created
deployment.apps/tekton-triggers-webhook created



# リソース確認
$ kubectl get pods -n tekton-pipelines
NAME                                           READY   STATUS    RESTARTS   AGE
tekton-pipelines-controller-559bd4d4df-w5fm6   1/1     Running   0          72s
tekton-pipelines-webhook-7bfd859f8c-2qdd8      1/1     Running   0          72s
tekton-triggers-controller-96b9d4dfd-ckpvh     1/1     Running   0          12s
tekton-triggers-webhook-b7c46d8ff-926hw        1/1     Running   0          11s

$ kubectl get svc -n tekton-pipelines
NAME                          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                     AGE
tekton-pipelines-controller   ClusterIP   172.21.9.211   <none>        9090/TCP                    15m
tekton-pipelines-webhook      ClusterIP   172.21.1.189   <none>        9090/TCP,8008/TCP,443/TCP   15m
tekton-triggers-controller    ClusterIP   172.21.6.147   <none>        9090/TCP                    14m
tekton-triggers-webhook       ClusterIP   172.21.5.232   <none>        443/TCP                     14m

次にパイプライン作成に利用する定義ファイルをGitHubから取得し、必要なファイルを使って各リソースを作成します。

# 定義ファイルのダウンロード
$ git clone https://github.com/tektoncd/triggers.git
$ cd triggers/example
$ ll
total 12
drwxr-xr-x 1 root root 4096 Jul  9 15:59 ./
drwxr-xr-x 1 root root 4096 Jul  9 15:59 ../
-rw-r--r-- 1 root root 4850 Jul  9 15:59 README.md
drwxr-xr-x 1 root root 4096 Jul  9 15:59 bitbucket/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 clustertriggerbindings/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 cron/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 event-interceptors/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 eventlisteners/
-rw-r--r-- 1 root root 1994 Jul  9 15:59 example-pipeline.yaml
drwxr-xr-x 1 root root 4096 Jul  9 15:59 github/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 gitlab/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 role-resources/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 triggerbindings/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 triggertemplates/
drwxr-xr-x 1 root root 4096 Jul  9 15:59 v1alpha1-task/
# リソースの作成
## Tektonリソースを操作するServiceAccount・Roleなど
$ kubectl apply -f role-resources/secret.yaml
secret/tekton-triggers-example-secret created

$ kubectl apply -f role-resources/serviceaccount.yaml
serviceaccount/tekton-triggers-example-sa created

$ kubectl apply -f role-resources/triggerbinding-roles/
rolebinding.rbac.authorization.k8s.io/tekton-triggers-example-binding created
role.rbac.authorization.k8s.io/tekton-triggers-example-minimal created


## TriggerTemplate・TriggerBinding・EventListener
$ kubectl apply -f triggertemplates/triggertemplate.yaml
triggertemplate.triggers.tekton.dev/pipeline-template created

$ kubectl apply -f triggerbindings/triggerbinding.yaml
triggerbinding.triggers.tekton.dev/pipeline-binding created

$ kubectl apply -f triggerbindings/triggerbinding-message.yaml
triggerbinding.triggers.tekton.dev/message-binding created

$ kubectl apply -f eventlisteners/eventlistener.yaml
eventlistener.triggers.tekton.dev/listener created


## Pipeline
$ kubectl apply -f example-pipeline.yaml
## リソース確認
$ kubectl get svc
NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
el-listener   ClusterIP   172.21.14.31   <none>        8080/TCP   39s
kubernetes    ClusterIP   172.21.0.1     <none>        443/TCP    28m

$ kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
el-listener-6f676cd886-4kbp7   1/1     Running   0          68s

$ kubectl get eventlistener
NAME       ADDRESS                                             AVAILABLE   REASON
listener   http://el-listener.default.svc.cluster.local:8080   True        MinimumReplicasAvailable

$ kubectl get triggertemplate
NAME                AGE
pipeline-template   2m49s

$ kubectl get triggerbindings
NAME               AGE
message-binding    2m25s
pipeline-binding   2m37s

$ kubectl get task
NAME          AGE
say-bye       6s
say-hello     7s
say-message   6s

$ kubectl get pipeline
NAME              AGE
simple-pipeline   12s

$ kubectl get tekton-pipelines
NAME                                  AGE
pipeline.tekton.dev/simple-pipeline   29s

NAME                          AGE
task.tekton.dev/say-bye       29s
task.tekton.dev/say-hello     30s
task.tekton.dev/say-message   29s

リソースの作成が完了したので、curlコマンドによってPOSTリクエストを送信します。なお、curlコマンドを実行する前にEventListenerPodへアクセスするための操作を行います。ここではkubectl port-forwardにより、ローカルからEventListenerPodへのアクセスを可能にします。

# Port forward
$ kubectl port-forward $(kubectl get pod -o=name -l eventlistener=listener) 8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080


# POSTリクエスト
## 別のターミナルから実行する
$ curl -X POST http://localhost:8080 -H 'Content-Type: application/json' -H 'X-Hub-Signature: sha1=2da37dcb9404ff17b714ee7a505c384758ddeb7b' -d '{"head_commit":{"id": "master"},"repository":{"url": "https://github.com/tektoncd/triggers.git"}}'

{"eventListener":"listener","namespace":"default","eventID":"xs7pp"}

上記コマンド実行後、Podやログを確認します。

# 実行結果の確認
## PipelineRun (実行中)
$ kubectl get pipelinerun
NAME                        SUCCEEDED   REASON    STARTTIME   COMPLETIONTIME
simple-pipeline-run-wmpnx   Unknown     Running   47s


## Podの状態遷移
## Pipeline中のTaskがPodとして立ち上がる
$ kubectl get pods -w
NAME                           READY   STATUS    RESTARTS   AGE
el-listener-6f676cd886-4kbp7   1/1     Running   0          97m
simple-pipeline-run-wmpnx-say-hello-6rjbj-pod-2dktx   0/2     Pending   0          0s
simple-pipeline-run-wmpnx-say-hello-6rjbj-pod-2dktx   0/2     Pending   0          0s
simple-pipeline-run-wmpnx-say-hello-6rjbj-pod-2dktx   0/2     Init:0/1   0          0s
simple-pipeline-run-wmpnx-say-hello-6rjbj-pod-2dktx   0/2     PodInitializing   0          6s
simple-pipeline-run-wmpnx-say-hello-6rjbj-pod-2dktx   2/2     Running           0          17s
simple-pipeline-run-wmpnx-say-hello-6rjbj-pod-2dktx   2/2     Running           0          17s
simple-pipeline-run-wmpnx-say-hello-6rjbj-pod-2dktx   0/2     Completed         0          24s
simple-pipeline-run-wmpnx-say-message-kw27w-pod-6brzd   0/2     Pending           0          0s
simple-pipeline-run-wmpnx-say-message-kw27w-pod-6brzd   0/2     Pending           0          0s
simple-pipeline-run-wmpnx-say-message-kw27w-pod-6brzd   0/2     Init:0/1          0          0s
simple-pipeline-run-wmpnx-say-message-kw27w-pod-6brzd   0/2     PodInitializing   0          2s
simple-pipeline-run-wmpnx-say-message-kw27w-pod-6brzd   2/2     Running           0          6s
simple-pipeline-run-wmpnx-say-message-kw27w-pod-6brzd   2/2     Running           0          6s
simple-pipeline-run-wmpnx-say-message-kw27w-pod-6brzd   0/2     Completed         0          15s
simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb       0/2     Pending           0          0s
simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb       0/2     Pending           0          0s
simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb       0/2     Init:0/1          0          0s
simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb       0/2     PodInitializing   0          2s
simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb       2/2     Running           0          6s
simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb       2/2     Running           0          6s
simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb       1/2     Running           0          13s
simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb       0/2     Completed         0          14s


## tknコマンド
## Pipelineのログを表示する
$ tkn pipeline logs --last -f
[say-hello : git-source-git-source-hqhll] {"level":"info","ts":1595648342.4830065,"caller":"git/git.go:139","msg":"Successfully cloned https://github.com/tektoncd/triggers.git @ 81dc28e2784c58798fcc1cbe851de4f661913ee9 (grafted, HEAD, origin/master) in path /workspace/git-source"}
[say-hello : git-source-git-source-hqhll] {"level":"info","ts":1595648342.5367808,"caller":"git/git.go:180","msg":"Successfully initialized and updated submodules in path /workspace/git-source"}

[say-hello : say-hi] Hello Triggers!
[say-hello : say-hi] Content-Type is application/json

[say-message : git-source-git-source-2k5wl] {"level":"info","ts":1595648357.5576613,"caller":"git/git.go:139","msg":"Successfully cloned https://github.com/tektoncd/triggers.git @ 81dc28e2784c58798fcc1cbe851de4f661913ee9 (grafted, HEAD, origin/master) in path /workspace/git-source"}
[say-message : git-source-git-source-2k5wl] {"level":"info","ts":1595648357.6270146,"caller":"git/git.go:180","msg":"Successfully initialized and updated submodules in path /workspace/git-source"}

[say-message : say-message] Hello from the Triggers EventListener!

[say-bye : git-source-git-source-5qpll] {"level":"info","ts":1595648369.9986367,"caller":"git/git.go:139","msg":"Successfully cloned https://github.com/tektoncd/triggers.git @ 81dc28e2784c58798fcc1cbe851de4f661913ee9 (grafted, HEAD, origin/master) in path /workspace/git-source"}
[say-bye : git-source-git-source-5qpll] {"level":"info","ts":1595648370.0498636,"caller":"git/git.go:180","msg":"Successfully initialized and updated submodules in path /workspace/git-source"}

[say-bye : say-bye] Goodbye Triggers!


## PipelineRun (実行後)
$ kubectl get pipelinerun
NAME                        SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
simple-pipeline-run-wmpnx   True        Succeeded   59s         6s

$ kubectl get tekton-pipelines
NAME                                  AGE
pipeline.tekton.dev/simple-pipeline   97m

NAME                          AGE
task.tekton.dev/say-bye       97m
task.tekton.dev/say-hello     97m
task.tekton.dev/say-message   97m

NAME                                                             SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
taskrun.tekton.dev/simple-pipeline-run-wmpnx-say-bye-b6gmk       True        Succeeded   2m59s       2m45s
taskrun.tekton.dev/simple-pipeline-run-wmpnx-say-hello-6rjbj     True        Succeeded   3m38s       3m14s
taskrun.tekton.dev/simple-pipeline-run-wmpnx-say-message-kw27w   True        Succeeded   3m14s       2m59s

NAME                                               SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
pipelinerun.tekton.dev/simple-pipeline-run-wmpnx   True        Succeeded   3m38s       2m45s

以上のようにパイプラインに実行が完了しました。

パイプライン実行後にPipelineRunリソースを確認すると、curlで指定したパラメータがSpecに設定されていることがわかります。

# PipelineRunの詳細確認
$ kubectl describe pipelinerun.tekton.dev/simple-pipeline-run-wmpnx
Name:         simple-pipeline-run-wmpnx
Namespace:    default
Labels:       tekton.dev/pipeline=simple-pipeline
              triggers.tekton.dev/eventlistener=listener
              triggers.tekton.dev/trigger=foo-trig
              triggers.tekton.dev/triggers-eventid=xs7pp
Annotations:  API Version:  tekton.dev/v1beta1
Kind:         PipelineRun
Metadata:
  Creation Timestamp:  2020-07-25T03:38:39Z
  Generate Name:       simple-pipeline-run-
  Generation:          1
  Resource Version:    1109314995
  Self Link:           /apis/tekton.dev/v1beta1/namespaces/default/pipelineruns/simple-pipeline-run-wmpnx
  UID:                 5c242ed8-ed42-420a-97b8-f8a233b85d17
Spec:
  Params:
    Name:   message
    Value:  Hello from the Triggers EventListener!
    Name:   contenttype
    Value:  application/json★
  Pipeline Ref:
    Name:  simple-pipeline
  Resources:
    Name:  git-source
    Resource Spec:
      Params:
        Name:   revision
        Value:  master★
        Name:   url
        Value:  https://github.com/tektoncd/triggers.git★
      Type:     git
  Timeout:      1h0m0s
Status:
  Completion Time:  2020-07-25T03:39:32Z
  Conditions:
    Last Transition Time:  2020-07-25T03:39:32Z
    Message:               Tasks Completed: 3 (Failed: 0, Cancelled 0), Skipped: 0
    Reason:                Succeeded
    Status:                True
    Type:                  Succeeded
  Pipeline Spec:
    Params:
      Default:      This is the default message
      Description:  The message to print
      Name:         message
      Type:         string
      Description:  The Content-Type of the event
      Name:         contenttype
      Type:         string
    Resources:
      Name:  git-source
      Type:  git
    Tasks:
      Name:  say-hello
      Params:
        Name:   contenttype
        Value:  $(params.contenttype)
      Resources:
        Inputs:
          Name:      git-source
          Resource:  git-source
      Task Ref:
        Kind:  Task
        Name:  say-hello
      Name:    say-message
      Params:
        Name:   message
        Value:  $(params.message)
      Resources:
        Inputs:
          Name:      git-source
          Resource:  git-source
      Run After:
        say-hello
      Task Ref:
        Kind:  Task
        Name:  say-message
      Name:    say-bye
      Resources:
        Inputs:
          Name:      git-source
          Resource:  git-source
      Run After:
        say-message
      Task Ref:
        Kind:  Task
        Name:  say-bye
  Start Time:  2020-07-25T03:38:39Z
  Task Runs:
    simple-pipeline-run-wmpnx-say-bye-b6gmk:
      Pipeline Task Name:  say-bye
      Status:
        Completion Time:  2020-07-25T03:39:32Z
        Conditions:
          Last Transition Time:  2020-07-25T03:39:32Z
          Message:               All Steps have completed executing
          Reason:                Succeeded
          Status:                True
          Type:                  Succeeded
        Pod Name:                simple-pipeline-run-wmpnx-say-bye-b6gmk-pod-5p5gb
        Resources Result:
          Key:            commit
          Resource Name:  git-source
          Resource Ref:
            Name:    git-source
          Value:     81dc28e2784c58798fcc1cbe851de4f661913ee9
        Start Time:  2020-07-25T03:39:18Z
        Steps:
          Container:  step-say-bye
          Image ID:   docker-pullable://bash@sha256:21caabbff34a7432be0f88d24f756a96f1e5bbb93703e8a8b34df7c5536d466a
          Name:       say-bye
          Terminated:
            Container ID:  docker://0b2ab225233e4d9a01c4a0e456edc45fe9e5e6e0d51455c6e14a8d00851cb7a7
            Exit Code:     0
            Finished At:   2020-07-25T03:39:31Z
            Reason:        Completed
            Started At:    2020-07-25T03:39:31Z
          Container:       step-git-source-git-source-5qpll
          Image ID:        docker-pullable://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:f7524d37431fa7d301dfa5c6d0bb3c2e2b029172ec2f823f0e0c6ce9d3c2718c
          Name:            git-source-git-source-5qpll
          Terminated:
            Container ID:  docker://271ac24ae9ce43124b94b2b9efec89319fb034f94e5f1cf8eba2d7c29ac01d0c
            Exit Code:     0
            Finished At:   2020-07-25T03:39:30Z
            Message:       [{"key":"commit","value":"81dc28e2784c58798fcc1cbe851de4f661913ee9","resourceName":"git-source","resourceRef":{"name":"git-source"}}]
            Reason:        Completed
            Started At:    2020-07-25T03:39:24Z
        Task Spec:
          Resources:
            Inputs:
              Name:  git-source
              Type:  git
          Steps:
            Args:
              echo 'Goodbye Triggers!'
            Command:
              bash
              -c
            Image:  bash
            Name:   say-bye
            Resources:
    simple-pipeline-run-wmpnx-say-hello-6rjbj:
      Pipeline Task Name:  say-hello
      Status:
        Completion Time:  2020-07-25T03:39:03Z
        Conditions:
          Last Transition Time:  2020-07-25T03:39:03Z
          Message:               All Steps have completed executing
          Reason:                Succeeded
          Status:                True
          Type:                  Succeeded
        Pod Name:                simple-pipeline-run-wmpnx-say-hello-6rjbj-pod-2dktx
        Resources Result:
          Key:            commit
          Resource Name:  git-source
          Resource Ref:
            Name:    git-source
          Value:     81dc28e2784c58798fcc1cbe851de4f661913ee9
        Start Time:  2020-07-25T03:38:39Z
        Steps:
          Container:  step-say-hi
          Image ID:   docker-pullable://bash@sha256:21caabbff34a7432be0f88d24f756a96f1e5bbb93703e8a8b34df7c5536d466a
          Name:       say-hi
          Terminated:
            Container ID:  docker://9ad1928c51ec681d1e44524de6cbe0e835d4e4ddffd5747690703cc4d078f0ba
            Exit Code:     0
            Finished At:   2020-07-25T03:39:02Z
            Reason:        Completed
            Started At:    2020-07-25T03:39:02Z
          Container:       step-git-source-git-source-hqhll
          Image ID:        docker-pullable://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:f7524d37431fa7d301dfa5c6d0bb3c2e2b029172ec2f823f0e0c6ce9d3c2718c
          Name:            git-source-git-source-hqhll
          Terminated:
            Container ID:  docker://b4b05d34327c0a5486b2515767c42095418b5baae15553e6b074c8497d7b1373
            Exit Code:     0
            Finished At:   2020-07-25T03:39:02Z
            Message:       [{"key":"commit","value":"81dc28e2784c58798fcc1cbe851de4f661913ee9","resourceName":"git-source","resourceRef":{"name":"git-source"}}]
            Reason:        Completed
            Started At:    2020-07-25T03:38:56Z
        Task Spec:
          Params:
            Description:  The Content-Type of the event
            Name:         contenttype
            Type:         string
          Resources:
            Inputs:
              Name:  git-source
              Type:  git
          Steps:
            Args:
              echo -e 'Hello Triggers!\nContent-Type is $(params.contenttype)'
            Command:
              bash
              -c
            Image:  bash
            Name:   say-hi
            Resources:
    simple-pipeline-run-wmpnx-say-message-kw27w:
      Pipeline Task Name:  say-message
      Status:
        Completion Time:  2020-07-25T03:39:18Z
        Conditions:
          Last Transition Time:  2020-07-25T03:39:18Z
          Message:               All Steps have completed executing
          Reason:                Succeeded
          Status:                True
          Type:                  Succeeded
        Pod Name:                simple-pipeline-run-wmpnx-say-message-kw27w-pod-6brzd
        Resources Result:
          Key:            commit
          Resource Name:  git-source
          Resource Ref:
            Name:    git-source
          Value:     81dc28e2784c58798fcc1cbe851de4f661913ee9
        Start Time:  2020-07-25T03:39:03Z
        Steps:
          Container:  step-say-message
          Image ID:   docker-pullable://bash@sha256:21caabbff34a7432be0f88d24f756a96f1e5bbb93703e8a8b34df7c5536d466a
          Name:       say-message
          Terminated:
            Container ID:  docker://ddf9b4e4c0193881be829eeddcdf55520a0f75af1895e5f48a39bd5b30ee1294
            Exit Code:     0
            Finished At:   2020-07-25T03:39:17Z
            Reason:        Completed
            Started At:    2020-07-25T03:39:17Z
          Container:       step-git-source-git-source-2k5wl
          Image ID:        docker-pullable://gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init@sha256:f7524d37431fa7d301dfa5c6d0bb3c2e2b029172ec2f823f0e0c6ce9d3c2718c
          Name:            git-source-git-source-2k5wl
          Terminated:
            Container ID:  docker://201ea779bb6556909779bfa9e10c0eb7b089decaac3bdb1243cf3d9bb70bc9c5
            Exit Code:     0
            Finished At:   2020-07-25T03:39:17Z
            Message:       [{"key":"commit","value":"81dc28e2784c58798fcc1cbe851de4f661913ee9","resourceName":"git-source","resourceRef":{"name":"git-source"}}]
            Reason:        Completed
            Started At:    2020-07-25T03:39:10Z
        Task Spec:
          Params:
            Default:      This is the default message
            Description:  The message to print
            Name:         message
            Type:         string
          Resources:
            Inputs:
              Name:  git-source
              Type:  git
          Steps:
            Args:
              echo '$(params.message)'
            Command:
              bash
              -c
            Image:  bash
            Name:   say-message
            Resources:
Events:
  Type    Reason     Age    From         Message
  ----    ------     ----   ----         -------
  Normal  Started    4m12s  PipelineRun
  Normal  Running    4m12s  PipelineRun  Tasks Completed: 0 (Failed: 0, Cancelled 0), Incomplete: 3, Skipped: 0
  Normal  Running    3m48s  PipelineRun  Tasks Completed: 1 (Failed: 0, Cancelled 0), Incomplete: 2, Skipped: 0
  Normal  Running    3m33s  PipelineRun  Tasks Completed: 2 (Failed: 0, Cancelled 0), Incomplete: 1, Skipped: 0
  Normal  Succeeded  3m19s  PipelineRun  Tasks Completed: 3 (Failed: 0, Cancelled 0), Skipped: 0

参考ドキュメント

CephのBest Practiceを探る②:ブロックサイズによるパフォーマンスの変化

はじめに

本記事はCephのBest Practiceを探る第二弾になります。今回は先日紹介した記事の続編としてCephの公式ブログで公開されているこちらの記事の内容を紹介いたします。今回も記事内容への補足や個人的なメモは青文字で加えております

ceph.com

要約

  • Part-1に引き続き、Red Hat Ceph Storage (RHCS) 3.2でBlueStoreを利用した際のパフォーマンス検証を行った。本章では、ブロックサイズを3つのカテゴリー(Small / Medium / Large)に分け、それぞれのブロックサイズでどのようにパフォーマンスが変化するかを検証した。
  • Small / Mediumサイズの場合、CPUとメディアの競合により、パフォーマンスが制限された。
  • Largeサイズの場合、4Mサイズの時にピーク値を迎えた。クライアントのネットワーク帯域が上限に達したため、パフォーマンスが制限された。

検証内容

まず今回実施した検証内容を簡単に紹介します。

今回の検証は、Part-1の環境を引き続き利用した結果になります。検証環境については前回の記事をご覧ください。内容としては、Small / Medium / Largeという3つのブロックサイズで、それぞれパフォーマンスを比較しました。3つのブロックサイズの具体的なサイズ値は以下の通りです。

  • Small:4KB
  • Medium:8KB - 64KB
  • Large:1MB - 4MB

パフォーマンスの測定では、Random Read / Random Read-Write / Random Writeの場合を比較し、IOPS・Latencyなどを計測しました。なおRandom-Read-Writeは、Read:Write=70:30の割合で行っています。

パフォーマンス計測の際、合わせてCPU使用率、メディア使用率などを測定しており、3つのブロックサイズでどの要因がボトルネックとなり、パフォーマンスに影響しているかを推定しています。

1. Small ブロックサイズ

早速ですがSmallブロックサイズの計測結果は以下のようになりました。3つのグラフは、縦軸のIOPSと横軸のIO Depthは共通ですが、それぞれ平均レイテンシ・CPU使用率・メディア使用率をプロットしています。

Graph 1:平均レイテンシ

Graph 1. small-block-size-result-01

Graph 2:CPU使用率

Graph 2. small-block-size-result-02

Graph 3:メディア使用率

Graph 3. small-block-size-result-03

上記グラフから、以下のことがわかります。

  • Graph 2より、Random ReadはCPUリソースの競合がボトルネックとなっています。しかしGraph 3を見ると、メディア使用率もRandom Readに強く影響していることが分かります(※1参照)。今回の検証ではCephノード(CPU/メディア)を追加していたために、Random Readがより高くスケールをした可能性があります。

  • Graph 2より、Random Read-Write、Random WriteはCPU使用率がボトルネックとなっていることがわかります。また一方で、この時メディアは十分余裕のあるスループットを記録したのですが、計算パワー(CPU)が足りずに使われない状態を維持していました(※2参照)。このため、Ceph OSDにより多くのCPUを集積することで、Random Write・Random Read-Writeのパフォーマンスを向上できる可能性があります。

  • OSDデータデバイス(今回はP4500 NVMe)の使用率は50%未満にとどまり、BlueStoreのWAL・RocksDBのメタデータをホストするデバイス(Optane P4800)は平均90%の使用率となりました。ceph.confに設定したbluestore_min_alloc_size_ssd16KBに設定しているため、16KB以下のすべての書き込み処理は遅延されます(※3参照)。書き込み処理はまずWAL、その後非同期的にOSDへと送られます。そのため、WALデバイスがホスト上のOSDへのすべての書き込み処理を吸収しました。RocksDB/WALは高い使用率だったにもかかわらず、OSDデータデバイス上での競合の兆候は見られませんでした。しかし、これまでの経験から、WAL/RocksDB用の単一のP4800の後ろに、7つ以上のP4500 NVMeデバイスを配置することは推奨できません。

  • Small Blockサイズでは、他にもクライアントのSweep Testを実施しました(結果は以下のGraph 4の通り)。これはクライアントの数を20~140に徐々に増やし、その時のIOPS・レイテンシを計測するものになります。クライアント数を増やしたとき、Ceph OSDのCPU輻湊が発生するまで(およそ100クライアントまで)は線形にパフォーマンスが改善しました。100クライアント以降はシステムリソースの輻湊により、高いTail Latencyが発生しました(※4参照)。メタデータバイスに利用したOptane P4800は、大量の並列クライアントによるレイテンシのスパイクを吸収するのに役立ち、その結果平均のレイテンシは40ms以下に収まりました。

Graph 4:Sweep Test

Graph 4

※1:Graph 2、Graph 3のCPU使用率・メディア使用率の数値を見ると、Random Readはいずれも80-90%程度の高い数値を記録しています。またCPU使用率はIO Depthが32から64に推移する際に殆ど増加していない様子も見られます。メディア使用率も同じように推移していますが、IO Depthが16→32、32→64に変化した際のグラフの傾きは、CPU使用率のほうが急激に変化しているため、CPU使用率が特に影響しているようにも見えます。

※2:Graph 2よりRandom Read-Write、Random WriteのいずれもCPU使用率が80%近くまで上昇しています。一方Graph 3ではRandom Read-Writeは50%程度、Random Writeは17%と低い使用率で頭打ちとなっています。このためCPU使用率のほうがボトルネックとなっていると言えます。

※3:16KB以下の処理はすぐに処理せず、他の処理と合わせてまとめて処理する、ということ。

※4:CPUなどの共有リソースや並列処理によるTail Latencyの発生と増幅はこちらでも指摘されています


※参考リンク:


2. Medium ブロックサイズ

次にMediumブロックサイズの場合を見てみます。Mediumブロックサイズの結果は以下の4つのグラフの通りです(IO Depthは32で固定)。

Graph 5:平均レイテンシ

medium-block-size-result-01

Graph 6:Tailレイテンシ

medium-block-size-result-01

Graph 7:メディア使用率

medium-block-size-result-01

Graph 8:CPU使用率

medium-block-size-result-01

上記グラフより、以下のことが分かります。

  • Meidumブロックサイズに比べ、Smallブロックサイズのほうが、高いIOPSと低い平均レイテンシ・Tailレイテンシを記録している。

  • Ceph OSDノードでは、CPU使用率は80%程度、メディア使用率は90%程度まで上昇しました。そのため、Smallブロックサイズの時と同じように、Ceph OSDノードを追加することでパフォーマンスが向上する可能性があります。

※5:Graph 5、Graph 6では、ブロックサイズが増加するほどレイテンシも増加していることがわかります。ブロックサイズが増加するほど、一つのブロックの読み取りにかかる時間が増加するため、レイテンシが増加します(こちらの記事を参照

※6:Graph 7、Graph 8を見ると、Random ReadRandom Read-Writeについて、CPU使用率はブロックサイズの増加に従って減少しているのに対し、メディア使用率は常に高い値を記録しています。このことから、特にメディア使用率のほうがボトルネックとして強く影響しているようにも見ます。

※7:Graph 8において、ブロックサイズが上昇するに従いCPU使用率が減少していることがわかりますが、その理由は分かってないです。ブロックサイズが小さいときはOSDノードでの処理の呼び出し回数が増加するため?


※参考リンク:


3. Large ブロックサイズ

最後にLargeブロックサイズの検証結果です。結果は以下のグラフの通りです。

Graph 9:スループット、メディア・CPU使用率

graph9

上記グラフを見ると、ブロックサイズが4MBの場合、Random ReadRandom Read-Write、Random Writeのいずれも、10.9GB/sというスループットを記録していることがわかります。このパフォーマンスのボトルネックとなっているのは、クライアントネットワークだと思われます(※8参照)。次に最も使用率の高いリソースはメディア使用率ですが、メディア使用率は上限には達しておらず、ネットワークがボトルネックにならなければより高いパフォーマンスを発揮できる余地があります。

※8:クライアントネットワークについてはPart-1にて「クライアントからCephノードへの最大スループットは~10GB/sである」という記述があり、今回の結果はそれに近しいものとなっています。

※9:LargeブロックサイズはIOPSやレイテンシでなくスループットの値を利用しています。他のブロックサイズとの比較を行うため、Small / Mediumブロックサイズでのスループットを算出(IOPS×ブロックサイズの値を利用)し比較をしてみました。Graph 9の結果を見ると、ブロックサイズが増加するに従ってスループットの値も増加すること、Random Readスループットのほうが、Random Read-Write、Random Writeよりも高い値であることが分かります。この傾向はSmall / Mediumブロックサイズのスループット値(仮)を並べたときにも同様に見られるものでした。

CephのBest Practiceを探る①:BlueStore使用時のチューニング

はじめに

本ブログでは、2020年になってからRook-Cephについて、機能や使い方などをいろいろと調べてまいりました。しかし一方で、そうやって調べた機能は、具体的にどのように利用するのか、どのような設定を行えばRook(というよりCeph)の機能を引き出すことができるのかについて、あまり理解をすることができていませんでした。例えば、Rook-Cephを利用する際、IOPSやスループットなどのパフォーマンスを引き上げるためには、どのような設定を行う必要があるのか、といった点が気になっております。

Rook-Cephの場合、ストレージの処理を行うのはCephの役割です。Cephは10年以上前に登場し、多くの本番環境で利用されてきたソフトウェアです。そのため、これまでのCephの運用や設計に関する知見・情報は多く公開されており、これらをたどることでCephを利用するうえでの最善策、Best Practiceを探ることができるのではないか、と考えました。

本記事では、CephのBest Practiceを探る第一弾として、Cephの公式Blogで紹介されているこちらの記事の内容について紹介いたします。また紹介をする中で、私個人の理解を進めるために補足として加えたもの、また本記事公開の時点で解釈が十分できていないものに対するメモなどを青字で加えています。解釈が不十分なもの、誤った記載のものについては、分かり次第すぐに修正していきたいと思います。

ceph.com

要点

  • CephではBlueStoreという新しいストレージバックエンドが利用できるようになり、従来のFileStoreと比較してパフォーマンスが向上した。
  • All-Flash deviceのCephクラスター環境でBlueStoreのパフォーマンス検証を行った。デフォルトの設定値ではLargeブロックサイズに対するパフォーマンスはよかったが、Smallブロックサイズに対するパフォーマンスは優れなかった。
  • Smallブロックサイズに合わせてCephクラスターのパラメータチューニングを行った。具体的には、RocksDBのパフォーマンスの最適化、BlueStoreメタデータキャッシュの確保、PG Logの保存数の削減などを行った。
  • チューニングの結果、特に書き込みのパフォーマンスにおいて大きな改善が見られ、IOPSは134%上昇、平均レイテンシは70%減少、そしてTailレイテンシは91%減少した。

Introduction

現代では、All-flashな構成のストレージに対する需要は高まっています。All-Flash構成のストレージは、高スループットと低レイテンシを両立するだけでなく、電力消費や冷却にかかるパワーを削減し、TCO (Total Cost of Ownership) を低下させる効果もあります。ストレージやデータベースに保存されたデータがビジネスの根幹を支えるようになり、より高いパフォーマンスや耐久性を求められる中で、Software-Definedでスケールアウトするようなストレージテクノロジーを利用することが求められています。

Red Hat Ceph Storage(RHCS)オープンソースでスケーラビリティに優れたSoftware-Defined Storageソフトウェアです。NANDテクノロジーの向上により、フラッシュメディアをストレージに利用することは、手の届くようなものとなりました。RHCSにより、数百万IOPSと低レイテンシとを実現し、ミッションクリティカルなワークロードに向けたストレージを利用することができます。

高パフォーマンスを必要とするワークロードでは、しばしばブロックデバイスを利用します。CephはRBDというライブラリ経由でブロックデバイスを提供します。RBDブロックデバイスは多数のオブジェクトに分けられ、クラスター内に分散されています。このオブジェクトはPlacement Group (PG) と呼ばれるグループに分けられ、分散配置されます。またPGはクラスター全体にまたがってCeph OSDにオブジェクトを分散します。これはRBDボリュームからデータにアクセスする際の並列性を大きく向上します。

ceph-architecture ※上図は元記事より引用

Cephは新しいストレージバックエンドとしてBlueStoreをリリースし、これが以前のFileStoreと比較してパフォーマンスを向上することにつながりました。その理由としてはIOデータパスがシンプルになり、2重のwrite penaltyを避けていることが挙げられます。またBlueStoreがCephのパフォーマンスを向上したことにより、OSDのCPU使用率、特にフラッシュメディアを利用するOSDの値を削減することにつながりました。


※参考リンク:


BlueStore Intro

BlueStoreはCeph OSDのバックエンドストレージとして新しく利用できるものです。CephのオリジナルのオブジェクトストアであるFileStoreはRawブロックデバイス上にファイルシステムを必要とし、オブジェクトはファイルシステムに書き込まれるという形でした。一方BlueStoreはファイルシステムを介さずブロックデバイスに直接オブジェクトを書き込み、これによりパフォーマンスの改善がもたらされました。

BlueStore Under the Covers

以下の図はBlueStoreとFileStoreの構成の違いを表しています。

bluestore-filestore ※上図は元記事より引用

FileStoreではデータを書き込む場合、まずCephジャーナルに書き込みます。その後データ・メタデータはそれぞれXFSを経由してデバイスへ書き込まれます。

BlueStoreでは、FileStoreと比べデータパスがシンプルになっています。データは直接Rawデバイスに書き込まれ、メタデータはRocksDBによって管理されます。またデバイスはデータ用・メタデータ用に分かれ、Rawデバイスにデータオブジェクトが書き込まれると、RocksDBは新しいデータオブジェクトに関する情報をアップデートします。

RocksDBLevelDBをベースにFacebook社で開発が開始されたKey-Value形式のデータベースエンジンです。Log Structured Merge Tree (LSM Tree)を採用しており、LevelDBにはなかった多数の新機能(トランザクショナル、など)が追加されています。 CephのBlueStoreで採用した理由として、トランザクショナルでログ・ジャーナルへのコミットが素早いKey-Valueエンジンであったこと、ストレージバックエンドを抽象化すること、C++で書かれていることなどが挙げられています。

RocksDBは直接デバイスには書き込めず、BlueFSという専用のファイルシステムを利用します。BlueFSはRocksDBに必要な機能のみを備えたシンプルなファイルシステムです。

RocksDBは永続データのトランザクションログとしてWAL(Write-Ahead Log)を利用します。FileStoreでは全ての書き込みはジャーナルから始まるのに対し、BlueStoreは2つのデータパスが存在します。一つはデータの直接書き込みパス、もう一つはWALデバイスに書き込まれ、後ほど非同期的にディスクにフラッシュされるパスです。

BlueStoreの新機能として、他にも以下のようなものが紹介されています。

  • lowest levelのデータに対する圧縮が可能になった:RawデバイスにあるデータBlobを圧縮することが可能になり、RHCSで管理するあらゆるデータが圧縮できるようになりました。

  • データ・メタデータチェックサム付きで保存することによる一致性の向上:永続ストレージからデータを読み込む際は、常にチェックサムを確認します。


※参考リンク:


テスト環境

本記事のテスト環境は、以下に示す通り、5台のRHCS All-Flash (NVMe) サーバーと7台のクライアントノード (Red Hat OpenStack Platform)を利用しています。

server/client factors value
5 x RHCS OSD Node Configuration Chassis Cisco UCS C220-M5SN Rack Server
CPU 2 x Intel® Xeon® Platinum 8180 28 core (56 HT cores) @ 2.50 GHz
Memory 196GB
NIC Cisco UCS VIC 1387 2 port x 40Gb
Storage (Ceph Data) 7x Intel® SSD DC P4500 4.0 TB
Storage (Ceph Metadata) 1x Intel® Optane™ SSD DC P4800X 375 GB
Storage (Ceph Pool Placement Groups) 4096
Software Configuration RHEL 7.6, Linux Kernel 3.10, RHCS 3.2 (12.2.8-52)
7 x Client Hardware Configuration Chassis Cisco UCS B200 M4 Blade servers
CPU 2x Intel® Xeon® CPU E5-2640 v4 @ 2.40GHz
Memory 528GB
NIC Cisco UCS VIC 1387 2 port x 10Gb
Software Configuration RHOSP 10, RHEL 7.6, Linux Kernel 3.10, Pbench-FIO 3.3

その他の情報は以下の通りです。

OSD

回転するメディアの場合は、メディアデバイスあたり1つのOSDを割り当てるのが推奨されています。今回はNVMeデバイスを使用しており、パフォーマンスを最大化するため、NVMeデバイスあたり2つのOSDを割り当てています。

ネットワーク

クライアントが利用する帯域を確保するため、2つのネットワークに分けています。1つはクライアントからOSDノードへのアクセス、もう一つはOSDノード間でのアクセスに利用します。

rhcs-nw-component-01

※上図は元記事より引用

テスト環境はACI Leaf-Spineトポロジーであり、2つのネットワークはいずれもJumbo Frame(MTUサイズは9000)を利用するよう設定しています。

rhcs-nw-component-02

※上図は元記事より引用

OSP(OpenStack Platform)側のネットワーク帯域幅はFiber Interconnect uplinkがボトルネックとなり、80Gbit/sしか利用することができませんでした。そのため、クライアントからCephクラスターまでのスループットは最大で~10GBytes/sになりました。


※参考リンク:


コンテナ化

今回利用するRHCSでは、Containerized Storage Daemons (CSD)を利用し、Cephのデーモンをコンテナとしてデプロイします。

Ceph Daemonをコンテナ化することで、一つのノード上に複数のサービスを配置することを可能にする柔軟性を提供します。これはOSD、Monitorなどの専用ノードの必要性をなくし、TCO (Total Cost of Ownership) を削減することができます(※1参照)。

今回は5つのノードのうち、3つのノードはOSD+MGR+MON、2つのノードはOSDのみが動作しています。各Cephサービスのリソース制限は次項に記載されています。

※1:TCOを削減できる理由としては、ノード数を削減することができるためかと推察しています。

CPUの割り当て

各RHCSノードは2×28の物理コアを備えています。さらにハイパースレッディングを利用し、合計112 vCPUを利用することが可能です。各サービスに対してどのようにCPUを割り当てるかについては、OSに充てる分をできるだけ温存しつつ、CPUの割り当てを決定しました。

まず、各サービスに対するCPUの割り当ては、以下の表に様になりました。

vCPU (Core) Memory (GB)
OS 8 12
OSDコンテナ 7 12
MONコンテナ 3 6
MGRコンテナ 3 6

OSDについては、1ノードあたり7つのNVMeがアタッチされており、それぞれのデバイスに2つOSDを割り当てます。そのため、OSDには合計98vCPUを割り当てることになります(※2参照)。

※2:(OSDコンテナあたりのvCPU数) × (1ノードあたりのNVMe数) × (1デバイスあたりのOSD数) = 7vCPU × 7NVME × 2OSD = 98vCPU

ベンチマークの方法

ベンチマークには標準ツールであるFIOを利用し、Ceph Block Storageのパフォーマンス計測を行いました。

計測当初は2つの異なるボリューム数で計測を行いました。

  • 1クライアントあたり200GBのボリュームを12個(合計84ボリューム、16.8 TB)
  • 1クライアントあたり200GBのボリュームを15個(合計105ボリューム、21TB)

しかしボリューム数12個の場合、ストレージサブシステムへの負荷をピークにすることができなかったため、いくつかの試験ではボリューム数を15個にし、より多くの負荷をかけました。なお、Replicated Poolの分を含めて、ストレージボリュームの合計使用量は42TBになりました。

FIO librbd IOengineを利用することで、 KVM/QEMUの設定をすることなく、Ceph RBDボリュームのストレージパフォーマンスをFIOで計測することができます。ここで利用するlibrbdライブラリはQEMUバックエンドで使うものとまったく同じなので、KVM/QEMUのパフォーマンスに近似することができます。例えば1クライアントあたり15個のRBDボリュームを7クライアントに与えた場合、1インスタンスあたり1つのRBDボリュームを割り当てた105のOSPインスタンスと同程度のワークロードを作り出すことができます。

なおテストに利用したFIOテンプレートファイルは以下の通りです。

[global]
ioengine=rbd
clientname=admin
pool=rbdpool
#IO-Depth changes depending on the test
iodepth=$IODEPTH
runtime=600
direct=1
sync=0
buffered=0
#Blocksize changes depending on the test
bs=$BLOCKSIZE
#RR,RW, or a mixed workload, this changes depending on the test
rw=$TYPEOFTEST
norandommap
randrepeat=0
startdelay=15
rwmixread=70
invalidate=0    # mandatory
time_based=1
refill_buffers
###compression/dedupe related
#dedupe compress tests
#dedupe_percentage=80
#buffer_compress_percentage=10
#buffer_pattern=0xdeadface
ramp_time=180
write_bw_log=fio
write_iops_log=fio
write_lat_log=fio
log_avg_msec=6000
write_hist_log=fio
log_hist_msec=60000

[rbd_vol00]
rbdname=template-vol00
numjobs = 1
clientname=admin
pool=rbdpool

# One section per volume

[rbd_vol0X]
rbdname=template-vol0X
numjobs = 1
clientname=admin
pool=rbdpool

検証前にはフルサイズボリュームのシーケンシャルな書き込みを行い、Cephのthin-provision メカニズムのインパクトを除去することで(※3参照)、安定して再現性のある結果を取得しました。また各テストの前には、OSD・クライアントノードからOSキャッシュをドロップしました。

各検証はランタイム900秒、起動時間180秒で4回行い、それらの平均を算出して結果としました。

※3:thin-provisioningにより必要な容量だけが割り当てられるため、あらかじめフルサイズボリュームの書き込みを行うことでその影響を除去することを目的とした作業と思われます。


※参考リンク:


パフォーマンスの比較

デフォルトの設定値の場合、RHCSはLargeブロックサイズのワークロードの場合、優れたパフォーマンスを記録しました。一方で、Smallブロックサイズのワークロードでは「保守的」な結果となり、あまりすぐれた結果とはなりませんでした。

この結果を受け、ブロックサイズの小さなワークロードに合わせてチューニングを行い、パフォーマンスの改善を目指しました。

今回利用したceph.confは以下のようになりました。

### PLEASE TAKE IN ACCOUNT THIS FILE IS CONFIGURED FOR BECHNMARK TESTING OF CEPH NOT PRODUCTION USE

[client]
rbd cache = False


[client.openstack]
admin socket = /var/run/openstack/$cluster-$type.$id.$pid.$cctid.asok
log file = /var/log/ceph/qemu-guest-$pid.log

# Please do not change this file directly since it is managed by Ansible and will be overwritten
[global]
# let's force the admin socket the way it was so we can properly check for existing instances
# also the line $cluster-$name.$pid.$cctid.asok is only needed when running multiple instances
# of the same daemon, thing ceph-ansible cannot do at the time of writing
admin socket = $run_dir/$cluster-$name.asok
auth client required = none
auth cluster required = none
auth service required = none
auth supported = none
cephx require signatures = False
cephx sign messages = False
cluster network = 1.1.1.0/24
debug asok = 0/0
debug auth = 0/0
debug bdev = 0/0
debug bluefs = 0/0
debug bluestore = 0/0
debug buffer = 0/0
debug civetweb = 0/0
debug client = 0/0
debug compressor = 0/0
debug context = 0/0
debug crush = 0/0
debug crypto = 0/0
debug dpdk = 0/0
debug eventtrace = 0/0
debug filer = 0/0
debug filestore = 0/0
debug finisher = 0/0
debug fuse = 0/0
debug heartbeatmap = 0/0
debug javaclient = 0/0
debug journal = 0/0
debug journaler = 0/0
debug kinetic = 0/0
debug kstore = 0/0
debug leveldb = 0/0
debug lockdep = 0/0
debug mds = 0/0
debug mds balancer = 0/0
debug mds locker = 0/0
debug mds log = 0/0
debug mds log expire = 0/0
debug mds migrator = 0/0
debug memdb = 0/0
debug mgr = 0/0
debug mgrc = 0/0
debug mon = 0/0
debug monc = 0/00
debug ms = 0/0
debug none = 0/0
debug objclass = 0/0
debug objectcacher = 0/0
debug objecter = 0/0
debug optracker = 0/0
debug osd = 0/0
debug paxos = 0/0
debug perfcounter = 0/0
debug rados = 0/0
debug rbd = 0/0
debug rbd mirror = 0/0
debug rbd replay = 0/0
debug refs = 0/0
debug reserver = 0/0
debug rgw = 0/0
debug rocksdb = 0/0
debug striper = 0/0
debug throttle = 0/0
debug timer = 0/0
debug tp = 0/0
debug xio = 0/0
fsid = 66f14b44-1e42-4869-9ebb-cbe0b0c4053d
log file = /var/log/ceph/$cluster-$type-$id.log
max open files = 131072
mon compact on trim = False
mon host = 10.48.XX.14,10.48.XX.32,10.48.XX.16
mon initial members = ceph04,ceph05,ceph06
osd deep scrub interval = 137438953472
osd max scrubs = 16
osd objectstore = bluestore
osd op threads = 2
osd pool default min size = 1
osd pool default size = 2
osd scrub load threshold = 0.01
osd scrub max interval = 137438953472
osd scrub min interval = 137438953472
perf = True
public network = 10.48.22.0/24
rbd readahead disable after bytes = 0
rbd readahead max bytes = 4194304
rocksdb perf = True
throttler perf counter = False

[mon]
mon allow pool delete = True
mon health preluminous compat = True
mon osd down out interval = 300

[osd]
bluestore cache autotune = 0
bluestore cache kv ratio = 0.2
bluestore cache meta ratio = 0.8
bluestore cache size ssd = 8G
bluestore csum type = none
bluestore extent map shard max size = 200
bluestore extent map shard min size = 50
bluestore extent map shard target size = 100
bluestore rocksdb options = compression=kNoCompression,max_write_buffer_number=32,min_write_buffer_number_to_merge=2,recycle_log_file_num=32,compaction_style=kCompactionStyleLevel,write_buffer_size=67108864,target_file_size_base=67108864,max_background_compactions=31,level0_file_num_compaction_trigger=8,level0_slowdown_writes_trigger=32,level0_stop_writes_trigger=64,max_bytes_for_level_base=536870912,compaction_threads=32,max_bytes_for_level_multiplier=8,flusher_threads=8,compaction_readahead_size=2MB
osd map share max epochs = 100
osd max backfills = 5
osd memory target = 4294967296
osd op num shards = 8
osd op num threads per shard = 2
osd min pg log entries = 10
osd max pg log entries = 10
osd pg log dups tracked = 10
osd pg log trim min = 10

チューニング

ここから最も重要だったチューニングの内容について触れていきます。

RocksDBのチューニング

RocksDBはOSDの書き込みパフォーマンスにおいて重要な役割を担います。RocksDBでのDB Compactionによるwrite amplificationを最小化するため(※4参照)、ceph.confbluestore_rocksdb_optionsに以下のような設定を行いました。

bluestore_rocksdb_options = 
compression=kNoCompression, 
max_write_buffer_number=32, 
min_write_buffer_number_to_merge=2, 
recycle_log_file_num=32, 
compaction_style=kCompactionStyleLevel,
write_buffer_size=67108864, 
target_file_size_base=67108864,
max_background_compactions=31,
level0_file_num_compaction_trigger=8,
level0_slowdown_writes_trigger=32,
level0_stop_writes_trigger=64,
max_bytes_for_level_base=536870912,
compaction_threads=32,
max_bytes_for_level_multiplier=8,
flusher_threads=8,
compaction_readahead_size=2MB 

※4:DB Compactionとは、データベース・ファイルを書き直し、古いドキュメント・リビジョンや削除されたドキュメントを除去する作業を指します。ストレージ領域の確保やデータ量削減によるパフォーマンス向上を主な目的とします。

一方、Write Amplificationとは、フラッシュデバイス利用時に発生する書き込み処理が実際のデータ量を上回ることを指します。

フラッシュデバイスはその性質上、データを直接書き換えるということができません。書き込みの単位は「ページ」と呼ばれ、使用するページが「使用済み」(既にデータが存在)の場合は消去処理が必要となります。この消去できる単位は「ブロック」というページよりも大きな単位であり、新規に書き込む分がブロックに満たない場合でも、ブロック単位でデータは削除され、元のデータを再書き込みする必要があります。Write Amplificationにより、書き込み性能の低下やデバイスの寿命を早く迎えるという問題が発生します。


※参考リンク:


BlueStoreキャッシュ

RHCS 3.2はbluestore cache autotuningという機能が追加されました。これはBlueStoreで利用するキャッシュサイズを自動的に変更する機能であり、多くのワークロードでは有効になります。しかし今回の検証環境ではこの機能を無効にしたほうが良い結果が得られたことから、検証中はマニュアルでBlueStoreキャッシュの設定を行いました。

ランダムなSmallブロックサイズのワークロードに対しては、できる限り多くのBlueStore Metadata Cacheをキープすることが重要になります。OSDノードに適切なメモリが割り当てられていれば、BlueStoreキャッシュサイズを増加することで、パフォーマンスの向上がもたらされます。bluestore_cache_size_ssdの値はデフォルトでは4GBに設定されていますが、ここでは8GBに変更しました。これはOSDあたりに割り当てた12GBのメモリのうち実に3分の2にあたります。

上記のようにBlueStore Cache Autotuningを無効化し、メモリ割り当てサイズを指定すると、BlueStoreのキャッシュはさらに3つに分かれます。

  • cache_meta:BlueStore Onodeとそれに関連するデータに利用
  • cache_kv:RocksDBのブロックキャッシュに利用 (index/bloom-filters(※5参照)を含む)
  • data cache:データバッファ用にBlueStoreキャッシュとして利用

各キャッシュの容量は比率で指定することができます。ここではRBDワークロード向けにbluestore_cache_meta_ratioの比率を上げ、BlueStore Onodeキャッシュ専用のキャッシュサイズを増加した。テスト中は以下のような比率にすることで最も優れた結果となりました。

bluestore_cache_autotune = 0
 
bluestore_cache_kv_ratio = 0.2

bluestore_cache_meta_ratio = 0.8

※5:Bloom Filtersとは確率的データ構造の一つで、ある要素が集合のメンバーであるかどうかのテストに使われます。RocksDBではSST (Sorted Static Table)にデータが含まれており、目的のデータを含むSST(エントリテーブル)を検索するためにBloom Filterを利用します。


※参考リンク:


リカバリープロセス

RHCSが(何らかの理由で)失敗したOSDに対してリカバリープロセスを行うとき、ログベースのリカバリを行います(※6参照)。この時、Smallブロックサイズの書き込みの場合は長い変更点のリストを生成し(※7参照)、それはPGログに書かなければならないものです。これがWrite Amplificationを発生し、パフォーマンスに影響を与えます。

検証中、PGログの保存する数を少なくしたときに、パフォーマンスが向上することが確認されました。ただこれには欠点もあり、結果的にリカバリ時間が増加します。

ここでのリストアのプロセスではBackfillを利用します(※8参照)。Backfillを利用する場合、、PGのハッシュ空間を徐々に移動し、元のPGと移動先のPGとを比較します。その結果、リカバリ時間が増加します(※9参照)。

検証中、PGログの数を減少させることで、Write Amplificationが減少することを確認しました。上記PGログに関するチューニングは、以下の通りになります。

osd_min_pg_log_entries = 10 

osd_max_pg_log_entries = 10
osd_pg_log_dups_tracked = 10
osd_pg_log_trim_min = 10

※6:CephではすべてのCeph Poolのタイプを一致するために、Primary Log-Based Replicationを採用しています。まずデータを読み取るには、書き込みの完了したものを返す必要があります。これを扱う上で、Cephでは特定のPGに対する書き込みを、一つのPrimary OSDを経由するようにし、Primary OSDからSecondary OSDへと出力します。

ceph-primary-replication

Ceph公式ドキュメントより

この時のPGの状態は、2つの数値で表すことができます。それは、Primary OSDにおける最新の書き込みのマップのエポック(バージョン番号)と、PGごとのバージョン番号です。

これに加えて、可能な限り最新の操作ログを保持します。この操作ログには、開始したがコミットされていない不安定な書き込みログや、ローカルでは最新でないオブジェクトなども含みます。このログを利用し、最新の書き込みがされたOSDとやり取りすることで、クライアントからコミットされたすべての書き込みログを含むログが決定されます。

※7:Smallブロックサイズのほうが変更点に関するログが多くなる理由については分かっておりません。Small ブロックサイズの場合Largeブロックサイズと比べてepochのリストが増加し、変更点に関するログが多く発生する、ということ?

※8:Cephではデータのリバランスや復旧の際、PG内の別のOSD上にあるデータを利用します。これをBackfill(埋め戻し)と呼んでいるのでは、と考えています。

※9:PG数が少ない場合、PGあたりのOSD数が増加するため、Backfill後のデータ比較の対象が多くなり、結果的にリカバリ時間が増加するのでは、と考えています。


※参考リンク:


RHEL 7.6の設定

CephノードのRHEL 7.6に関するチューニング設定 (/usr/lib/tuned/ceph-tuned/tuned.conf)は、以下の通りです。

[main]
summary=ceph_perf

[cpu]
governor=performance
energy_perf_bias=performance
min_perf_pct=100
force_latency=1

[disk]
readahead=>4096

[sysctl]
kernel.sched_min_granularity_ns = 10000000
kernel.sched_wakeup_granularity_ns = 15000000
fs.aio-max-nr=1048576
kernel.pid_max=4194303
fs.file-max=26234859
vm.zone_reclaim_mode=0
vm.swappiness=1
vm.dirty_ratio = 10
vm.dirty_background_ratio = 5
net.ipv4.tcp_rmem=4096 87380 134217728
net.ipv4.tcp_wmem=4096 65536 134217728
net.core.rmem_max=268435456
net.core.wmem_max=268435456
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_timestamps = 0
net.core.netdev_max_backlog = 50000
net.ipv4.tcp_max_syn_backlog = 30000
net.ipv4.tcp_max_tw_buckets = 2000000

チューニングの結果

Smallブロックに合わせてチューニングした結果、下の図で見られるように、IOPS、平均レイテンシ、Tailレイテンシ(※1参照)0のいずれも改善されました。計測ではランダム読み込み、ランダム書き込み、ランダム読み込み・書き込みの両方について記録しており、このうち特にランダムの書き込みにおいてチューニング後の結果が大きく改善されました。

ワークロード IOPS 平均レイテンシ Tailレイテンシ
ランダム読み込み 43%向上 30%低下 36%低下
ランダム読み込み・書き込み 132%向上 73%低下 90%低下
ランダム書き込み 134%向上 70%低下 91%低下

ceph-tuning-results

※10:あるシステムが多くのRequestを処理する中で、その一部が処理に長時間を要する場合があります。Requestに対する処理時間の分布を表したときに、長時間かかるものがtail(=分布の裾の部分)にあたり、このようなレイテンシをTail Latencyと呼びます。Tail Latencyはシステムのパフォーマンスを低下させる影響があると指摘されており、その影響は、システムが複雑で大規模であるほど大きく、また並列処理を行う場合においては、より大きなパフォーマンス低下がもたらされる事があると言われています。


※参考リンク:


Tektonに入門する

はじめに

最近Kubernetes上でCI/CDパイプラインを構築するTektonというツールについて調べる機会があったので、本記事で簡単にまとめておきます。

Tektonとは

TektonKubernetes NativeなCI/CDソリューションとして開発され、Kubernetesクラスター上でのアプリケーションのビルド・テスト・デプロイなどを目的としたパイプラインの構築を実現します。

Tektonは主に4つのコンポーネントを利用しています。

  • Pipelines: CI/CDワークフローの基礎となるブロック。後述のTaskPipelineなどを含む。Tektonのコア機能や他の機能の基盤を提供する。
  • Triggers: CI/CDワークフロー向けのイベントトリガー機能を提供。
  • CLI: TektonのCI/CDワークフローを管理するCLIツール(tkn)を提供。
  • Dashboard: Pipeline向けWeb UIを提供。

Tektonのコンセプト

Pipelinesは複数のコンセプトで構成されています。こちらはConcept Modelとして公式ドキュメントでも記載されていますが、以下のようなコンセプトから成り立っています。

  • step:CI/CDワークフローでの操作はstepという単位で実行されます。stepでは、ユーザーの指定したコンテナイメージを利用してコマンドを実行します。

  • task:複数のStepをまとめたものがTaskになります。各TaskはPod内で実行され、stepはコンテナとして実行されます。TaskをPodとして実行することで、各stepに対して共通の環境を提供することができます。また、Taskで利用するVolumeを用意すれば、各stepからvolumeを利用し、ファイルの共有などが実現できます。

  • pipeline:複数のTaskをまとめたものがPipelineになります。複数のTaskPodを作成して、各Podを順番に実行します。Pipelinesは各Stepコンテナにentrypointバイナリを注入し、ユーザーの指定したコマンドを実行します。各Pipelineでのワークフローの実行や状態は、Kubernetes annotationsを利用して実行・追跡をします。Annotationsは各Stepコンテナにおいて、Kubernetes downward APIのファイルフォームで導入されます。entrypointバイナリは導入されたファイル群をチェックし、特定のannotationがファイルに出現した時のみコマンドを実行します。

concepts

Tekton公式ドキュメントより

Tekton Pipelinesでは複数のCustom Resourceを組み合わせてCI/CDパイプラインを構築します。利用する主なCustom Resourceは以下の通りです。

Custom Resource名 利用用途の概要
Task Taskを定義する。各Stepの内容や使用するコンテナイメージ、パラメータなどを指定する
TaskRun 定義したTaskを実行するために利用する。指定のTask内にあるStepを順番に実行し、処理が完了・失敗するまで動作する。TaskRunでは、実行するTaskの指定やTask内で利用するInput/Outputリソースを指定する
Pipeline Pipelineを定義する。Pipelineで利用するStepやパラメータなどを指定する
PipelineRun 定義したPipelineを実行するために利用する。指定したPipeline内のTaskに含まれるStepを順番に実行し、処理が完了・失敗するまで動作する。PipelineRunでは、実行するPipelineやPieline内で利用するInput/Outputリソースなどを指定する
PipelineResource Task・Pipelineで利用するリソースを定義する。利用できるリソース種別としてはgit image cluster storageなど ※7月15日時点でalphaのみ提供

※参考ドキュメント:


Tektonを利用する流れ

Tektonを利用する際の大きな流れとしては以下のようになります。各CRを作成後にTaskRun PipelineRunを実行することで、Taskを実行するPodが順次作成され、処理が実行されます。

f:id:FY0323:20200718153849p:plain

Tekton Custom Resource間の関連性

次に、CRの定義ファイル間でどのような関連があるかを図示しておきます。ここではPipelineResourceを利用する・しない場合で分けております。なお、ここでは各リソースの定義ファイルの例を載せておりますが、書き方は複数あるのであくまで一例です。また内容は説明用のものなので、実際のリソース作成に使うことはできません。

PipelineResourceを利用する場合(v1alpha1

TaskRunを利用する場合、まずはPipelineResource Taskを作成します。PipelineResourceには利用するリソースURLなどを、Taskには実行する各Stepでのコンテナイメージやコマンドを設定します。

PipelineResource Taskを作成後はTaskRunリソースを作成し、Taskを実行します。実行中はPodの状態やtknコマンドを利用することで状況を確認することができます。

f:id:FY0323:20200718122014p:plain

次にPipelineRunを利用する場合、PipelineResource Taskに加えてPipelineリソースを作成します。Pipelineでは利用するTaskやTask中で使用するリソースなどを指定します。

3種類のリソースを作成した後はPipelineRunを作成し、Pipelineを実行します。こちらも実行中はPodの状態やtknコマンドにより状況を確認することができます。

f:id:FY0323:20200718122125p:plain

PipelineResourceを利用しない場合(v1beta1

PipelineResourceを利用しない場合も、大きな流れは変わりません。TaskRunを利用する場合は事前にTaskを作成しておき、TaskRunによってそれを実行します。Taskで利用するGitHub URLなどのパラメータはTask TaskRunそれぞれで指定することが可能です。例えばTaskで利用するのと同じ名称のパラメータをTaskRunで指定することで、同じTaskを別々のTaskRunで利用する際にTaskRun側で必要なパラメータを指定することができます。

f:id:FY0323:20200718122449p:plain

PipelineRunを利用する場合も同様で、Task Pipelineリソースをあらかじめ作成してからPipelineRunによって実行します。

f:id:FY0323:20200718122712p:plain

Task/Pipelineの構成

今回は以下のようなPipelineの構築を行います。大まかな流れは以下の通りです。

  1. GitHubからソースコードをclone
  2. ソースコードをDockerイメージにbuild
  3. buildしたDockerイメージをDocker Hubにpush
  4. Docker Hubのイメージをpull
  5. pullしたイメージをKubernetes上にdeploy

f:id:FY0323:20200718124007p:plain

今回はPipelineResourcesは利用せず、各CR内で利用するリソースのURL等を直接指定しています。

また上図中にWorkspaceというものが見えます。Workspaceは複数のTaskが共有することのできる領域を表しており、例えばKubernetesSecretConfigMapPersistentVolumeClaimなどを共有することができます。これにより、あるTaskで取得した情報(今回で言えばGitHubから取得したソースコードなど)を別のTaskに渡すことが可能です。

Workspaceを利用する場合は、PersistentVolumeClaim emptyDir ConfigMap SecretのいずれかのKubernetesリソースを指定する必要があります。

TektonにはWorkspaceと似たような機能としてVolumeというものが存在しますが、あるTaskが複数のWorkspaceを利用する場合、TaskRun PipelineRunから、どのWorkspaceに対してどの保存領域(PersistentVolumeClaimなど)を利用するかを指定することができます。そのため、Taskの再利用を考えた場合はWorkspaceのほうが使いやすい機能となります。

なお、今回利用しているPipelineの構成やyamlファイルの多くはこちらの記事を参考にしている部分が多いため、合わせて読んでいただければと思います。

TektonのTasks/Pipelineの利用方法

構築環境情報

今回構築で利用した環境は以下の通りです。Pipeline中で利用するアプリケーションは、簡単なJSONのメッセージを返すだけのWebアプリです。

今回はGitHub、Docker Hubともにパブリックなものを利用しましたが、プライベートリポジトリを利用する場合は以下のリンク先を参照してください。


※参考ドキュメント:


Tektonの構築

まずはTektonの構築を行います。またTekton Pipelinesを利用するうえで必要となる設定も合わせて行います。なお構築・検証等で利用した定義ファイルはこちらに置いてあります

Tekton Controller / CRDのデプロイ

まずはTektonを利用するうえで必要なController Webhook Deployment、各種CustomResourceDefinitionなどをKubernetesクラスター上にデプロイします。Tektonのデプロイは、以下のコマンドを実行するだけで完了します。

# Tekton pipelineのデプロイ
$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml
namespace/tekton-pipelines created
podsecuritypolicy.policy/tekton-pipelines created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-tenant-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-webhook-cluster-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-leader-election created
role.rbac.authorization.k8s.io/tekton-pipelines-controller created
role.rbac.authorization.k8s.io/tekton-pipelines-webhook created
serviceaccount/tekton-pipelines-controller created
serviceaccount/tekton-pipelines-webhook created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-leaderelection created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-tenant-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook-cluster-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook-leaderelection created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook created
customresourcedefinition.apiextensions.k8s.io/clustertasks.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/conditions.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/images.caching.internal.knative.dev created
customresourcedefinition.apiextensions.k8s.io/pipelines.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelineruns.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelineresources.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/tasks.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/taskruns.tekton.dev created
secret/webhook-certs created
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.pipeline.tekton.dev created
mutatingwebhookconfiguration.admissionregistration.k8s.io/webhook.pipeline.tekton.dev created
validatingwebhookconfiguration.admissionregistration.k8s.io/config.webhook.pipeline.tekton.dev created
clusterrole.rbac.authorization.k8s.io/tekton-aggregate-edit created
clusterrole.rbac.authorization.k8s.io/tekton-aggregate-view created
configmap/config-artifact-bucket created
configmap/config-artifact-pvc created
configmap/config-defaults created
configmap/feature-flags created
configmap/config-leader-election created
configmap/config-logging created
configmap/config-observability created
deployment.apps/tekton-pipelines-controller created
service/tekton-pipelines-controller created
deployment.apps/tekton-pipelines-webhook created
service/tekton-pipelines-webhook created


# 実行後の確認
$ kubectl get pods -n tekton-pipelines
NAME                                           READY   STATUS    RESTARTS   AGE
tekton-pipelines-controller-548c8fb7b5-hbb9m   1/1     Running   0          15s
tekton-pipelines-webhook-9cfc4b4df-vw8ss       1/1     Running   0          15s

Docker HubへPushするためのSecret作成

KubernetesからDocker HubへコンテナイメージをPushするため、Docker Hubのログイン情報をSecretとして用意します。ここで作成したSecretを利用するには、Service Account・各Pipelineリソースのどちらかに関連付ける必要があります。今回作成したSecretは、後ほどService Accountを作成する際に指定することで、Service Accountと関連付けます。

annotationsに記載しているtekton.dev/docker-0: https://index.docker.io/v1/は、このSecretで指定している秘匿情報がどのWebアドレスに属したものかを表します。

apiVersion: v1
kind: Secret
metadata:
  name: basic-user-pass
  annotations:
    tekton.dev/docker-0: https://index.docker.io/v1/
type: kubernetes.io/basic-auth
stringData:
  username: <username>
  password: <password>
# Secretの作成
$ kubectl apply -f docker-secret.yml
secret/basic-user-pass created


# 作成後の確認
$ kubectl describe secret basic-user-pass
Name:         basic-user-pass
Namespace:    default
Labels:       <none>
Annotations:  tekton.dev/docker-0: https://index.docker.io/v1/

Type:  kubernetes.io/basic-auth

Data
====
password:  <byte number>
username:  <byte number>

※参考ドキュメント:


Tekton Pipeline実行用Service Accountの作成とRoleの付与

次にTekton Pipelineを実行するためのService Accountを用意します。作成の定義ファイルはこちらのファイルを利用しました(Service Accountと関連付けるSecretのみ変更しています)。

今回使用するpipeline-accountというService Accountには、合わせてSecret Roleを付与します。SecretにはAPIアクセスの認証に利用するトークンの情報を、Roleにはtekton-pipelinesNamespaceに含まれるリソースに対して操作する権限を追加し、RoleBindingを用いてService Accountと紐づけます。

# Service Account作成
$ kubectl apply -f pipeline-account.yml
serviceaccount/pipeline-account created
secret/kube-api-secret created
role.rbac.authorization.k8s.io/pipeline-role created
rolebinding.rbac.authorization.k8s.io/pipeline-role-binding created



# 作成後の確認
$ kubectl describe sa pipeline-account
Name:                pipeline-account
Namespace:           default
Labels:              <none>
Annotations:         Image pull secrets:  <none>
Mountable secrets:   basic-user-pass
                     pipeline-account-token-sg475
Tokens:              kube-api-secret
                     pipeline-account-token-sg475
Events:              <none>

Workspace用のPersistent Volume Claim作成

次に、Pipelineで利用するWorkspaceのためのストレージ領域を用意します。今回は、Alibaba Cloudで利用できるStorage Classのうちalicloud-disk-ssdを利用します。Persistent Volume Claim作成時に、Storage Classとしてalicloud-disk-ssdを指定することでPersistent Volumeが作成され、Kubernetesの提供する永続ストレージを利用することができます。

なおAlibaba Cloud DiskをPVCで利用する場合は、20Gi以上のサイズを指定する必要があります。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: tekton-volume
spec:
  accessModes: 
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: alicloud-disk-ssd
# 利用可能なStorage Class
$ kubectl get sc
NAME                       PROVISIONER                       AGE
alicloud-disk-available    diskplugin.csi.alibabacloud.com   3h28m
alicloud-disk-efficiency   diskplugin.csi.alibabacloud.com   3h28m
alicloud-disk-essd         diskplugin.csi.alibabacloud.com   3h28m
alicloud-disk-ssd          diskplugin.csi.alibabacloud.com   3h28m
alicloud-disk-topology     diskplugin.csi.alibabacloud.com   3h28m



# 20Gi未満を指定した場合
$ kubectl apply -f tekton-pvc.yml
$ kubectl get pvc
NAME            STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
tekton-volume   Pending                                      alicloud-disk-ssd   5s
$ kubectl describe pvc tekton-volume

(中略)

Message: disk size is not supported., PVC defined storage should equal/greater than 20Gi
  Normal  ExternalProvisioning  0s (x3 over 19s)  persistentvolume-controller  waiting for a volume to be created, either by external provisioner "diskplugin.csi.alibabacloud.com" or manually created by system administrator



# 20Gi以上を指定した場合
$ kubectl apply -f tekton-pvc.yml
persistentvolumeclaim/tekton-volume created

$ kubectl get pvc
NAME            STATUS   VOLUME                   CAPACITY   ACCESS MODES   STORAGECLASS        AGE
tekton-volume   Bound    d-6we0n5028zulr8h5qwzo   20Gi       RWO            alicloud-disk-ssd   3s

※参考ドキュメント:


Task/TaskRunの利用方法

ここから実際のTask/Pipelineの利用方法を紹介します。

まずはTaskRunを利用する方法について紹介します。ここでは3つのTaskのうち一つ目のTaskのみを利用します。

まずはTaskについてです。TaskはTektonのGitHub RepositoryでCatalogとして紹介されているこちらの定義ファイルを利用しました。

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: git-clone
spec:
  workspaces:
    - name: output
      description: The git repo will be cloned onto the volume backing this workspace
  params:
    - name: url
      description: git url to clone
      type: string
    - name: revision
      description: git revision to checkout (branch, tag, sha, ref…)
      type: string
      default: master
    - name: refspec
      description: (optional) git refspec to fetch before checking out revision
      default: ""
    - name: submodules
      description: defines if the resource should initialize and fetch the submodules
      type: string
      default: "true"
    - name: depth
      description: performs a shallow clone where only the most recent commit(s) will be fetched
      type: string
      default: "1"
    - name: sslVerify
      description: defines if http.sslVerify should be set to true or false in the global git config
      type: string
      default: "true"
    - name: subdirectory
      description: subdirectory inside the "output" workspace to clone the git repo into
      type: string
      default: ""
    - name: deleteExisting
      description: clean out the contents of the repo's destination directory (if it already exists) before trying to clone the repo there
      type: string
      default: "false"
    - name: httpProxy
      description: git HTTP proxy server for non-SSL requests
      type: string
      default: ""
    - name: httpsProxy
      description: git HTTPS proxy server for SSL requests
      type: string
      default: ""
    - name: noProxy
      description: git no proxy - opt out of proxying HTTP/HTTPS requests
      type: string
      default: ""
  results:
    - name: commit
      description: The precise commit SHA that was fetched by this Task
  steps:
    - name: clone
      image: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.12.1
      script: |
        CHECKOUT_DIR="$(workspaces.output.path)/$(params.subdirectory)"
        cleandir() {
          # Delete any existing contents of the repo directory if it exists.
          #
          # We don't just "rm -rf $CHECKOUT_DIR" because $CHECKOUT_DIR might be "/"
          # or the root of a mounted volume.
          if [[ -d "$CHECKOUT_DIR" ]] ; then
            # Delete non-hidden files and directories
            rm -rf "$CHECKOUT_DIR"/*
            # Delete files and directories starting with . but excluding ..
            rm -rf "$CHECKOUT_DIR"/.[!.]*
            # Delete files and directories starting with .. plus any other character
            rm -rf "$CHECKOUT_DIR"/..?*
          fi
        }
        if [[ "$(params.deleteExisting)" == "true" ]] ; then
          cleandir
        fi
        test -z "$(params.httpProxy)" || export HTTP_PROXY=$(params.httpProxy)
        test -z "$(params.httpsProxy)" || export HTTPS_PROXY=$(params.httpsProxy)
        test -z "$(params.noProxy)" || export NO_PROXY=$(params.noProxy)
        /ko-app/git-init \
          -url "$(params.url)" \
          -revision "$(params.revision)" \
          -refspec "$(params.refspec)" \
          -path "$CHECKOUT_DIR" \
          -sslVerify="$(params.sslVerify)" \
          -submodules="$(params.submodules)" \
          -depth "$(params.depth)"
        cd "$CHECKOUT_DIR"
        RESULT_SHA="$(git rev-parse HEAD | tr -d '\n')"
        EXIT_CODE="$?"
        if [ "$EXIT_CODE" != 0 ]
        then
          exit $EXIT_CODE
        fi
        # Make sure we don't add a trailing newline to the result!
        echo -n "$RESULT_SHA" > $(results.commit.path)

定義ファイルの各項目について簡単に紹介します。

  • spec.workspaces: ここではoutputというWorkspaceを指定しており、Cloneしたソースコードの置き場所として主に利用します。また実行後のSHAの値をファイルに出力し、Workspaceに保存しています。

  • spec.results: resultsを指定すると、Taskの実行結果をファイルに出力することができます。ここではspec.steps内で利用し、Taskの実行結果を$(results.commit.path)に保存しています。

  • spec.steps: ここではscriptを設定してStepコンテナ内で実行するコマンドを指定しています。コンテナはTektonが用意したgit-initというイメージを利用し、指定したGitHubリポジトリからのcloneと実行結果のSHAを保存する処理を実行します。

続いてTaskRunについてです。TaskRunはTekton GitHub Repository中のこちらの定義ファイルを参考にしました。元の定義ファイルはapiVersion: v1alpha1となっているので、apiVersion: v1beta1に合わせて一部修正しております。

apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
  name: git-clone-run
spec:
  workspaces:
    - name: output
      emptyDir: {}
  params:
    - name: url
      value: https://github.com/FY0323/tekton-test.git
  taskRef:
    name: git-clone
  • spec.params: ここではTask中で利用するパラメータを指定しています。今回はclone元のGitHub RepositoryのURLを指定しています。
  • spec.taskRef: taskRefには、TaskRun中に利用するTaskの名称を指定します。

※参考ドキュメント:


上記定義ファイルを利用してTaskを実行します。

# Taskの作成
$ kubectl apply -f tk_task/task1.yml
task.tekton.dev/git-clone created

$ kubectl get tekton-pipelines
NAME                        AGE
task.tekton.dev/git-clone   17m



# TaskRunの作成
$ kubectl apply -f taskrun1.yml
taskrun.tekton.dev/git-clone-run created

# 実行中
$ kubectl get tekton-pipelines
NAME                        AGE
task.tekton.dev/git-clone   18m

NAME                               SUCCEEDED   REASON    STARTTIME   COMPLETIONTIME
taskrun.tekton.dev/git-clone-run   Unknown     Running   4s


# 完了後
$ kubectl get tekton-pipelines
NAME                        AGE
task.tekton.dev/git-clone   18m

NAME                               SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
taskrun.tekton.dev/git-clone-run   True        Succeeded   13s         6s


$ kubectl get pods
NAME                      READY   STATUS      RESTARTS   AGE
git-clone-run-pod-8827g   0/1     Completed   0          27s

$ tkn taskrun logs git-clone-run --last -f
[clone] + CHECKOUT_DIR=/workspace/output/
[clone] + '[[' false '==' true ]]
[clone] + test -z
[clone] + test -z
[clone] + test -z
[clone] + /ko-app/git-init -url https://github.com/FY0323/tekton-test.git -revision master -refspec  -path /workspace/output/ '-sslVerify=true' '-submodules=true' -depth 1
[clone] {"level":"info","ts":1594704754.7358825,"caller":"git/git.go:136","msg":"Successfully cloned https://github.com/FY0323/tekton-test.git @ d33aa9b539e788c4f7b90ca8c4879664effd306c (grafted, HEAD, origin/master) in path /workspace/output/"}
[clone] {"level":"info","ts":1594704754.7876565,"caller":"git/git.go:177","msg":"Successfully initialized and updated submodules in path /workspace/output/"}
[clone] + cd /workspace/output/
[clone] + git rev-parse HEAD
[clone] + tr -d '\n'
[clone] + RESULT_SHA=d33aa9b539e788c4f7b90ca8c4879664effd306c
[clone] + EXIT_CODE=0
[clone] + '[' 0 '!=' 0 ]
[clone] + echo -n d33aa9b539e788c4f7b90ca8c4879664effd306c

※参考ドキュメント:


Pipeline/PipelineRunの利用方法

次にPipelineの利用方法についてです。

Pipelineで利用する3つのTaskは先ほどのtask1.ymlに加え、こちらこちらの定義ファイルをそれぞれ利用しました。

task2.yml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: kaniko
spec:
  params:
  - name: IMAGE
    description: Name (reference) of the image to build.
  - name: DOCKERFILE
    description: Path to the Dockerfile to build.
    default: ./Dockerfile
  - name: CONTEXT
    description: The build context used by Kaniko.
    default: ./
  - name: EXTRA_ARGS
    default: ""
  - name: BUILDER_IMAGE
    description: The image on which builds will run
    default: gcr.io/kaniko-project/executor:latest
  workspaces:
  - name: source
  results:
  - name: IMAGE-DIGEST
    description: Digest of the image just built.

  steps:
  - name: build-and-push
    workingDir: $(workspaces.source.path)
    image: $(params.BUILDER_IMAGE)
    # specifying DOCKER_CONFIG is required to allow kaniko to detect docker credential
    # https://github.com/tektoncd/pipeline/pull/706
    env:
    - name: DOCKER_CONFIG
      value: /tekton/home/.docker
    command:
    - /kaniko/executor
    - $(params.EXTRA_ARGS)
    - --dockerfile=$(params.DOCKERFILE)
    - --context=$(workspaces.source.path)/$(params.CONTEXT)  # The user does not need to care the workspace and the source.
    - --destination=$(params.IMAGE)
    - --oci-layout-path=$(workspaces.source.path)/$(params.CONTEXT)/image-digest
    # kaniko assumes it is running as root, which means this example fails on platforms
    # that default to run containers as random uid (like OpenShift). Adding this securityContext
    # makes it explicit that it needs to run as root.
    securityContext:
      runAsUser: 0
  - name: write-digest
    workingDir: $(workspaces.source.path)
    image: gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/imagedigestexporter:v0.11.1
    # output of imagedigestexport [{"key":"digest","value":"sha256:eed29..660","resourceRef":{"name":"myrepo/myimage"}}]
    command: ["/ko-app/imagedigestexporter"]
    args:
    - -images=[{"name":"$(params.IMAGE)","type":"image","url":"$(params.IMAGE)","digest":"","OutputImageDir":"$(workspaces.source.path)/$(params.CONTEXT)/image-digest"}]
    - -terminationMessagePath=$(params.CONTEXT)/image-digested
  - name: digest-to-results
    workingDir: $(workspaces.source.path)
    image: stedolan/jq
    script: |
      cat $(params.CONTEXT)/image-digested | jq '.[0].value' -rj | tee /tekton/results/IMAGE-DIGEST

task3.yml

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: deploy-using-kubectl
spec:
  workspaces:
    - name: git-source
      description: The git repo
  params:
    - name: pathToYamlFile
      description: The path to the yaml file to deploy within the git source
    - name: imageUrl
      description: Image name including repository
    - name: imageTag
      description: Image tag
      default: "latest"
    - name: imageDigest
      description: Digest of the image to be used.
  steps:
    - name: update-yaml
      image: alpine
      command: ["sed"]
      args:
        - "-i"
        - "-e"
        - "s;__IMAGE__;$(params.imageUrl):$(params.imageTag);g"
        - "-e"
        - "s;__DIGEST__;$(params.imageDigest);g"
        - "$(workspaces.git-source.path)/$(params.pathToYamlFile)"
    - name: run-kubectl
      image: lachlanevenson/k8s-kubectl
      command: ["kubectl"]
      args:
        - "apply"
        - "-f"
        - "$(workspaces.git-source.path)/$(params.pathToYamlFile)"

Pipelineにはこちらのファイルを利用しています。

pipeline1.yml

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: build-and-push-pipeline
spec:
  workspaces:
    - name: git-source
      description: The git repo
  params:
    - name: gitUrl
      description: Git repository url
    - name: gitRevision
      description: Git revision to check out
      default: master
    - name: pathToContext
      description: The path to the build context, used by Kaniko - within the workspace
      default: src
    - name: imageUrl
      description: Image name including repository
    - name: imageTag
      description: Image tag
      default: "latest"
    - name: pathToYamlFile
      description: The path to the yaml file to deploy within the git source
  tasks:
    - name: clone-repo
      taskRef:
        name: git-clone
      workspaces:
        - name: output
          workspace: git-source
      params:
        - name: url
          value: "$(params.gitUrl)"
        - name: revision
          value: "$(params.gitRevision)"
        - name: subdirectory
          value: "."
        - name: deleteExisting
          value: "true"
    - name: source-to-image
      taskRef:
        name: kaniko
      runAfter:
        - clone-repo
      workspaces:
        - name: source
          workspace: git-source
      params:
        - name: CONTEXT
          value: $(params.pathToContext)
        - name: IMAGE
          value: $(params.imageUrl):$(params.imageTag)
    - name: deploy-to-cluster
      taskRef:
        name: deploy-using-kubectl
      runAfter: 
        - source-to-image
      workspaces:
        - name: git-source
          workspace: git-source
      params:
        - name: pathToYamlFile
          value: $(params.pathToYamlFile)
        - name: imageUrl
          value: $(params.imageUrl)
        - name: imageTag
          value: $(params.imageTag)
        - name: imageDigest
          value: $(tasks.source-to-image.results.IMAGE-DIGEST)
  • spec.tasks: Pipeline中のTaskにrunAfterを設定することで、Task間の実行順を設定することができます。ここではclone-repo source-to-image deploy-to-clusterという順で実行されるようrunAfterを設定しています。

最後にPipelineRunこちらのファイルを利用しています。PipelineRunではPipelineで設定したパラメータやWorkspaceに利用するPersistent Volume Claimなどを設定します。

pipelinerun1.yml

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: build-and-push-pieline
spec:
  pipelineRef:
    name: build-and-push-pipeline
  params:
    - name: gitUrl
      value: https://github.com/FY0323/tekton-test.git
    - name: pathToContext
      value: .
    - name: pathToYamlFile
      value: "test-app.yml"
    - name: imageUrl
      value: fy0323/tekton-test
    - name: imageTag
      value: "1"
  serviceAccountName: pipeline-account
  workspaces:
    - name: git-source
      persistentVolumeClaim:
        claimName: tekton-volume
  • spec.pipelineRef: ここには実行するPipelineを指定します。
  • spec.serviceAccountName: ここではPipelineを実行する際に利用するService Account(ここではpipeline-account)を指定します。
  • spec.workspaces: ここではPipeline中に利用するWorkspaceと、それに利用するKubernetesリソースを指定します。ここではPersistent Volume Claimを利用します。

※参考ドキュメント:


それでは用意した定義ファイルを用いて各リソースを作成します。

# Taskの作成
$ kubectl apply -f tk_task/
task.tekton.dev/kaniko created
task.tekton.dev/deploy-using-kubectl created

$ kubectl get tekton-pipelines
NAME                                   AGE
task.tekton.dev/deploy-using-kubectl   7s
task.tekton.dev/git-clone              18h
task.tekton.dev/kaniko                 18h



# Pipelineの作成
$ kubectl apply -f pipeline1.yml
pipeline.tekton.dev/build-and-push-pipeline created

$ kubectl get tekton-pipelines
NAME                                          AGE
pipeline.tekton.dev/build-and-push-pipeline   1m

NAME                                   AGE
task.tekton.dev/deploy-using-kubectl   2m
task.tekton.dev/git-clone              19h
task.tekton.dev/kaniko                 19h



# PipelineRunの作成(Pipelineの実行)

$ kubectl apply -f pipelinerun1.yml
pipelinerun.tekton.dev/build-and-push-pieline created

$ tkn pipelinerun logs --last -f
[clone-repo : clone] + CHECKOUT_DIR=/workspace/output/.
[clone-repo : clone] + '[[' true '==' true ]]
[clone-repo : clone] + cleandir
[clone-repo : clone] + '[[' -d /workspace/output/. ]]
[clone-repo : clone] + rm -rf /workspace/output/./Dockerfile /workspace/output/./image-digest /workspace/output/./image-digested /workspace/output/./pipeline-account.yml /workspace/output/./pipeline1.yml /workspace/output/./pipelinerun1.yml /workspace/output/./sa_clusterrolebinding.yml /workspace/output/./src /workspace/output/./taskrun1.yml /workspace/output/./test-app.yml /workspace/output/./tk_resource /workspace/output/./tk_task
[clone-repo : clone] + rm -rf /workspace/output/./.git
[clone-repo : clone] + rm -rf '/workspace/output/./..?*'
[clone-repo : clone] + test -z
[clone-repo : clone] + test -z
[clone-repo : clone] + test -z
[clone-repo : clone] + /ko-app/git-init -url https://github.com/FY0323/tekton-test.git -revision master -refspec  -path /workspace/output/. '-sslVerify=true' '-submodules=true' -depth 1
[clone-repo : clone] {"level":"info","ts":1594779966.749329,"caller":"git/git.go:136","msg":"Successfully cloned https://github.com/FY0323/tekton-test.git @ 3d34a47b3de078961ebf8c7b71143bf9ea17f2ec (grafted, HEAD, origin/master) in path /workspace/output/."}
[clone-repo : clone] {"level":"info","ts":1594779966.8057873,"caller":"git/git.go:177","msg":"Successfully initialized and updated submodules in path /workspace/output/."}
[clone-repo : clone] + cd /workspace/output/.
[clone-repo : clone] + git rev-parse HEAD
[clone-repo : clone] + tr -d '\n'
[clone-repo : clone] + RESULT_SHA=3d34a47b3de078961ebf8c7b71143bf9ea17f2ec
[clone-repo : clone] + EXIT_CODE=0
[clone-repo : clone] + '[' 0 '!=' 0 ]
[clone-repo : clone] + echo -n 3d34a47b3de078961ebf8c7b71143bf9ea17f2ec

[source-to-image : build-and-push] INFO[0002] Retrieving image manifest golang:latest
[source-to-image : build-and-push] INFO[0004] Retrieving image manifest golang:latest
[source-to-image : build-and-push] INFO[0008] Built cross stage deps: map[]
[source-to-image : build-and-push] INFO[0008] Retrieving image manifest golang:latest
[source-to-image : build-and-push] INFO[0010] Retrieving image manifest golang:latest
[source-to-image : build-and-push] INFO[0014] Executing 0 build triggers
[source-to-image : build-and-push] INFO[0014] Unpacking rootfs as cmd COPY src/main.go /go/src/work/ requires it.
[source-to-image : build-and-push] INFO[0038] EXPOSE 8080
[source-to-image : build-and-push] INFO[0038] cmd: EXPOSE
[source-to-image : build-and-push] INFO[0038] Adding exposed port: 8080/tcp
[source-to-image : build-and-push] INFO[0038] COPY src/main.go /go/src/work/
[source-to-image : build-and-push] INFO[0038] Taking snapshot of files...
[source-to-image : build-and-push] INFO[0038] WORKDIR /go/src/work
[source-to-image : build-and-push] INFO[0038] cmd: workdir
[source-to-image : build-and-push] INFO[0038] Changed working directory to /go/src/work
[source-to-image : build-and-push] INFO[0038] No files changed in this command, skipping snapshotting.
[source-to-image : build-and-push] INFO[0038] RUN go get -u github.com/gin-gonic/gin && go build main.go
[source-to-image : build-and-push] INFO[0038] Taking snapshot of full filesystem...
[source-to-image : build-and-push] INFO[0045] cmd: /bin/sh
[source-to-image : build-and-push] INFO[0045] args: [-c go get -u github.com/gin-gonic/gin && go build main.go]
[source-to-image : build-and-push] INFO[0045] Running: [/bin/sh -c go get -u github.com/gin-gonic/gin && go build main.go]
[source-to-image : build-and-push] INFO[0104] Taking snapshot of full filesystem...
[source-to-image : build-and-push] INFO[0109] CMD ["./main"]


[source-to-image : digest-to-results] + cat ./image-digested
[source-to-image : digest-to-results] + jq .[0].value -rj
[source-to-image : digest-to-results] + tee /tekton/results/IMAGE-DIGEST
[source-to-image : digest-to-results] sha256:b5248ad0c8ff878ad795c17fdad3b0a842cf38e3dc7df97f66012511af803c5a


[deploy-to-cluster : run-kubectl] deployment.apps/test-app configured
[deploy-to-cluster : run-kubectl] service/test-app created



# 実行後の確認
$ kubectl get tekton-pipelines
NAME                                          AGE
pipeline.tekton.dev/build-and-push-pipeline   37m

NAME                                            SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
pipelinerun.tekton.dev/build-and-push-pieline   True        Succeeded   14m         10m

NAME                                   AGE
task.tekton.dev/deploy-using-kubectl   39m
task.tekton.dev/git-clone              19h
task.tekton.dev/kaniko                 19h

NAME                                                                SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
taskrun.tekton.dev/build-and-push-pieline-clone-repo-hwc7g          True        Succeeded   14m         13m
taskrun.tekton.dev/build-and-push-pieline-deploy-to-cluster-n9wdw   True        Succeeded   11m         10m
taskrun.tekton.dev/build-and-push-pieline-source-to-image-jmqg8     True        Succeeded   13m         11m


$ kubectl get pods
NAME                                                       READY   STATUS      RESTARTS   AGE
build-and-push-pieline-clone-repo-hwc7g-pod-flpw6          0/1     Completed   0          6m7s
build-and-push-pieline-deploy-to-cluster-n9wdw-pod-ttp7l   0/2     Completed   0          3m9s
build-and-push-pieline-source-to-image-jmqg8-pod-zfrxg     0/3     Completed   0          5m49s
test-app-6b47db66b5-cfgjz                                  1/1     Running     0          2m51s

$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   172.21.0.1      <none>        443/TCP          45h
test-app     NodePort    172.21.15.253   <none>        8080:30008/TCP   3m11s

なお、実行後のPersistent Volumeにアクセスすると、先ほどのPipelineで作成したファイルなどが確認できます。

# Persistent VolumeへアクセスするPodを作成
$ kubectl apply -f testpod.yml
pod/mypod created



# Podへログイン
$ kubectl exec -it mypod /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
root@mypod:/#


# Persistent Volumeの内容確認
root@mypod:/# pwd
/
root@mypod:/# ls -l /var/www/html
total 48
-rw-r--r-- 1 root root  174 Jul 15 02:26 Dockerfile
drwxr-xr-x 3 root root 4096 Jul 15 02:28 image-digest★
-rw-r--r-- 1 root root  146 Jul 15 02:28 image-digested★
-rw-r--r-- 1 root root  951 Jul 15 02:26 pipeline-account.yml
-rw-r--r-- 1 root root 2099 Jul 15 02:26 pipeline1.yml
-rw-r--r-- 1 root root  676 Jul 15 02:26 pipelinerun1.yml
-rw-r--r-- 1 root root  283 Jul 15 02:26 sa_clusterrolebinding.yml
drwxr-xr-x 2 root root 4096 Jul 15 02:26 src
-rw-r--r-- 1 root root  464 Jul 15 02:26 taskrun1.yml
-rw-r--r-- 1 root root  601 Jul 15 02:29 test-app.yml
drwxr-xr-x 2 root root 4096 Jul 15 02:26 tk_resource
drwxr-xr-x 2 root root 4096 Jul 15 02:26 tk_task

root@mypod:/# cat /var/www/html/image-digested
[{"key":"digest","value":"sha256:b5248ad0c8ff878ad795c17fdad3b0a842cf38e3dc7df97f66012511af803c5a","resourceRef":{"name":"fy0323/tekton-test:1"}}]

root@mypod:/# ls -l /var/www/html/image-digest
total 12
drwxr-xr-x 3 root root 4096 Jul 15 02:28 blobs
-rwxr-xr-x 1 root root  259 Jul 15 02:28 index.json
-rwxr-xr-x 1 root root   37 Jul 15 02:28 oci-layout

root@mypod:/# cat /var/www/html/image-digest/oci-layout
{
    "imageLayoutVersion": "1.0.0"
}

root@mypod:/# cat /var/www/html/image-digest/index.json
{
   "schemaVersion": 2,
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1738,
         "digest": "sha256:b5248ad0c8ff878ad795c17fdad3b0a842cf38e3dc7df97f66012511af803c5a"
      }
   ]
}

root@mypod:/# ls -l /var/www/html/image-digest/blobs/sha256
total 401604
-rw-r--r-- 1 root root  51827493 Jul 15 02:28 5573c4b3094956931fd68c310ae92c9eb1a787f0c77ac2730be9d16cce172d5e
-rw-r--r-- 1 root root  98813395 Jul 15 02:28 56269221df77fbedf580f9a2caacbcb7dc4e7ea2d33922cd95973f70323e13a6
-rw-r--r-- 1 root root       125 Jul 15 02:28 56c4bef2c0bbf3cff5165f41fc838bc6eb019e04a44afcb8e6e7d5ab1da0f0c9
-rw-r--r-- 1 root root      5836 Jul 15 02:28 8ea17da6b6f374412f284f9d00ec18ee7926f87456ae16f30f5c7e3cfb37de2b
-rw-r--r-- 1 root root 123712482 Jul 15 02:28 90833167b3a65b4f9c43490478a2b69758cd005193ac60dbb81b20717ba4a5a0
-rw-r--r-- 1 root root   7811709 Jul 15 02:28 989e6b19a265d6b8b7934e7ddd7dc07f6e2fc945b3a28dda9b8aecb12cdb30e0
-rw-r--r-- 1 root root   9996168 Jul 15 02:28 af14b6c2f8785723bceb5964c5dec1f0489b7750e9d4ec671e49bfba15d80a39
-rw-r--r-- 1 root root      1738 Jul 15 02:28 b5248ad0c8ff878ad795c17fdad3b0a842cf38e3dc7df97f66012511af803c5a
-rw-r--r-- 1 root root       365 Jul 15 02:28 bda1f29e2d9758bd7e97f0e8cd6855a27f1a5015ed64b5d9ef763f5ba88c762e
-rw-r--r-- 1 root root  68650396 Jul 15 02:28 d4020e2aa747ca4d0ca2e87305f22f089fd57a696fd19bfa30182316d51b089a
-rw-r--r-- 1 root root  50389504 Jul 15 02:28 e9afc4f90ab09248d75c8081b6dfba749a7f7efdac704ced7e0ceb506e02fa4a

なお上記mypodで利用した定義ファイルはこちらになります。

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: tekton-volume

参考ドキュメント