YAML ファイル内の重複記述を減らし DRY を保つためにエイリアス機能を使う

Docker,Kubernetes,YAML

はじめに

僕が YAML ファイルを触りだしたのは、Ansible を使い始めてからでした。

そこから Docker ComposesKubernetes ともはや YAML を触ることがないシステムはない状況となりました。

AWSGCPAzure といったクラウド環境でも YAML ファイルで設定するリソースは非常に多いですし、Rest API のインターフェイス定義として OpenAPI を YAML で記述します。

YAML ファイルの重要性が高まるにつれて、ファイルの内容はどんどん肥大化する傾向にあります。
同時に 1 つの YAML ファイルの中に記述する内容が重複することも多く(Kubernetes マニフェストのラベル名など)、なんとか DRY に記述できないかと悩むことがあります。

重複記述を回避するために エイリアス を使おう

そこで有効なのが YAML の標準機能として提供されている エイリアス 機能です。

現在担当してるシステムのローカル開発環境は Docker Compose を使って構築していますが、エイリアス機能を使って docker-compose.yaml ファイルの簡素化を行っています。

それでは、エイリアス機能について見ていきましょう。

エイリアス(別名)機能とは?

YAML のエイリアス機能とは、 単一の値ブロック(YAML のツリー構造) のどちらかに名前をつけて、あとから参照するための仕組みになります。

エイリアス機能を利用した例

値にエイリアスを設定し、参照する

値に名前をつけるというのが最もシンプルな使い方です。

&エイリアス名 で名前をつけ、 *エイリアス名 で値を参照します。

hello: &hello "hello"
greeting:
  audience: "world"
  hello: *hello # 'hello' という文字列となります
new_greeting:
  audience: "room"
  hello: *hello # 'hello' という文字列となります

正しく値が参照できているかを確認するために、 yq というコマンドラインから YAML ファイルを操作するツールを使ってみます。

yq には YAML ファイル読み込み JSON フォーマットで出力する機能があるので、これで変換してみます。

# 先程のYAMLファイルを sample.yaml という名前で保存しておきます
$ yq e --tojson sample.yaml
{
  "hello": "hello",
  "greeting": {
    "audience": "world",
    "hello": "hello"
  },
  "new_greeting": {
    "audience": "room",
    "hello": "hello"
  }
}

*hello と記述したノードの値が 'hello' になっているのがわかります。

ブロックにエイリアスを設定し、参照する

「値に名前をつける」だけでなく、データ構造である ブロック に対して名前をつけることもできます。

これを使えば更に重複した記述を減らすことができます。

foo:
  bar: &bar
    qux: "quxqux"
    baz: "bazbaz"
greeting:
  audience: "world"
  bar: *bar # greeting.bar は foo.bar と同義。greeting.bar.baz の値は 'bazbaz' となります。

yq で JSON に変換した結果は以下のとおりです。

# 先程のYAMLファイルを sample.yaml という名前で保存しておきます
$ yq e --tojson sample.yaml
{
  "foo": {
    "bar": {
      "qux": "quxqux",
      "baz": "bazbaz"
    }
  },
  "greeting": {
    "audience": "world",
    "bar": {
      "qux": "quxqux",
      "baz": "bazbaz"
    }
  }
}

ブロックにエイリアスを設定し、参照し、一部を書き換える

「殆どのブロックを参照したいが、一部だけ異なる値にしたい。これができないのであればしょうがないのでコピペしようか。」

という悩みに対処できます。

エイリアスでブロックを参照しつつ、一部を書き換えることができます。

bar: &bar
  qux: "quxqux"
  baz: "bazbaz"
greeting:
  audience: "world"
  bar:
    <<: *bar # greeting.bar.qux の値は 'quxqux' となります。
    baz: "notbaz" # greeting.bar.baz は引き継いだ値ではなく 'notbaz' となります。

yq で JSON に変換した結果は以下のとおりです。

# 先程のYAMLファイルを sample.yaml という名前で保存しておきます
$ yq e --tojson sample.yaml
{
  "bar": {
    "qux": "quxqux",
    "baz": "bazbaz"
  },
  "greeting": {
    "audience": "world",
    "bar": {
      "qux": "quxqux",
      "baz": "notbaz"
    }
  }
}

ブロックにエイリアスを設定し、参照し、一部を書き換えたものにエイリアスを設定

タイトルではもうなんのこっちゃですね。

こんなこともできるよ、というレベルの話ですが、あまりやりすぎると可読性が下がります。

使うことは少ないでしょう。

bar: &bar
  qux: "quxqux"
  baz: "bazbaz"
greeting:
  audience: "world"
  bar: &newalias
    <<: *bar
    baz: "notbaz"
new_greeting:
  audience: "room"
  bar: *newalias # new_greeting.bar.baz の値は 'notbaz' となります。

yq で JSON に変換した結果は以下のとおりです。

# 先程のYAMLファイルを sample.yaml という名前で保存しておきます
$ yq e --tojson sample.yaml
{
  "bar": {
    "qux": "quxqux",
    "baz": "bazbaz"
  },
  "greeting": {
    "audience": "world",
    "bar": {
      "qux": "quxqux",
      "baz": "notbaz"
    }
  },
  "new_greeting": {
    "audience": "room",
    "bar": {
      "qux": "quxqux",
      "baz": "notbaz"
    }
  }
}

ひとこと

実際に運用で適用したのは "Ansible""Docker Compose" だけですが、 YAML の仕様として定義されている機能 ですので "Kubernetes" のマニフェストファイルにも利用できるはずです。

特に Kubernetes のマニフェストファイルはラベル名などの重複が多いので、うまく活用すれば軽量化、メンテナンス性の向上につながります。