`jq`コマンドによるJSONデータ処理の基本と応用

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

jqコマンドによるJSONデータ処理の基本と応用

DevOpsエンジニアにとって、JSONデータはAPI連携、設定管理、ログ解析など、様々な場面で日常的に扱われる形式です。本記事では、軽量かつ強力なCLI JSONプロセッサであるjqコマンドに焦点を当て、その基本操作からcurlとの連携、systemdによる自動実行までを、安全なbashスクリプトの原則に則って解説します。

要件と前提

本記事で解説する手順は、以下の環境と原則に基づいています。

  • 実行環境: jq, curl, systemdが利用可能なLinux環境 (例: CentOS, Ubuntu)。

  • バージョン:

    • jq: v1.7 (2023年11月20日リリース [1])。記事執筆時点での最新安定版を想定しています。

    • curl: 8.x系(TLS v1.2対応、リトライ機能利用)。

    • systemd: 24x系(Unit/Timer機能利用)。

  • 安全なシェルスクリプトの原則:

    • set -euo pipefail: スクリプト内のエラーを早期に検出し、パイプライン中のエラーも伝播させます。未定義変数の使用を禁止します [2]。

    • trap 'cleanup' EXIT: スクリプト終了時に特定のリソース(一時ファイルなど)を確実にクリーンアップします。

    • mktemp -d: 一時ディレクトリや一時ファイルを安全に作成し、競合状態やパーミッション問題を避けます。

  • 冪等性 (Idempotence): スクリプトは何度実行しても同じ結果が得られるように設計します。これにより、予期せぬ状態変化や副作用を防ぎ、自動化の信頼性を高めます。

  • 権限分離: 最小権限の原則に基づき、rootでの実行を避ける、またはsystemdUser=オプションを使用して、特定のユーザー権限でサービスを実行します。

実装

jqの基本操作

jqは、JSONデータを整形、抽出、変換、操作するための強力なツールです。

  1. JSONデータの整形と表示:

    echo '{"name":"Alice","age":30,"city":"Tokyo"}' | jq .
    
    # 出力例:
    
    
    # {
    
    
    #   "name": "Alice",
    
    
    #   "age": 30,
    
    
    #   "city": "Tokyo"
    
    
    # }
    

    jq .は、入力JSONをそのまま整形して出力する最も基本的なフィルタです。

  2. 特定のフィールドの抽出:

    echo '{"user":{"id":123,"name":"Bob"},"status":"active"}' | jq '.user.name'
    
    # 出力例: "Bob"
    

    ドット.を使ってオブジェクトのキーを指定します。

  3. 配列の処理:

    echo '[{"item":"A","price":100},{"item":"B","price":200}]' | jq '.[].item'
    
    # 出力例:
    
    
    # "A"
    
    
    # "B"
    

    []で配列内の全要素にアクセスし、.itemで各要素のitemフィールドを抽出します。 mapフィルタを使うと、配列の各要素に処理を適用し、新しい配列を生成できます。

    echo '[{"item":"A","price":100},{"item":"B","price":200}]' | jq 'map(.price)'
    
    # 出力例:
    
    
    # [
    
    
    #   100,
    
    
    #   200
    
    
    # ]
    
  4. オブジェクトの作成・更新: 既存のJSONに新しいフィールドを追加したり、変換したりできます。

    echo '{"name":"Charlie"}' | jq '. + {status: "active", createdAt: "2024-07-30"}'
    
    # 出力例:
    
    
    # {
    
    
    #   "name": "Charlie",
    
    
    #   "status": "active",
    
    
    #   "createdAt": "2024-07-30"
    
    
    # }
    
  5. 条件分岐とフィルタリング: selectフィルタを使って、条件に合う要素のみを抽出します。

    echo '[{"id":1,"status":"active"},{"id":2,"status":"inactive"}]' | jq '.[] | select(.status == "active")'
    
    # 出力例:
    
    
    # {
    
    
    #   "id": 1,
    
    
    #   "status": "active"
    
    
    # }
    

curlとjqの連携

APIからJSONデータを取得し、jqで処理する典型的なDevOpsのパターンです。

#!/bin/bash


# my_api_processor.sh

set -euo pipefail

# 一時ディレクトリの作成とクリーンアップ


# tmp_dirは安全な一時ディレクトリのパスを保持

tmp_dir=$(mktemp -d -t json_proc_XXXXXXXX)
function cleanup {
    echo "Cleaning up temporary directory: ${tmp_dir}"
    rm -rf "${tmp_dir}"
}
trap cleanup EXIT

# APIエンドポイント

API_URL="https://api.example.com/data"
OUTPUT_FILE="${tmp_dir}/processed_data.json"
ERROR_LOG="${tmp_dir}/error.log"

echo "$(date '+%Y-%m-%d %H:%M:%S') - Fetching data from ${API_URL}"

