jqとcurlで実現する堅牢なJSONデータ処理:DevOpsプラクティス
DevOpsの世界では、外部APIからのデータ取得、変換、そしてシステムへの統合が日常的に行われます。本稿では、jqとcurlを組み合わせ、JSONデータを安全かつ冪等に抽出し、整形する一連のプロセスを解説します。さらに、systemdを用いた定期実行と、DevOpsエンジニアとして重要な権限分離やトラブルシューティングのプラクティスも示します。
要件と前提
要件
- GitHub APIからJSONデータを取得し、特定の条件でフィルタリング・整形する。
- 処理は
bashスクリプトとして実装し、冪等性、安全性、エラーハンドリングを考慮する。 curlでTLS検証、再試行、バックオフを実装する。jqでJSONデータの抽出、フィルタリング、変換、整形を行う。systemd unit/timerを用いて、この処理を定期実行する。- 処理全体を
mermaidのフロー図で表現する。 root権限の扱いと権限分離に関する注意点を明記する。
前提
- Linux環境(Ubuntu/CentOSなど)が利用可能であること。
curl、jq、systemdがインストールされていること。sudo apt update && sudo apt install -y curl jqまたはsudo yum install -y curl jq
実装
外部APIからGo言語のリポジトリ情報を取得し、スター数が5000以上のものを抽出し、CSV形式で整形するスクリプトを作成します。
スクリプトの作成 (fetch_github_repos.sh)
#!/bin/bash
# set -euo pipefail:
# -e: 終了コードが非ゼロのコマンドがあれば即座に終了
# -u: 未定義の変数を使おうとするとエラー
# -o pipefail: パイプライン中のコマンドが一つでも失敗すれば終了コードが非ゼロになる
set -euo pipefail
# 一時ディレクトリの作成と自動削除
# mktemp -d: 安全な一時ディレクトリを作成
TMP_DIR=$(mktemp -d -t github_data_XXXXXX)
# trap: スクリプト終了時に一時ディレクトリを削除
trap 'rm -rf "$TMP_DIR"' EXIT
# 出力ファイルの定義
OUTPUT_FILE="/var/lib/github_data/go_popular_repos.csv"
# 出力ディレクトリが存在しなければ作成 (冪等性)
mkdir -p "$(dirname "$OUTPUT_FILE")"
# GitHub APIのURL
API_URL="https://api.github.com/search/repositories?q=language:go&sort=stars&order=desc"
# 権限に関する注意点:
# APIトークンなど機密情報は直接スクリプトに書かず、環境変数やセキュアなストレージから取得する。
# 例: GITHUB_TOKEN="${GITHUB_TOKEN:-}"
# 環境変数を使用する場合、systemd unitファイルでEnvironment=設定する。
# 本例では公開APIのため不要。
echo "--- $(date '+%Y-%m-%d %H:%M:%S') ---"
echo "Fetching data from GitHub API..."
# curlでGitHub APIからJSONデータを取得
# --fail: HTTPステータスコードが200番台以外の場合、即座にエラー終了
# --silent: 進捗表示を抑制
# --show-error: エラー発生時にエラーメッセージを表示
# --retry 5: 失敗時に5回まで再試行
# --retry-delay 5: 5秒間隔で再試行
# --retry-max-time 60: 合計60秒まで再試行
# --location: リダイレクトを追跡
# --compressed: 圧縮をサポート
# --connect-timeout 10: 接続タイムアウト10秒
# --max-time 30: 最大30秒で処理を終了
if ! curl -sS --fail --location --compressed --connect-timeout 10 --max-time 30 \
--retry 5 --retry-delay 5 --retry-max-time 60 \
"$API_URL" > "$TMP_DIR/github_repos.json"; then
echo "ERROR: Failed to fetch data from GitHub API." >&2
exit 1
fi
echo "Processing JSON data with jq..."
# jqでJSONデータを抽出・変換・整形
# .items[]: 配列の各要素を展開
# select(.stargazers_count > 5000): スター数が5000以上のリポジトリをフィルタリング
# {name: .full_name, stars: .stargazers_count, url: .html_url}: 必要なフィールドを抽出・リネーム
# @csv: CSV形式に整形 (ヘッダ行は含まず)
if ! jq -r '.items[] | select(.stargazers_count > 5000) | {name: .full_name, stars: .stargazers_count, url: .html_url} | @csv' \
"$TMP_DIR/github_repos.json" > "$TMP_DIR/processed_data.csv"; then
echo "ERROR: Failed to process JSON data with jq." >&2
exit 1
fi
# ヘッダ行を追加して最終的なCSVファイルを作成
echo "name,stars,url" > "$OUTPUT_FILE"
cat "$TMP_DIR/processed_data.csv" >> "$OUTPUT_FILE"
echo "Data successfully extracted and saved to $OUTPUT_FILE"
echo "--- End ---"
フロー図
graph TD
A["systemd Timer starts"] --> B("systemd Service executes script");
B --> C{"Create Temporary Directory"};
C --> D{"Fetch JSON from GitHub API via curl"};
D -- Success --> E{"Process JSON with jq"};
D -- Failure --> F("Log Error & Exit");
E -- Success --> G("Add CSV Header & Save to Output File");
E -- Failure --> F;
G --> H("Delete Temporary Directory");
H --> I["Script Ends"];
F --> I;
検証
スクリプトを直接実行して、期待通りに動作するか確認します。
# スクリプトに実行権限を付与 chmod +x fetch_github_repos.sh # スクリプトを実行 ./fetch_github_repos.sh # 出力ファイルの内容を確認 cat /var/lib/github_data/go_popular_repos.csv # 例: 出力は以下のようになるはず (内容は変動します) # name,stars,url # gin-gonic/gin,73000,https://github.com/gin-gonic/gin # go-gorm/gorm,71000,https://github.com/go-gorm/gorm # ...
エラーケースの確認:
– API URLを間違えてみる(例: API_URL="https://api.github.com/invalid_url")。curlがエラーで終了し、スクリプトも停止することを確認します。
– jqのパスを間違えてみる(例: select(.non_existent_field > 5000))。jqがエラーで終了し、スクリプトも停止することを確認します。
運用
systemdのUnitとTimerを用いて、スクリプトを定期的に実行するように設定します。
権限分離の注意点
systemdサービスは、User=とGroup=ディレクティブを用いて、root権限ではなく専用の非特権ユーザーで実行することを強く推奨します。これにより、スクリプトが誤動作した場合の影響範囲を最小限に抑え、セキュリティを向上させます。ここでは例としてdevopsuserというユーザーを作成し、そのユーザーで実行します。
# 非特権ユーザーの作成 sudo useradd -r -s /bin/false devopsuser # 出力ディレクトリの所有者をdevopsuserに変更 sudo mkdir -p /var/lib/github_data sudo chown devopsuser:devopsuser /var/lib/github_data
systemd Unitファイル (/etc/systemd/system/github-data-fetch.service)
[Unit] Description=Fetch and process GitHub repository data After=network.target [Service] # スクリプトはoneshotタイプで一度実行したら終了 Type=oneshot # スクリプトのフルパスを指定 ExecStart=/usr/local/bin/fetch_github_repos.sh # 実行ユーザーとグループを指定 (重要: 権限分離) User=devopsuser Group=devopsuser # 作業ディレクトリ WorkingDirectory=/var/lib/github_data # 標準出力と標準エラーをsyslogに送る StandardOutput=journal StandardError=journal [Install] # Timerユニットによって呼び出される WantedBy=timers.target
systemd Timerファイル (/etc/systemd/system/github-data-fetch.timer)
[Unit] Description=Run GitHub data fetcher daily [Timer] # 毎日午前3時に実行 OnCalendar=daily # Persistent=true: タイマーが停止していた間に実行すべきタイミングがあった場合、起動後すぐに実行 Persistent=true [Install] # systemctl enable で有効化されるように WantedBy=timers.target
配置と起動
- スクリプトを適切な場所に配置(例:
/usr/local/bin/)。sudo mv fetch_github_repos.sh /usr/local/bin/ sudo chmod +x /usr/local/bin/fetch_github_repos.sh
systemdユニットとタイマーファイルを配置。# 上記の内容をそれぞれファイルとして保存 sudo vi /etc/systemd/system/github-data-fetch.service sudo vi /etc/systemd/system/github-data-fetch.timer
systemd設定をリロード。sudo systemctl daemon-reload
- タイマーを有効化して起動。
sudo systemctl enable github-data-fetch.timer sudo systemctl start github-data-fetch.timer
- タイマーの状態を確認。
systemctl list-timers | grep github-data-fetch # 例: Mon 2023-10-26 03:00:00 UTC 14h left github-data-fetch.timer github-data-fetch.service
ログ確認
スクリプトの実行ログはjournalctlで確認できます。
# サービスの実行ログを確認 journalctl -u github-data-fetch.service # 直近の実行ログ (root権限で実行されていれば不要ですが、念のため) journalctl -u github-data-fetch.service -f
トラブルシュート
- スクリプトのエラー:
set -euo pipefailにより、エラー発生箇所で即座にスクリプトが停止します。journalctlでエラーメッセージを確認し、スクリプト内の処理を見直してください。trapコマンドにより一時ファイルが適切にクリーンアップされることも確認してください。 curlのエラー: ネットワーク接続問題、APIレート制限、APIキーの失効、不正なURLなどが考えられます。journalctlでエラーメッセージを確認するか、スクリプト内でcurl --verboseを追加して詳細なリクエスト/レスポンス情報を確認します。再試行ロジックが有効かどうかも確認ポイントです。jqのエラー: 入力JSONの形式が不正、jqのフィルタリングパスが間違っている、期待するデータが存在しないなどが原因です。journalctlでjqのエラーメッセージ(例:parse error)を確認し、jqの式を修正します。入力JSONが期待通りか、jq -c .などで出力して確認するのも有効です。systemdのエラー:systemctl status github-data-fetch.serviceやsystemctl status github-data-fetch.timerで状態を確認します。journalctl -xeu github-data-fetch.serviceで詳細なログとシステムメッセージを合わせて確認することで、権限問題(User=設定とファイルのパーミッションが合っていないなど)やパスの問題(ExecStartのパスが間違っている)を発見できます。
まとめ
本稿では、jqとcurlを用いたJSONデータ処理のDevOpsプラクティスを解説しました。set -euo pipefailやtrapによる安全なbashスクリプトの書き方、curlの堅牢なデータ取得方法、jqによる柔軟なデータ抽出・変換・整形、そしてsystemdによる自動化と権限分離の重要性を示しました。
これらの技術とプラクティスを組み合わせることで、外部システムからのデータ取得・処理を自動化し、安定性と信頼性の高いDevOpsパイプラインを構築できます。特に、非特権ユーザーでの実行や機密情報の安全な管理といった権限分離は、システムのセキュリティを確保する上で不可欠です。本記事が、日々のDevOps業務におけるJSONデータ処理の一助となれば幸いです。

