Skip to content
Published on

Kubernetes Operator パターン完全理解 — コントローラー、リコンサイル、そして運用知識のコード化

Authors

はじめに — 運用者の知識をコードへ移すということ

Kubernetes を初めて学ぶとき、私たちは Deployment、Service、ConfigMap といったビルトインリソースを扱います。これらは明確に定義された振る舞いを持つため、YAML を適用すればクラスターが望ましい状態へ自動的に収束します。Deployment に replica を 3 と書けばポッドが 3 つに保たれ、1 つが落ちれば再び立ち上がります。この「宣言すれば勝手に収束する」体験こそが Kubernetes の核心的な魅力です。

しかし実際の運用現場には、ビルトインリソースだけでは表現できない複雑な運用知識があふれています。たとえば PostgreSQL クラスターを運用するとしましょう。単にポッドを 3 つ立てれば終わりではありません。誰がプライマリで誰がレプリカかを決め、プライマリが落ちればレプリカの 1 つを昇格(フェイルオーバー)させ、定期的にバックアップを取り、バージョンアップ時には順序を守る必要があります。こうした運用手順は通常、運用者の頭の中かランブック(runbook)文書の中にあります。

Operator パターンとは、まさにこの「運用者の知識」をコードへ移し、クラスター内で 24 時間自動で回るようにする方法です。 CoreOS が 2016 年に最初に提案したこの概念は、いまや Kubernetes エコシステムの標準的な拡張方式として定着しています。本記事では、Operator が何を解決するのか、その土台であるコントロールループとリコンサイルがどう動くのか、そして実務でいつ導入し、いつ避けるべきかを深く扱います。

Operator が解決する問題 — ステートフルと Day-2 運用

ステートレス対ステートフル

Kubernetes のビルトインコントローラーはステートレスなワークロードに非常に強いです。Web サーバーのようにどのポッドが落ちても別のポッドで置き換えればよい場合、Deployment 1 つで十分です。ポッド間のアイデンティティも不要で、終了順序も重要ではありません。

問題はステートフルなワークロードです。データベース、メッセージブローカー、分散キャッシュ、合意(consensus)ベースのシステムは、各インスタンスが固有のアイデンティティと状態を持ちます。StatefulSet は安定したネットワーク ID とストレージを提供しますが、「どのようにクラスターをブートストラップし、メンバーを追加し、障害を復旧するか」は知りません。それはアプリケーション固有のドメイン知識だからです。

Day-1 と Day-2 運用

運用を時間軸で分けると、こう区別します。

  • Day-0: 設計と計画
  • Day-1: インストールとデプロイ(一度だけ行う作業)
  • Day-2: それ以降のすべての運用 — アップグレード、スケーリング、バックアップ/リストア、障害復旧、設定変更、セキュリティパッチ

ほとんどのツールは Day-1 をうまく助けてくれます。Helm チャートでのインストールは簡単です。しかし本当に難しいのは Day-2 です。Helm は一度インストールしたあとに起こることを知りません。プライマリが落ちたときに誰かが手で介入しなければならないなら、それは自動化ではありません。

Operator は Day-2 運用の自動化に特化しています。 コントローラーがクラスターの状態を絶えず観察し、望ましい状態とずれたら、運用者が行ったであろう対処を自ら実行します。深夜 3 時にページャーを鳴らさないこと、それが Operator の核心的な価値です。

比較: ビルトイン対 Helm 対 Operator

観点ビルトインコントローラーHelmOperator
主な対象ステートレスパッケージング/インストールステートフル/複雑な運用
Day-1 インストール手動 YAML自動自動
Day-2 運用限定的ほぼなし核心的な強み
継続的な調整ビルトイン限定なし(テンプレート 1 回)常に動作
ドメイン知識なしなしコードに内蔵
学習曲線低い低い高い

コントロールループとリコンサイルの原理

宣言的モデル: desired 対 observed

Kubernetes のすべての動作は、一つのシンプルな原理に還元されます。それは 望ましい状態(desired state)と観測された状態(observed state)を比較し、その差を縮めること です。ユーザーが「こうあるべき」と desired state を宣言すると、コントローラーは現在のクラスターの observed state を読み、両者を比較し、差があれば埋める行動を取ります。