# curlコマンドでAPIからデータを取得


# -sSL: サイレントモード、リダイレクト追従、エラー時に失敗


# --retry 5: 5回までリトライ


# --retry-delay 3: リトライ間隔を3秒に設定


# --retry-max-time 30: リトライを含め最大30秒


# --cacert: 証明書検証パス(システムデフォルトを利用推奨)


# --tlsv1.2: TLSv1.2を使用


# -f: HTTPエラーコード(4xx/5xx)が返された場合に即座に失敗

curl_output=$(curl -sSL --retry 5 --retry-delay 3 --retry-max-time 30 \
                   --cacert /etc/ssl/certs/ca-certificates.crt \
                   --tlsv1.2 \
                   -f "${API_URL}" 2>> "${ERROR_LOG}")

# curlのエラーチェック

if [ $? -ne 0 ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: Failed to fetch data from API. See ${ERROR_LOG}" >&2
    exit 1
fi

echo "$(date '+%Y-%m-%d %H:%M:%S') - Data fetched successfully. Processing with jq."

# jqでJSONデータを処理


# 例: id, name, statusフィールドを抽出し、新しいオブジェクトの配列を作成


# .[] | select(.status == "active") によりアクティブなデータのみをフィルタ


# {id, name, status} で新しいオブジェクトを作成

echo "${curl_output}" | jq '[ .[] | select(.status == "active") | {id: .id, name: .user.name, status: .status} ]' > "${OUTPUT_FILE}"

# jqのエラーチェック

if [ $? -ne 0 ]; then
    echo "$(date '+%Y-%m-%d %H:%M:%S') - ERROR: jq processing failed." >&2
    exit 1
fi

echo "$(date '+%Y-%m-%d %H:%M:%S') - Processed data saved to ${OUTPUT_FILE}"

# 最終的な処理結果を標準出力に表示(必要であれば)


# cat "${OUTPUT_FILE}"

データ処理フロー図

Mermaidでデータ処理のフローを視覚化します。

graph TD
    A["外部API/データソース"] --> |HTTPS GET with curl| B("Raw JSONデータ");
    B --> |エラーハンドリング & jqによるフィルタ/変換| C("整形済みJSONデータ");
    C --> |一時ファイルへ保存| D["永続ストレージ/DB"];
    C --> |ログ出力/通知| E("監視システム");

検証

スクリプトが正しく動作するか、以下の方法で検証します。

  1. 手動実行: 開発環境でスクリプトを手動で実行し、期待される出力ファイルが作成され、その内容が正しいか確認します。

    chmod +x my_api_processor.sh
    ./my_api_processor.sh
    cat /tmp/json_proc_*/processed_data.json
    
  2. ログ確認: スクリプトが出力するログメッセージ(標準出力、標準エラー出力)を確認し、エラーがないか、期待通りの処理が実行されているか確認します。

  3. 冪等性の確認: スクリプトを複数回実行し、常に同じ結果(例: 最終的なデータが同じ、不要な重複作成がない)が得られることを確認します。

  4. エラーシミュレーション: API_URLを意図的に間違えたり、jqフィルタに構文エラーを仕込んだりして、エラーハンドリングが適切に機能するか確認します。

運用

systemdを使用して、上記のJSON処理スクリプトを定期的に自動実行する仕組みを構築します。これにより、監視やリソースの管理が容易になります。

systemdによる定期実行

  1. スクリプトの配置: 作成したmy_api_processor.sh/usr/local/bin/などの適切なパスに配置し、実行権限を与えます。

    sudo install -m 755 my_api_processor.sh /usr/local/bin/my_api_processor.sh
    sudo chown jsonuser:jsonuser /opt/my_json_app # ワーキングディレクトリの権限設定
    
  2. 専用ユーザーの作成: 最小権限の原則に従い、このサービスを実行するための専用ユーザーを作成します。

    sudo useradd -r -s /sbin/nologin jsonuser
    
  3. Service Unitファイルの作成: /etc/systemd/system/my_json_processor.service を作成します。

    # /etc/systemd/system/my_json_processor.service
    
    [Unit]
    Description=My JSON Processing Service
    
    # ネットワークが利用可能になってからサービスを開始
    
    After=network.target
    
    [Service]
    Type=oneshot
    
    # 実行するスクリプトを指定
    
    ExecStart=/usr/local/bin/my_json_processor.sh
    
    # スクリプトを実行するユーザーとグループを指定(重要!)
    
    User=jsonuser
    Group=jsonuser
    
    # スクリプトのワーキングディレクトリを指定
    
    WorkingDirectory=/opt/my_json_app
    
    # 標準出力と標準エラー出力をjournalctlに送信
    
    StandardOutput=journal
    StandardError=journal
    
    # リソース保護 (推奨)
    
    
    # ProtectSystem=full: /usr, /boot, /etc を読み取り専用にする
    
    
    # PrivateTmp=true: サービス専用の一時ディレクトリを作成し、他のサービスと分離
    
    ProtectSystem=full
    PrivateTmp=true
    
    # 環境変数を設定する場合 (例: API_KEYなど)
    
    
    # Environment="API_KEY=YOUR_API_KEY"
    
    [Install]
    
    # timerユニットから起動されるため、ここではWantedByは不要なことが多い
    
    • root権限の扱いと権限分離: User=jsonuserGroup=jsonuserディレクティブは、スクリプトがjsonuserという非特権ユーザーとして実行されることを保証し、セキュリティリスクを軽減します。root権限が必要な操作は、スクリプト内で行わず、必要最小限の権限で動作するよう設計することが重要です。
  4. Timer Unitファイルの作成: /etc/systemd/system/my_json_processor.timer を作成し、定期実行スケジュールを定義します。

    # /etc/systemd/system/my_json_processor.timer
    
    [Unit]
    Description=Run my JSON Processing Service every 5 minutes
    
    [Timer]
    
    # 5分ごとにサービスを起動
    
    OnCalendar=*:0/5
    
    # システム起動時にもタイマーを起動するか(永続性)
    
    Persistent=true
    
    # 起動するサービスユニットを指定
    
    Unit=my_json_processor.service
    
    [Install]
    
    # timers.targetに紐付けて、システム起動時に有効化されるようにする
    
    WantedBy=timers.target
    
  5. systemd設定のロードと有効化:

    sudo systemctl daemon-reload # systemd設定ファイルを再読み込み
    sudo systemctl enable my_json_processor.timer # タイマーを有効化(システム起動時に自動開始)
    sudo systemctl start my_json_processor.timer # タイマーを即座に開始
    
  6. ステータスの確認:

    systemctl status my_json_processor.timer
    systemctl status my_json_processor.service
    
  7. ログの確認:

    journalctl -u my_json_processor.service --since "1 hour ago"
    

    journalctlコマンドは、サービスが出力したログを確認するために不可欠です。--since--untilオプションで期間を指定できます。

トラブルシュート

自動化されたjq処理で問題が発生した場合の一般的なトラブルシューティング手順です。

  • jqの構文エラー:

    • ログにjq: error: syntax error, unexpected ...のようなメッセージが出力されていないか確認します。

    • 複雑なjqフィルタは、部分的にテストし、echo '{"key":"value"}' | jq '...' のように手動で実行してデバッグします。

  • curlのエラー:

    • ネットワーク接続が確立されているか確認します (ping api.example.com)。

    • APIエンドポイントのURLが正しいか確認します。

    • SSL/TLS証明書の問題の場合、curl -vオプションで詳細なデバッグ情報を確認します。--cacertのパスが正しいか、OSの証明書ストアが最新か確認します。

    • HTTPステータスコード(4xx, 5xx)が返されていないか、ERROR_LOGファイルを確認します。

  • systemdサービスの問題:

    • systemctl status my_json_processor.serviceでサービスの状態を確認します。Active: failedの場合は、その原因をjournalctlで特定します。

    • journalctl -u my_json_processor.serviceで、サービスが出力したすべてのログを確認します。スクリプト内のecho>&2によるエラー出力がここに記録されます。

    • User=オプションで指定したユーザーが存在し、必要なファイルやディレクトリへのアクセス権限を持っているか確認します。特にWorkingDirectoryや一時ファイルの作成パスの権限を確認します。

  • 一時ファイル/ディレクトリのパーミッション問題:

    • スクリプトが作成する一時ファイルや最終出力ファイルのパス、およびそれらの親ディレクトリに対して、jsonuserが書き込み権限を持っているか確認します。PrivateTmp=trueを使用している場合、/tmp内に作成される一時ディレクトリはサービス専用であり、通常パーミッション問題は発生しにくいですが、WorkingDirectoryは注意が必要です。

まとめ

jqコマンドを用いたJSONデータ処理の基本から応用、curlによるAPI連携、そしてsystemdによる定期実行の自動化までをDevOpsの観点から解説しました。

jqはそのシンプルな構文にもかかわらず、JSONデータの抽出、変換、整形を柔軟に行える強力なツールです。これにcurlの堅牢なデータ取得機能と、systemdの信頼性の高いジョブスケジューリングとリソース管理能力を組み合わせることで、JSONデータを扱うDevOpsパイプラインを効率的かつ安全に構築できます。

特に、安全なbashスクリプトの原則 (set -euo pipefail, trap, mktemp) と最小権限の原則 (systemdUser=オプション) は、運用環境における安定性とセキュリティを確保するために不可欠です。これらのプラクティスを遵守することで、日々の運用作業の自動化をより信頼性の高いものにできるでしょう。


参考文献

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました