TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

KubernetesのCRDとオペレーターでLLMサービングのデプロイを自動化する

ステータス:ドラフト(2026-10-07更新、2026-10-09規模テストの実測値補強)。 公式ドキュメントで確認した記述がベースであり、**第8節の隔離環境テストと末尾の「kopfオペレーター直接テスト」(規模テスト含む)、「LWS直接テスト」**のみが直接確認した値です。残りはまだ実測していません。 記事末尾の「確認したバージョンと日付」と「確認できなかったこと」を先にお読みください。 作成ルール:実測データを基本とし、測定できなかったものは「未実測」と表記します。

目次

LLMサービングのデプロイには、Deploymentでは表現しきれない要件が揃っています。複数ノードにまたがるリーダーとワーカー、重みのロードという事前段階、比率を守るべきロールアウト、GPUトポロジー、単一のPodではなくまとまりを単位とするスケールです。KubernetesはこれをCRD(Custom Resource Definition)とオペレーターで拡張します。この記事では、そのAPIの現在の姿とオペレーターのコアパターンを整理し、隔離された検証環境で確認した動作を根拠として添えます。プロダクション規模のGoオペレーターを運用した経験は私にはないという点を先にお断りしておきます。

この記事を読んだ後に面接で説明できるようになること

  1. DeploymentでLLMサービングを表現することが難しい理由と、LeaderWorkerSet(LWS)、KServe、Dynamoがそれぞれどのような抽象化を追加するのか。
  2. 調整(reconcile)ループの冪等性、レベルベースのトリガー、オーナー参照とfinalizer、ステータス条件、バックオフ、リーダー選出がなぜそのように設計されているのか。
  3. CRDのバージョン移行手順、ロールアウト戦略、Argo CDとオペレーターが同じフィールドをめぐって競合する問題の解決方法。

1. なぜDeployment単体では不十分なのか

2. LWS、KServe、DynamoのAPIの構造

プロジェクト(確認したバージョン) CRD(APIグループ/バージョン) 注目すべきフィールド
LWS v0.11.1 LeaderWorkerSet (leaderworkerset.x-k8s.io/v1) replicas(グループ数)、 leaderWorkerTemplate.size, restartPolicy, rolloutStrategy.rollingUpdateConfiguration(maxUnavailable デフォルト1、 maxSurge デフォルト0、 partition), groupIdentity(変更不可)、scaleサブリソース
LWS v0.11.1 DisaggregatedSet (disaggregatedset.x-k8s.io/v1) roles[]ごとにLWS1つ、ロールアウトをロール全体で調整
KServe v0.21.0 InferenceService(v1beta1), ServingRuntime(v1alpha1) predictor など、ランタイムが対応モデルフォーマットを宣言
KServe v0.21.0 LLMInferenceService(v1alpha1、ストレージは v1alpha2) model, template, worker(マルチノードの場合はLWS)、 prefill, router, rolloutStrategy, kvCacheOffloading(v1alpha2のみ)
Dynamo v1.5.0 DynamoGraphDeployment(nvidia.com、ストレージは v1beta1) components[](v1alpha1では services マップ)、 restart, topologyConstraint
Dynamo v1.5.0 DynamoComponentDeployment, DynamoGraphDeploymentRequest, DynamoGraphDeploymentScalingAdapter, DynamoModel コンポーネントのデプロイ、SLAベースのリクエスト(プロファイリング後にDGDを生成)、HPA用のscale、モデル登録

KServeのドキュメントでは、 InferenceService を従来の予測モデル用に位置づけ、プリフィル/デコードの分離とマルチノード、プレフィックス認識ルーティングは LLMInferenceService が担うとしています(0.20のページを開きました)。DynamoのCRD名とフィールドは、v1.5.0タグのCRD YAMLから直接読み取りました。

 DynamoGraphDeployment (dgd)  ← 사용자가 선언: 컴포넌트 목록, 복제 수, 재시작 id
        │ 오퍼레이터가 만듦
        ▼
 DynamoComponentDeployment (dcd)  ← 컴포넌트마다, 이름에 worker hash 접미사
        │ 배포 모드에 따라
        ├─▶ Deployment (단일 노드)
        └─▶ Grove PodCliqueSet 또는 LWS (다중 노드, 갱 스케줄링)

