Kubernetes ストレージ: CSI プラグインの実装方法についての簡単な説明

Kubernetes ストレージ: CSI プラグインの実装方法についての簡単な説明

さて、K8s ストレージの重要なコンポーネントである Container Storage Interface (CSI) について引き続きお話しします。次のコンテンツでは、CSI の動作原理、コア概念、およびそれをコンテナ化された環境に統合する方法について学習します。

CSI はなぜ必要なのでしょうか?それはどんな問題を解決するのでしょうか?

CSIを学ぶ前に、その登場の背景や、CSIが解決できる問題などを理解する必要があると思います。

CSI はなぜ必要なのでしょうか?

Kubernetes プラットフォーム自体は多くのストレージ プラグインをサポートしていますが、結局のところ制限があり、ユーザーの増大するニーズを満たすことはできません。たとえば、顧客が当社の PaaS プラットフォームを国内ストレージに接続する必要がある場合はどうなりますか?

私たちが直面している問題は、それをどのように統合するかということです。

Kubernetes 自体は強力なボリューム プラグイン システムを提供します。最も直接的な方法は、ボリューム プラグインに新しいプラグインを追加することです。

しかし、Kubernetes は非常に複雑であり、一定の学習曲線があることを誰もが知っている必要があります。これはコストがかかるだけでなく、サードパーティのコードを直接統合すると、Kubernetes プラットフォーム システムの信頼性とセキュリティに脅威をもたらす可能性があります。

さらに、この方法はテストやメンテナンスには不便です。たとえば、サードパーティのストレージ サービスに変更があった場合、変更されたコードを Kubernetes に送信し、コード レビューを待つ必要があります。つまり、ストレージ サービスへの変更を展開するには Kubernetes がリリースされるまで待たなければなりません。つまり、ストレージの統合は Kubernetes のリリース サイクルに結びついているということです。

そのため、Kubernetes と直接統合するのは非常に不便です。

上で繰り返し言及した In-Tree と Out-Of-Tree という 2 つの概念を振り返ってみましょう。誰もが文字通りの意味を理解したと思います。次の表と組み合わせると、答えが思い浮かびますか?

写真

どのような問題点が解決されましたか?

CSI はサードパーティのストレージ コードを K8s コードから分離します。これらの統一されたインターフェースを実装している限り、さまざまなストレージ プラグインを K8s に接続できます。ユーザーはコア K8s コードに触れる必要はありません。

最も重要なことは、CSI 仕様が現在、コンテナ オーケストレーションの業界統一ストレージ アクセス標準となっていることです。

写真

コンテナ オーケストレーター (CO)

それで、CSIとは何でしょうか?

ChatGPTからの回答は次のとおりです。

CSI (Container Storage Interface) は、コンテナ オーケストレーション エンジンとストレージ プロバイダー間の通信のための標準化されたインターフェイスです。これにより、ストレージ ベンダーは標準プラグインを作成し、コンテナ オーケストレーション エンジンを統合できるようになり、より柔軟でスケーラブルなストレージ ソリューションを実現できます。

CSI ドライバーは 3 つの主要コンポーネントで構成され、それぞれが特定の役割を果たします。

  1. ノード サービス: 各 Kubernetes ノードで実行され、ノード上のストレージ ボリュームのマウントとアンマウント、およびノー​​ド レベルのストレージ操作の処理を担当します。

  2. コントローラー サービス: Kubernetes コントロール プレーンで実行され、作成、削除、拡張などの操作を含むストレージ ボリュームのライフサイクルの管理を担当します。

  3. アイデンティティ サービス: CSI ドライバーの 3 番目のコンポーネントであり、CSI ドライバーが登録されたときにアイデンティティ情報を提供し、ドライバーのサポートされる機能を Kubernetes クラスターに公開します。 Kubernetes にドライバーの存在を通知し、ドライバーの基本情報と機能サポートを提供する役割を担います。

CSI の設計思想は、ストレージ管理システムとコンテナ オーケストレーション システムを分離し、Kubernetes のコア コードを変更することなく、標準化された一連のインターフェースを実装することで、新しいストレージ システムを Kubernetes と統合できるようにすることです。

