TechQuant Blog

yq で YAML を操作する実践Tips|jq 派でも違和感なく使える書き方

7分で読める

金曜の夜、k8s manifest の image: タグを20ファイル分書き換える作業が降ってきた。sed で書きかけて、インデントが崩れてあきらめた。yq を入れたのはその週末だ。月曜の朝には同じ作業が30秒で終わった。

YAML は人間が書きやすい代わりに、機械的に編集しようとすると面倒な相手になる。インデント、アンカー、コメント、複数ドキュメントの区切り。yq はこの面倒を引き受けてくれるツールで、jq と同じ感覚で YAML を絞り込んだり書き換えたりできる。

この記事では、自宅サーバーや Raspberry Pi で運用している自動化スクリプトで実際に使ってきた yq の使い方をまとめる。マニュアルにも書いてある基本だけでなく、ハマりどころと jq との使い分けも書いた。

yq には2種類ある(重要)

最初に間違えやすいポイント。yq という名前のツールは現在2系統あって、書き方がまったく違う。

  • mikefarah/yq(Go 製): 単独バイナリ。jq 風だが文法は独自。
  • kislyuk/yq(Python 製): 内部で jq を呼ぶラッパー。文法は jq そのまま。

2026 年現在、人気もメンテも mikefarah 版が圧倒的なので、迷ったら mikefarah を入れる。Homebrew で brew install yq したら mikefarah が入る。Ubuntu の apt install yq は Python 版が入ることがあって、ここでハマる人を何度か見た。

# mikefarah 版か確認
yq --version
# yq (https://github.com/mikefarah/yq/) version v4.x.x

バージョンが v4.x 系なら mikefarah 版。v3 表記なら同じ作者の旧文法で、これも書き方がまた違う。以降の例はすべて v4 を前提にしている。

基本:値を取り出す

jq と似たクエリ言語を使う。点記法と配列の角括弧、フィルタ。慣れたものだ。

# シンプルな値の取得
yq '.metadata.name' deployment.yaml

# 配列の最初の要素
yq '.spec.template.spec.containers[0].image' deployment.yaml

# 配列を全部
yq '.spec.template.spec.containers[].image' deployment.yaml

標準入力からも当然読める。kubectl get の出力をそのまま流せる。

kubectl get pod nginx -o yaml | yq '.status.podIP'

このパターンが意外と便利で、運用中のクラスタの状態を雑に確認する用途にも使っている。kubectl describe より機械的に拾える。

JSON 出力に切り替える

結果を JSON で欲しい場面(さらに jq に渡したい等)は -o json を付ける。

yq -o json '.spec' deployment.yaml | jq '.template.spec.containers'

逆に JSON を YAML にするのも一発。

cat config.json | yq -p json -o yaml

以前書いたjq の実践Tipsでも触れたが、JSON と YAML を行き来する場面は CI のログ解析やクラウド API の応答処理で頻発する。yq 1本で完結するのは思った以上に楽。

更新:in-place 編集

ここが yq の真価。-i でファイルを直接書き換えられる。

# image を書き換える
yq -i '.spec.template.spec.containers[0].image = "myapp:v2.3.1"' deployment.yaml

# 環境変数を追加
yq -i '.spec.template.spec.containers[0].env += [{"name": "LOG_LEVEL", "value": "debug"}]' deployment.yaml

# キーを削除
yq -i 'del(.metadata.annotations."deprecated.io/key")' deployment.yaml

地味だが = で代入、+= で配列追加、del() で削除。これだけ覚えれば日常作業の8割は済む。

冒頭で書いた「20ファイルの image タグ書き換え」は、こうやって片付けた。