3. オペレーターのコアパターン

 API Server ─watch▶ Informer(로컬 캐시) ─▶ WorkQueue(키 중복 제거, 백오프) ─▶ Reconcile(키)
      ▲                                                                        │
      └────── 변경(자식 생성, status 기록) ◀── 관측 → 비교 → 행동 → 보고 ◀────┘

レベルベースの調整。 controller-runtimeのソース(v0.25.2)のコメントには、調整はイベントによってではなく、apiserverやキャッシュから読み取った実際のステートによって駆動されると記載されています。 Request には名前とネームスペースしか含まれておらず何が変更されたかは分からないため、Pod削除イベントを受け取った場合でも調整関数は「Podがない」という状態を読み取ってそれを把握します。kubebuilderの書籍のGood practicesの章でも、イベントごとにロジックを書くとリソースが詰まり人間の手が必要になると警告しています。kopfはハンドラーが作成、変更、削除という原因別に呼び出されるモデルであるため、監視していない子リソースの変化はタイマーで補う必要があります(第9節)。ただし、稼働停止中の変更は起動時にすべてのオブジェクトをリストして最後の状態のみを処理するレベルベースで扱われます。

冪等性。 「作成せよ」ではなく「望ましい姿で存在させよ」という方針で記述します。すでに一致している場合は何も書き込まないことで、下位オブジェクトの resourceVersion がそのまま維持され、不要な監視イベントが発生しなくなります。リーダー選出が行われていても、一時的にリーダーが2つ存在する可能性があるため必須です。

オーナー参照とfinalizer。 Kubernetesのドキュメントによると、子リソースの ownerReferences は同じネームスペース内のオーナー名とUIDを指し示し、ネームスペースをまたぐ参照は設計上禁止されており、ガベージコレクターが OwnerRefInvalidNamespace の警告イベントを残します。finalizerが存在する場合、削除リクエストは deletionTimestamp が付与されるだけで、コントローラーがクリーンアップを完了してfinalizerを削除するまで実際には削除されません。kubebuilderの書籍では、削除が更新に置き換わるため、finalizerの追加とクリーンアップロジックの両方を冪等に書くよう求めています。

ステータスと条件。 status.conditions と observedGeneration により、状態を標準形式で公開します。DynamoのDGDも conditions, observedGeneration, state, rollingUpdate をstatus内に保持します。Argo CDのドキュメントでは、CRDのstatus形式はまちまちであるためカスタムヘルスチェックが必要であり、 observedGeneration を用いることで調整が完了する前の誤判定を防ぐことができると案内されています。

リトライとバックオフ。 調整関数がエラーを返すとワークキューが指数バックオフで再キューイングし、 RequeueAfter は指定時間後に再キューイングを行い、 Requeue フィールドは非推奨(deprecated)です。 TerminalError は再キューイングしません。「まだ準備できていない」という状態はエラーではなく、遅延付きの再キューイングとして表現するのが適切です。

リーダー選出。 controller-runtimeのマネージャーは LeaderElection, LeaderElectionID, LeaderElectionNamespace オプションでLeaseを取得します(Dynamoの main.go で確認済みであり、LMCacheオペレーターのインストール用YAMLにもleader-election Roleが存在します)。Kubernetesのドキュメントでは、Leaseがノードのハートビートやコントロールプレーンのリーダー選出に使用されると説明されています。

Webhook検証。 単純なルールであればCRDスキーマのCEL(x-kubernetes-validations)で十分ですが、複雑なルールやインジェクションにはWebhookを使用します。Dynamoは検証Webhookにおいて field.ErrorList を使って複数の独立したエラーをまとめて返すルールをドキュメント化しており、LMCacheオペレーターは変異WebhookによってvLLMのPodに接続設定をインジェクションし、Webhook証明書をcert-managerで発行します。

4. Go(controller-runtime/kubebuilder)対Python(kopf)