CSI ドライバーの登場により、Kubernetes ユーザーにさらに多くのストレージ オプションが提供され、ストレージ ベンダーと開発者にとってより便利なアクセス ポイントも提供されるため、クラスター ストレージ管理の柔軟性とスケーラビリティが向上します。

写真

クラウドネイティブ環境でのストレージ

CSI の適応は、コンテナ オーケストレーション システム (CO) とストレージ プロバイダー (SP) によって共同で行われます。 CO は gRPC を介して CSI プラグインと通信します。ここで CSI がリンクとして機能し、上位層でコンテナ オーケストレーション システムを接続し、下位層でサードパーティのストレージ サービスを操作することは、誰もが気づいていると思います。

CSI はどのように機能しますか?どのように機能しますか?

典型的なCSIドライバアーキテクチャ

以下は CSI の典型的なアーキテクチャです。 CSI ではストレージ プロバイダーに 3 つのモジュールの実装のみを求めていますが、オーケストレーション プロセス全体は非常に複雑であると言えます。

写真

CSI を使用した Kubernetes クラスター

CSI の全体的な運用プロセスには、次の 2 つのコンポーネントが含まれます。

✔️ Kubernetes によって公式に管理されている標準の外部コンポーネントのセット。これらは主に、K8s 内のリソース オブジェクトをリッスンし、CSI ドライバーへの gRPC 呼び出しを開始する役割を担っています。詳細については、Kubernetes CSIサイドカーコンテナ[1]を参照してください。これらは、CSI ドライバーが追加のタスクや機能を完了できるように支援するために、CSI ドライバーと同じポッドにデプロイされます。 ✔️ さまざまなストレージベンダーによって開発されたコンポーネント(アイデンティティサービス、コントローラーサービス、ノードサービスを実装する必要があります)

左側の CSI ドライバー コントローラーを見てみましょう。これは、複数の Sidecar コンポーネントとサードパーティのプラグイン (コントローラー サービス) を通じて実装されます。

前述のように、作成、削除、拡張などの操作を含むストレージ ボリュームのライフ サイクルを管理する役割を担います。つまり、コントローラーをデプロイするときには、ストレージベンダーが提供できる機能に基づいて、対応するサイドカー コンテナーを提供する必要があります。

たとえば、私の CSI ドライバーは動的プロビジョニング機能のみを提供し、他の機能のインターフェイスは実装していません。コントローラーをデプロイするときは、外部プロビジョナーとコントローラー サービスをポッドにデプロイするだけで済みます。コンポーネントの選択は、3 つの当事者の実装に完全に依存します。

Kubernetes CSI サイドカー コンテナ

#1。外部プロビジョナー

external-provisioner は、Kubernetes で外部ストレージ ボリュームを動的に作成および削除するために使用されるサイドカー コンテナーです。

新しい PVC (PersistentVolumeClaim) が作成されると、外部プロビジョニング担当者は外部ストレージ システムへの要求を開始し、対応するストレージ ボリュームを作成して PVC に関連付け、Pod の永続ストレージの要求を満たします。

external-provisioner は実際にチェックを実行し、注釈 volume.kubernetes.io/storage-provisioner が PVC に存在し、その注釈の値が CSI ドライバーの名前と一致することを確認します。プロセス全体は PV コントローラー コンポーネントを介して実行されます。

写真

規定

ここで関係する 2 つの操作は、コントローラー サービスの CreateVolume および DeleteVolume インターフェイスの実装に対応しており、その呼び出し元は外部プロバイダーです。

このプロセスの中核では、external-provisioner が仲介者として機能し、Kubernetes の PVC および StorageClass メカニズムを通じて、Pod の永続ストレージ要件を外部ストレージ システムに渡します。これにより、ストレージ ボリュームの作成と管理を Kubernetes クラスターにシームレスに統合できるようになり、アプリケーションに永続的なストレージ ソリューションを提供できます。

考える

各 PVC に対応するボリュームは個別に暗号化する必要があり、暗号化キーも異なると仮定します。 PVC 仕様には、この情報を提供するための追加パラメータはありません。では、これらの暗号化キーを CSI インターフェイスに渡すにはどうすればよいでしょうか?

ここで、CSI 操作シークレットの概念について言及する必要があります。これにより、異なる CSI 操作ごとに異なるシークレットをカスタマイズし、StorageClass で使用できるようになります。

