Cron式完全ガイド | 基礎から上級テクニックまで
Cron式構文の包括的ガイド。10の実践例、デバッグ手法、ベストプラクティス、FAQを含む。Linuxスケジュールタスクをマスターしよう。
Learn by doing
Practice with the while reading - free, no registration required
Real-World Examples
Case Study 1: 自動データベースバックアップ
Problem
スタートアップ企業が信頼できる毎日のPostgreSQLバックアップを必要としていました。最初の試みは失敗し、Cronログに "command not found" エラーが表示されていました。
Solution
問題はpg_dumpが環境変数を必要とし、スクリプトが相対パスを使用していたことでした。解決策は、スクリプトの先頭で環境変数を設定し、絶対パスを使用し、ログ記録とエラー処理を追加することでした:
#!/bin/bash
# データベースバックアップスクリプト
# 環境変数
export PGPASSWORD='your_password'
export PATH=/usr/local/bin:/usr/bin:/bin
# 設定
DB_NAME="mydb"
BACKUP_DIR="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${DATE}.sql"
# バックアップ作成
/usr/bin/pg_dump -h localhost -U postgres "$DB_NAME" > "$BACKUP_FILE" 2>&1
# 成功を確認
if [ $? -eq 0 ]; then
echo "バックアップ成功: $BACKUP_FILE" >> /var/log/db_backup.log
# 過去7日間のバックアップを保持
find "$BACKUP_DIR" -name "${DB_NAME}_*.sql" -mtime +7 -delete
else
echo "バックアップ失敗、エラーメッセージを確認してください" >> /var/log/db_backup.log
exit 1
fi
Case Study 2: マイクロサービスヘルスチェック
Problem
Kubernetesにデプロイされたマイクロサービスは定期的なヘルスチェックを必要としていました。サービスが時々ハングアップし、自動再起動が必要でした。
Solution
定期的にヘルスチェックエンドポイントを呼び出すKubernetes CronJobリソースを作成:
apiVersion: batch/v1
kind: CronJob
metadata:
name: health-check
spec:
schedule: "*/5 * * * *" # 5分ごと
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
containers:
- name: health-check
image: curlimages/curl:latest
imagePullPolicy: Always
command:
- /bin/sh
- -c
- |-
response=$(curl -s -o /dev/null -w "%{http_code}" http://service:8080/health)
if [ $response -ne 200 ]; then
echo "ヘルスチェック失敗、HTTPステータス: $response"
# 再起動ロジックをトリガー
kubectl rollout restart deployment/my-service
fi
restartPolicy: OnFailure
Case Study 3: ログローテーションとアーカイブ
Problem
Webサーバーのログファイルが急速に増加し、ディスク容量が頻繁に不足していました。ログの自動ローテーション、圧縮、クリーンアップが必要でした。
Solution
logrotateとcronを使用したログ管理:
/var/log/nginx/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
lastaction
# 7日以上前の圧縮ログをアーカイブディレクトリに移動
find /var/log/nginx -name "*.gz" -mtime +7 -exec mv {} /archive/nginx/ \;
endaction
}
# Cronタスク(logrotateが自動作成)
0 0 * * * /usr/sbin/logrotate -f /etc/logrotate.conf
Case Study 4: 定時メールレポート
Problem
財務部門は毎朝8時に前日の売上レポートをメールで受信する必要がありました。
Solution
レポートを生成しメールを送信するスクリプトを作成:
#!/usr/bin/env python3
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import subprocess
from datetime import datetime, timedelta
def generate_report():
"""売上レポートを生成"""
yesterday = datetime.now() - timedelta(days=1)
date_str = yesterday.strftime('%Y-%m-%d')
# SQLクエリを実行
cmd = f'''
mysql -u reporter -ppassword -h db.internal -e "
SELECT product, SUM(amount) as revenue
FROM sales
WHERE DATE(created_at) = '{date_str}'
GROUP BY product
"
'''
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return result.stdout
def send_email(report):
"""メールを送信"""
msg = MIMEMultipart()
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
msg['Subject'] = f'売上レポート - {datetime.now().strftime("%Y-%m-%d")}'
body = f'''
売上レポート
{'='*40}
{report}
{'='*40}
このメールはシステムにより自動生成されました。返信しないでください。
'''
msg.attach(MIMEText(body, 'plain'))
with smtplib.SMTP('smtp.company.com', 587) as server:
server.starttls()
server.login('[email protected]', 'password')
server.send_message(msg)
if __name__ == '__main__':
report = generate_report()
send_email(report)
Case Study 5: 分散スケジュールタスクの同期
Problem
複数のサーバーで実行されるスケジュールタスクは、重複処理を避けるために同時に1台のみ実行を確保する必要がありました。
Solution
相互排他を保証するために分散ロックを使用:
#!/bin/bash
# 分散データ処理タスク
LOCK_FILE="/tmp/data_processing.lock"
LOCK_TIMEOUT=3600 # 1時間タイムアウト
REDIS_HOST="redis.internal"
# ロックを取得
acquire_lock() {
redis-cli -h "$REDIS_HOST" SETNX "$LOCK_FILE" "$HOSTNAME:$$" > /dev/null
return $?
}
# ロックを解放
release_lock() {
redis-cli -h "$REDIS_HOST" DEL "$LOCK_FILE" > /dev/null
}
# メインロジック
main() {
echo "データ処理を開始..."
# ロックの有効期限を確認
lock_time=$(redis-cli -h "$REDIS_HOST" GET "$LOCK_FILE.ttl")
if [ -n "$lock_time" ]; then
current_time=$(date +%s)
if [ $((current_time - lock_time)) -gt $LOCK_TIMEOUT ]; then
echo "ロックが期限切れ、強制取得"
release_lock
fi
fi
# ロックの取得を試行
if ! acquire_lock; then
owner=$(redis-cli -h "$REDIS_HOST" GET "$LOCK_FILE")
echo "タスク実行中: $owner"
exit 0
fi
# ロックタイムアウトを設定
redis-cli -h "$REDIS_HOST" SETEX "$LOCK_FILE.ttl" "$LOCK_TIMEOUT" "$(date +%s)" > /dev/null
# タスクを実行
python3 /scripts/process_data.py
# ロックを解放
release_lock
echo "データ処理完了"
}
# 実行
main
Case Study 6: 段階的デプロイメント
Problem
サービス中断なしですべてのサーバーを段階的に更新する必要があり、各バッチ5台、2分間隔でした。
Solution
Cronトリガー段階的デプロイメントシステム:
#!/bin/bash
# 段階的デプロイスクリプト
DEPLOYMENT_CONFIG="/etc/deployment/config.json"
CURRENT_BATCH=$(cat /var/run/deployment_batch 2>/dev/null || echo 0)
BATCH_SIZE=5
BATCH_DELAY=120 # 2分
# サーバーリストを取得
servers=$(jq -r '.servers[]' "$DEPLOYMENT_CONFIG")
total_servers=$(echo "$servers" | wc -l)
# 現在のバッチを計算
start=$((CURRENT_BATCH * BATCH_SIZE))
end=$((start + BATCH_SIZE))
echo "デプロイバッチ $((CURRENT_BATCH + 1)) 開始、サーバー $start-$end"
# 現在のバッチをデプロイ
echo "$servers" | sed -n "${start},${end}p" | while read server; do
echo "$server にデプロイ中..."
ssh "$server" 'bash -s' < /scripts/update_service.sh
done
# 進行状況を記録
if [ $end -lt $total_servers ]; then
echo $((CURRENT_BATCH + 1)) > /var/run/deployment_batch
echo "バッチ完了、次を待機..."
else
echo "全バッチデプロイ完了"
rm -f /var/run/deployment_batch
fi
Case Study 7: 証明書自動更新
Problem
SSL証明書は90日ごとの更新が必要で、手動手順は忘れられがちでサービス中断を引き起こしていました。
Solution
Let's Encrypt自動更新を使用:
#!/bin/bash
# SSL証明書自動更新
DOMAINS="example.com www.example.com api.example.com"
EMAIL="[email protected]"
CERT_DIR="/etc/letsencrypt/live"
LOG_FILE="/var/log/cert_renewal.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
# 証明書有効期限を確認
check_cert_expiry() {
local domain=$1
local cert_file="$CERT_DIR/$domain/cert.pem"
if [ ! -f "$cert_file" ]; then
log "証明書が見つかりません: $domain"
return 1
fi
# 残り日数を計算
expiry_date=$(openssl x509 -enddate -noout -in "$cert_file" | cut -d= -f2)
expiry_epoch=$(date -d "$expiry_date" +%s)
current_epoch=$(date +%s)
days_left=$(( (expiry_epoch - current_epoch) / 86400 ))
echo $days_left
}
# 証明書を更新
renew_cert() {
log "証明書更新開始: $DOMAINS"
certbot certonly --non-interactive --agree-tos --email "$EMAIL" --webroot -w /var/www/html -d $DOMAINS >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
log "証明書更新成功"
# nginxをリロード
systemctl reload nginx
# 通知を送信
echo "証明書更新成功" | mail -s "証明書更新" "$EMAIL"
else
log "証明書更新失敗"
echo "証明書更新失敗、ログを確認してください" | mail -s "更新失敗" "$EMAIL"
exit 1
fi
}
# メインロジック
for domain in $DOMAINS; do
days_left=$(check_cert_expiry "$domain")
if [ $days_left -lt 30 ]; then
log "証明書は$days_left日後に有効期限切れ、更新開始"
renew_cert
else
log "証明書はあと$days_left日あります、更新不要"
fi
done
Case Study 8: キャッシュウォームアップ
Problem
Eコマースサイトはピーク時間中にキャッシュヒット率が低く、ピーク前にキャッシュをウォームアップする必要がありました。
Solution
ピーク前に人気商品キャッシュを自動的にウォームアップ:
#!/usr/bin/env python3
import requests
import json
from datetime import datetime
API_BASE = "https://api.example.com"
CACHE_ENDPOINT = f"{API_BASE}/cache/warmup"
def get_popular_products():
"""人気商品を取得"""
response = requests.get(f"{API_BASE}/analytics/popular", params={
'limit': 1000,
'time_range': '24h'
})
return response.json()['products']
def warmup_cache(product_ids):
"""キャッシュをウォームアップ"""
chunks = [product_ids[i:i+50] for i in range(0, len(product_ids), 50)]
for chunk in chunks:
try:
response = requests.post(CACHE_ENDPOINT, json={
'product_ids': chunk,
'ttl': 3600
}, timeout=30)
if response.status_code == 200:
print(f"ウォームアップ成功: {len(chunk)} 商品")
else:
print(f"ウォームアップ失敗: {response.text}")
except Exception as e:
print(f"ウォームアップエラー: {e}")
def main():
print(f"[{datetime.now()}] キャッシュウォームアップ開始")
try:
# 人気商品を取得
products = get_popular_products()
product_ids = [p['id'] for p in products]
print(f"{len(product_ids)} 人気商品を取得")
# キャッシュをウォームアップ
warmup_cache(product_ids)
print(f"[{datetime.now()}] キャッシュウォームアップ完了")
except Exception as e:
print(f"ウォームアップ失敗: {e}")
exit 1
if __name__ == '__main__':
main()
Case Study 9: データ同期とクリーンアップ
Problem
本番環境の匿名化データをテスト環境に同期する必要があり、過去30日間のみ保持する必要がありました。
Solution
データ同期とクリーンアップスクリプトを作成:
#!/bin/bash
# データベースをテスト環境に同期
PROD_DB="production"
TEST_DB="test_staging"
RETENTION_DAYS=30
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# 本番データをエクスポート(匿名化)
dump_data() {
log "本番データエクスポート開始..."
mysqldump -h prod-db.internal -u exporter -ppassword \
--single-transaction \
--quick \
--lock-tables=false \
--where="DATE(created_at) >= DATE(NOW()) - INTERVAL $RETENTION_DAYS DAY" \
"$PROD_DB" \
users orders products \
| gzip > /tmp/dump.sql.gz
# 匿名化
zcat /tmp/dump.sql.gz | sed \
-e 's/"\(email\|phone\|ssn\)":"[^"]*"/"\1":"***"/g' \
-e 's/"\(credit_card\|password\)":"[^"]*"/"\1":"***"/g' \
| gzip > /tmp/dump_anon.sql.gz
log "データエクスポートと匿名化完了"
}
# テスト環境にインポート
import_data() {
log "テスト環境へのインポート開始..."
gunzip < /tmp/dump_anon.sql.gz | mysql -h test-db.internal -u importer -ppassword "$TEST_DB"
log "データインポート完了"
}
# 一時ファイルをクリーンアップ
cleanup() {
log "一時ファイルをクリーンアップ..."
rm -f /tmp/dump*.sql.gz
}
# 古いテスト環境データをクリーンアップ
cleanup_old_data() {
log "古いテスト環境データをクリーンアップ..."
mysql -h test-db.internal -u admin -ppassword "$TEST_DB" <<EOF
DELETE FROM orders WHERE DATE(created_at) < DATE(NOW()) - INTERVAL $RETENTION_DAYS DAY;
DELETE FROM audit_logs WHERE DATE(created_at) < DATE(NOW()) - INTERVAL $RETENTION_DAYS DAY;
OPTIMIZE TABLE users, orders, products;
EOF
log "古いデータクリーンアップ完了"
}
# メインプロセス
main() {
log "===== データ同期開始 ====="
dump_data
import_data
cleanup_old_data
cleanup
log "===== データ同期完了 ====="
}
main
Case Study 10: モニタリングとアラート統合
Problem
スケジュールタスクが失敗したとき、運用チームに即座に通知する必要があり、既存のモニタリングシステムとの統合が必要でした。
Solution
PrometheusとAlertmanagerを使用してアラート統合:
#!/bin/bash
# モニタリング統合付きタスクラッパー
TASK_NAME="data_import"
TASK_COMMAND="/usr/bin/python3 /scripts/import.py"
LOG_FILE="/var/log/tasks/${TASK_NAME}.log"
METRICS_FILE="/var/lib/node_exporter/textfile_collector/${TASK_NAME}.prom"
# 初期化
START_TIME=$(date +%s)
echo "# HELP task_last_success_timestamp 最後の成功時刻" > "$METRICS_FILE"
echo "# TYPE task_last_success_timestamp gauge" >> "$METRICS_FILE"
# タスクを実行
run_task() {
echo "[$(date)] タスク実行開始: $TASK_NAME" >> "$LOG_FILE"
$TASK_COMMAND >> "$LOG_FILE" 2>&1
EXIT_CODE=$?
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
if [ $EXIT_CODE -eq 0 ]; then
echo "[$(date)] タスク成功、所要時間: ${DURATION}s" >> "$LOG_FILE"
# 成功メトリックを記録
echo "task_last_success_timestamp{task="$TASK_NAME"} $END_TIME" >> "$METRICS_FILE"
echo "task_duration_seconds{task="$TASK_NAME",status="success"} $DURATION" >> "$METRICS_FILE"
else
echo "[$(date)] タスク失敗、終了コード: $EXIT_CODE" >> "$LOG_FILE"
# 失敗メトリックを記録
echo "task_last_success_timestamp{task="$TASK_NAME"} 0" >> "$METRICS_FILE"
echo "task_duration_seconds{task="$TASK_NAME",status="failed"} $DURATION" >> "$METRICS_FILE"
# アラートを送信
curl -X POST http://alertmanager:9093/api/v1/alerts -d '[
{
"labels": {
"alertname": "TaskFailed",
"task": "'$TASK_NAME'",
"severity": "critical"
},
"annotations": {
"summary": "タスク失敗: '$TASK_NAME'",
"description": "終了コード: '$EXIT_CODE'、ログ: '$LOG_FILE'"
}
}
]'
exit 1
fi
}
run_task