項目 Go: controller-runtime v0.25.2, kubebuilder v4.16.0 Python: kopf 1.44.6 (2026-06-03)
モデル 型付きクライアント、インフォーマーキャッシュ、ワークキュー、マネージャー デコレーターハンドラー、ステータスをオブジェクトのアノテーションやstatusに保存
コード生成 controller-gen でCRDを生成(Dynamoはv0.17.3) CRD YAMLを手動で記述
重複防止 Leaseリーダー選出 KopfPeeringと優先度
エコシステム DynamoオペレーターはKubebuilderで作成されたとREADMEに記載されており、変換は conversion.Convertible インターフェースで実装。LWS、KServe、LMCacheのCRDもcontroller-genで生成されている ステータスをオブジェクトに持たせ、 kopf.adopt などのツールを提供。kopf自身はKubernetesクライアントライブラリではないと表明している

私の判断です。変異Webhookや大規模な監視が必要となるアップストリーム級のCRDではGoエコシステムがデフォルトであり、1〜2チームが使用する内部自動化ツールであればチームが最も使い慣れている言語が有利です。私にはGoオペレーターを運用した経験がないため、上記の比較はドキュメントとコードを読み込んだ結果に基づいています。

5. CRDスキーマ設計の原則

6. ロールアウト戦略

7. GitOps(Argo CD)との競合

競合は、Git に記述されているフィールドをオペレーターや HPA が変更するときに発生します。 replicas を Git に記述すると HPA と競合します(HPA が増やすと Argo が戻し、さらに HPA が増やす)。解決手段は次の通りです。

よくある罠はヘルスチェックです。アプリの健康状態が下位リソースの状態によって決まり、同期がその健康状態を待つ構造になっている場合、組み込みのチェックが特定のリソース(例:一度失敗した CronJob)を Degraded と判定すると、同期が健康状態を待ったまま失敗し、新しい変更が反映されないことがあります。チェックルールは Lua で再定義することで解決できます(この動作がどのバージョンで発生するかは確認していません)。ApplicationSet でも、子アプリケーションの自動化をオフにしても親テンプレートが再びオンにしてしまうという 2 つのコントローラー間の競合が発生し、その解決策は 1 つのフィールドのみに名前とパスを絞って例外を設定することです。

8. 隔離された環境で確認した動作

以下は、隔離された検証環境(kwok クラスターまたは個人の VM)において実際の Kubernetes コントローラーで確認した内容であり、筆者のプロダクションでの障害記録ではありません。

制限 上記は検証によって確認された Kubernetes 自体の動作であり、特定のオペレーター実装や Go 製の本番オペレーターの障害対応ではありません。

9. 実際にやってみる

ローカルの kind クラスター 1 つでのみ行います。すべてのコマンドが kind-op-lab コンテキストを明示するため、他のクラスターには影響しません。終わったら最後の行でクラスターごと削除します。

kind create cluster --name op-lab
kubectl --context kind-op-lab create ns op-lab
cat <<'EOF' | kubectl --context kind-op-lab apply -f -
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: {name: modelcaches.labs.example.dev}
spec:
  group: labs.example.dev
  scope: Namespaced
  names: {kind: ModelCache, plural: modelcaches, singular: modelcache, shortNames: [mc]}
  versions:
  - name: v1
    served: true
    storage: true
    subresources: {status: {}}
    additionalPrinterColumns:
    - {name: Phase, type: string, jsonPath: .status.phase}
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            required: [model]
            properties:
              model: {type: string}
              replicas: {type: integer, default: 1, minimum: 0, maximum: 8}
            x-kubernetes-validations:
            - {rule: "self.model.size() > 0", message: "model must not be empty"}
          status:
            type: object
            x-kubernetes-preserve-unknown-fields: true
EOF

最小調整ループ(op.py)は、1 つの CR に 1 つの ConfigMap をオーナー参照で結びつけておきます。kopf は pip install kopf kubernetes でインストールします。

import kopf, kubernetes as k8s

@kopf.on.resume("labs.example.dev", "v1", "modelcaches")
@kopf.on.create("labs.example.dev", "v1", "modelcaches")
@kopf.on.update("labs.example.dev", "v1", "modelcaches")
@kopf.timer("labs.example.dev", "v1", "modelcaches", interval=30, idle=10)
def reconcile(spec, name, namespace, patch, **_):
    cm = {"apiVersion": "v1", "kind": "ConfigMap",
          "metadata": {"name": f"{name}-cfg"},
          "data": {"model": spec["model"], "replicas": str(spec.get("replicas", 1))}}
    kopf.adopt(cm)                      # ownerReferences: 이 CR의 이름과 uid
    api = k8s.client.CoreV1Api()
    try:
        api.create_namespaced_config_map(namespace, cm)
    except k8s.client.ApiException as e:
        if e.status != 409:
            raise
        api.patch_namespaced_config_map(f"{name}-cfg", namespace, cm)   # 멱등
    patch.status["phase"] = "Ready"