例として、次の StorageClass 定義を見てみましょう。

 apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: xfs2-sc-4-per-volume provisioner: xfs2.csi.basebit.ai parameters: csi.storage.k8s.io/provisioner-secret-name: ${pvc.name} csi.storage.k8s.io/provisioner-secret-namespace: ${pvc.namespace}

変数 ${pvc.name} を使用する、パラメータ フィールドの csi.storage.k8s.io/provisioner-secret-name フィールドの値に注意してください。どのように理解しますか?

具体的には、csi.storage.k8s.io/provisioner-secret-name パラメータの値として ${pvc.name} を使用すると、PVC が作成されるたびに、それに対応する Provisioner Secret が作成され、PVC の名前が Provisioner Secret の名前として使用されます。

この方法では、各 PVC に ID 検証と認証のための一意の Provisioner Secret が設定されます。 Secrets の正確な定義は、各 CSI ドライバーの実装によって異なります。ストレージ ボリュームを作成するシナリオでは、CreateVolumeRequest は Secret に関する詳細情報を取得できます。

このパラメータ化手法は、Kubernetes で実行時に構成ファイルを動的に生成できる柔軟な方法です。

プロビジョニング、削除、拡張、アタッチ、デタッチなどの操作では、通常、CSI ドライバーがストレージ バックエンドで資格情報を使用する必要がありますが、これについては以下で詳しく説明しません。

より高度な使用方法については、ドキュメントを参照してください:StorageClass Secrets[2]

#2。外部アタッチメント

external-attacher は、Kubernetes ノード上の外部ストレージ ボリュームを動的に接続および切断するサイドカー コンテナーです。

Kubernetes API サーバーの VolumeAttachment オブジェクトをリッスンして、コントローラー サービス内の ControllerPublishVolume および ControllerUnpublishVolume インターフェースの呼び出しをトリガーします。

ただし、アタッチ/デタッチ操作はすべてのケースで必要なわけではなく、特に一部の分散ファイル システムの CSI ドライバーでは、このような操作は適用できない場合があります。したがって、アタッチ/デタッチはオプション機能であると言えます。

#3。外部リサイザー

external-resizer は、外部ストレージ ボリュームのサイズを変更するために使用されるサイドカー コンテナーです。

PVC のストレージ要件が変更されると、external-resizer は要件に応じて外部ストレージ ボリュームのサイズを調整し、ストレージ リソースを最適に使用できるようにします。

コントローラー サービス内の ControllerExpandVolume インターフェイスを呼び出します。

#4。外部スナップショット

external-snapshotter は、外部ストレージ ボリュームのスナップショット機能を実装するために使用される Sidecar コンテナーです。

Kubernetes スナップショット CRD オブジェクトをリッスンして、コントローラー サービスで CreateSnapshot および DeleteSnapshot インターフェース呼び出しをトリガーします。

データのバックアップ、リカバリ、レプリケーションを容易にするために、外部ストレージ システム上のスナップショットを作成、削除、管理する役割を担います。

考える

これは何の役に立つのでしょうか? VolumeSnapshot を使用すると、Kubernetes クラスター内のボリュームのスナップショットを作成できます。これらのスナップショットは、データのバックアップやアプリケーションの回復に使用できます。アプリケーションに障害が発生したり、データが破損したりした場合は、以前に作成したスナップショットを使用してアプリケーションの状態を復元できます。

 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: redis-data-my-redis-master-0 spec: dataSource: name: my-server-snapshot-0 kind: VolumeSnapshot apiGroup: snapshot.storage.k8s.io accessModes: - ReadWriteOnce resources: requests: storage: 2Gi
#5。活性プローブ

liveness-probe は、CSI ドライバーの健全性を監視し、Liveness Probe メカニズムを通じて Kubernetes にレポートするサイドカー コンテナーです。

ドライブに障害が発生したり、応答が停止したりした場合、Kubernetes は関連するポッド (コントローラー サービス/ノード サービス) を再起動して、サービスの可用性を確保します。

詳細な使用方法と設定例については、ここ[3]のドキュメントを参照してください。

考える

実際には、livenessProbe は CSI ドライバーを介してコンテナーに設定されます。 liveness-probe サイドカー コンテナの主な目的は、/healthz HTTP エンドポイントを提供することです。その背後にある checkProbe は、最終的に CSI ドライバーの Identity Service 内の Probe インターフェイスを呼び出して、プラグインが正常な状態にあるかどうかを検出します。