この絶えず回る比較・調整の過程を コントロールループ(control loop) または リコンサイルループ(reconcile loop) と呼びます。文章にするとこうです。

無限に繰り返す:
  desired  = ユーザーが宣言した望ましい状態を読む
  observed = クラスターの実際の状態を読む
  if desired != observed:
      差を縮める行動を実行
  少し待つ、またはイベント発生までブロック

このシンプルなループが Kubernetes 全体を支えています。Deployment コントローラー、ReplicaSet コントローラー、Node コントローラーはすべてこのパターンに従います。Operator もまったく同じ原理で動作し、ただその対象がユーザー定義リソースである点だけが異なります。

ASCII 図で見る流れ

        +-------------------+
        |  ユーザー / Git   |
        |  desired state    |
        |  (CR 適用)        |
        +---------+---------+
                  | apply
                  v
        +-------------------+        watch        +-----------------+
        |   API Server      |<-------------------+|  コントローラー  |
        |   (etcd 保存)     |                     |  (reconcile)    |
        +---------+---------+                     +--------+--------+
                  ^                                        |
                  | status 更新                            | 1. observed を読む
                  | / リソース作成・修正                   | 2. desired と比較
                  |                                        | 3. 差を調整
                  +----------------------------------------+
                          実際のクラスター状態を合わせていく

肝心なのは、コントローラーが直接 etcd を触らず、常に API Server を通して状態を読み書きする という点です。コントローラーは API Server の watch メカニズムで変更を通知され、変更が生じたオブジェクトに対して reconcile を呼び出します。

冪等性(idempotency)が核心だ

reconcile 関数を設計するうえで最も重要な原則は 冪等性 です。同じ入力に対して何度実行しても結果が同一でなければならない、という意味です。reconcile は同じオブジェクトに対して数十回、数百回呼び出されることがあります。watch イベントが重複して来ることもあれば、定期的に再実行されることもあり、エラー後に再試行されることもあります。

したがって reconcile は「X を作成せよ」ではなく「X が望ましい姿で存在するか確認し、そうでなければ合わせよ」と書くべきです。次の疑似コードを比較しましょう。

誤った方式(非冪等):
  create Deployment   # 2 回目の呼び出しで「すでに存在」エラー

正しい方式(冪等):
  desired = buildDesiredDeployment()
  existing = get Deployment
  if not found:
      create desired
  else if existing != desired:
      update to desired
  # すでに同じなら何もしない

冪等性を守れないと、コントローラーは重複作成エラーを出したり、無限ループに陥ったり、リソースを揺らし続けてクラスターを不安定にします。

CRD + コントローラー = Operator

CRD: 新しいリソース型を定義する

Operator は 2 つの部分で構成されます。1 つ目は CRD(CustomResourceDefinition) です。CRD は Kubernetes API に新しいリソース型(kind)を登録します。たとえば PostgresCluster という新しい種類を定義すれば、ユーザーは次のようにビルトインリソースとまったく同じ方法で扱えるようになります。

apiVersion: db.example.com/v1
kind: PostgresCluster
metadata:
  name: orders-db
spec:
  replicas: 3
  version: "16"
  storage: 50Gi
status:
  phase: Running
  primary: orders-db-0

CRD を登録すると kubectl get postgrescluster のようなコマンドが動作し、RBAC も適用され、etcd に保存されます。つまり CRD は Kubernetes API の語彙を増やしてくれます。しかし CRD だけでは何も起こりません。ただデータが etcd に保存されるだけです。

コントローラー: 定義に命を吹き込む

2 つ目の部分が コントローラー です。コントローラーは上で定義した CR(Custom Resource)を watch し、作成・修正・削除が起きると reconcile を回して実際の動作を生み出します。PostgresCluster を見て StatefulSet、Service、Secret、ConfigMap を作り、プライマリ選出を管理し、バックアップ CronJob を仕掛ける処理がすべてコントローラーのコードの中にあります。

まとめると次の等式が成り立ちます。

Operator = CRD(新しい API 型) + コントローラー(その型のための reconcile ロジック)

CRD が「何を望むかを述べる言語」なら、コントローラーは「それを実現する運用者の頭脳」です。この 2 つが合わさって初めて、ドメインに特化した自動運用が完成します。

owner reference によるガベージコレクション