@kopf.on.delete("labs.example.dev", "v1", "modelcaches")
def cleanup(name, logger, **_):
    logger.info("%s: 외부 자원 정리 자리", name)     # 삭제 핸들러가 있으면 kopf 가 finalizer 를 붙인다
kubectl config current-context            # kind-op-lab 인지 확인(kind 가 만들면서 바꿉니다)
kopf run --standalone --namespace op-lab op.py &
kubectl --context kind-op-lab -n op-lab apply -f - <<'EOF'
apiVersion: labs.example.dev/v1
kind: ModelCache
metadata: {name: demo}
spec: {model: tiny-llm}
EOF
kubectl --context kind-op-lab -n op-lab get mc,cm -o wide
kubectl --context kind-op-lab -n op-lab get cm demo-cfg -o jsonpath='{.metadata.ownerReferences}'
# 에지 대 레벨 관찰: 자식을 지워도 CR 이벤트가 없으므로 타이머(30초 주기)가 되살립니다
kubectl --context kind-op-lab -n op-lab delete cm demo-cfg && sleep 40 && kubectl --context kind-op-lab -n op-lab get cm
# CEL 검증: spec.model 을 빈 문자열로 넣은 CR 을 적용하면 "model must not be empty" 로 거절됩니다
# 정리
kill %1; kind delete cluster --name op-lab

続いて試すこと: ownerReferences の uid を意図的に間違えて書いた ConfigMap が削除されるか確認し、CR 削除中に deletionTimestamp と finalizer のリストを確認します。このスケルトンは子の変更を監視しないため、タイマーで補完した学習用です。

10. 面接で問われそうな質問

  1. LLM サービングに Deployment の代わりに LWS を使用する理由は? レプリカがリーダーとワーカーのセットであり、スケール、ローリングアップデート、障害復旧、トポロジ配置をグループ単位で行う必要があるためです。1つの Pod が失敗すると集合通信が途切れるため、グループ全体を再作成するポリシーがデフォルトになっています。
  2. 調整ループがイベントを取り逃がしても問題ない理由は? リクエストにイベントの内容が含まれておらず、毎回現在の状態を読み取って差分を埋めるためです。コントローラーが一度死んでから生き返っても、最初からやり直して終わっていない部分を処理するため、すべての書き込みがべき等である必要があります。
  3. ownerReference と finalizer の違いは? 前者は子が親を指すことでガベージコレクターにクリーンアップを任せる仕組みであり、uid が間違っていると子は削除されます。後者は外部リソースのクリーンアップのために削除を遅らせる仕組みであり、コントローラーなしで外すとクリーンアップがスキップされます。
  4. CRD を v1alpha1 から v1beta1 に昇格させる手順は? 新しいバージョンを served として追加し、必要に応じて変換 Webhook をアタッチし、storage を移行して既存のオブジェクトをすべて再書き込みした後に storedVersions を整理し、古いバージョンを停止します。戦略 None は、フィールドの形状が同じ場合にのみ安全です。
  5. Argo CD がオペレーターによって変更されたフィールドを戻してしまう場合は? Git からそのフィールドを外し、それが難しい場合は ignoreDifferences で比較から外し、 RespectIgnoreDifferences で同期から除外します。健康状態は CR 用の Lua チェックによって observedGeneration を反映させます。

11. よくある誤解

外部の根拠

