98
123

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KubernetesはOCI, CRI, CNI, SMI, CSI, CDI, NRIこれだけを理解すればいいから簡単に学習できます

Last updated at Posted at 2025-02-12

Kubernetesでは、コンテナやその周辺機能を扱うために、さまざまな標準インターフェース(~Iと省略されるもの)が使われています。これらは、コンテナの実行基盤を柔軟かつプラグイン可能にするための仕組みで、それぞれ異なる目的と役割を担っています。ここでは OCI, CRI, CNI, SMI, CSI, CDI, NRI について、概要と目的、Kubernetesにおける役割、相互の関連性、実践例を分かりやすく説明します。

OCI (Open Container Initiative: オープンコンテナ・イニシアティブ)

概要と目的: OCIはコンテナの標準仕様を策定するためのオープンな業界団体です (Open Container Initiative)。2015年にDocker社やCoreOS社などが中心となって発足し、コンテナのイメージフォーマットランタイム仕様のオープン標準を定めています (Kubernetes — Open Standards (OCI, CRI, CNI, CSI, SMI, CPI ...)。OCIの目的は、コンテナイメージやコンテナ実行エンジンがベンダーロックインなく互換性を保てるようにすることです (Kubernetes — Open Standards (OCI, CRI, CNI, CSI, SMI, CPI ...)。例えばOCIが定めたOCIイメージ仕様OCIランタイム仕様に従えば、あるツールでビルドしたコンテナイメージを別のランタイムでも問題なく実行できます。

Kubernetesにおける役割: Kubernetes自体はOCIの実装ではありませんが、OCIによって定義されたコンテナイメージランタイムの標準を間接的に利用しています。例えば、Kubernetesで使用する代表的なコンテナランタイムであるcontainerdCRI-OはOCI仕様に準拠しており、OCI準拠のイメージ(DockerやPodmanで作成されたOCI対応イメージ)をKubernetesのPodとして実行できます (Open Container Initiative) (Kubernetes — Open Standards (OCI, CRI, CNI, CSI, SMI, CPI ...)。つまり、OCIのおかげで**「どのツールで作ったコンテナでも、Kubernetes上で動く」**という互換性が保たれています。

他システムとの関連: OCIは後述するCRIやCNIなどとは直接連携するものではなく、土台となる標準を提供する存在です。CRIを介してKubernetesが利用するコンテナランタイム(例: Dockerやcontainerd)は内部でOCIランタイム(例: runc)を使ってコンテナを起動します。したがって、OCIはCRIやCSIなど他のインターフェースを下支えする基盤と言えます。

実践的な活用例: 開発者がDockerでコンテナイメージをビルドする際、docker buildで作られるイメージはOCIイメージ仕様に沿っています。これにより、そのイメージをKubernetesクラスタにデプロイするとき、クラスタ上のcontainerdやCRI-Oが問題なく読み込んでコンテナを起動できます。また、OCIランタイム仕様準拠のruncなどが各Node上でPod内コンテナを実行することで、異なる環境間でも一貫してコンテナを取り扱えます。

CRI (Container Runtime Interface: コンテナランタイム・インターフェース)

概要と目的: CRIはKubernetesがコンテナランタイムを操作するための標準インターフェースです。具体的には、各Node上で動く kubelet(Kubernetesエージェント) が、コンテナを実行・停止などする際に使うプラグインAPIを提供します (Container Runtime Interface Explained | by David Mosyan - Medium)。CRIが策定される以前、kubeletはDockerなど特定のランタイムと密結合していました。しかしCRIによって、Docker以外のランタイム(例: containerd, CRI-O 等)もkubeletに簡単に接続できるようになりました (Container Runtime Interface Explained | by David Mosyan - Medium)。要するに、CRIの目的は**「Kubernetesが様々なコンテナ実行エンジンを統一的に扱えるようにする」**ことです (Container Runtime Interface Explained | by David Mosyan - Medium)。

Kubernetesにおける役割: kubeletはCRIを通じてコンテナランタイムに対し、「このコンテナを起動して」「このPodを終了して」といった操作命令を送ります (Container Runtime Interface (CRI): Past, Present, and Future)。CRIはgRPCという仕組みで定義されており、コンテナランタイム側はCRIサーバ(ランタイムShimとも呼ばれます)を実装します (Container Runtime Interface (CRI): Past, Present, and Future)。例えばcontainerdCRI-OはCRIに対応したサーバ実装を持っており、kubeletからの指示を受け取って実際のコンテナ操作を行います (Container Runtime Interface (CRI): Past, Present, and Future)。Kubernetes v1.24以降ではDockershimの廃止により、Dockerも内部的にcontainerd経由でCRI対応となりました。これによりKubernetesはどのランタイムでも同じようにPodを動かせます。

他システムとの関連: CRIはOCIと連携する部分があります。CRI対応ランタイム(例: containerd)は、コンテナを起動する際にOCIランタイム(runcなど)を呼び出します (Container Runtime Interface (CRI): Past, Present, and Future)。つまりKubernetes (kubelet)CRIコンテナランタイム (例えばcontainerd)OCIランタイム (runc) という流れでコンテナが起動されます。また、ネットワークやストレージ周りではCRIからCNIプラグインCSIプラグインが呼び出される場面もあります(Pod作成時にネットワーク設定やボリュームマウントを行うため) (List of CNI plugins used in kubernetes - DevOpsSchool.com) (Container storage 101: What is CSI and how does it work?)。CRIは全体の中心に位置し、他のインターフェースと協調してPodの環境を構築します。

実践的な活用例: MinikubeKindなどでKubernetesクラスタを構築する際、デフォルトでDockerではなくcontainerdがコンテナ実行に使われることがあります。しかしユーザは特に意識せずにkubectl runkubectl applyでPodを作成できます。これは、CRI経由でkubeletがcontainerdを扱っているためです。同じマニフェストでも、裏側のランタイムを差し替える(例えばCRI-Oに変更する)ことが可能で、クラスタを構築し直すことなく様々なランタイムを試せます。

CNI (Container Network Interface: コンテナネットワーク・インターフェース)

概要と目的: CNIはコンテナのネットワーク接続を設定するための標準インターフェースです (40 Days Of Kubernetes (32/40) - DEV Community)。具体的には、コンテナ(Pod)が起動する際にそのネットワークインターフェースにIPアドレスを割り当てたり、ルーティングを設定したりするプラグインの仕組みを定めています (40 Days Of Kubernetes (32/40) - DEV Community)。CNI自体は**Cloud Native Computing Foundation (CNCF)のプロジェクトで、プラグインの仕様とライブラリから成り、さまざまなベンダーが独自のネットワークプラグインを作れるようにするのが目的です (GitHub - containernetworking/cni: Container Network Interface)。要は、CNIによって「コンテナのネットワーク周りをどの実装でも同じ手順で構成できる」**ようになります。

Kubernetesにおける役割: Kubernetesではデフォルトのネットワーク実装を持たず、CNIプラグインにネットワーク設定を委ねています (40 Days Of Kubernetes (32/40) - DEV Community)。各Node上でkubelet(またはCRI内のコンポーネント)は、Pod起動時にCNIプラグインを呼び出してそのPod用のネットワークをセットアップします (List of CNI plugins used in kubernetes - DevOpsSchool.com)。例えば、FlannelCalicoWeave NetといったCNI対応プラグインをクラスタに導入すると、それがPod間通信やネットワークポリシーの適用を担います。CNIのおかげで、Kubernetesはさまざまなネットワーク方式(オーバーレイネットワーク、ルーティングベース、SDNなど)に対応できるようになっています (List of CNI plugins used in kubernetes - DevOpsSchool.com)。

他システムとの関連: CNIは主にKubernetesのネットワーク部分を担当し、CRIやOCIとは異なる領域ですが、Podのライフサイクルで連携します。Podを起動する際、CRIでコンテナが作られると直後にCNIプラグインが動き、そのコンテナに対してネットワークインターフェースの設定を行います (List of CNI plugins used in kubernetes - DevOpsSchool.com)。また、サービスメッシュ(後述のSMI)を使う場合でも、基本となるPodネットワークはCNIプラグインが管理します。つまり、**CRI→(コンテナ生成)→CNI→(ネットワーク設定)**という順序で協調してPodがセットアップされます。

実践的な活用例: Kubernetesクラスタ構築時に「どのCNIプラグインを使うか」を選択する場面があります。例えばシンプルな構成ならFlannelを使って各PodにIPを割り当て、ノード間をVXLANで繋ぐことができます。より高度なネットワークポリシー制御が必要な場合はCalicoを導入し、CNI経由でPod間通信にファイアウォールルールを適用できます。クラウド環境では、AWSのAmazon VPC CNIプラグインのように各PodにクラウドのVPC内IPを直接割り当てるものもあり、システム要件に応じて差し替え可能です。

SMI (Service Mesh Interface: サービスメッシュ・インターフェース)

概要と目的: SMIはサービスメッシュ向けの標準インターフェース仕様です。サービスメッシュとは、マイクロサービス間の通信をプロキシ経由で管理し、トラフィック制御観測セキュリティを提供する手法です。SMIは各種サービスメッシュ(IstioやLinkerdなど)の共通の機能について、ベンダーニュートラルなAPI(KubernetesのCRD群)を定義することを目的としています (smi-spec/SPEC_LATEST_STABLE.md at main - GitHub)。つまり、サービスメッシュ固有のカスタムリソースではなく、SMIが提供する標準リソースを使えば、裏側の実装が異なっても一貫したやり方でメッシュ機能を利用できるようにするのが狙いです (A standard interface for service meshes on Kubernetes - SMI)。

SMIは、GAMMA (Gateway API for Mesh Management and Administration)に移行しました。https://gateway-api.sigs.k8s.io/mesh/gamma/

Kubernetesにおける役割: SMI自体はKubernetesのコア機能ではなく、CNCFの標準仕様として追加で導入するものです。SMIを使う場合、クラスタにはSMI対応のサービスメッシュ実装(例えば Linkerd や Consul Connect など)がデプロイされます。開発者はKubernetes上でTrafficSplit(トラフィックの分割ルール)やTrafficTarget(アクセス制御)などSMIで定義されたカスタムリソースを作成し、サービスメッシュに対してポリシーを適用できます (Hello Service Mesh Interface (SMI) - Microsoft Open Source)。サービスメッシュ側(例: Linkerd)はそのSMIリソースを解釈して実際の挙動(特定サービスへの通信を50%別バージョンにルーティングする等)を実現します。Kubernetesは単にCRDとしてSMIリソースを保持し、コントロールプレーンとして各メッシュ実装と連携する形です。

他システムとの関連: SMIは上述の他のインターフェース(CRI/CNI/CSIなど)とは層が異なります。CRI/CNI/CSI/CDI/NRIが主にPodやコンテナの実行基盤に関わるのに対し、SMIはサービス(アプリケーション)間の通信レベルに関わります。ただし、SMIを利用する際もその基盤としてはKubernetes上でPodが動いている必要があり、CNIによるネットワークやOCIコンテナ上でサービスメッシュ用プロキシ(例: Envoy)が稼働しています。まとめると、SMIはアプリケーションレイヤーで他のインターフェース群(インフラレイヤー)の上に構築され、相互補完的にクラスタ全体の機能を拡張します。

実践的な活用例: たとえば、Linkerdを用いているクラスタでCanaryリリース(新バージョンへの段階的な切り替え)を行いたい場合、SMIのTrafficSplitリソースを定義してトラフィックの何割を新バージョンに流すか指定できます。LinkerdはSMIをサポートしているため、その設定に従って自動的にトラフィックを分配します。後日、サービスメッシュ実装をIstioに替えたくなっても、IstioのSMI Adapterを導入すれば同じTrafficSplit定義で動作させることができます。これにより、サービスメッシュの切り替えによる設定変更を最小限に抑えられます (A standard interface for service meshes on Kubernetes - SMI)。

CSI (Container Storage Interface: コンテナストレージ・インターフェース)

概要と目的: CSIはコンテナオーケストレーターとストレージシステム間の標準インターフェースです (Container Storage Interface: The Foundation of K8s Storage)。コンテナ環境で外部のディスクやストレージを使う場合、各プラットフォーム(KubernetesやMesosなど)ごとに専用のストレージドライバを作成する必要がありました。CSIはそれを解決するため、統一されたストレージプラグインの仕組みを提供します (Container Storage Interface: The Foundation of K8s Storage)。ストレージベンダーはCSI仕様に沿った**プラグイン(CSIドライバ)を実装することで、自社のストレージを様々なコンテナ基盤に接続できます (Container storage 101: What is CSI and how does it work?)。目的は「ストレージのプラグインを一回作れば、どのコンテナ基盤でも使える」**ようにすることです (Container storage 101: What is CSI and how does it work?)。

Kubernetesにおける役割: KubernetesではCSI導入以前、in-tree(クラスター内部組み込み)のボリュームプラグインしか使えず、クラスタをアップデートする度にストレージドライバをビルドインする必要がありました。CSI対応後は、外部ストレージプロバイダのドライバを動的にデプロイ可能になりました (Container Storage Interface: The Foundation of K8s Storage)。たとえばAWSのEBSやGoogle CloudのPersistent Disk、NetAppのストレージなども、それぞれCSIドライバをKubernetesにデプロイすれば、PersistentVolumeClaimを通じてボリュームを動的プロビジョニングできます。KubernetesはCSIプラグインを介して「ボリュームを作成して」「アタッチして」「マウントして」といったリクエストを送り、ストレージ側はそれに応じて操作します (Container Storage Interface: The Foundation of K8s Storage)。CSIによってKubernetesのストレージ拡張はプラグイン方式となり、多種多様なストレージに対応できるようになりました。

他システムとの関連: CSIはCRIやCNIと同様にKubernetesの主要コンポーネント(kubeletやコントローラ)がプラグインを呼び出す点で共通しています。Podがスケジュールされると、必要な永続ボリュームについてKubernetesコントローラがCSIコントローラプラグインにボリューム作成を要求します。また、Node上ではkubeletがCSIノードプラグインを呼び出してボリュームをアタッチ・マウントします。つまり、スケジューラ/コントローラ→CSI(プロビジョニング)と、kubelet→CSI(マウント)という二方向でCRI/CNIと連携しつつPodのストレージを準備します。OCIとの直接的な関係はありませんが、コンテナ内から見ればOCIイメージ上のアプリがCSIで提供されたストレージを利用するといった形で最終的に連携しています。

実践的な活用例: 実運用では、クラウド上のブロックストレージを利用したいケースでCSIが活躍します。例えばAWS EBS CSIドライバーをクラスタにデプロイしておけば、開発者はPersistentVolumeClaim (PVC)を作成するだけで、自動的に対応するEBSボリュームがプロビジョンされてPodにアタッチされます。オンプレミスでも、CephやNFS用のCSIプラグインを使うことで、ストレージ管理をKubernetes経由で一元化できます。また、CSIスナップショット機能を使ってボリュームのバックアップを取るような発展的な機能も、CSI統一仕様の上で各ベンダーが提供しています。

CDI (Container Device Interface: コンテナデバイス・インターフェース)

概要と目的: CDIはコンテナランタイムにおける特殊デバイス利用のための標準仕様です (cncf-tags/container-device-interface - CDI - GitHub)。コンテナでGPUやFPGA、特殊なネットワークインターフェースなどのサードパーティ製デバイスを扱う際、各デバイスごとに個別対応するのは複雑です。CDIはこれを抽象化し、OCI対応のコンテナランタイム(例: containerdやCRI-O)が標準的な方法でデバイスをコンテナに組み込めるようにします (cncf-tags/container-device-interface - CDI - GitHub)。具体的には、デバイスごとの設定(環境変数やマウントするデバイスファイル等)をまとめたデバイスプロファイルを定義し、ランタイムがコンテナ起動時にそれを読み取って設定を反映できるようにします (cncf-tags/container-device-interface - CDI - GitHub)。目的は**「GPU等の特殊ハードウェアを、プラグインで簡単にコンテナに提供する」**ことにあります。

Kubernetesにおける役割: Kubernetesでは、GPUなど特殊デバイスの割り当てにデバイスプラグイン機構があります。CDIはこのデバイスプラグイン機構と連携して動作します。例えばNVIDIAのGPUの場合、NVIDIA社提供のデバイスプラグインがNode上でGPUを検出し、Pod要求に応じてその情報をCDI仕様のデバイスプロファイルとしてコンテナランタイムに渡します (Container Device Interface Support in the GPU Operator)。containerdやCRI-OはCDIサポートにより、そのプロファイルに沿ってコンテナを起動し、GPUデバイスファイルをコンテナ内に正しくマウントしたり必要なドライバをロードしたりします。これにより、Kubernetes上のPodは特別な設定をしなくてもGPUやその他デバイスを利用可能なコンテナとして起動できます。

他システムとの関連: CDIはCRIと密接に関連しています。CRI対応ランタイム(containerdなど)の更に内部の仕組みとしてCDIが組み込まれており、CRI経由でPod作成要求を受けたときにCDIの情報を考慮してコンテナをセットアップします。OCIランタイム(runcなど)レベルでは、CDIによって生成された設定がコンテナのconfig.json(OCIランタイム仕様の設定ファイル)に反映され、デバイスが追加されます。したがって、CRI → (CDIを考慮した)コンテナランタイム → OCIランタイムという流れで連携しています。また、CDIはNRI(後述)とも補完関係にあり、CDIがデバイス固有設定の標準化を提供する一方、NRIはより汎用的な拡張ポイントを提供します。

実践的な活用例: 機械学習のワークロードでGPUを使うPodをデプロイする場合を考えましょう。まずKubernetesノードにNVIDIAのデバイスプラグインをデプロイすると、ノード上のGPU情報が検出され、リソースとしてnvidia.com/gpuが使えるようになります。ユーザがそのリソースを要求するPodを作成すると、containerdがPod起動時にCDI仕様のファイルからGPUデバイス設定を読み込み、コンテナに必要な/dev/nvidia0などのデバイスファイルやドライバ設定を注入します。結果として、Pod内のコンテナはGPUにアクセスでき、TensorFlowなどのアプリケーションがGPUを利用した計算を行えます。同様に、SR-IOV対応ネットワークデバイスやFPGAも、CDI対応プラグインを介してシームレスにコンテナへ提供できます。

NRI (Node Resource Interface: ノードリソース・インターフェース)

概要と目的: NRIはコンテナランタイムにカスタムな拡張処理を組み込むためのフレームワークです (CRI-O v1.26.0)。簡単に言うと、OCI互換のコンテナランタイムに対し、プラグイン的に**「コンテナの詳細設定を動的に書き換える処理」「ライフサイクルイベント時の追加処理」を挿入できる仕組みです (CRI-O v1.26.0)。たとえば、コンテナ起動直前に特定のcgroup制御を追加したり、コンテナ停止後にリソースのクリーンアップを行ったりするカスタムロジックをプラグインとして実装できます (CRI-O v1.26.0)。NRIの目的は、KubernetesのスケジューラやCRIだけでは細かく制御しきれないノード上のリソース管理コンテナ設定の最適化**を、拡張可能にすることです。

Kubernetesにおける役割: NRI自体はKubernetesの公式コンポーネントというより、containerdやCRI-Oなどコンテナランタイム側の拡張ポイントですが、結果的にKubernetesの動作に影響を与えます。NRIプラグインはコンテナランタイムに登録され、Podのコンテナが作成されるタイミング開始されるタイミングなどにフックして呼び出されます (CRI-O v1.26.0)。これを利用して例えば、PodごとにCPUやメモリの割当を微調整したり、NUMAノードへのバインドを自動化したりできます。Kubernetesのスケジューリング決定後、実際にコンテナを起動する段階でNRIプラグインが介入し、ポリシーに応じた詳細なリソース設定を適用することで、デフォルトでは実現しにくい最適化を施します。

他システムとの関連: NRIはCRIの補助的な位置づけです。CRIが標準APIを提供する一方、NRIは各種ポリシー適用のカスタムフックを提供します。NRIプラグインはOCI互換ランタイムで動くため、OCI仕様に準拠したコンテナ設定をプログラム的に書き換えることも可能です (CRI-O v1.26.0)。たとえばCDIが特定デバイスの設定を標準化するのに対し、NRIはデバイス以外も含めたあらゆるコンテナ設定(環境変数の挿入やセキュリティ設定の変更など)のフックポイントを提供できます。結果として、CRI+NRI+OCIランタイムという形で、KubernetesのPod実行時に高度なカスタマイズ処理が挿入できる仕組みが完成します。

実践的な活用例: 金融や通信キャリア向けのワークロードでは、Podごとにきめ細かなリソース管理(例えばCPUコアのピン留めメモリアロケーション制御)が求められることがあります。そんな場合、NRIプラグインとしてカスタムスケジューリングロジックを実装し、特定のラベルを持つPodについては起動時に自動でそのPodのコンテナを特定のCPUソケットに割り当てる、といった処理を組み込めます。また、将来的にWebAssemblyランタイムをKubernetes上で扱う際など、既存のCRIにはない初期処理をNRIで追加する、といった応用も議論されています (CRI-O v1.26.0)。NRIはまだ新しく発展中の仕組みですが、高度な最適化が必要なシナリオで徐々に利用が進んでいます。

これらインターフェースの相互関係と全体像

上記の各インターフェースはそれぞれ異なる役割を持ちますが、Kubernetes上では連携してPodやサービスを支える基盤を構成しています。大まかな関係性をまとめると次の通りです。

  • OCIはコンテナの共通フォーマット・実行方法を定める土台であり、CRIや各ランタイムがOCI標準のコンテナイメージ・ランタイムを利用することで互換性を維持します (Open Container Initiative)。
  • CRIはKubernetes (kubelet)とコンテナランタイムを繋ぐ中核インターフェースで、OCI準拠の複数ランタイムを差し替え可能にします (Container Runtime Interface Explained | by David Mosyan - Medium)。CRIを軸にして、その下でOCIランタイムによるコンテナ実行が行われ、また周辺のCNI/CSIプラグインを呼び出します。
  • CNICSIはそれぞれネットワークとストレージのプラグインインターフェースで、CRIと協調してPodにネットワーク接続やボリュームを提供します (List of CNI plugins used in kubernetes - DevOpsSchool.com) (Container Storage Interface: The Foundation of K8s Storage)。これによりネットワーク実装やストレージ実装を環境に合わせて選択できます。
  • SMIはアプリケーション層(サービスメッシュ)の統一インターフェースで、Kubernetes上のサービス間通信を抽象化します。他のインターフェースとはレイヤーが異なり、ユーザー側の利便性(サービスメッシュの切替容易さ)を高める役割です (A standard interface for service meshes on Kubernetes - SMI)。
  • CDINRIはCRI/コンテナランタイムを拡張する新しい仕組みで、ハードウェア資源(GPU等)やリソース管理の高度化に対応します。CDIは特定デバイス用設定の標準化、NRIは汎用的なカスタム処理のフック提供と、アプローチは異なりますがどちらもKubernetesの柔軟性をさらに拡張するものです (cncf-tags/container-device-interface - CDI - GitHub) (CRI-O v1.26.0)。

すべてを統合すると、「OCI標準に従ったコンテナイメージ」を、Kubernetesが 「CRI経由でOCI対応ランタイムに渡し」 てPodとして起動します。その際に 「CNIプラグインでネットワークを付与」 し、必要なら 「CSIプラグインでストレージを接続」 します。さらに特殊な要件があれば 「CDIで特殊デバイスを割り当て」 たり、 「NRIで追加のリソース調整」 を行います。上位では必要に応じて 「SMI経由でサービスメッシュ制御」 が動作し、アプリ間通信が管理されます。これらがそれぞれ独立したプラグイン可能部品になっているおかげで、Kubernetesは様々な環境やニーズに合わせて柔軟に拡張・適応できているのです。

まとめ: 実践での活用

初心者から中級者の立場で押さえておきたいポイントとしては、Kubernetesは単一の巨大なモノリシックシステムではなく、OCI/CRI/CNI/CSI/SMIなど多数の標準仕様に支えられたプラットフォームだということです。それぞれのインターフェースを理解すると、例えば「自分のクラスタではDockerではなく別のコンテナランタイムを使いたい」「このクラスタに新しいストレージ機能を追加したい」「GPUや特殊デバイスを使いたい」といった場合に、どの部分を設定・追加すればよいかが見えてきます。また、トラブルシューティングの際にも「ネットワーク周りの不具合はCNI設定を確認しよう」「ストレージがマウントできないならCSIドライバのログを見よう」というように、原因を切り分けやすくなります。Kubernetesを実践で使う上でも、ここで紹介したOCI, CRI, CNI, SMI, CSI, CDI, NRIの概念を理解しておくことは、クラスタの構成要素を把握し適切に活用するうえで大いに役立つでしょう。

98
123
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
98
123

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?