はじめに
今までほんとうにただの雰囲気で読み書きしてきており、YAMLの構文についてもうちょっと体系的に理解したいと思ったので記事にしました。超初級編です。
参考
以下 Ansible
のドキュメントが割といい感じにまとまっていたので、こいつを参考にします。
※ Ansible
のドキュメントを参考にしていますが、YAMLの基本的な考え方としては他のDevOps系のツール(GitHub Actions
/ Docker Compose
/ Kubernetes
など)も同様です。
もくじ
そもそもYAMLとは
YAMLの概要
「YAMLはデータシリアライズ言語です!」とよく説明されることが多いが、もうちょいかみ砕いた説明をするなら、「YAMLはデータをシリアライズ(データ構造を保存や転送に適した形に変換すること)するための形式言語の1種です!」 と説明できる。なので、YAMLはデータシリアライズ言語であり、同時に形式言語であるとも言えそう。
-
データシリアライズ言語:
- データ構造をテキスト形式に変換するための言語で、それによってデータの保存や転送が容易になる
- YAMLはこの目的に使われ、データをテキストベースの形式で表現する
-
形式言語:
- 文字列の集合を生成するための厳格な規則(文法)を持った言語
- YAMLは特定の構文規則に従って書かれることで、データの構造を表す
そういう意味で、YAMLとよく比較されるJSONやXMLも同様に、データシリアライズ言語であり形式言語であると言える。つまり、みんなプログラミング言語のように計算を実行するためのものではなく、むしろデータを表現、交換、保存するための言語。
YAMLの特徴
同じデータシリアライズ言語であり形式言語であるJSONやXMLと比較したときのYAMLの特徴は何といってもその可読性。
-
XMLやJSONなどの他の形式言語に比べてより簡潔で可読性が高いとされる
- インデントを利用してデータの階層構造を表現でき、視覚的にデータ構造を把握しやすい
- 中括弧や波括弧(JSON)、開始タグや閉じタグ(XML)がなく必要最低限の記号しか使わない(ファイルの中身がきれいな状態になる)
- 数値や真偽値、日付といった基本的なデータ型が自然にサポートされている(XMLではデータ型をマークアップによって明示的に指定する必要があったりする)
- コメントがシャープ記号 (
#
)で簡単に書ける - DevOpsツールで広く採用されており、CI/CD、コンテナオーケストレーションなどで好まれている
- などなど
-
データのシリアライズがそのプロセスを含めて容易になる
- 構文がシンプルである点
- YAMLは少ない記号で構成されているため他のフォーマットよりも非常にシンプルかつ直感的
- これがデータ記述の誤りを減らし、結果としてデータのシリアライズがスムーズに進む
- データのシリアライズが容易(な場合が多い)
- 多くの言語で提供される専用ライブラリを利用することで、複雑なコードを書かずにデータ構造を YAML 形式へ変換したりその逆を行ったりできる
- 例えば、
Python
のpyyaml
やJava
のSnakeYAML
など - これらの利用で、数行のコードを書けばデータの読み書きが可能になる
- 構文がシンプルである点
YAMLのデータ単位
データの大きさという単位で考えるとイメージしやすいかも?
- ファイル:拡張子は「
.yaml
」でも「.yml
」でも可 - データの塊(大きさの単位)
- ドキュメント:ファイル内に記述したデータのまとまり
-
scalar
(スカラー):文字列や数値などの単一のデータ- 文字列
- 数値(整数・浮動小数点数)
- 日付
- 真偽値
- Null値
-
mapping
(ハッシュ / ディクショナリ):- キーと値のペア
-
key: value
の形式で表現する
-
sequences
(配列 / リスト):- 順序付けられた要素のリスト
-
-
(ハイフンとスペース)の記号で各要素が始まる
- コメント:
-
#
を使って単一行をコメントアウトできる
-
-
- ドキュメント:ファイル内に記述したデータのまとまり
YAML読解ざっくり
YAMLの学び方
学ぶというか、YAMLを使ったDevOps系のツールを理解するには2つの観点を意識することが大切な気がしている。
-
観点1:DevOps系ツールやその仕様理解
- ツール類
-
GitHub Actions
/Docker Compose
/Ansible
/Kubernetes
など
-
- ツールの基本的な仕組み
- ツール固有の用語や概念
- ツール類
-
観点2:YAMLの理解
- 文法や構文
- 実例との関連(「このファイルはどういう目的のファイル?」の視点)
実務とかでなにかのYAMLファイルに触れてみて、詰まったタイミングでYAMLの体系的な理解を挟むと理解が一歩進む。最初にYAMLを体系的に学んでしまうのもアリ。
で、同じYAMLといってもツールによって使えるキーワードやその使用方法が違ったりするので、ツール単位で使えるキーワードや書き方をざっくりつかんで理解するのもとてもよいと思う。例えば、以下のようにツールによってよく使われるキーワードが全然違う。
-
GitHub Actions
におけるworkflowファイルで用いられるYAMLキーワード-
name
:Workflowの名前を設定 -
on
:Workflowが実行されるトリガーのイベントを定義 -
jobs
:実行するジョブを定義 -
runs-on
:ジョブが実行される環境(例:ubuntu-latestなど)を指定 -
steps
:ジョブ内で実行するステップの一覧を設定 -
uses
:公式アクションやコミュニティアクション(例えば、actions/checkout@v2)を使用するときに指定 -
run
:シェルコマンドの実行
-
-
Docker Compose
で使用するdocker-compose.ymlファイルにおけるYAMLキーワード-
version
:Docker Composeファイルのバージョンを指定 -
services
:コンテナ化されたアプリケーションのサービスを定義 -
image
:コンテナのベースとなるイメージを指定 -
build
:Dockerfileからイメージをビルドするための設定 -
ports
:ホストとコンテナ間のポートマッピングを定義 -
volumes
:データ永続化のためのボリュームマウントを指定 -
environment
:環境変数を設定 -
networks
:コンテナ間のネットワーキングを定義 -
depends_on
:他のサービスへの依存関係を指定
-
ちなみに上記のキーワードはすべてディクショナリ(mapping)型のキーにあたるものなので、この辺のデータ型の理解をまずできると理解がぐっと進むはず。
YAMLで立ち止まったら
- YAMLファイル内のデータ構造の把握(全体的にどういったデータ構造なのか確認する)
- このYAMLファイルがどういった目的で利用されるのか?の視点で1つ1つ丁寧に確認する
見ていく中でデータ構造が分からなくなったら、1度JSONに変換すると分かりやすいと思う。(リストなのかディクショナリなのか、データ構造などが分かりやすくなる)
データ構造さえ分かってしまえばなんてことない気がしてる。
YAML構文の基礎
公式ドキュメントにも記載があるけど、基本的にはプリミティブなデータ型を把握できてしまえば基礎は出来上がると思う。
There are many kinds of data structures, but they can all be adequately represented with three basic primitives: mappings (hashes/dictionaries), sequences (arrays/lists) and scalars (strings/numbers).
(データ構造には多くの種類があるが、それらはすべて
- マッピング(ハッシュ/ディクショナリ)
- シーケンス(配列/リスト)
- スカラー(文字列/数値)
という3つの基本的なプリミティブで適切に表現できる。)
個人的には、マッピング(ディクショナリ)とシーケンス(リスト)が分かればあとはだいたいOK!感がある。
書くときに「これ構文イケてる?」を確認するのにYAMLのLinter使うと捗るので使ってみてもいいかも。
ドキュメント分割
YAMLでは、最も大きなデータの単位を「ドキュメント」と呼ぶ。
YAMLの構文では複数のドキュメントを単一のファイルで扱うことができます。その際にセパレータ(---
)を使用して、ドキュメントの区切りを記述することができる。
ドキュメント分割の主な目的は、異なるデータ構造を独立して定義・管理しやすくすることであり、複数の異なる設定項目をグループ化させたい場合や、複数のリソースを1つのYAMLファイルに統合して管理したいい場合に利用される。
# 開発環境
---
database:
host: localhost
username: dev_user
password: dev_pass
# テスト環境
---
database:
host: test-db.example.com
username: test_user
password: test_pass
とはいえ、一般的に「ファイル内にドキュメントを1つしか定義しない場合」はセパレータ(---
)の既述は任意。
Scalar:文字列
文字列を表現するとき、3つの方法がある
-
クォートなし:
- シンプルで特殊文字が含まれない文字列はクォートなしで記述可能
- その場合は以下のような予約語や構文に気を付ける必要がある(YAMLパーサーに誤認される)
- 例えば以下
- 文字列内にスペースが含まれている
- 特定のキーワード(
true
/false
/null
など)がある - 構文を誤解させる(
[]
や{}
とか)
- 例えば以下
-
シングルクォート:
- 特殊文字をエスケープせずにそのままの形で扱われる
- つまり
\n
や\t
などのエスケープシーケンスはただの文字列として扱われ、改行やタブとして解釈されない - 文字列内でシングルクォートを使用したい場合は、2つのシングルクォート (
''
) を使用してエスケープする- 例:
example: 'Here''s a string in YAML'
- YAML Lint:
example: Here's a string in YAML
- 例:
-
ダブルクォート:
- ダブルクォートで囲まれた文字列はエスケープシーケンスを使用できる
- これにより改行(
\n
)やタブ(\t
)、ユニコード文字(\u003e
)などが利用可能
Apple: red
Orange: orange
Mango: yellow
# シングルクォートを使う(より明示的)
Apple: 'red'
Orange: 'orange'
Mango: 'yellow'
# ダブルクォートを使う(より明示的)
Apple: "red"
Orange: "orange"
Mango: "yellow"
# string型を明示的に指定する
Apple: !!str red
Orange: !!str orange
Mango: !!str yellow
# YAML Lintは上記すべてこれになる
---
Apple: red
Orange: orange
Mango: yellow
Scalar:数値
数値型は大きく2つ、整数(int
)または浮動小数点数(float
)がある
# int型
Apple: 1
Orange: 2
Mango: 3
# int型を明示的に指定する
Apple: !!int 1
Orange: !!int 2
Mango: !!int 3
# float型
Apple: 1.11
Orange: 2.22
Mango: 3.33
# float型を明示的に指定する
Apple: !!float 1.11
Orange: !!float 2.22
Mango: !!float 3.33
Scalar:日付
YAMLではISO8601標準に準拠し、日付や時刻を表現するいくつかの形式をサポートしている。
- 完全な日付時間は
YYYY-MM-DDTHH:MM:SSZ
の形式が基本-
T
は日付と時刻の区切りを示す -
Z
はUTC(協定世界時)であることを示す- ※
Z
の代わりに正または負の時間オフセット(例:+HH:MM
もしくは-HH:MM
)を利用することで、異なるタイムゾーンを指定することが可能
- ※
-
- 秒(second)以下のより詳細な時間表現もできる(例:
YYYY-MM-DDTHH:MM:SS.SSSZ
) - 時刻を省略して日付のみを
YYYY-MM-DD
で記述もできる - YAML 1.2 以降では、
ISO8601
の拡張形式による表記もサポートされている
んーーー日付はいつもややこしい!!
# 完全な日付時間の例(UTCを表す 'Z' の使用)
fullDateTime: 2024-06-23T13:45:30Z
# timestamps型を明示的に指定する
dateTimeWithTag: !!timestamps 2024-06-23T13:45:30Z
# 日付と時刻の区切りを示す 'T' の使用
dateAndTimeSeparator: 2024-06-23T13:45:30Z
# UTCの代わりに時間オフセットを利用した例(日本標準時を指定)
timeWithOffset: 2024-06-23T13:45:30+09:00
# タイムゾーンを持つ別の時間オフセット例(東部標準時を指定)
timeWithOffsetEST: 2024-06-23T13:45:30-05:00
# タイムゾーンなし(タイムゾーンを特定しないローカルタイム)
localDateTime: 2024-06-23T13:45:30
# 秒以下のより詳細な時間(ミリ秒を含む)表現の例
detailedTime: 2024-06-23T13:45:30.123Z
# 時刻を省略し、日付のみの表現の例
justDate: 2024-06-23
# ISO8601の拡張形式の使用例(週番号を使用した日付)
iso8601Extended: 2024-W25-7
Scalar:ブール(真偽値)
TRUE/FALSEを表現する。実はtrue/falseだけでなくいろいろ表現方法がある。
- 真:
true
,TRUE
,y
,Y
,Yes
,on
- 偽:
false
,FALSE
,n
,N
,No
,off
boolVal: True
boolVal: False
# ブール型を明示的に指定する
boolVal: !!bool True
boolVal: !!bool False
Scalar:Null値
値が何もないことを表す。
null
だけでなく、NULL
, ~
でも表現できる。
name: Null
# NULLを明示的に指定する
name: !!null Null
Sequence(配列 / リスト)
リストの基礎
- リストを作成するために各要素の前に
-
(ダッシュ)を置き、その後にスペースを1つ入れる - 重要なのが、リストの各要素は同じレベルでインデントされている必要があること
- Apple
- Orange
- Strawberry
# JSONにするとこうなっている
[
"Apple",
"Orange",
"Strawberry"
]
NGなパターン
- Apple
Orange
Strawberry
# JSONにすると、1つの文字列として解釈される
[
"apple orange banana"
]
- Apple
- Orange
- Strawberry
# JSONにすると、1つの文字列として解釈される
[
"Apple - Orange - Strawberry"
]
ちなみに、途中に空の要素がある場合は、一般的に Null
で判定される。(YAMLパーサー側で空をどう認識されるかはパーサー側の仕様による)
- Apple
-
- Strawberry
- Mango
# JSONにするとこうなっている
[
"Apple",
null,
"Strawberry",
"Mango"
]
リスト(シーケンス)のネスト(2重配列)
リストの中にリストを持たせることももちろんできる。
-
- Apple
# JSONにするとこうなっている
[
[
"Apple"
]
]
-
- Apple
-
- Orange
-
- Strawberry
# 上記と同じ
- - Apple
- - Orange
- - Strawberry
# JSONにするとこうなっている
[
[
"Apple"
],
[
"Orange"
],
[
"Strawberry"
]
]
補足:大文字と小文字の区別
- Apple
- Orange
- Strawberry
- apple
# JSONにすると別のものとして認識されている
[
"Apple",
"Orange",
"Strawberry",
"apple"
]
mapping(ハッシュ / ディクショナリ)
キーと値のペアを key: value
の形式で表現する。(コロンの後ろにスペースが必要)
Apple: 1
Orange: 2
Strawberry: 3
# 上記と同じ
Apple:
1
Orange:
2
Strawberry:
3
# JSONにするとこうなっている
{
"Apple": 1,
"Orange": 2,
"Strawberry": 3
}
# 1つのキーAppleに対して、値として複数のディクショナリを持たせる場合
Apple:
small: 1
large: 2
# mapping型を明示的に指定する
Apple: !!map
small: 1
large: 2
# JSONにするとこうなっている
{
"Apple": {
"small": 1,
"large": 2
}
}
NGパターン
Apple:
small: 1 # インデントなしではキーAppleの値にならない
large: 2
# JSONにするとこうなっている
{
"Apple": null,
"small": 1,
"large": 2
}
Sequence × Mapping(リストとディクショナリの組み合わせ)
fruits
というキーに対してリスト形式で複数の値(Apple
, Orange
, Strawberry
)が割り当てられている構造
fruits:
- Apple
- Orange
- Strawberry
# シーケンス型を明示的に指定する
fruits: !!seq
- Apple
- Orange
- Strawberry
# JSONにするとこうなっている
{
"fruits": [
"Apple",
"Orange",
"Strawberry"
]
}
fruits
というキーに対してリスト形式で複数の値(Apple
, Orange
, Strawberry
)が割り当てられている構造、かつ、リストの各項目はキーと値のペア(ディクショナリ型)として表現されている構造。
fruits:
- Apple: 1
- Orange: 2
- Strawberry: 3
# シーケンス型を明示的に指定する
fruits: !!seq
- Apple: 1
- Orange: 2
- Strawberry: 3
# JSONにするとこうなっている
{
"fruits": [
{
"Apple": 1
},
{
"Orange": 2
},
{
"Strawberry": 3
}
]
}
リストの中に複数のディクショナリを持つデータ構造。(よく見るやつ)
- Apple: 1
Orange: 2
Strawberry: 3
# JSONにするとこうなっている
[
{
"Apple": 1,
"Orange": 2,
"Strawberry": 3
}
]
補足:インラインスタイル(フロースタイル)
YAMLのデータ構造をJSONに似た構文で、1行で表現することもできる。この形式を「インライン形式」(または「フロースタイル」)と呼ばれる。
fruits:
- Apple
- Orange
- Strawberry
# インラインスタイル
{fruits: [Apple, Orange, Strawberry]}
fruits:
- Apple: 1
- Orange: 2
- Strawberry: 3
# インラインスタイル
{fruits: [{Apple: 1}, {Orange: 2}, {Strawberry: 3}]}
コメントアウト
YAMLにおいてコメントアウトは #
を使う。単一行のコメントアウトしかサポートされていないので、複数行の記述をコメントアウトしたい場合はそれぞれの文頭に #
をつける必要がある。
# これはコメント1
# これはコメント2
# これはコメント3
description: >
YAML can often be read and written based on intuition.
However, truly understanding the syntax is crucial.
データの再利用(Anchor / Alias)
YAMLでのAnchorとAliasの概念は、繰り返しデータを使いまわすための非常に便利な機能。
-
Anchor (アンカー):
-
&
を使うことで特定のデータに名前を付けることができる - これはアンカーと呼ばれ、この名前(アンカー)を付けることで、データを他の場所で再利用する準備が整う
-
-
Alias (エイリアス):
-
*
と アンカーとして名付けたデータ を使って、アンカーとして設定したデータを参照する - これはエイリアスと呼ばれ、実質的に「ここにアンカーとして設定したデータを挿入してね」という指示になる
-
default_settings: &defaultSettingsAsAnchor # ここでアンカーを設定
setting1: 'STR VALUE'
setting2: 2
profile1:
<<: *defaultSettingsAsAnchor # Alias:アンカーを参照するときは(<<: )と(* + アンカー)を使う
setting1: 'CUSTOM VALUE' # Anchorで設定した値をここでは上書きする
profile2:
<<: *defaultSettingsAsAnchor # 同じアンカーを使いまわすこともできる
# JSONにするとこうなっている
{
"default_settings": {
"setting1": "STR VALUE",
"setting2": 2
},
"profile1": {
"setting1": "CUSTOM VALUE",
"setting2": 2
},
"profile2": {
"setting1": "STR VALUE",
"setting2": 2
}
}
複数行の文字列(改行あり)
キーに対してVALUEを書く前にパイプ記号を記述することで、YAMLパーサーに複数行であると認識させることができる。
description: |
YAML can often be read and written based on intuition.
However, truly understanding the syntax is crucial.
# JSONにするとこうなっている
{
"description": "YAML can often be read and written based on intuition.\nHowever, truly understanding the syntax is crucial.\n"
}
複数行の文字列(改行なし)
キーに対してVALUEを書く前に >
記号を記述することで、YAMLパーサーに「改行しないよ!」と認識させることができる。
description: >
YAML can often be read and written based on intuition.
However, truly understanding the syntax is crucial.
# JSONにするとこうなっている
{
"description": "YAML can often be read and written based on intuition. However, truly understanding the syntax is crucial.\n"
}
おわりに
思ったより長い記事になっちゃった。
でも、だいたいこんくらい分かっておけばやっていけそうな気がするでしょ。