コントローラーが作った下位リソース(StatefulSet など)には通常 owner reference を付けます。こうすると親である CR が削除されるとき、Kubernetes のガベージコレクターが子リソースを自動的に片付けます。owner reference は冪等性とともに Operator 設計の基本です。これによりコントローラーは「自分が作ったもの」を追跡し、片付けロジックをシンプルに保てます。

コントロールプレーンの拡張メカニズム — CRD 対 API Aggregation

Kubernetes API を拡張する方法は実は 2 つあります。Operator はほぼ常に 1 つ目を使います。

1. CRD 方式

CRD は Kubernetes が提供する基本ストレージ(etcd)と API 処理パイプラインをそのまま借りて使います。スキーマ(OpenAPI v3)だけ登録すれば、検証・バージョン変換・保存・watch を Kubernetes が自動でやってくれます。別途サーバーを運用する必要がなく、圧倒的にシンプルです。今日ほぼすべての Operator が CRD 方式です。

2. API Aggregation 方式

API Aggregation は、ユーザーが自前で拡張 API サーバーを立て、それをメインの API Server の後ろに登録する方式です。独自のストレージバックエンドを使え、より複雑な検証・変換ロジックを実装できます。しかし別途サーバーを運用・認証・スケーリングしなければならず負担が大きいです。metrics.k8s.io のような特殊なケースだけで使います。

比較表

観点CRDAPI Aggregation
ストレージetcd 共用独自バックエンド可
運用負担低い高い(サーバー運用)
検証OpenAPI スキーマ + Webhook任意のコード
一般的な用途ほとんどの Operator特殊ケース(メトリクスなど)
推奨度既定の選択必要なときだけ

加えて、検証やデフォルト値の注入がさらに必要なら アドミッション Webhook(validating/mutating)を CRD と一緒に付けます。CRD + Webhook の組み合わせなら API Aggregation なしでもかなり精緻な拡張が可能です。

Operator Capability Level — 成熟度の 5 段階

Operator Framework は Operator の成熟度を 5 段階で定義します。自分の Operator がどのあたりにいるかを測る良い基準です。

レベル名前核心的な能力
1Basic Install自動インストール、基本設定のプロビジョニング
2Seamless Upgrades無停止バージョンアップグレード、パッチ管理
3Full Lifecycleバックアップ/リストア、フェイルオーバー、スケーリングなど全ライフサイクル
4Deep Insightsメトリクス/アラート/ログ、ワークロード分析の公開
5Auto Pilotオートスケーリング、自動チューニング、異常検知・自己回復
  • レベル 1(Basic Install): CR を 1 つ適用すればアプリがインストールされます。ほとんどの Operator がここから始まります。
  • レベル 2(Seamless Upgrades): バージョンを変えればダウンタイムなくローリングアップグレードします。ステートフルアプリでは思ったより難しいです。
  • レベル 3(Full Lifecycle): バックアップスケジュール、自動フェイルオーバー、メンバー追加/削除のような本物の Day-2 運用を自動化します。
  • レベル 4(Deep Insights): Prometheus メトリクスを公開し、意味のある status conditions とイベントを発行します。
  • レベル 5(Auto Pilot): 負荷に応じて自らスケールし、性能をチューニングし、異常の兆候を検知して自己回復します。

レベルが高いほど価値も大きいですが、実装難易度と保守コストも急激に上がります。すべての Operator がレベル 5 を目指す必要はありません。

いつ Operator を使い、いつ避けるべきか

使うとよい場合

  • 複雑なステートフルアプリケーション: データベース、メッセージブローカー、分散ストレージのようにブートストラップ・フェイルオーバー・バックアップロジックがドメイン特化している場合。
  • 繰り返される Day-2 運用: 人がランブックを見て手で行っていた手順が明確に定義されており、頻繁に繰り返される場合。
  • 複数チーム・環境に同一アプリをデプロイ: CR 1 つで標準化されたデプロイを提供すれば一貫性が大きく上がります。
  • 自己修復が運用上重要な場合: 深夜の障害に人が介入しなければならないリスクを減らしたいとき。

使うべきでない場合

  • ステートレスな単純アプリ: Deployment + HPA で十分なものを Operator で作るのは過剰なエンジニアリングです。
  • 一度インストールして終わり: Day-2 運用がほぼないなら Helm チャートのほうが適切です。
  • 保守人員がいないとき: Operator はコードです。Kubernetes のバージョンが上がれば追随して保守する必要があります。作って放置すれば負債になります。
  • すでに良い公式 Operator があるとき: 自分で作る前に OperatorHub を先に探してください。