このアーキテクチャは、プラグインのヘルス チェックをアプリケーションから分離し、CSI ドライバーのプローブ インターフェイスを介して通信します。

実際、Kubernetes には v1.23 以降 gRPC ヘルス検出機能が組み込まれているため、これはもう必要ありません。

#6。ノード ドライバー レジストラ

node-driver-registrar は、Sidecar コンテナとして実行されるコンポーネントです。その主な役割は、CSI ドライバーのノード サービス内の NodeGetInfo インターフェイスを直接呼び出すことによって、ドライバー情報を取得することです。

次に、kubelet のプラグイン登録メカニズムを使用して、これらのドライバーの情報を対応するノードの kubelet に登録します。

まとめ

この機能関係の組み合わせテーブルを覚えておく必要があります。これは、CSI ドライバーの展開や問題のトラブルシューティングに非常に役立ちます。

写真

CSI プラグインを実装するにはどうすればいいですか?

CSI ドライバーを実装するには、一連のインターフェースを実装するだけで済みます。ただし、これらのインターフェースを実装するだけでは、堅牢で使いやすい CSI ドライバーを構築するには不十分です。堅牢なドライバーを構築するには、多くの側面を考慮する必要があります。

CSI プラグイン コンポーネント

以下は、すべての CSI ドライバーが実装することが期待されるインターフェースのリストです。

アイデンティティ サービスは CSI ドライバーのアイデンティティ情報の提供を担当し、コントローラー サービスはボリューム管理を担当し、ノード サービスはボリュームをポッドにマウントする役割を担います。

写真

前述したように、CSI ドライバーが提供できる機能は、各ストレージ ベンダーの実装によって異なります。 3つのコンポーネントには、次のような機能を公開するインターフェースがあります。

  1. Identity Service の GetPluginCapabilities メソッドは、CSI ドライバーによって提供される主な機能を示します。
  2. コントローラー サービスの ControllerGetCapabilities メソッドは、実際に CSI ドライバーがどのような機能を持っているかを K8s に伝えます。これらの機能には、ボリュームの作成、削除、容量の拡張、スナップショットなどの操作が含まれる場合があります。
  3. Node サービスの NodeGetCapabilities メソッドは、Node プラグインの機能のリストを提供します。

CSIライフサイクル

一般に、各ボリュームは完全なライフ サイクルを経ます。

写真

PersistentVolumeClaim (PVC) の作成から始まり、Pod によって使用されるまでのプロセスは、プロビジョニング -> アタッチ -> マウントという 3 つの主要な段階で構成されます。

その後、ポッドが削除されてから PVC が削除されるまで、プロセス全体は、アンマウント -> デタッチ -> 削除という 3 つの主要な段階を経ます。

ただし、特別な種類のストレージ ボリュームである Ephemeral Inline Volumes があり、そのライフサイクルは CSIDriver 仕様の volumeLifecycleModes パラメータを変更することで変更できます。

 apiVersion: storage.k8s.io/v1 kind: CSIDriver metadata: name: xfs2.csi.basebit.ai spec: ... volumeLifecycleModes: - Ephemeral

エフェメラル モードとは、ストレージ ボリュームが一時的なものであり、Pod のライフ サイクルが終了すると解放されることを意味します。このタイプのストレージ ボリュームの場合、Kubelet が CSI ドライバーからボリュームを要求すると、NodePublishVolume のみが呼び出され、他のステージ (CreateVolume や NodeStageVolume など) への呼び出しは省略されます。 Pod が終了し、ストレージ ボリュームを解放する必要がある場合は、NodeUnpublishVolume のみが呼び出されます。

具体的なポッドの仕様は次のとおりです。

 kind: Pod apiVersion: v1 metadata: name: my-csi-app spec: containers: - name: my-frontend image: busybox volumeMounts: - mountPath: "/data" name: my-csi-inline-vol command: ["sleep","1000000"] volumes: - name: my-csi-inline-vol csi: driver: xfs2.csi.basebit.ai volumeAttributes: foo: bar

ここでの volumeAttributes は、ドライバーが準備する必要があるボリュームの属性を指定するために使用されます。これらのプロパティは各ドライバーに固有であり、実装するための標準化された方法はありません。