主張 資料 確認した内容
調整はイベントではなく現在の状態によって駆動される controller-runtime pkg/reconcile/reconcile.go の Request·Reconciler コメント Request には名前とネームスペースのみが含まれており、イベントの内容はありません。調整はレベルベースであり、apiserver やキャッシュから読み取った実際の状態で駆動されると記載されており、Pod が削除されたという事実も削除イベントからではなく状態を読み取る過程で知ることになるという例が挙げられています。 Requeue フィールドは非推奨であり、 RequeueAfter を使用するように記載されている
イベントごとのロジックはアンチパターン kubebuilder ブック、Good practices イベントごとに調整ロジックを書くとオペレーターパターンから外れ、リソースがブロックされて手動作業が必要になる可能性があると警告しており、再調整が何度実行されても同じ結果を出すように書くよう求めている
コントローラーは停止していた時間を認識していない Kubernetes コミュニティドキュメント(sig-api-machinery、controllers.md) 遷移ではなく現在の状態を監視する必要があり、インフォーマーが定期的に再同期を行い、ワークキューにはキーのみを入れて重複を排除し、エラーは指数バックオフで再キューイングする
kopf の監視範囲とタイマー kopf ドキュメント(handlers、timers、continuity、errors) ハンドラーは diff で検出した本質的な変化にのみ反応し、監視対象外の子の変化についての記述はない。タイマーはオブジェクトが存在する間周期的に実行され、停止中の変更は再起動時に列挙されて最後の状態のみが処理され、通常エラーの再試行間隔のデフォルト値は 60 秒
「level-triggered」という用語の出処 上記の controller-runtime のコメントおよびコミュニティドキュメント kubernetes.io のコントローラー概念ページでは制御ループと「現在の状態を望ましい状態に近づける」については説明しているが、この用語自体は使用していない。本稿ではこの用語を controller-runtime と kubebuilder ブックに帰属させる

参考資料(実際に参照したもの)

kopf オペレーターの直接テスト

第9節の最小限の調整ループ(op.py、修正なしでそのまま)を、使い捨てのシングルノード k3s(v1.36.5、4 vCPU 仮想マシン)で kopf 1.44.6 を使って実行し、第3節のパターンを確認しました。呼び出し回数をカウントするためにログを1行追加したバージョン(op2.py)を、重複実行とエラーのテストに使用しました。

確認事項 結果
CR作成から子ConfigMapが作成されるまで、 status.phase=Ready まで それぞれ0.1秒
子オブジェクトの所有者参照 CRの名前とUID、 controller: true, blockOwnerDeletion: true
スキーマのデフォルト値 replicas を指定しない場合、1が設定されてConfigMapに "replicas":"1"
CEL検証 空の model は model must not be empty, model 、欠落は Required value, replicas: 99 は8以下のルールにより拒否される(APIサーバーの段階)
finalizer kopf.zalando.org/KopfFinalizerMarker が付与される
子ConfigMapを削除した際の復旧時間(5回、開始位相を分散) 2.5、15.9、24.9、17.8、26.9秒
子の内容を変更した際の元の値への復旧 29.9秒
CRのspec変更がConfigMapに反映されるまで 0.1秒
調整が3回実行される間(95秒)、子とCRの resourceVersion どちらも不変(べき等)
オペレーター停止中のspec変更と子の削除、再起動後 0.6秒でConfigMapを復元(replicas=5を反映)
オペレーターなしでのCR削除 CRが deletionTimestamp がついたまま残り、子ConfigMapもそのまま。オペレーターを起動すると0.6秒後にCRが消え、子はガベージコレクションにより0.1秒後に消える

エラー処理とリトライ間隔。 調整関数が常に失敗するようにしたCRを100秒間放置し、呼び出し時刻を記録しました(kopfのデフォルト値)。

発生した例外 100秒間の呼び出し回数 間隔
通常の例外(RuntimeError) 4回 作成ハンドラーが失敗して60秒後にリトライ、タイマーも別途失敗して60秒後にリトライ(呼び出し時刻 +0、+10、+60、+70秒)
kopf.TemporaryError(delay=5) 20回 5秒ごと
kopf.PermanentError 4回 作成ハンドラーはリトライしない(0 succeeded; 1 failed)。ただし、 タイマーは動き続けるため 、30秒ごとに同じエラーが発生する(+10、+40、+70秒)

スケールテスト(CR 10〜300個)。 ノートPC(Apple M1 Max)のDocker Desktop(仮想マシンCPU 10コア、メモリ 8GB)上にシングルノードのkindクラスター(Kubernetes v1.36.1)を作成し、上記の op.py を変更せずに、kopf 1.44.6をクラスター外のプロセスとして実行しました。CRごとに、監視がCRを最初に認識した時刻から status.phase=Ready が記載された最初のイベントを受信するまでの時間を遅延として測定し、各条件でウォームアップ1回を除外して3回繰り返した中央値を算出しました。

