yq で YAML を操作する実践Tips|jq 派でも違和感なく使える書き方
金曜の夜、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 関連 | yq | YAML エコシステムに最適化されている |
| 複雑なクエリ(reduce 等) | jq | jq のほうが文法が成熟している |
最後の点は補足が要る。yq v4 のクエリ言語は jq とよく似ているが、reduce や foreach、複雑な代入の振る舞いに差がある。複雑な集計をしたいときは 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 ツールのストックが増えてきて、ripgrep や fzf、jq あたりと組み合わせると、シェルだけで大抵の運用作業がこなせる。yq はその中で「YAML を扱う口」として欠かせない存在になった。普段から k8s や CI/CD の YAML を触る人なら、入れない理由はない。
ちなみに、こういう CLI ツール群を試すのに便利な軽量ツールをDevToolsで公開している。JSON/YAML 変換あたりはブラウザでサクッと済ませたいときもあるので、CLI と使い分けると効率がいい。