一時的な使用例

Secrets Store CSIドライバー[4]を使用すると、ユーザーはSecretsをインラインボリュームとして外部からPodにマウントできます。これは、キーが外部管理サービスまたは Vault インスタンスに保存されている場合に役立ちます。

Cert-Manager CSIドライバー[5]はcert-manager[6]と連携して、証明書とキーのペアをシームレスに要求し、Podにマウントします。これにより、アプリケーション ポッドで証明書を自動的に更新できるようになります。

この機能は、すべてのインターフェースを実装する必要がないことを改めて示しています。プラグイン実装者が提供する機能に応じて、対応するロジックを実装します。

CSI 冪等性

すべての CSI 操作がべき等性を持つようにする必要があります。つまり、同じ操作が複数回呼び出された場合、結果は常に一貫しており、複数の呼び出しによって状態が変化したり、追加の副作用が発生したりすることはありません。このべき等性は、システムの安定性と一貫性を確保するための重要な要素です。

たとえば、DeleteVolume 操作を実行するとします。基礎となるボリュームが存在しない場合は、エラーは報告されません。 DeleteVolume を初めて実行するか、複数回再試行するかに関係なく、操作の最終結果は同じになります。

ここで、CSI ドライバーの基本的な機能と安定性を検証するために使用できる CSI サニティ テストについて説明します。既存のボリュームの作成、存在しないボリュームのアンマウントなど、さまざまなエラーや例外状況をシミュレートして、ドライバーがこれらの状況を正しく処理することを確認します。

CSI ドライバーが Kubernetes CSI 仕様に準拠し、正しく実行できるかどうかを確認できるため、CSI ドライバーの開発に非常に役立ちます。たとえば、開発者が一般的な問題を迅速に特定して修正し、運用環境で予期しない問題が発生する可能性を減らすのに役立ちます。

公式ドキュメントでは、仕様 (Container Storage Interface、CSI) が詳細に説明されており、開発関連の考慮事項も提供されています。これらのメモでは、仕様の重要なポイントと、開発プロセス中に遭遇する可能性のある課題と解決策について説明します。これらの詳細はコンテナストレージインターフェース(CSI)仕様[7]に記載されています。

CSI を展開するには?

標準の CSI ドライバー展開アーキテクチャを次の図に示します。これには、DaemonSet によって実行される CSI ノード コンポーネントと、StatefulSet で実行される CSI コントローラー コンポーネントが含まれます。

⚠️ 2 つのコンテナはローカル ソケット (Unix ドメイン ソケット、UDS) を介して通信し、gRPC プロトコルを使用します。 CSI プラグインは、ローカル プロセス間の Unix ドメイン ソケット通信を介して同じホスト マシン上の K8s コンポーネントと直接対話します。これは、TCP ソケットよりも通信効率とパフォーマンスが高くなります。

写真

CSI ノードをデプロイする場合は、ホスト上の kubelet ディレクトリ (/var/lib/kubelet) をドライバーのコンテナーにマウントし、マウント伝播を双方向に設定する必要があります。この方法では、ドライバー コンテナー内での後続のマウント/アンマウント操作をホスト マシンに伝播できます。

これは単なる高レベルのアーキテクチャの概要であり、正確な実装の詳細は CSI プラグインや環境によって異なる場合があることに注意してください。

CSI クラスターが正常にデプロイされたら、次の 2 つのコマンドを使用して確認できます。

#1。クラスターにインストールされている CSI ドライバーを表示する

➜ kubectl get csidrivers

#2。 CSIを持つノードを一覧表示する

➜ kubectl get csinodes

要約する

CSI は、Kubernetes ストレージ システムのコア コンポーネントです。ストレージベンダーには柔軟でスケーラブルな統合方法を提供し、Kubernetes ユーザーには効率的で安定したストレージ ソリューションを提供します。

CSI は、コンテナ オーケストレーターとストレージ ベンダー間のインターフェイスを標準化することで、すべての CSI 互換ストレージ システムが同じ実装仕様に従うことを保証する統一されたパラダイムを構築します。実際、CSI ドライバーを作成することで、Kubernetes ストレージ アーキテクチャに新しい次元が追加されただけでなく、ストレージ リソース管理に対する理解も深まりました。