一行の判断基準

「運用者が深夜に起きて手で行うべき手順が明確にあり、それが繰り返され、自動化の価値が保守コストを上回るなら Operator を検討せよ。」

そうでなければ、よりシンプルなツール(Deployment、StatefulSet、Helm、GitOps)がほぼ常に正解です。Operator は強力ですがタダではありません。

エコシステム — OperatorHub とフレームワーク

自分で作る前に、すでに検証済みの Operator があるか確認することが重要です。

  • OperatorHub.io: コミュニティとベンダーが公開した Operator のカタログです。PostgreSQL、Kafka、Redis、Prometheus など主要なソフトウェアのほとんどの Operator を見つけられます。
  • OLM(Operator Lifecycle Manager): Operator 自体をインストール・アップグレード・依存関係管理するメタ運用ツールです。Operator の Operator と考えればよいです。
  • Kubebuilder: Go で Operator を作る事実上の標準 SDK です。controller-runtime の上でプロジェクトのスキャフォールディングとコード生成を提供します。
  • Operator SDK: Kubebuilder をラップしつつ、Go だけでなく Helm・Ansible ベースの Operator も支援する上位ツールです。

次の記事では Kubebuilder で実際に Operator を作る過程を、その次の記事では reconcile ループを深掘りして扱います。

命令型から宣言型への思考の転換

Operator をきちんと理解するには、思考様式そのものを変える必要があります。従来の自動化スクリプトは命令型(imperative)です。「これを作り、あれを設定し、次にこれを実行せよ」という手順の羅列です。問題は、スクリプトが途中で失敗するとシステムが中途半端な状態に陥ることです。再実行すると「すでに存在」エラーが出たり重複が生じたりします。

reconcile は宣言型(declarative)です。「最終的にこういう姿であるべき」だけを宣言し、現在の状態と比較してその姿に収束させます。どの地点で失敗しても、次の reconcile が最初から回りながら終わっていない部分だけを仕上げます。この違いを表にまとめると次のとおりです。

観点命令型スクリプト宣言型 reconcile
表現「この手順を実行せよ」「この結果になるべき」
失敗後中途半端な状態、手動復旧次の実行が自動復旧
再実行危険(重複/エラー)安全(冪等)
ドリフト検知できない自動補正

この思考の転換が Operator の本質です。「どうやるか」ではなく「何になるべきか」をコードで表現し、その隙間を絶えず埋めるループを信頼することです。この視点に慣れると、Kubernetes 全体が一つの巨大な宣言型システムに見え始めます。

2026 年の文脈

2026 年現在、Operator パターンは完全に主流になりました。クラウドベンダーのマネージドサービスの相当数が内部的に Operator で実装されており、プラットフォームエンジニアリングチームは内部開発者プラットフォーム(IDP)を作るときに CRD と Operator でセルフサービスの抽象化を提供します。Kubebuilder は Kubernetes 1.36 と Go 1.26 をサポートし、controller-runtime は v0.24.x 系で安定化しました。

特に注目すべき変化はセキュリティ面です。過去にはメトリクスエンドポイント保護のために別途 kube-rbac-proxy サイドカーを付けていましたが、いまは controller-runtime が提供する認証・認可ミドルウェア(WithAuthenticationAndAuthorization)を使って追加コンテナなしでメトリクスを保護します。運用面が減り、よりシンプルで安全になりました。

実際の Operator 事例で見るパターン

抽象的な説明より実際の事例を見ると、Operator が何を自動化するのかがより鮮明になります。代表的なオープンソース Operator をいくつか見てみましょう。

データベース Operator

PostgreSQL や MySQL Operator は Operator パターンの教科書的な事例です。これらが自動化する運用を並べると次のとおりです。

  • プライマリ/レプリカのトポロジーのブートストラップ
  • プライマリ障害時の自動 failover とレプリカ昇格
  • 定期バックアップとポイントインタイムリカバリ(PITR)
  • 無停止のマイナーバージョンアップグレード
  • コネクションプーラー(例: PgBouncer)の統合管理