CR数 単一マニフェストによる適用: 全体処理 遅延中央値 / p95 / 最大 kubectl 20개씩 병렬: 전체 처리 遅延中央値 / p95 / 最大
10 0.17秒 0.030 / 0.041 / 0.043秒 0.22秒 0.082 / 0.095 / 0.096秒
50 0.56秒 0.029 / 0.043 / 0.044秒 1.08秒 0.127 / 0.187 / 0.196秒
100 1.96秒 0.049 / 0.064 / 0.086秒 2.10秒 0.073 / 0.127 / 0.180秒
300 3.61秒 0.032 / 0.063 / 0.115秒 7.28秒(2回) 0.102 / 0.188 / 0.254秒

LWSの直接テスト

使い捨てのシングルノードk3s(v1.36.5、4 vCPU、4GiB仮想マシン)にLWS v0.11.1をインストールし、 busybox sleep のPodを使用してグループの構造、復旧、ローリングアップデート、スケールを確認しました。GPUやモデルは使用していないためモデルのロード時間は含まれておらず、時間は0.5秒間隔で測定しました。

確認事項 結果
インストール マニフェスト適用からコントローラーの準備完了まで17秒。CRDは LeaderWorkerSet のほかに DisaggregatedSet, DisaggregatedSetRoleScaler も同時にインストールされる
グループの作成 replicas: 2, size: 3 の場合、6つのPodすべてが4.8秒でRunningになります。名前はリーダーが demo-0、ワーカーが demo-0-1, demo-0-2、2番目のグループが demo-1, demo-1-1, demo-1-2
作成されたサブリソース リーダー用StatefulSetが1つ(demo、グループ数分)、 グループごとにワーカー用StatefulSetが1つ(demo-0, demo-1)、すべてのグループで共有されるヘッドレスサービスが1つ(demo)。Podの所有者はリーダーが StatefulSet/demo、ワーカーが StatefulSet/demo-0
Podのラベル leaderworkerset.sigs.k8s.io/ 配下に name, group-index, worker-index, group-key(グループごとに固有のハッシュ)、 template-revision-hash
インジェクションされた環境変数 LWS_LEADER_ADDRESS(例: demo-1.demo.default、ワーカーも 自グループのリーダーのアドレス)、 LWS_GROUP_SIZE=3, LWS_WORKER_INDEX(リーダーは0、ワーカーは1から)
デフォルト値 rolloutStrategy: RollingUpdate, maxSurge: 0, maxUnavailable: 1, partition: 0(第6節の記述と一致)。 startupPolicy: LeaderCreated, networkConfig.subdomainPolicy: Shared, restartPolicy: RecreateGroupOnPodRestart

制限事項: 仮想マシン1台、Pod が行う処理は sleep、イメージは事前に取得済みの busyboxであり、プローブ(準備状態検査)はありません。ノード障害によるグループの復旧および DisaggregatedSet は検証していません(未計測)。

確認したバージョンと日付 (2026-10-05)

LWS v0.11.1(2026-10-01), KServe v0.21.0(2026-09-25, ドキュメントは 0.20 ページ), Dynamo v1.5.0(2026-09-21), controller-runtime v0.25.2(2026-10-01), kubebuilder v4.16.0(2026-09-10), kopf 1.44.6(2026-06-03), Argo CD v3.5.3(2026-09-14), LMCache v0.5.5, Kubernetes v1.37.1(2026-09-23).

確認できなかった項目。 StorageVersionMigration のデフォルトの有効性(Kubernetes のドキュメント内で記述が食い違っている)、kopf の CR 1,000個以上・複数ノードにおける性能の限界(300個までは上記の規模検証で測定しました)、コントローラー単位のテストツール(envtest)の詳細、ValidatingAdmissionPolicy と Webhook の得失、KServe の最新ドキュメントと v0.21.0 の間の差異、Dynamo の DGD デプロイモード別の正確なサブリソース生成パス(CRD の説明とドキュメントから図式化し、コードパスは追跡していません)。第9章の kopf のスケルトンは私がドキュメントを基に作成したものであり、実際のクラスターで実行して検証したものではありません。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます