<p><!--META
{
"title": "jqコマンドによるJSONデータ処理の基本と応用",
"primary_category": "DevOps",
"secondary_categories": ["CLI", "JSON", "Linux"],
"tags": ["jq", "curl", "systemd", "bash", "JSONPath"],
"summary": "jqコマンドを使ったJSONデータ処理の基本から応用までを、安全なbashスクリプトやsystemd連携の具体例を交えて解説します。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"jqコマンドでJSON処理をマスター!安全なbashスクリプト、curl連携、systemd自動化のDevOps活用術を解説。
#jq #DevOps #JSON","hashtags":["#jq","#DevOps"]},
"link_hints": ["https://github.com/stedolan/jq/releases/tag/jq-1.7", "https://curl.se/docs/manpage.html", "https://www.freedesktop.org/software/systemd/man/systemd.unit.html", "https://www.redhat.com/sysadmin/secure-shell-scripts"]
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading"><code>jq</code>コマンドによるJSONデータ処理の基本と応用</h1>
<p>DevOpsエンジニアにとって、JSONデータはAPI連携、設定管理、ログ解析など、様々な場面で日常的に扱われる形式です。本記事では、軽量かつ強力なCLI JSONプロセッサである<code>jq</code>コマンドに焦点を当て、その基本操作から<code>curl</code>との連携、<code>systemd</code>による自動実行までを、安全なbashスクリプトの原則に則って解説します。</p>
<h2 class="wp-block-heading">要件と前提</h2>
<p>本記事で解説する手順は、以下の環境と原則に基づいています。</p>
<ul class="wp-block-list">
<li><p><strong>実行環境</strong>: <code>jq</code>, <code>curl</code>, <code>systemd</code>が利用可能なLinux環境 (例: CentOS, Ubuntu)。</p></li>
<li><p><strong>バージョン</strong>:</p>
<ul>
<li><p><code>jq</code>: v1.7 (2023年11月20日リリース [1])。記事執筆時点での最新安定版を想定しています。</p></li>
<li><p><code>curl</code>: 8.x系(TLS v1.2対応、リトライ機能利用)。</p></li>
<li><p><code>systemd</code>: 24x系(Unit/Timer機能利用)。</p></li>
</ul></li>
<li><p><strong>安全なシェルスクリプトの原則</strong>:</p>
<ul>
<li><p><code>set -euo pipefail</code>: スクリプト内のエラーを早期に検出し、パイプライン中のエラーも伝播させます。未定義変数の使用を禁止します [2]。</p></li>
<li><p><code>trap 'cleanup' EXIT</code>: スクリプト終了時に特定のリソース(一時ファイルなど)を確実にクリーンアップします。</p></li>
<li><p><code>mktemp -d</code>: 一時ディレクトリや一時ファイルを安全に作成し、競合状態やパーミッション問題を避けます。</p></li>
</ul></li>
<li><p><strong>冪等性 (Idempotence)</strong>: スクリプトは何度実行しても同じ結果が得られるように設計します。これにより、予期せぬ状態変化や副作用を防ぎ、自動化の信頼性を高めます。</p></li>
<li><p><strong>権限分離</strong>: 最小権限の原則に基づき、rootでの実行を避ける、または<code>systemd</code>の<code>User=</code>オプションを使用して、特定のユーザー権限でサービスを実行します。</p></li>
</ul>
<h2 class="wp-block-heading">実装</h2>
<h3 class="wp-block-heading"><code>jq</code>の基本操作</h3>
<p><code>jq</code>は、JSONデータを整形、抽出、変換、操作するための強力なツールです。</p>
<ol class="wp-block-list">
<li><p><strong>JSONデータの整形と表示</strong>:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">echo '{"name":"Alice","age":30,"city":"Tokyo"}' | jq .
# 出力例:
# {
# "name": "Alice",
# "age": 30,
# "city": "Tokyo"
# }
</pre>
</div>
<p><code>jq .</code>は、入力JSONをそのまま整形して出力する最も基本的なフィルタです。</p></li>
<li><p><strong>特定のフィールドの抽出</strong>:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">echo '{"user":{"id":123,"name":"Bob"},"status":"active"}' | jq '.user.name'
# 出力例: "Bob"
</pre>
</div>
<p>ドット<code>.</code>を使ってオブジェクトのキーを指定します。</p></li>
<li><p><strong>配列の処理</strong>:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">echo '[{"item":"A","price":100},{"item":"B","price":200}]' | jq '.[].item'
# 出力例:
# "A"
# "B"
</pre>
</div>
<p><code>[]</code>で配列内の全要素にアクセスし、<code>.item</code>で各要素の<code>item</code>フィールドを抽出します。
<code>map</code>フィルタを使うと、配列の各要素に処理を適用し、新しい配列を生成できます。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">echo '[{"item":"A","price":100},{"item":"B","price":200}]' | jq 'map(.price)'
# 出力例:
# [
# 100,
# 200
# ]
</pre>
</div></li>
<li><p><strong>オブジェクトの作成・更新</strong>:
既存のJSONに新しいフィールドを追加したり、変換したりできます。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">echo '{"name":"Charlie"}' | jq '. + {status: "active", createdAt: "2024-07-30"}'
# 出力例:
# {
# "name": "Charlie",
# "status": "active",
# "createdAt": "2024-07-30"
# }
</pre>
</div></li>
<li><p><strong>条件分岐とフィルタリング</strong>:
<code>select</code>フィルタを使って、条件に合う要素のみを抽出します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">echo '[{"id":1,"status":"active"},{"id":2,"status":"inactive"}]' | jq '.[] | select(.status == "active")'
# 出力例:
# {
# "id": 1,
# "status": "active"
# }
</pre>
</div></li>
</ol>
<h3 class="wp-block-heading"><code>curl</code>と<code>jq</code>の連携</h3>
<p>APIからJSONデータを取得し、<code>jq</code>で処理する典型的なDevOpsのパターンです。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">#!/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}"
</pre>
</div>
<h3 class="wp-block-heading">データ処理フロー図</h3>
<p>Mermaidでデータ処理のフローを視覚化します。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["外部API/データソース"] --> |HTTPS GET with curl| B("Raw JSONデータ");
B --> |エラーハンドリング & jqによるフィルタ/変換| C("整形済みJSONデータ");
C --> |一時ファイルへ保存| D["永続ストレージ/DB"];
C --> |ログ出力/通知| E("監視システム");
</pre></div>
<h2 class="wp-block-heading">検証</h2>
<p>スクリプトが正しく動作するか、以下の方法で検証します。</p>
<ol class="wp-block-list">
<li><p><strong>手動実行</strong>: 開発環境でスクリプトを手動で実行し、期待される出力ファイルが作成され、その内容が正しいか確認します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">chmod +x my_api_processor.sh
./my_api_processor.sh
cat /tmp/json_proc_*/processed_data.json
</pre>
</div></li>
<li><p><strong>ログ確認</strong>: スクリプトが出力するログメッセージ(標準出力、標準エラー出力)を確認し、エラーがないか、期待通りの処理が実行されているか確認します。</p></li>
<li><p><strong>冪等性の確認</strong>: スクリプトを複数回実行し、常に同じ結果(例: 最終的なデータが同じ、不要な重複作成がない)が得られることを確認します。</p></li>
<li><p><strong>エラーシミュレーション</strong>: <code>API_URL</code>を意図的に間違えたり、<code>jq</code>フィルタに構文エラーを仕込んだりして、エラーハンドリングが適切に機能するか確認します。</p></li>
</ol>
<h2 class="wp-block-heading">運用</h2>
<p><code>systemd</code>を使用して、上記のJSON処理スクリプトを定期的に自動実行する仕組みを構築します。これにより、監視やリソースの管理が容易になります。</p>
<h3 class="wp-block-heading"><code>systemd</code>による定期実行</h3>
<ol class="wp-block-list">
<li><p><strong>スクリプトの配置</strong>: 作成した<code>my_api_processor.sh</code>を<code>/usr/local/bin/</code>などの適切なパスに配置し、実行権限を与えます。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">sudo install -m 755 my_api_processor.sh /usr/local/bin/my_api_processor.sh
sudo chown jsonuser:jsonuser /opt/my_json_app # ワーキングディレクトリの権限設定
</pre>
</div></li>
<li><p><strong>専用ユーザーの作成</strong>: 最小権限の原則に従い、このサービスを実行するための専用ユーザーを作成します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">sudo useradd -r -s /sbin/nologin jsonuser
</pre>
</div></li>
<li><p><strong>Service Unitファイルの作成</strong>: <code>/etc/systemd/system/my_json_processor.service</code> を作成します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic"># /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は不要なことが多い
</pre>
</div>
<ul>
<li><strong>root権限の扱いと権限分離</strong>: <code>User=jsonuser</code>と<code>Group=jsonuser</code>ディレクティブは、スクリプトが<code>jsonuser</code>という非特権ユーザーとして実行されることを保証し、セキュリティリスクを軽減します。<code>root</code>権限が必要な操作は、スクリプト内で行わず、必要最小限の権限で動作するよう設計することが重要です。</li>
</ul></li>
<li><p><strong>Timer Unitファイルの作成</strong>: <code>/etc/systemd/system/my_json_processor.timer</code> を作成し、定期実行スケジュールを定義します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic"># /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
</pre>
</div></li>
<li><p><strong><code>systemd</code>設定のロードと有効化</strong>:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">sudo systemctl daemon-reload # systemd設定ファイルを再読み込み
sudo systemctl enable my_json_processor.timer # タイマーを有効化(システム起動時に自動開始)
sudo systemctl start my_json_processor.timer # タイマーを即座に開始
</pre>
</div></li>
<li><p><strong>ステータスの確認</strong>:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">systemctl status my_json_processor.timer
systemctl status my_json_processor.service
</pre>
</div></li>
<li><p><strong>ログの確認</strong>:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">journalctl -u my_json_processor.service --since "1 hour ago"
</pre>
</div>
<p><code>journalctl</code>コマンドは、サービスが出力したログを確認するために不可欠です。<code>--since</code>や<code>--until</code>オプションで期間を指定できます。</p></li>
</ol>
<h2 class="wp-block-heading">トラブルシュート</h2>
<p>自動化された<code>jq</code>処理で問題が発生した場合の一般的なトラブルシューティング手順です。</p>
<ul class="wp-block-list">
<li><p><strong><code>jq</code>の構文エラー</strong>:</p>
<ul>
<li><p>ログに<code>jq: error: syntax error, unexpected ...</code>のようなメッセージが出力されていないか確認します。</p></li>
<li><p>複雑な<code>jq</code>フィルタは、部分的にテストし、<code>echo '{"key":"value"}' | jq '...'</code> のように手動で実行してデバッグします。</p></li>
</ul></li>
<li><p><strong><code>curl</code>のエラー</strong>:</p>
<ul>
<li><p>ネットワーク接続が確立されているか確認します (<code>ping api.example.com</code>)。</p></li>
<li><p>APIエンドポイントのURLが正しいか確認します。</p></li>
<li><p>SSL/TLS証明書の問題の場合、<code>curl -v</code>オプションで詳細なデバッグ情報を確認します。<code>--cacert</code>のパスが正しいか、OSの証明書ストアが最新か確認します。</p></li>
<li><p>HTTPステータスコード(4xx, 5xx)が返されていないか、<code>ERROR_LOG</code>ファイルを確認します。</p></li>
</ul></li>
<li><p><strong><code>systemd</code>サービスの問題</strong>:</p>
<ul>
<li><p><code>systemctl status my_json_processor.service</code>でサービスの状態を確認します。<code>Active: failed</code>の場合は、その原因を<code>journalctl</code>で特定します。</p></li>
<li><p><code>journalctl -u my_json_processor.service</code>で、サービスが出力したすべてのログを確認します。スクリプト内の<code>echo</code>や<code>>&2</code>によるエラー出力がここに記録されます。</p></li>
<li><p><code>User=</code>オプションで指定したユーザーが存在し、必要なファイルやディレクトリへのアクセス権限を持っているか確認します。特に<code>WorkingDirectory</code>や一時ファイルの作成パスの権限を確認します。</p></li>
</ul></li>
<li><p><strong>一時ファイル/ディレクトリのパーミッション問題</strong>:</p>
<ul>
<li>スクリプトが作成する一時ファイルや最終出力ファイルのパス、およびそれらの親ディレクトリに対して、<code>jsonuser</code>が書き込み権限を持っているか確認します。<code>PrivateTmp=true</code>を使用している場合、<code>/tmp</code>内に作成される一時ディレクトリはサービス専用であり、通常パーミッション問題は発生しにくいですが、<code>WorkingDirectory</code>は注意が必要です。</li>
</ul></li>
</ul>
<h2 class="wp-block-heading">まとめ</h2>
<p>、<code>jq</code>コマンドを用いたJSONデータ処理の基本から応用、<code>curl</code>によるAPI連携、そして<code>systemd</code>による定期実行の自動化までをDevOpsの観点から解説しました。</p>
<p><code>jq</code>はそのシンプルな構文にもかかわらず、JSONデータの抽出、変換、整形を柔軟に行える強力なツールです。これに<code>curl</code>の堅牢なデータ取得機能と、<code>systemd</code>の信頼性の高いジョブスケジューリングとリソース管理能力を組み合わせることで、JSONデータを扱うDevOpsパイプラインを効率的かつ安全に構築できます。</p>
<p>特に、<strong>安全なbashスクリプトの原則</strong> (<code>set -euo pipefail</code>, <code>trap</code>, <code>mktemp</code>) と<strong>最小権限の原則</strong> (<code>systemd</code>の<code>User=</code>オプション) は、運用環境における安定性とセキュリティを確保するために不可欠です。これらのプラクティスを遵守することで、日々の運用作業の自動化をより信頼性の高いものにできるでしょう。</p>
<hr/>
<p><strong>参考文献</strong></p>
<ul class="wp-block-list">
<li><p>[1] stedolan/jq GitHub Releases: <code>jq-1.7</code> (2023年11月20日). <a href="https://github.com/stedolan/jq/releases/tag/jq-1.7">https://github.com/stedolan/jq/releases/tag/jq-1.7</a></p></li>
<li><p>[2] Red Hat: Secure your shell scripts (2023年8月1日). <a href="https://www.redhat.com/sysadmin/secure-shell-scripts">https://www.redhat.com/sysadmin/secure-shell-scripts</a></p></li>
<li><p>[3] curl man page. <code>https://curl.se/docs/manpage.html</code></p></li>
<li><p>[4] systemd.unit man page. <code>https://www.freedesktop.org/software/systemd/man/systemd.unit.html</code></p></li>
<li><p>[5] systemd.timer man page. <code>https://www.freedesktop.org/software/systemd/man/systemd.timer.html</code></p></li>
</ul>
本記事は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での実行を避ける、またはsystemdのUser=オプションを使用して、特定のユーザー権限でサービスを実行します。
実装
jqの基本操作
jqは、JSONデータを整形、抽出、変換、操作するための強力なツールです。
JSONデータの整形と表示:
echo '{"name":"Alice","age":30,"city":"Tokyo"}' | jq .
# 出力例:
# {
# "name": "Alice",
# "age": 30,
# "city": "Tokyo"
# }
jq .は、入力JSONをそのまま整形して出力する最も基本的なフィルタです。
特定のフィールドの抽出:
echo '{"user":{"id":123,"name":"Bob"},"status":"active"}' | jq '.user.name'
# 出力例: "Bob"
ドット.を使ってオブジェクトのキーを指定します。
配列の処理:
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
# ]
オブジェクトの作成・更新:
既存のJSONに新しいフィールドを追加したり、変換したりできます。
echo '{"name":"Charlie"}' | jq '. + {status: "active", createdAt: "2024-07-30"}'
# 出力例:
# {
# "name": "Charlie",
# "status": "active",
# "createdAt": "2024-07-30"
# }
条件分岐とフィルタリング:
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("監視システム");
検証
スクリプトが正しく動作するか、以下の方法で検証します。
手動実行: 開発環境でスクリプトを手動で実行し、期待される出力ファイルが作成され、その内容が正しいか確認します。
chmod +x my_api_processor.sh
./my_api_processor.sh
cat /tmp/json_proc_*/processed_data.json
ログ確認: スクリプトが出力するログメッセージ(標準出力、標準エラー出力)を確認し、エラーがないか、期待通りの処理が実行されているか確認します。
冪等性の確認: スクリプトを複数回実行し、常に同じ結果(例: 最終的なデータが同じ、不要な重複作成がない)が得られることを確認します。
エラーシミュレーション: API_URLを意図的に間違えたり、jqフィルタに構文エラーを仕込んだりして、エラーハンドリングが適切に機能するか確認します。
運用
systemdを使用して、上記のJSON処理スクリプトを定期的に自動実行する仕組みを構築します。これにより、監視やリソースの管理が容易になります。
systemdによる定期実行
スクリプトの配置: 作成した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 # ワーキングディレクトリの権限設定
専用ユーザーの作成: 最小権限の原則に従い、このサービスを実行するための専用ユーザーを作成します。
sudo useradd -r -s /sbin/nologin jsonuser
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=jsonuserとGroup=jsonuserディレクティブは、スクリプトがjsonuserという非特権ユーザーとして実行されることを保証し、セキュリティリスクを軽減します。root権限が必要な操作は、スクリプト内で行わず、必要最小限の権限で動作するよう設計することが重要です。
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
systemd設定のロードと有効化:
sudo systemctl daemon-reload # systemd設定ファイルを再読み込み
sudo systemctl enable my_json_processor.timer # タイマーを有効化(システム起動時に自動開始)
sudo systemctl start my_json_processor.timer # タイマーを即座に開始
ステータスの確認:
systemctl status my_json_processor.timer
systemctl status my_json_processor.service
ログの確認:
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) と最小権限の原則 (systemdのUser=オプション) は、運用環境における安定性とセキュリティを確保するために不可欠です。これらのプラクティスを遵守することで、日々の運用作業の自動化をより信頼性の高いものにできるでしょう。
参考文献
コメント