次回も引き続き、CSI ドライバーを実際の業務で使用したときに遭遇した問題や課題についてお伝えします。

参考文献

[1]Kubernetes CSIサイドカーコンテナ: https://kubernetes-csi.github.io/docs/sidecar-containers.html

[2]ストレージクラスのシークレット: https://kubernetes-csi.github.io/docs/secrets-and-credentials-storage-class.html

[3]こちら: https://github.com/kubernetes-csi/livenessprobe/blob/master/deployment/kubernetes/livenessprobe-sidecar.yaml

[4]シークレットストアCSIドライバー: https://github.com/kubernetes-sigs/secrets-store-csi-driver

[5]Cert-Manager CSIドライバー: https://github.com/cert-manager/csi-driver

[6]証明書マネージャー: https://cert-manager.io/

[7]コンテナストレージインターフェース(CSI)仕様:https://github.com/container-storage-interface/spec/blob/master/spec.md

[8]CSIドキュメント: https://kubernetes-csi.github.io/docs/

[9] CloudNativeCon EU 2018 CSI Jie Yu: https://schd.ws/hosted_files/kccnceu18/fb/CloudNativeCon%20EU%202018%20CSI%20Jie%20Yu.pdf

<<:  時間を旅するエンジン: Kafka メッセージのタイミングの謎を解明

>>:  独自の ES クラスターをクラウドに移行するための完全なガイド

推薦する

chicagovps - $30/年/2g メモリ/15g SSD+$25/月/専用サーバー

chicagovps が SSD ハード ドライブ VPS のプロモーションを実施しているのをめった...

中小企業のマイクロブログマーケティングプロセスに存在する問題を分析し、まとめる

Weiboマーケティングは素晴らしいツールです。大企業でも中小企業でも、多くの企業がWeiboマーケ...

ShoveHost - 768m メモリ/20g ハードディスク/250g トラフィック/月額 6 ドル

ShoveHostは2011年に設立され、同年に商業運営を開始した会社です。カリフォルニア州サンディ...

chicagovps-全品50%オフ/Windows/1gメモリ/4コア/40g SSD/2Tデータ/年額35ドル

chicagovps.net は、米国選挙日に VPS イベントを開催し、chicagovps シリ...

サイトランキングがウェブサイトランキングに与える影響を分析する

友人とリンクを交換するときに、誰もがよく気にする質問は、「そのサイトは最初のサイトですか?」です。で...

現在の SEO、路地に入ってしまったのでしょうか?

現在の SEO の道は少し迷っているようで、人々はとらえどころがないと感じています。たとえば、検索エ...

#黒5# gcorelabs: 専用サーバー、30% 割引、香港/韓国/日本/ロシア極東の 41 のデータセンターなど...

gcorelabs は、今から 12 月 6 日まで、ブラック フライデー プロモーションを開始し、...

48 時間以内に Baidu のトップ 3 に入ることは信頼できるのでしょうか?

最近では、主要なウェブマスターフォーラムのすべてに、48時間以内にBaiduでトップ3にランクインす...

JD Cloud Director が分散コンピューティングの本質を説明します (ビデオを含む)

[[264940]] 1953 年、エブ・グロッシュは、コンピュータのパフォーマンスはコストの 2 ...

ウェブサイトの内部リンクをうまく活用する方法のまとめ

外部リンクが王様で、内部リンクが最も重要だと言われています。では、ウェブサイトで内部リンクをうまく活...

草の根レベルでは新しいインターネットモデルを理解する必要があるが、すべてを行う必要はない

インターネットが人々の生活に重要な役割を果たしている理由は、利便性などの本来の要素に加えて、革新のス...

推奨: kazila-$48/D525/5IP/8g メモリ/120g SSD/G ポート/Incero コンピュータ ルーム

2008 年に設立された企業である Kazila は、IPMI を備えた、ダラス データ センター、...

2020年天一雲中国ツアーの第一弾がスタート、多方面とのウィンウィンの共生を実現

「新インフラ」の潮流の下、企業のデジタル変革は需要のピークを迎え、クラウドへの移行とクラウドの利用が...

暁蘇:ウェブサイトのオリジナルアクセスログの浅いところから深いところまでの分析について語る

最近、筆者はSEOを始めたばかり、またはSEOに1~2年従事している50人以上のSEO担当者を対象に...