TT Lab
はじめる
学ぶ 学習パス コース

CBA — Backstage認定アソシエイト

設定が一ファイルでない理由とマージの規則

TT Labで続きを見る

一言でいうと

Backstageの設定は、app-config.yaml1枚ではなく、複数枚を順番に積み重ねた結果です。マッピングは深くマージされ、リストはまるごと置き換えられ、あとに来るファイルが勝ちます。このルール1つを正確に知っていれば、「なぜ自分が直した値が効かないのか」の大半が解決します。

なぜ必要なのか

同じコードが、4か所で動きます。開発者のノートPC、CI、ステージング、本番です。ところが、この4つは、ほぼすべての値が違っている必要があります。

値を1つのファイルにまとめておくと、誰かが自分の環境に合わせて直してコミットするたびに、ほかの人の環境が壊れます。しかも、トークンやパスワードは、そもそもファイルに置けません。そのため、Backstageは、設定を複数のファイルに分け、順番に重ねて読む方式を選びました。

どう動くのか

ファイル複数枚が順番に積み重なる

慣例として使うファイルは、3つです。

ファイル どこに置くか 何を入れるか
app-config.yaml リポジトリにコミットします すべての環境に共通のデフォルト値
app-config.local.yaml 個人のノートPC。gitからは除外します その人だけの上書き
app-config.production.yaml コンテナイメージに一緒に載せます 本番でだけ違う値

起動するときに--configで並べた順序で読み、あとに来たものが前のものを上書きします。ファイル名が特別なのではなく、順序がすべてです。

マージのルールは、たった2行

매핑(map)  : 키 단위로 깊게 합친다. 뒤에 없는 키는 앞의 값이 그대로 남는다
리스트(list): 합치지 않는다. 뒤에 있으면 통째로 교체된다

リストのルールが、事故の原因です。基本のファイルにcatalog.locationsを3件書いておき、ローカルのファイルに1件だけ書くと、結果は4件ではなく1件です。残りの2件は静かに消え、警告は何も出ません。

そして、マッピング側にも落とし穴が1つあります。値の種類が変わると、深くマージする対象がないので、そのまま置き換えられます。connectionが基本のファイルでは文字列1つだったのに、オーバーライドでマッピングになると、文字列は消えて、マッピングだけが残ります。

秘密情報はファイルではなく、環境から来る

値の位置に${GITHUB_TOKEN}のように書くと、起動するときに環境変数で置換されます。この形式を使う理由は、便宜ではなく、設定ファイルがリポジトリにコミットされ、コンテナイメージに焼き込まれるからです。値を直接書いた瞬間、その秘密情報はgitの履歴とイメージのレイヤーに、永遠に残ります。

逆方向もあります。APP_CONFIG_で始まる環境変数は、設定の特定のパス1か所を上書きします。パスのドットをアンダースコアに変えた名前を使います。

APP_CONFIG_app_baseUrl=https://portal.example.com   →  app.baseUrl 을 덮는다
APP_CONFIG_backend_listen_port=7007                 →  backend.listen.port 를 덮는다

イメージを焼き直さずに、デプロイのマニフェストだけを直して値を変えられるので、Kubernetesに載せるときに特に重宝します。

何がブラウザーまで届くのか

設定値には、可視性(visibility)があります。デフォルトはバックエンド専用で、スキーマでfrontendと表示した値だけが、フロントエンドのバンドルに入ります。この区別がなければ、integrations.github[0].tokenのような値が、ブラウザーのソースにそのまま載ります。試験で「設定に書いた値は、すべてフロントエンドから読めるか」と問われたら、答えはいいえです。

よく使うセクション

キー 役割
app · organization ブラウザーが使うアドレスと名前
backend リッスンのアドレスとポート、CORS、データベース
integrations GitHub・GitLabのようなSCMの認証情報
proxy ブラウザーの代わりに、バックエンドが外部APIを呼び出す通り道
catalog 許可するkind(rules)、手で書く入口(locations)、ディスカバリー(providers)
auth ログインプロバイダーと身元の決定
techdocs ドキュメントを誰がビルドし、どこに置くか

proxyがなぜ別にあるのかが、試験のポイントです。フロントエンドが外部APIを直接呼び出すと、CORSに止められ、止められなくても、認証情報をブラウザーに置く必要があります。そのため、バックエンドが代わりに呼び出し、フロントエンドは、自分のバックエンドのパスだけを呼び出します。

catalog.providersのscheduleも、注目すべき箇所です。周期を短くすると鮮度は上がりますが、SCMのAPIの上限に引っかかり、長くすると、人々がポータルの情報を信じなくなります。

現場での姿

著者のホームラボのクラスターで、まったく同じ性格の事故が2回起きました。

1つ目は、containerdでした。/etc/containerd/conf.d/の下のドロップインファイルが複数、同じプラグインに触れると、フィールドが合わさるのではなく、名前順で最後のファイルが、そのプラグインの設定をまるごと持っていきます。前のファイルが書いておいた値は残らず、残らなかった場所は、前のファイルの値ではなく、デフォルトに落ちます。「1行だけ追加」するつもりでファイルを1つ置いたら、ノードのランタイム設定がすべてデフォルトになりました。Backstageのリスト置換のルールは、これと同じ性格です。

2つ目は、ゲートウェイでした。HTTPSリダイレクトを有効にすると、証明書の更新が止まり、/.well-known/acme-challenge/をより具体的に書いても、効果がありませんでした。Ciliumは、パスの具体性を優先しないからです。ここで得た感覚が、設定の階層にもそのまま通用します。より詳しく書いたから勝つ、という理屈は通用しません。勝つのは順序です。

そして、このクラスターで繰り返し得た教訓、つまり「状態がReadyであることと、実際に動作することは別の命題」という点も、設定にそのまま当てはまります。ポータルが起動しているからといって、自分が意図した値で起動しているわけではありません。マージの結果を目で確認する習慣が必要です。

次のラボですること

/root/cba-config/に、基本設定、ローカルのオーバーライド、本番設定の3枚を、自分で書きます。そのあと、2枚を重ねたときのマージ結果を手で予測して書き、採点ツールが同じルールで再計算した値と照らし合わせます。最後に、本番設定をConfigMapとしてクラスターに作成し、APP_CONFIG_環境変数で1行を上書きしてみます。