TL;DR
- コードを見ても分からない「repo外運用」 は、SSH設定・cron・環境変数・ログ出力先に埋まっている
- 最初の15分で確認すべきことをチェックリスト化すれば、初動で8割は潰せる
- この記事のコマンドはすべてread-only(システムに影響を与えない)ので、本番でも安心して実行できる
- 「知らないのが悪い」ではなく「見える化されてないのが問題」——技術で解決する
目次
- この記事の前提と心構え
- まず最初の15分でやること
- カテゴリ別チェックリスト
- ユーザー / グループ / ログイン履歴
- SSH / 鍵 / 接続設定
- Cron / スケジュールジョブ(見えないジョブも)
- 設定ファイル / INI / 環境変数
- ログ / 出力先
- パーミッション / 所有者
- ネットワーク / 疎通
- プロセス / 実行状況
- ディスク / メモリ / システムリソース
- サービス / デーモン管理
- 操作履歴 / コマンド履歴
- 具体例:batch-user監視スクリプトの調査
- 付録:深掘り調査テクニック
- 調査結果メモテンプレート
- 「repo外運用」を減らすための改善ロードマップ
この記事の前提と心構え
想定シナリオ
あなたは PHP エンジニア。ある日、こう言われる。
上司「この監視スクリプト、たまに動かないんだけど調べて」
あなた「(コード見ても cron どこ...? SSH設定どこ...?)」
上司「え、1年目で知るだろそれくらい」
コードだけ見ても分からない。 なぜなら:
~/.ssh/configはリポジトリ外crontab -lの内容はリポジトリ外- ログの出力先は cron のリダイレクト次第
- 実行ユーザーの環境変数は cron 環境では違う
- INI ファイルのパスはコードにハードコードされてるかも
この記事のスタンス
❌「知らないのが悪い」
✅「見える化されてないのが問題」
→ だから、技術で殴って可視化する
安全に調査するための鉄則
⚠️ この記事のコマンドはすべて read-only
- cat, grep, ls, ps, lsof, ss, dig など「見るだけ」コマンド
- rm, mv, kill, systemctl restart などは使わない
- 本番環境でも安心して実行できる
万が一、変更が必要な場合は明示的に注記する
まず最初の15分でやること
0. 自分が誰として作業しているか確認(30秒)
# 今のユーザーとホスト
whoami
hostname
# 現在のシェルと環境
echo $SHELL
echo $PATH
1. 問題のスクリプトの場所と中身を確認(2分)
# スクリプトの場所を確認
ls -la /path/to/script.php
# 中身を確認(先頭100行)
head -100 /path/to/script.php
# 設定ファイルの読み込み箇所を探す
grep -n "include\|require\|\.ini\|\.conf\|config" /path/to/script.php
2. そのスクリプトを動かしている cron を探す(3分)
# 現在ユーザーの crontab
crontab -l
# 他のユーザーの crontab(root権限必要)
sudo crontab -l -u batch-user
# システム全体の cron
ls -la /etc/cron.d/
ls -la /etc/cron.daily/
ls -la /etc/cron.hourly/
# スクリプト名で grep
grep -r "script.php" /etc/cron* 2>/dev/null
sudo grep -r "script.php" /var/spool/cron/* 2>/dev/null
3. SSH 設定を確認(3分)
# 実行ユーザーの SSH 設定
sudo cat /home/batch-user/.ssh/config
sudo ls -la /home/batch-user/.ssh/
# known_hosts の確認
sudo cat /home/batch-user/.ssh/known_hosts
# 鍵ファイルの確認
sudo ls -la /home/batch-user/.ssh/*.pub
sudo ls -la /home/batch-user/.ssh/id_*
4. 最近のログ・エラーを確認(3分)
# cron のログ(Ubuntu/Debian)
sudo tail -100 /var/log/syslog | grep -i cron
# cron のログ(RHEL/CentOS)
sudo tail -100 /var/log/cron
# スクリプトの出力先を cron から特定
crontab -l | grep "script.php"
# 例: * * * * * /usr/bin/php /path/to/script.php >> /var/log/myapp/cron.log 2>&1
# そのログを確認
sudo tail -100 /var/log/myapp/cron.log
5. プロセスの状態を確認(2分)
# 今動いているか
ps aux | grep "script.php"
# 関連プロセス
ps aux | grep "batch-user"
# SSH 接続中のプロセス
ps aux | grep ssh
6. ネットワーク疎通を確認(2分)
# SSH 先への疎通
ping -c 3 <対象IP>
# ポート22への接続確認
nc -zv <対象IP> 22
# または
timeout 5 bash -c "</dev/tcp/<対象IP>/22" && echo "OK" || echo "NG"
# DNS解決
dig <対象ホスト名> +short
カテゴリ別チェックリスト
1. ユーザー / グループ / ログイン履歴
目的
サーバー上にどんなユーザーがいて、誰がどの権限で動いているかを把握する
確認コマンド
# ========================================
# システムユーザーの一覧
# ========================================
# /etc/passwd からユーザー一覧を取得
cat /etc/passwd
# 人間が使うユーザーだけ抽出(UID 1000以上、一般的な基準)
awk -F: '$3 >= 1000 && $3 < 65534 {print $1, $3, $6, $7}' /etc/passwd
# 特定ユーザーの詳細を確認
getent passwd batch-user
# 出力例: batch-user:x:1001:1001:Log Monitor User:/home/batch-user:/bin/bash
# /etc/passwd の見方
# ユーザー名:パスワード:UID:GID:コメント:ホームディレクトリ:シェル
#
# シェルが /sbin/nologin や /bin/false → ログイン不可のサービスアカウント
# シェルが /bin/bash や /bin/sh → ログイン可能
# ========================================
# ログインできるユーザーを特定
# ========================================
# ログイン可能なシェルを持つユーザー
grep -v '/nologin\|/false\|/sync\|/shutdown\|/halt' /etc/passwd | cut -d: -f1
# sudo 権限を持つユーザー
grep -E '^sudo:|^wheel:' /etc/group
getent group sudo
getent group wheel # RHEL/CentOS
# sudoers の設定確認
sudo cat /etc/sudoers
sudo ls -la /etc/sudoers.d/
sudo cat /etc/sudoers.d/*
# ========================================
# グループの確認
# ========================================
# グループ一覧
cat /etc/group
# 特定ユーザーの所属グループ
id batch-user
groups batch-user
# 特定グループのメンバー
getent group docker
getent group www-data
# ========================================
# ログイン履歴・現在の状態
# ========================================
# 現在ログイン中のユーザー
who
w
# 最近のログイン履歴
last | head -20
# 各ユーザーの最終ログイン日時
lastlog
# 失敗したログイン試行(セキュリティ確認)
sudo lastb | head -20
# ========================================
# サービスアカウントの特定
# ========================================
# cron を持っているユーザーを探す
for user in $(cut -d: -f1 /etc/passwd); do
sudo crontab -l -u "$user" 2>/dev/null | grep -q . && echo "$user has crontab"
done
# ホームディレクトリがあるユーザー
ls -la /home/
# 特定ディレクトリの所有者から逆引き
stat -c "%U" /var/www/html/
stat -c "%U" /usr/local/tmp/log_check/
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| 知らないユーザーでプロセスが動いている | サービスアカウント | ps aux + getent passwd <user> |
| sudo できない | sudo グループに入っていない | id <user>, /etc/sudoers |
| ログインできない | シェルが /sbin/nologin | getent passwd <user> |
| 「誰がこのファイル作った?」が分からない | 所有者から逆引き | stat -c "%U" <file> |
次の一手
# 「このスクリプトを実行しているユーザー」を特定する流れ
1. ps aux | grep script.php # 実行中ならユーザー名が見える
2. sudo crontab -l -u <user> # そのユーザーの cron を確認
3. getent passwd <user> # ユーザーの詳細(ホームディレクトリ等)
4. sudo ls -la /home/<user>/.ssh/ # SSH 設定があるか
2. SSH / 鍵 / 接続設定
目的
SSHで外部サーバーに接続する処理が、どの設定・どの鍵で動いているかを把握する
確認コマンド
# ========================================
# SSH設定ファイルの確認
# ========================================
# ユーザーの SSH config
cat ~/.ssh/config
sudo cat /home/<user>/.ssh/config
# SSH config で実際に解決される設定を見る(超重要)
ssh -G <host>
# 例:batch-user ユーザーが worker-server に接続する設定
sudo -u batch-user ssh -G worker-server
# ========================================
# 鍵ファイルの確認
# ========================================
# 鍵の一覧
ls -la ~/.ssh/
# 鍵のパーミッション(600でないとエラー)
stat -c "%a %U:%G %n" ~/.ssh/id_*
# 公開鍵の内容(接続先の authorized_keys と照合用)
cat ~/.ssh/id_rsa.pub
cat ~/.ssh/id_ed25519.pub
# ========================================
# known_hosts の確認
# ========================================
# 接続先が登録されているか
grep "<対象IP>" ~/.ssh/known_hosts
grep "<対象ホスト名>" ~/.ssh/known_hosts
# ========================================
# 接続テスト(デバッグモード)
# ========================================
# 詳細ログ付きで接続テスト
ssh -vvv <user>@<host> exit
# タイムアウト付き
ssh -o ConnectTimeout=5 <user>@<host> "echo OK"
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
Permission denied (publickey) |
鍵が間違っている / 権限が緩い | ssh -vvv で使われている鍵を確認 |
Host key verification failed |
known_hosts に登録がない / 変わった | ~/.ssh/known_hosts を確認 |
Connection timed out |
ネットワーク不通 / FW | ping, nc -zv で疎通確認 |
Could not resolve hostname |
DNS解決失敗 / config 未設定 | ssh -G <host> で HostName 確認 |
| cron では失敗するが手動では成功 | 鍵のパス / known_hosts が違う | sudo -u <user> ssh ... で再現 |
次の一手
# 接続できない場合、段階的に切り分け
1. ping <ip> # L3疎通
2. nc -zv <ip> 22 # L4ポート
3. ssh -vvv user@ip exit # SSH認証
4. sudo -u <cron-user> ssh ... # ユーザー差異
3. Cron / スケジュールジョブ(見えないジョブも)
目的
スクリプトがいつ・誰によって・どのように実行されているかを把握する
⚠️ crontab -l で見えないケースがある
# crontab -l は「自分の crontab」しか見えない
crontab -l
# → 自分のジョブしか表示されない
# 以下は crontab -l では見えない:
# 1. 他のユーザーの crontab
# 2. /etc/cron.d/ 配下のファイル
# 3. /etc/cron.daily/ 等のディレクトリ
# 4. /etc/crontab(システム crontab)
# 5. anacron のジョブ
# 6. systemd timer
# 7. at コマンドで登録されたジョブ
確認コマンド
# ========================================
# 1. ユーザー crontab(自分以外も)
# ========================================
# 自分の crontab
crontab -l
# 特定ユーザーの crontab(root権限必要)
sudo crontab -l -u batch-user
sudo crontab -l -u www-data
sudo crontab -l -u root
# ★★★ 全ユーザーの crontab を一括確認 ★★★
for user in $(cut -d: -f1 /etc/passwd); do
echo "=== $user ==="
sudo crontab -l -u "$user" 2>/dev/null
done
# crontab ファイルの場所(直接確認)
# Ubuntu/Debian
sudo ls -la /var/spool/cron/crontabs/
sudo cat /var/spool/cron/crontabs/*
# RHEL/CentOS
sudo ls -la /var/spool/cron/
sudo cat /var/spool/cron/*
# ========================================
# 2. システム cron(/etc/cron*)
# ========================================
# /etc/crontab(システム全体の crontab)
cat /etc/crontab
# /etc/cron.d/ 配下(パッケージがインストールするジョブ)
ls -la /etc/cron.d/
cat /etc/cron.d/*
# 定期実行ディレクトリ(スクリプトを置くだけで動く)
ls -la /etc/cron.hourly/
ls -la /etc/cron.daily/
ls -la /etc/cron.weekly/
ls -la /etc/cron.monthly/
# 中身も確認
cat /etc/cron.daily/*
cat /etc/cron.hourly/*
# ========================================
# 3. anacron(電源オフ時も補完実行)
# ========================================
# anacron の設定
cat /etc/anacrontab
# anacron のタイムスタンプ(最終実行日時)
ls -la /var/spool/anacron/
# ========================================
# 4. at コマンド(一回限りのジョブ)
# ========================================
# 登録されている at ジョブ一覧
atq
sudo atq
# 特定ジョブの内容を確認
at -c <job-number>
# at ジョブの保存場所
sudo ls -la /var/spool/at/
sudo ls -la /var/spool/cron/atjobs/ # 一部ディストリ
# ========================================
# 5. systemd timer(cron の代替)
# ========================================
# 全タイマー一覧(次回実行時刻付き)
systemctl list-timers --all
# 特定タイマーの詳細
systemctl status <timer-name>.timer
systemctl cat <timer-name>.timer
# タイマーに紐づくサービスの確認
systemctl cat <timer-name>.service
# タイマーのログ
journalctl -u <timer-name>.timer
journalctl -u <timer-name>.service
# ========================================
# 6. 特定スクリプトを実行している cron を探す
# ========================================
# 全箇所を一括検索
grep -r "check_log" /etc/cron* 2>/dev/null
sudo grep -r "check_log" /var/spool/cron/* 2>/dev/null
systemctl list-timers --all | grep -i "check"
# 特定スクリプトがどこで呼ばれているか完全調査
script_name="check_log"
echo "=== /etc/crontab ===" && grep "$script_name" /etc/crontab
echo "=== /etc/cron.d/ ===" && grep -r "$script_name" /etc/cron.d/
echo "=== /etc/cron.*/ ===" && grep -r "$script_name" /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/ 2>/dev/null
echo "=== user crontabs ===" && sudo grep -r "$script_name" /var/spool/cron/ 2>/dev/null
echo "=== systemd timers ===" && systemctl list-timers --all | grep -i "$script_name"
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
crontab -l で見えないのに動いている |
/etc/cron.d/ や他ユーザーの crontab | 上記の全箇所検索を実行 |
| cron が実行されない | cron デーモン停止 | systemctl status cron |
| 手動では動くが cron では動かない | PATH が違う | cron 内で PATH= を明示 |
| 出力がどこにもない | リダイレクトされてない | cron 行の末尾を確認 |
| 「file not found」 | 相対パス問題 | 絶対パスに変更 |
| 権限エラー | 実行ユーザーの権限不足 | sudo -u <user> で再現 |
| 毎日実行のはずが動かない日がある | anacron がサーバー起動時に実行 | /var/spool/anacron/ を確認 |
| 一度だけ動いて止まった | at コマンドで登録されていた | atq で確認 |
cron 環境の罠
# cron の環境変数を確認(非常に少ない)
# 以下を crontab に追加して確認
* * * * * env > /tmp/cron_env.txt
# 確認
cat /tmp/cron_env.txt
# → PATH=/usr/bin:/bin しかないことが多い
# 解決策:crontab 内で PATH を明示
PATH=/usr/local/bin:/usr/bin:/bin
* * * * * /path/to/script.sh
タイムゾーンの罠
# ========================================
# タイムゾーンの確認
# ========================================
# システムのタイムゾーン
timedatectl
cat /etc/timezone # Debian/Ubuntu
# 現在時刻
date
date +"%Y-%m-%d %H:%M:%S %Z"
# ========================================
# cron のタイムゾーン問題
# ========================================
# 問題:サーバーが UTC で cron が期待通りに動かない
# → JST 9:00 に実行したいのに、UTC 9:00(JST 18:00)に実行される
# 確認
timedatectl | grep "Time zone"
# 解決策1:crontab で TZ を指定
TZ=Asia/Tokyo
0 9 * * * /path/to/script.sh # JST 9:00 に実行
# 解決策2:UTC で計算して設定(JST 9:00 = UTC 0:00)
0 0 * * * /path/to/script.sh
次の一手
# cron で動かない時のデバッグ
1. crontab に `* * * * * /path/to/script.sh >> /tmp/cron_debug.log 2>&1` を追加
2. 1分待つ
3. /tmp/cron_debug.log を確認
4. エラーメッセージに応じて対処
4. 設定ファイル / INI / 環境変数
目的
スクリプトが読み込む設定ファイルの場所・内容・権限を把握する
確認コマンド
# ========================================
# 設定ファイルの場所を探す
# ========================================
# コード内で設定を読んでいる箇所を探す
grep -rn "\.ini\|\.conf\|\.env\|config" /path/to/app/
# 例:PHP の ini ファイル読み込み
grep -n "parse_ini" /path/to/script.php
# 環境変数参照
grep -n "getenv\|ENV\|\$_ENV\|\$_SERVER" /path/to/script.php
# ========================================
# 設定ファイルの確認
# ========================================
# 場所・所有者・パーミッション
ls -la /usr/local/tmp/log_check/*.ini
# 内容
cat /usr/local/tmp/log_check/targets.ini
# 機密情報がないか(パスワード等)
grep -i "pass\|secret\|key\|token" /path/to/config.ini
# ========================================
# 環境変数
# ========================================
# 現在の環境変数
env | sort
# 特定の環境変数
echo $DATABASE_URL
echo $API_KEY
# /etc/environment(システム全体)
cat /etc/environment
# /etc/profile.d/(ログイン時に読まれる)
ls -la /etc/profile.d/
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| 設定ファイルが見つからない | パスが間違っている | コード内のパスを確認 |
| 設定が反映されない | 古いキャッシュ | 設定ファイルの mtime 確認 |
| 権限エラー | 実行ユーザーが読めない | ls -la + sudo -u <user> cat |
| 環境変数が空 | cron 環境では設定されない | cron 内で export |
次の一手
# 設定ファイルの依存関係を追う
1. コードで読み込み箇所を特定
2. そのパスのファイルを確認
3. ファイル内で include/import している別ファイルを確認
4. 環境変数で上書きされていないか確認
5. ログ / 出力先
目的
スクリプトの実行結果・エラーがどこに出力されているかを把握する
確認コマンド
# ========================================
# cron の出力先を特定
# ========================================
# crontab の行を確認
crontab -l
# 例: * * * * * /path/to/script.sh >> /var/log/myapp.log 2>&1
# リダイレクト先を確認
tail -100 /var/log/myapp.log
# リダイレクトがない場合 → メールに送られる
# /var/mail/<user> を確認
cat /var/mail/batch-user
# ========================================
# syslog / journal
# ========================================
# Ubuntu/Debian: syslog
sudo tail -200 /var/log/syslog | grep -i "cron\|<script-name>"
# RHEL/CentOS: messages
sudo tail -200 /var/log/messages | grep -i "cron\|<script-name>"
# systemd journal
journalctl -u cron --since "1 hour ago"
journalctl -t CRON --since "1 hour ago"
# ========================================
# アプリケーションログ
# ========================================
# よくある場所
ls -la /var/log/
ls -la /var/log/apache2/ # Debian系
ls -la /var/log/httpd/ # RHEL系
ls -la /var/log/nginx/
ls -la /var/log/php*/
# 最近更新されたログ
find /var/log -mmin -60 -type f 2>/dev/null
# ========================================
# プロセスが開いているファイル
# ========================================
# 特定プロセスのファイル
lsof -p <PID>
# 特定ユーザーが開いているファイル
lsof -u batch-user
# 特定ファイルを開いているプロセス
lsof /var/log/myapp.log
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| ログがない | リダイレクトされてない | cron 行を確認 |
| ログが古い | スクリプトが動いてない | ls -la で mtime 確認 |
| Permission denied | ログディレクトリの権限 | ls -la /var/log/myapp/ |
| ディスクフル | 書き込めない | df -h |
次の一手
# ログが見つからない場合
1. cron の行でリダイレクト先を確認
2. なければ /var/mail/<user> を確認
3. それでもなければ syslog/journal を確認
4. プロセスが動いているなら lsof -p <PID>
6. パーミッション / 所有者
目的
実行ユーザーがファイル・ディレクトリにアクセスできるかを確認する
確認コマンド
# ========================================
# ファイルの権限確認
# ========================================
# 基本
ls -la /path/to/file
# 数値で表示
stat -c "%a %U:%G %n" /path/to/file
# ディレクトリを再帰的に
ls -laR /path/to/dir/
# ========================================
# ユーザーが読める/実行できるか確認
# ========================================
# そのユーザーとして実行
sudo -u batch-user cat /path/to/config.ini
sudo -u batch-user ls -la /path/to/dir/
# ========================================
# 特殊なパーミッション
# ========================================
# SUID/SGID/Sticky bit を探す
find /path/to/app -perm /6000 -type f
# 実行権限のあるファイル
find /path/to/app -perm /111 -type f
# ========================================
# SSH 鍵の権限(重要)
# ========================================
# 正しい権限
# ~/.ssh/ → 700 (drwx------)
# ~/.ssh/config → 600 (-rw-------)
# ~/.ssh/id_* → 600 (-rw-------)
# ~/.ssh/*.pub → 644 (-rw-r--r--)
# ~/.ssh/known_hosts → 644 (-rw-r--r--)
# ~/.ssh/authorized_keys → 600 (-rw-------)
stat -c "%a %n" ~/.ssh ~/.ssh/*
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| Permission denied | 読み取り権限がない | sudo -u <user> cat |
| SSH鍵エラー | 鍵の権限が緩すぎる | stat ~/.ssh/id_* |
| 書き込みできない | ディレクトリに w がない | 親ディレクトリの権限 |
| 実行できない | x がない | chmod +x が必要 |
次の一手
# 権限問題の切り分け
1. sudo -u <user> cat /path/to/file # 読めるか
2. sudo -u <user> ls /path/to/dir/ # ディレクトリ見えるか
3. namei -l /path/to/file # パス全体の権限を表示
7. ネットワーク / 疎通
目的
SSH先やAPIエンドポイントへの接続が可能かを確認する
確認コマンド
# ========================================
# L3 疎通(ICMP)
# ========================================
ping -c 3 <対象IP>
ping -c 3 <対象ホスト名>
# ========================================
# L4 疎通(TCP ポート)
# ========================================
# nc (netcat)
nc -zv <IP> 22
nc -zv <IP> 443
# bash 組み込み(nc がない場合)
timeout 5 bash -c "</dev/tcp/<IP>/22" && echo "OK" || echo "NG"
# telnet
telnet <IP> 22
# ========================================
# DNS 解決
# ========================================
# 基本
dig <hostname>
dig <hostname> +short
# 使用している DNS サーバー
cat /etc/resolv.conf
# 逆引き
dig -x <IP>
# ========================================
# ルーティング
# ========================================
# 経路確認
ip route
ip route get <対象IP>
# traceroute
traceroute <対象IP>
traceroute -n <対象IP> # DNS解決しない
# ========================================
# ファイアウォール(ローカル)
# ========================================
# iptables
sudo iptables -L -n
# firewalld(RHEL/CentOS 7+)
sudo firewall-cmd --list-all
# ufw(Ubuntu)
sudo ufw status verbose
# ========================================
# 現在の接続状況
# ========================================
# 確立中の接続
ss -tnp
netstat -tnp # 古い環境
# 特定ポートへの接続
ss -tnp | grep ":22"
# LISTEN しているポート
ss -tlnp
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| ping は通るが SSH できない | FW で 22 がブロック | nc -zv <ip> 22 |
| 名前解決できない | DNS 設定 | dig, /etc/resolv.conf |
| 接続がタイムアウト | 経路問題 / FW | traceroute |
| 接続が拒否される | サービス停止 / FW | 対象サーバー側を確認 |
次の一手
# 接続できない場合の切り分け
1. ping <ip> # ICMP通るか
2. nc -zv <ip> 22 # TCPポート通るか
3. dig <hostname> # DNS解決できるか
4. traceroute <ip> # どこで止まるか
5. 対象サーバーで ss -tlnp | grep 22 # Listen してるか
8. プロセス / 実行状況
目的
スクリプトやアプリケーションが実行中か、どのように動いているかを把握する
確認コマンド
# ========================================
# プロセスの確認
# ========================================
# 全プロセス一覧
ps aux
# 特定のプロセスを探す
ps aux | grep "script.php"
ps aux | grep "python"
ps aux | grep "node"
# プロセスツリー(親子関係を表示)
pstree -p
pstree -p <PID>
# 特定ユーザーのプロセス
ps -u batch-user
# ========================================
# リアルタイム監視
# ========================================
# top(CPU/メモリ使用率のリアルタイム表示)
top
top -u batch-user # 特定ユーザーのみ
# htop(より見やすい版、要インストール)
htop
# 特定プロセスを監視
watch -n 1 "ps aux | grep script.php"
# ========================================
# プロセスの詳細情報
# ========================================
# PID からプロセス情報を取得
ps -p <PID> -f
# プロセスが使用しているファイル
lsof -p <PID>
# プロセスの環境変数
cat /proc/<PID>/environ | tr '\0' '\n'
# プロセスの起動コマンド
cat /proc/<PID>/cmdline | tr '\0' ' '
# プロセスの作業ディレクトリ
ls -la /proc/<PID>/cwd
# ========================================
# ゾンビ・孤児プロセスの確認
# ========================================
# ゾンビプロセス(Z状態)
ps aux | awk '$8 ~ /Z/'
# 親のいないプロセス(PPID=1)
ps -ef | awk '$3 == 1'
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| プロセスが見つからない | 既に終了している / 別名で動いている | pstree で親子関係を確認 |
| CPU 100% で張り付き | 無限ループ / デッドロック | top で確認、strace -p <PID> |
| プロセスが増え続ける | fork bomb / リーク | ps aux --sort=-%mem |
| cron で起動したはずがいない | すぐ終了している | ログを確認 |
次の一手
# プロセスが動いているか確認する流れ
1. ps aux | grep <name> # まず探す
2. pstree -p <PID> # 親子関係を確認
3. lsof -p <PID> # 使用中のファイルを確認
4. cat /proc/<PID>/environ # 環境変数を確認
9. ディスク / メモリ / システムリソース
目的
ディスク容量・メモリ使用量などシステムリソースの状態を把握する
確認コマンド
# ========================================
# ディスク使用量
# ========================================
# ファイルシステム全体
df -h
# inode 使用量(ファイル数の上限)
df -i
# 特定ディレクトリのサイズ
du -sh /var/log/
du -sh /home/*
# 大きいファイル/ディレクトリを探す
du -ah /var/log | sort -rh | head -20
# 最近更新された大きいファイル
find /var/log -type f -mtime -1 -size +100M
# ========================================
# メモリ使用量
# ========================================
# メモリ概要
free -h
# 詳細なメモリ情報
cat /proc/meminfo
# プロセス別メモリ使用量(上位10)
ps aux --sort=-%mem | head -10
# ========================================
# スワップ
# ========================================
# スワップ使用状況
swapon --show
cat /proc/swaps
# スワップを使っているプロセス
for pid in /proc/[0-9]*; do
awk '/VmSwap/{print FILENAME, $2}' $pid/status 2>/dev/null
done | sort -k2 -rn | head -10
# ========================================
# ロードアベレージ
# ========================================
# 現在の負荷
uptime
cat /proc/loadavg
# CPU コア数(ロードアベレージの基準)
nproc
cat /proc/cpuinfo | grep processor | wc -l
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| ディスクフル | ログ肥大化 / 古いファイル | du -sh /var/log/* |
| inode 枯渇 | 小さいファイルが大量 | df -i, find / -xdev -printf '%h\n' | sort | uniq -c | sort -rn | head |
| メモリ不足 | リーク / キャッシュ肥大 | free -h, ps aux --sort=-%mem |
| ロードアベレージ高い | CPU バウンド / IO wait | top で %wa を確認 |
次の一手
# ディスクフルの調査
1. df -h # どこがフルか
2. du -sh /* 2>/dev/null | sort -rh | head # 大きいディレクトリ
3. find /var -type f -size +100M # 大きいファイル
4. lsof +L1 # 削除済みだが開いているファイル
10. サービス / デーモン管理
目的
バックグラウンドで動作するサービスの状態を把握する
確認コマンド
# ========================================
# systemd(Ubuntu 16.04+, CentOS 7+)
# ========================================
# 全サービス一覧
systemctl list-units --type=service
# 稼働中のサービスのみ
systemctl list-units --type=service --state=running
# 特定サービスの状態
systemctl status nginx
systemctl status php-fpm
systemctl status mysql
# サービスの設定ファイルを確認
systemctl cat nginx
# サービスが有効か(自動起動)
systemctl is-enabled nginx
# 起動に失敗したサービス
systemctl --failed
# ========================================
# サービスのログ
# ========================================
# 特定サービスのログ
journalctl -u nginx
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx -f # リアルタイム
# ブート以降のログ
journalctl -b
# ========================================
# 古い init.d 形式(CentOS 6 以前など)
# ========================================
# サービス一覧
service --status-all
# 特定サービスの状態
service nginx status
# 起動スクリプトの場所
ls -la /etc/init.d/
# ========================================
# ポートとサービスの対応
# ========================================
# どのサービスがどのポートを Listen しているか
ss -tlnp
netstat -tlnp # 古い環境
# 特定ポートを使っているサービス
lsof -i :80
lsof -i :3306
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| サービスが起動しない | 設定エラー / 依存関係 | journalctl -u <service> |
| 再起動後に動かない | enabled になっていない | systemctl is-enabled |
| ポートが使えない | 既に別プロセスが使用中 | lsof -i :<port> |
| 設定変更が反映されない | reload していない | systemctl reload |
次の一手
# サービスが動かない時の調査
1. systemctl status <service> # 状態確認
2. journalctl -u <service> -n 50 # ログ確認
3. systemctl cat <service> # 設定確認
4. ss -tlnp | grep <port> # ポート確認
11. 操作履歴 / コマンド履歴
目的
過去に誰がどんな操作をしたかを調査する(トラブルの原因特定・引き継ぎ)
確認コマンド
# ========================================
# コマンド履歴
# ========================================
# 自分の履歴
history
history | tail -50
# 履歴ファイルを直接確認
cat ~/.bash_history
# 他のユーザーの履歴(root権限必要)
sudo cat /home/batch-user/.bash_history
sudo cat /root/.bash_history
# 全ユーザーの履歴を一括確認
for user in $(ls /home); do
echo "=== $user ==="
sudo cat /home/$user/.bash_history 2>/dev/null | tail -20
done
# ========================================
# 履歴の検索
# ========================================
# 特定コマンドを検索
history | grep "crontab"
history | grep "ssh"
history | grep "rm"
# 他ユーザーの履歴を検索
sudo grep "deploy" /home/*/.bash_history
# ========================================
# sudo の履歴
# ========================================
# sudo で実行されたコマンド
sudo cat /var/log/auth.log | grep sudo
sudo cat /var/log/secure | grep sudo # RHEL/CentOS
# 最近の sudo 実行
sudo journalctl _COMM=sudo --since "1 day ago"
# ========================================
# ファイルの変更履歴
# ========================================
# 最近変更されたファイル
find /etc -mtime -7 -type f
find /var/www -mtime -1 -type f
# 特定ファイルの最終更新者(auditd が有効な場合)
ausearch -f /etc/nginx/nginx.conf
# ========================================
# ログイン履歴
# ========================================
# 誰がいつログインしたか
last | head -30
# 現在ログイン中
who
w
よくあるハマり
| 症状 | 原因 | 確認方法 |
|---|---|---|
| 履歴が消えている | HISTSIZE=0 / 手動削除 | .bashrc の設定確認 |
| 誰が設定を変えたか分からない | 履歴に残っていない | last + ファイルの mtime |
| sudo の履歴がない | ログ設定 | /var/log/auth.log を確認 |
次の一手
# 「誰がこのファイルを変更した?」を調べる
1. stat <file> # 最終更新日時
2. last | grep "<その日時頃>" # その頃ログインしていた人
3. sudo cat /home/<user>/.bash_history | grep "<ファイル名>"
4. sudo cat /var/log/auth.log | grep "<user>" # sudo 履歴
具体例:batch-user監視スクリプトの調査
シナリオ
監視スクリプト:
- html/cron/check_log_end.php
- /usr/local/tmp/log_check/*.ini を読む
- ssh <ip> -o 'ConnectTimeout=5' tail -n 1 <logName> で最終行取得
- 正常終了メッセージがなければ Slack 通知
SSHユーザー:batch-user
問題:「たまに Slack 通知が来ない」
調査手順
Step 1:スクリプトの場所と中身を確認
# スクリプトの確認
cat /var/www/html/cron/check_log_end.php
# INI ファイルの読み込み箇所を探す
grep -n "\.ini" /var/www/html/cron/check_log_end.php
# → "/usr/local/tmp/log_check/*.ini" を読んでいることを確認
Step 2:INI ファイルの確認
# INI ファイルの一覧
ls -la /usr/local/tmp/log_check/
# 内容確認
cat /usr/local/tmp/log_check/targets.ini
# 例:
# [production-worker]
# host = 192.168.1.100
# log = /var/log/worker/batch.log
# success_pattern = "BATCH COMPLETED"
# パーミッション確認(batch-user が読めるか)
sudo -u batch-user cat /usr/local/tmp/log_check/targets.ini
Step 3:cron の確認
# batch-user の crontab
sudo crontab -l -u batch-user
# 例:
# */5 * * * * /usr/bin/php /var/www/html/cron/check_log_end.php >> /var/log/check_log/cron.log 2>&1
# ログの確認
sudo tail -100 /var/log/check_log/cron.log
Step 4:SSH 設定の確認
# batch-user の SSH 設定
sudo cat /home/batch-user/.ssh/config
# 例:
# Host production-worker
# HostName 192.168.1.100
# User batch-user
# IdentityFile ~/.ssh/id_ed25519
# ConnectTimeout 5
# 鍵の確認
sudo ls -la /home/batch-user/.ssh/
# known_hosts の確認
sudo cat /home/batch-user/.ssh/known_hosts | grep "192.168.1.100"
Step 5:SSH 接続テスト
# batch-user として接続テスト
sudo -u batch-user ssh -o ConnectTimeout=5 production-worker "echo OK"
# 詳細ログ
sudo -u batch-user ssh -vvv production-worker exit 2>&1 | head -50
# 実際のコマンドをテスト
sudo -u batch-user ssh production-worker "tail -n 1 /var/log/worker/batch.log"
Step 6:ネットワーク疎通
# ping
ping -c 3 192.168.1.100
# SSH ポート
nc -zv 192.168.1.100 22
調査結果の例
■ 原因特定
1. /home/batch-user/.ssh/known_hosts に 192.168.1.100 が登録されていなかった
2. 初回接続時に「Are you sure you want to continue connecting?」で止まっていた
3. cron はインタラクティブではないので、ここで失敗していた
■ 解決策
sudo -u batch-user ssh-keyscan 192.168.1.100 >> /home/batch-user/.ssh/known_hosts
付録:深掘り調査テクニック
ssh -G で実際に解決される設定を見る
# ssh -G <host> で、config を解決した結果を表示
ssh -G production-worker
# 出力例:
# user batch-user
# hostname 192.168.1.100
# port 22
# identityfile /home/batch-user/.ssh/id_ed25519
# connecttimeout 5
# → コードに書かれた host名 が、実際にどの IP・ユーザー・鍵で接続されるか分かる
ssh -vvv の読み方
ssh -vvv user@host exit 2>&1 | less
# 見るべきポイント:
# 1. 使用される鍵
debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519
# 2. 認証結果
debug1: Authentication succeeded (publickey).
# または
debug1: Authentications that can continue: publickey
debug1: No more authentication methods to try. # ← 失敗
# 3. known_hosts
debug1: Host 'hostname' is known and matches the ED25519 host key.
# または
Host key verification failed. # ← known_hosts にない
# 4. 接続先
debug1: Connecting to hostname [192.168.1.100] port 22.
cron で動かない時の典型パターン
# ========================================
# パターン1:PATH が通っていない
# ========================================
# 症状
/usr/bin/php: not found
# 確認
crontab -l
# → php とだけ書いている
# 解決
# crontab の先頭に追加
PATH=/usr/local/bin:/usr/bin:/bin
# ========================================
# パターン2:相対パスで動かない
# ========================================
# 症状
require_once('./config.php'): failed to open stream
# 確認
# cron は / ディレクトリで実行される
pwd # → /
# 解決
cd /var/www/html && /usr/bin/php script.php
# または
/usr/bin/php /var/www/html/script.php(絶対パス)
# ========================================
# パターン3:SSH 鍵が見つからない
# ========================================
# 症状
Permission denied (publickey).
# 確認
sudo -u batch-user ssh -vvv host exit
# → 鍵のパスが違う / 権限が違う
# 解決
# ~/.ssh/config で IdentityFile を絶対パスで指定
# ========================================
# パターン4:known_hosts に登録がない
# ========================================
# 症状
Host key verification failed.
# 確認
grep "hostname" ~/.ssh/known_hosts
# 解決
ssh-keyscan hostname >> ~/.ssh/known_hosts
# ========================================
# パターン5:環境変数がない
# ========================================
# 症状
Error: DATABASE_URL is not set
# 確認
# cron は .bashrc を読まない
# 解決
# crontab 内で export
DATABASE_URL="postgres://..." /usr/bin/php script.php
# または
source /etc/environment && /usr/bin/php script.php
「repo外運用」を減らすための改善ロードマップ
Phase 0: 現状把握(この記事)
└─ どこに何があるか調査・ドキュメント化
Phase 1: README 化
└─ 設定ファイルの場所・cron の内容をリポジトリに記載
└─ docs/operations.md を作成
Phase 2: リポジトリ管理化
└─ crontab を cron.d ファイルとしてリポジトリ管理
└─ SSH config をテンプレート化(機密情報は除く)
└─ 設定ファイルをリポジトリに含める
Phase 3: 構成管理ツール導入
└─ Ansible / Chef / Puppet で設定をコード化
└─ サーバー構築の再現性を確保
Phase 4: 監視基盤への移行
└─ Prometheus + Alertmanager
└─ Datadog / Mackerel
└─ cron + SSH 監視からの脱却
調査結果メモテンプレート
# 調査結果メモ
## 基本情報
- 調査日時:YYYY-MM-DD HH:MM
- 調査者:
- 対象サーバー:
- 対象スクリプト:
## 1. スクリプト情報
- 場所:
- 実行ユーザー:
- 設定ファイル:
- パス:
- 権限:
- 内容(要約):
## 2. Cron 情報
- crontab 内容:
(ここに crontab -l の結果)
- ログ出力先:
- 最終実行時刻:
## 3. SSH 情報
- SSH config:
(ここに ssh -G host の結果)
- 鍵ファイル:
- known_hosts 登録:あり / なし
- 接続テスト結果:
## 4. ネットワーク
- 対象IP:
- ping:OK / NG
- port 22:OK / NG
- DNS解決:OK / NG
## 5. 発見した問題
1.
2.
3.
## 6. 解決策・対応内容
1.
2.
3.
## 7. 残課題・今後の改善
- [ ]
- [ ]
まとめ
この記事で伝えたかったこと
-
「コードを見ても分からない」のは、あなたのせいではない
- repo外に運用が埋まっている構造が問題
- 見える化されていないことを、技術で可視化する
-
最初の15分で8割は潰せる
- whoami → crontab → SSH config → ログ → ネットワーク
- この順番で確認すれば、大体の問題は特定できる
-
すべて read-only で安全に確認できる
- 本番でも安心して実行できるコマンドだけを使う
- 「調査したら壊れた」を防ぐ
-
調査結果を残す習慣が、次の人を救う
- メモテンプレートを使って、調査結果をドキュメント化
- 「repo外運用」を減らす第一歩
最後に
「1年目で知るだろ」と言われて悔しい思いをしたことがあるなら、この記事のチェックリストを使って、逆にマウントを取り返してほしい。
知識は武器だ。見える化されていないものを見える化できるエンジニアは、現場で重宝される。
この記事を保存して、次に「動かない」と言われた時に使ってほしい。
設計判断の背景
レガシー運用の調査は「知っているかどうか」で大きく効率が変わる。この記事では、経験則に頼らず「チェックリスト化」することで、誰でも同じ調査ができるようにした。特に「cron 環境の罠」「SSH の known_hosts 問題」は、初見で詰まる人が多いポイントなので重点的に解説した。
現場での判断基準
調査を依頼されたら、まず「そのスクリプトを誰が・いつ・どのように実行しているか」を確認する。これが分かれば、問題の8割は特定できる。コードだけ見て悩む時間を減らし、実行環境を見る時間を増やすことで、調査効率は劇的に上がる。
見るべきポイント
他のエンジニアの調査を見ていて「コードばかり見ている」場合は、「crontab -l と ~/.ssh/config は見た?」と聞くようにしている。この2つを見るだけで、大抵の「動かない」問題は解決への糸口が見つかる。