運用者が深夜に起きて手動で行ったであろうこれらすべてが、CR 1 枚の宣言とコントローラーの reconcile に置き換わります。これがレベル 3(Full Lifecycle)以上の価値です。

モニタリング Operator

Prometheus Operator はもう一つの有名な事例です。ここでは PrometheusServiceMonitorPrometheusRule のような CRD を定義し、モニタリング設定自体を Kubernetes リソースとして扱います。開発者は巨大な Prometheus 設定ファイルを直接編集する代わりに、ServiceMonitor という小さな CR を作って「私のサービスのメトリクスを収集せよ」と宣言します。Operator がこれらの宣言を集めて実際の Prometheus 設定に変換しリロードします。

証明書 Operator

cert-manager は証明書の発行と更新を自動化する Operator です。Certificate という CR を作ると、Operator が ACME(例: Let's Encrypt)と通信して証明書を発行し、有効期限前に自動で更新します。ここで RequeueAfter の周期点検が光ります。証明書の有効期限は watch イベントで捉えられないので、Operator は周期的に再び reconcile して更新タイミングを確認します。

共通パターンの抽出

これらの事例から繰り返し現れる共通構造が見えます。

1. ユーザーが小さな CR で「望む結果」を宣言
2. Operator がそれを複数の下位リソース/外部呼び出しに変換
3. 継続的に観測しドリフトを補正
4. 時間依存の作業(更新/バックアップ)は周期的 reconcile で処理

つまり Operator は「複雑な運用意図を単純な宣言に圧縮する」抽象化レイヤーです。ユーザーは結果を述べ、その結果を作る手順は Operator が責任を持ちます。

CR の一生 — 作成から削除まで

Operator を深く理解するには、一つの CR が生まれて死ぬまでどんな段階を経るのかを追ってみるのがよいです。抽象的な原理を具体的な流れに変えてみましょう。

1. 作成段階

ユーザーが CR を適用すると、API Server がこれを検証して etcd に保存します。この瞬間、コントローラーの informer が watch で作成イベントを受け取り、該当オブジェクトのキーを workqueue に入れます。コントローラーは reconcile を呼び、「この CR が望む下位リソースがまだない」という事実を発見し、必要な Deployment・Service などを作ります。

ユーザー apply
  -> API Server 検証/保存
  -> informer 作成イベント
  -> reconcile 1 回目: 下位リソース作成
  -> reconcile 2 回目: 下位リソース状態を観測、status 更新

興味深いのは、通常 1 回の reconcile で終わらないことです。最初の reconcile で Deployment を作っても、その Deployment のポッドが準備できるまで時間がかかります。コントローラーは RequeueAfter や下位リソースの watch を通じて再び呼ばれ、準備状態が整うまで status を段階的に更新します。

2. 通常運用段階

CR が望ましい状態に達すると、reconcile は「やることなし」を確認して静かに終わります。ここからコントローラーは監視者の役割を果たします。誰かが下位 Deployment を手で修正すると watch がこれを検知し reconcile が再び回り、望ましい状態に戻します。ノード障害でポッドが死んでもビルトインコントローラーがポッドを生かし、Operator はその上でドメインレベルの一貫性を維持します。

3. 変更段階

ユーザーが CR の spec を変えると(例: replica 3 → 5)、generation が増加し新しい reconcile がトリガーされます。コントローラーは新しい desired state を計算し下位リソースを更新します。冪等に書かれているので、何が変わったかを逐一追跡する必要なく「望ましい全体の姿」を再適用すればよいです。

4. 削除段階と finalizer

CR を削除すると 2 つの経路があります。owner reference でつながった単純な下位リソースはガベージコレクターが自動で片付けます。しかしクラスター外のリソース(例: クラウドロードバランサー、外部 DB ユーザー、S3 バケット)は Kubernetes が知らないので自動清掃されません。ここで finalizer が登場します。

finalizer はオブジェクトに付く一種の「削除ロック」です。finalizer が残っている限りオブジェクトは削除マーク(deletionTimestamp)が付くだけで実際には消えません。コントローラーは deletionTimestamp が付いたのを見て外部清掃作業を行ったあと、finalizer を取り除きます。finalizer がすべて消えてはじめてオブジェクトが etcd から削除されます。

削除要求
  -> deletionTimestamp 設定(オブジェクトはまだ存在)
  -> reconcile: 外部リソース清掃を実行
  -> finalizer 除去
  -> オブジェクト実際に削除

finalizer のおかげで Operator は「削除時に必ず行うべき清掃」を漏らさず保証できます。外部資源を扱う Operator なら finalizer は選択ではなく必須です。

パターンの限界とアンチパターン

Operator は強力ですが、誤って使うとかえってシステムを複雑で脆弱にします。よく見られるアンチパターンをまとめます。

アンチパターン 1: すべてを Operator で

単純なデプロイまで Operator で包もうとする欲は、よくある間違いです。Deployment + ConfigMap で終わることに CRD とコントローラーを導入すると、運用者が学ぶべき抽象化だけが増えデバッグは難しくなります。「これ Helm でできないか?」をまず問うてください。

アンチパターン 2: reconcile に副作用爆弾

reconcile は冪等でなければなりません。では reconcile の中でメールを送ったり、外部システムに非冪等な要求を送ったり、カウンターを増やしたらどうなるでしょうか。reconcile は数十回呼ばれうるので、メールが数十通送られカウンターがめちゃくちゃになります。副作用は必ず冪等に設計するか、「一度だけ」保証が必要なら status に完了マークを残して重複を防ぐべきです。

アンチパターン 3: 巨大な単一 reconcile

1 つの reconcile に数百行の分岐を詰め込むと、推論とテストが不可能になります。先に見た段階的設計のように、各段階を小さな冪等関数に分けるのが良いです。

アンチパターン 4: status を信頼の源に

status はコントローラーが観測した結果をキャッシュしたものに過ぎず、真実の源ではありません。本当の状態は常に実際のリソース(observed state)にあります。status だけ見て分岐すると、status が古いときに誤った決定を下します。

導入前チェックリスト

Operator を作ると決める前に次を自問してみてください。

[ ] すでに公式/コミュニティ Operator があるか?(OperatorHub 確認)
[ ] Helm + GitOps では本当に無理か?
[ ] 自動化したい Day-2 運用が明確に定義されているか?
[ ] 長期的に保守する人員と意志があるか?
[ ] reconcile を冪等に設計できるか?

この 5 つの質問に自信を持って「はい」と答えられるなら、Operator の導入は良い選択です。1 つでも迷うなら、より単純な道をもう一度検討する価値があります。

よくある誤解を解く

Operator に初めて触れるときによく生じる誤解をいくつか押さえておきます。

誤解事実
「Operator は魔法のような自動化だ」結局 reconcile 関数に人が書いた運用ロジックに過ぎません。コードが知らないことは自動化されません。
「CRD さえ作れば Operator だ」CRD はデータ定義に過ぎず、コントローラーがなければ何の動作もしません。
「一度作れば終わりだ」Kubernetes のバージョンアップとともに保守が必要な生きたコードです。
「Operator は必ず Go で書くべきだ」Go が標準ですが Helm・Ansible・Python(kopf)など他の方式もあります。
「Operator はステートフルだけに使う」主にそうですが、複雑な設定調整やポリシー強制など他の用途もあります。

特に最初の誤解が重要です。Operator は人の判断をコードに移したものであり、人より賢い何かではありません。reconcile 関数に誤った運用ロジックを入れると、その間違いが 24 時間自動で繰り返されます。だからこそ Operator コードの品質とテストが一般のアプリケーションよりも重要になりうるのです。

おわりに

Operator パターンの本質は華やかな技術ではなく、「運用者の知識を宣言的モデル上の reconcile ループとしてコード化する」 というシンプルなアイデアです。desired と observed を比較して差を埋めるループ、そのループを冪等に書く規律、CRD で語彙を増やしコントローラーで命を吹き込む構造 — この 3 つさえ確実に理解すれば、どんな Operator に出会ってもその動作原理を見抜けます。

ただし忘れてはならないのは、Operator がすべての問題の答えではないという点です。シンプルなものはシンプルに保ち、本当に複雑な Day-2 運用を自動化すべきときに初めて Operator を取り出すのが成熟した選択です。

続く記事では、これらの概念を実際のコードへ移します。次の記事で Kubebuilder を使って初めての Operator を自分で作り、その次の記事で reconcile ループの内部動作と性能チューニングを深く掘り下げます。原理を理解した今こそ、手で作ってみてその理解を固める最良のときです。

参考資料