for f in k8s/*.yaml; do
  yq -i '(.spec.template.spec.containers[] | select(.name == "api") | .image) = "registry.example.com/api:v1.4.2"' "$f"
done

select() で対象コンテナを絞り込んでから書き換える。複数コンテナがある Pod でも安全に動く。

コメントは保持される(v4 以降)

これは個人的にいちばん刺さったポイント。v4 の yq は YAML のコメントを基本的に保持する。sed で書き換えていた時代は、上のコメントごと吹き飛ばすミスをよくやった。

# 元の deployment.yaml
# replicas: 本番は 3 以上にすること
replicas: 1

# 書き換え後もコメントは残る
yq -i '.spec.replicas = 3' deployment.yaml

細かいフォーマット(クォートの種類、空行の位置)まで完璧に保つわけではないが、レビューで「コメントが消えてる」と指摘される事故はほぼ無くなった。

複数ドキュメント(multi-doc)の扱い

k8s の YAML でよくある --- 区切りの複数ドキュメント。yq はデフォルトで全ドキュメントを処理する。

# すべてのドキュメントから kind を抜き出す
yq '.kind' multi.yaml

# 特定の kind だけ取り出す
yq 'select(.kind == "Deployment")' multi.yaml

# Deployment の image だけ書き換える
yq -i '(select(.kind == "Deployment") | .spec.template.spec.containers[0].image) = "myapp:v2"' multi.yaml

Helm が吐く巨大な multi-doc YAML から特定 kind だけ取り出す、というのが定番のパターン。kubectl apply -f に流し込む前のフィルタとして重宝している。

複数ファイルのマージ

環境ごとの override を base にマージしたい、というのは Kustomize がやってくれるが、Kustomize を入れるほどでもないケースで yq のマージは便利。

# base.yaml と override.yaml をマージ(override 優先)
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' base.yaml override.yaml

# 配列を結合する場合は *+
yq eval-all 'select(fileIndex == 0) *+ select(fileIndex == 1)' base.yaml override.yaml

* がマージ演算子で、後から来たものが優先される。配列の扱い(置換するか結合するか)を *+*d で制御できる。ここは正直、毎回ドキュメントを引いている。覚えきれない。

jq との使い分け

「jq があれば yq いらないのでは」と思っていた時期もあった。実際 yq -o json | jq でほぼ同じことができる。だが、書き戻しを伴う作業では yq 一本のほうが圧倒的に楽だ。

個人的な使い分けはこんな感じ。

場面選ぶツール理由
YAML を読むだけyq でも jq でも一段 yq -o json 噛ませれば同じ
YAML を書き換えるyqコメント保持、in-place、フォーマット維持
JSON 中心の API 応答jqそもそも YAML じゃない
k8s/Helm/Compose 関連yqYAML エコシステムに最適化されている
複雑なクエリ(reduce 等)jqjq のほうが文法が成熟している

最後の点は補足が要る。yq v4 のクエリ言語は jq とよく似ているが、reduceforeach、複雑な代入の振る舞いに差がある。複雑な集計をしたいときは yq -o json | jq でバトンタッチするのが結局は速い。

実運用で使う:CI と Raspberry Pi

GitHub Actions のデプロイ前処理

自分の自動化用リポジトリでは、デプロイ前に yq でバージョンを書き換えて commit する流れをよく組む。

- name: Bump image tag
  run: |
    yq -i ".spec.template.spec.containers[0].image = \"myapp:${{ github.sha }}\"" k8s/deployment.yaml

- name: Validate YAML
  run: |
    yq '.' k8s/deployment.yaml > /dev/null
    echo "YAML is valid"

最後の yq '.' file.yaml > /dev/null は単なる構文チェックとして使える。yamllint を入れるほどでもない場面で軽いガードになる。

CI/CD まわりの設定はGitHub Actions の Python CI セットアップでも書いたが、YAML の前処理ステップを挟むだけでデプロイの再現性がぐっと上がる。

Raspberry Pi の構成管理に転用

うちの Raspberry Pi 5 では Ansible の代わりに、構成情報を YAML に書いて bash + yq で適用する自前のスクリプトを動かしている。Raspberry Pi 5 をホームサーバー化する Tipsでも触れた構成だが、Ansible のフルセットを乗せるほどリソースに余裕がないので、yq で必要な部分だけ自分で実装した。

たとえば nginx の設定を変えるとき、config/services.yaml から該当エントリを yq で抜き出して、テンプレートに差し込む。ロールバックは git で HEAD~1 に戻すだけ。シンプルだが、自宅サーバーの規模ならこれで十分回る。

ちなみに自宅サーバーが手狭になって VPS に移したときは、お名前.comの高性能VPSに同じ構成スクリプトをそのまま持っていけた。yq も bash も Linux なら動くので、移行コストはほぼゼロだった。

シェルのクォートでハマる

シェル経由で yq を呼ぶとき、クォートの扱いで詰まる。基本はシングルクォートで囲むのが安全。変数を埋め込むときだけダブルクォートに切り替える。

# NG: シェル変数が展開されない
yq -i '.image = "$NEW_IMAGE"' app.yaml

# OK: ダブルクォート + エスケープ
yq -i ".image = \"$NEW_IMAGE\"" app.yaml

# もっと OK: env() を使う
NEW_IMAGE=myapp:v2 yq -i '.image = env(NEW_IMAGE)' app.yaml

3つ目の env() パターンが安全で読みやすい。シェルのクォート地獄から解放される。

strict_equality と型の差

select(.replicas == 3)select(.replicas == "3") は別物。YAML は数値と文字列を区別するので、書き方を間違えると一致しない。kubectl get -o yaml から拾った値は意外と文字列だったりするので、tag を確認する癖を付けるといい。

yq '.replicas | tag' deployment.yaml
# !!int なら数値、!!str なら文字列

大きな YAML での速度

1万行クラスの YAML を扱うと、Go 製とはいえそれなりに時間がかかる。一度ハマったのが、ループの中で yq -i を何度も呼んでいて毎回ファイル読み書きが発生していたケース。クエリを1つにまとめると10倍速くなった。

# NG: 何度も呼ぶ
for k in a b c; do
  yq -i ".$k = \"value\"" big.yaml
done

# OK: 1回で
yq -i '.a = "value" | .b = "value" | .c = "value"' big.yaml

まとめ的なもの

yq は地味だが、入れた瞬間から作業時間を削ってくれるタイプのツールだ。jq ユーザーなら学習コストもほぼゼロで、文法の差分は1日触れば馴染む。

最近は CLI ツールのストックが増えてきて、ripgrepfzfjq あたりと組み合わせると、シェルだけで大抵の運用作業がこなせる。yq はその中で「YAML を扱う口」として欠かせない存在になった。普段から k8s や CI/CD の YAML を触る人なら、入れない理由はない。

ちなみに、こういう CLI ツール群を試すのに便利な軽量ツールをDevToolsで公開している。JSON/YAML 変換あたりはブラウザでサクッと済ませたいときもあるので、CLI と使い分けると効率がいい。