実際に打つ 104 コマンド、引数は一つずつ解説 —— 「何をしたいか」で分類
検索・一括処理・アーカイブ — オプションを間違えると、たいていは消すべきでないファイルを消すか、隠しディレクトリを取りこぼす
-l 長形式:パーミッション・所有者・サイズ・時刻を表示
-a . と .. を含め、すべての隠しファイルも表示
-h サイズに単位を付ける(1234 ではなく 1.2K)。-l と併用必須
-t 更新時刻の降順、新しいものが上
-r 並び順を反転。-t と組み合わせると古いものが上
-S サイズの降順 — 「誰が容量を食っているか」は -t より直接的
--time-style=long-iso 時刻を 2026-10-29 09:18 形式で表示。既定より読みやすい
# 最新のファイルが上に来る(サイズと時刻付き) ls -laht --time-style=long-iso # 容量を食っているものを探す(-S) ls -lhS /var/log | head
-name はファイル名で一致(大文字小文字を区別)。-iname は区別しない
-type f は通常ファイルのみ、-type d はディレクトリのみ、-type l はシンボリックリンク
-mtime +7 は更新から 7 日超。-mmin -30 は 30 分以内に更新
-size +50M は 50M 超、-size -1k は 1k 未満
-maxdepth 1 はサブディレクトリへ再帰しない。調査時はまずこれを付けると桁で速くなる
-ls は結果に ls -dils を実行し、サイズ・inode・時刻が一度に出る
-delete は直接削除(空ディレクトリのみ)。-exec ... \; は各結果に対してコマンドを実行
-prune は特定ディレクトリを飛ばす。-path との併用が必須
-o は「または」。条件を連ねるときに使う。-print は明示出力(-prune と併用時は必須)
# 30分以内に更新された大きいファイル(ディスク急増の調査) find /var -type f -mmin -30 -size +50M -ls # 7日前のログを一括削除 find /var/log -name '*.log.*' -mtime +7 -delete # .git と node_modules を除外して検索 find . -path ./node_modules -prune -o -name '*.js' -print
-r はディレクトリを再帰。-R は再帰かつシンボリックリンクを辿る
-i は大文字小文字を無視、-n は行番号を表示、-v は反転で不一致行のみ
-E は拡張正規表現(| は必ず -E。でないとリテラル扱い)
-l はファイル名のみ出力。まず絞り込んでから中身を見るのに向く
-A3 / -B3 / -C3 は一致行の後 3 行 / 前 3 行 / 前後 3 行を表示
--include='*.conf' は指定した拡張子のみ検索、--exclude-dir はディレクトリを飛ばす
-w は単語単位の一致。cat で category まで拾うのを防ぐ
-rn は最頻出の組み合わせ:再帰 + 行番号。これがないとどこを見るか分からない
# nginx 設定から特定の upstream を前後3行付きで探す grep -rn -C3 'upstream' /etc/nginx --include='*.conf' # 複数キーワードのいずれかに一致(-E 必須) grep -rnE 'timeout|refused|reset' /var/log/app/error.log # 設定を含むファイル名だけを列挙し、あとで個別に確認 grep -rli 'proxy_pass' /etc/nginx
-c は作成、-x は展開、-t は内容表示のみ、-z は gzip、-j は bzip2
-f の直後がファイル名なので、-f は常に最後に置く
-v は処理内容を列挙。大きいアーカイブでは非常に遅くなるのでリダイレクトする
-C は指定ディレクトリへ展開。ディレクトリは事前に存在している必要がある
--exclude は作成時の除外。相対パスの書き方が一致するか注意
-czf / -xzf / -tzf が最頻出の 3 つ。この 3 つを覚えれば足りる
展開前に -t で一度中身を見る。tar 爆弾でカレントディレクトリを上書きされるのを防げる
# キャッシュディレクトリを除外してアーカイブ tar -czf app.tar.gz --exclude='node_modules' --exclude='.git' -C /srv app # まず中身を確認し、指定ディレクトリへ展開 tar -tzf app.tar.gz | head -20 tar -xzf app.tar.gz -C /tmp/restore
rm -rf は再帰かつ強制削除で確認なし — 変数が空だと rm -rf "$DIR/" は rm -rf / になる
-f は強制でプロンプトなし、-r はディレクトリ再帰。この 2 つが揃うと最も事故りやすい組み合わせ
cp -a はアーカイブモード。パーミッション・時刻・シンボリックリンクを保持し、バックアップの第一選択
cp -n は既存ファイルを上書きしない。mv -n も同じ(-n は no-clobber)
mv はファイルシステムを跨ぐと「コピー + 削除」でアトミックでない。中断すると両側が中途半端になる
rm で消したファイルをプロセスが掴んでいると、そのプロセスが終了するまで容量は解放されない(ディスク調査を参照)
test -n "$DIR" は変数が非空かの判定。rm の前に必ず行う保護
-- は「以降はオプションではない」と伝える。- で始まるファイル名が引数扱いされるのを防ぐ
# 安全な削除:先に変数が空でないことを確認してから削除 [ -n "$DIR" ] && rm -rf -- "$DIR" # 属性を保持した完全バックアップ cp -a /srv/app /backup/app-$(date +%F)
df -h は各マウントポイントの使用率、-i は inode 使用率(inode 枯渇でも書き込みは失敗する)
du -sh は 1 ディレクトリの合計。-s は合計のみ、-h は人間が読める単位
du -sh * | sort -h でカレントディレクトリの容量順。sort -h は 1K/2M/3G を正しく並べる
--max-depth=1 は 1 階層のみ。全再帰より大幅に速い
du と df の数字が大きくずれるなら、ほぼ「削除済みだがプロセスが掴んでいる」ファイル
2>/dev/null は権限不足のエラーを捨てる。でないと出力が埋め尽くされる
# ルートから階層ごとに掘り下げ、急に大きくなった階層に入って確認 du -h --max-depth=1 / 2>/dev/null | sort -h | tail -10 # 削除済みだがプロセスが掴んでいるファイル(df は満杯なのに du が小さいとき) lsof +L1 2>/dev/null | head
ln -s ターゲット リンク名 — 引数は「ターゲットが先」。逆にすると壊れたリンクができる
-s はシンボリックリンク。付けないとハードリンク(ファイルシステム跨ぎ不可、ディレクトリ不可)
-f は既存リンクを上書き
-n はリンク先がディレクトリのとき、-f と併用して中に潜らず置き換える
-sfn はリリース切り替えの定番(current を新しい release へ向ける)
シンボリックリンクはパス文字列を保持するのでターゲット移動で壊れる。ハードリンクは同じ inode を指し、片方を消しても他方は無事
ln -sfn /srv/app/releases/20261029 /srv/app/current
stat はサイズ・inode・パーミッション・3 つの時刻を一度に出す — 「本当に変更されたか」はこれが最短
-c は独自フォーマット:%y 更新時刻、%s バイト数、%U:%G 所有者、%a 8 進パーミッション、%n ファイル名
-L はシンボリックリンクを辿りターゲットを見る(付けないとリンク自体を見る)
3 つの時刻:Access は読取、Modify は内容変更、Change はメタデータ(パーミッション/所有者)変更
-f はファイルシステム層の情報(ブロックサイズ、inode 総数と残数)。df と相補的
touch -d '2026-10-01 08:00' ファイル で更新時刻を変更。find -newer と組み合わせて変更範囲を絞る
file は実際の型を判定する。拡張子より信頼できる — 拡張子はいくらでも書き換えられる
stat -c '%a' は「パーミッションが実際に何ビットか」を確認する最速の書き方
stat -c '%y %s %U:%G %a %n' /etc/nginx/nginx.conf stat -L -c '%y %s' /srv/app/current # シンボリックリンクが指す実体を確認 file /usr/local/bin/app
設定変更・列単位の集計・コマンド組み立て — 運用で「200 行の設定を手で直す」類はすべて sed/awk に任せる
-i はファイルを直接書き換える(インプレース)— 本番ではまず -i なしで実行して出力を確認してから変更する
-i.bak は変更と同時に .bak を残す。問題が出たら即座に戻せる(習慣にすることを強く推奨)
-n は自動出力しない。p と組み合わせて指定行だけ出力、「数行を見る」に相当
-E は拡張正規表現(+ ? | ( ) をエスケープ不要)。古いシステムでは -r で同じ意味
-e は複数式を連ねる。1 つの式の中でセミコロンで区切ってもよい
s/旧/新/g の g は「1 行内のすべてを置換」。g なしは各行の最初の 1 箇所のみ
アドレス範囲:'/^server/,/}/ s/a/b/' は特定の設定ブロック内だけ変更
\1 \2 はキャプチャグループの参照、& はマッチ全体
-i の後には -E か式を置く。順序に注意:sed -E -i.bak 's/x/y/' ファイル
sed -n '1,20p' /etc/nginx/nginx.conf # 先頭20行だけ表示 sed -i.bak 's/listen 80;/listen 8080;/' /etc/nginx/sites-enabled/app sed -E -i.bak 's/^server_name[[:space:]]+([^;]+);/server_name \1.bak;/' app.conf sed '/^[[:space:]]*#/d;/^[[:space:]]*$/d' app.conf | head # コメントと空行を除去
-F は区切り文字の指定:-F: はコロン区切り、-F'\t' はタブ区切り、未指定なら連続空白
'{print $1,$3}' は 1 列目と 3 列目。$0 は行全体、NF は列数、NR は行番号
条件フィルタはプログラムの前に書く:'$9==500' は 9 列目が 500
数値比較は数値に変換する:$9+0 >= 500。でないと文字列比較になり予期しない結果になる
'{s+=$7} END {print s}' は合計のテンプレート。件数は '{c++} END {print c}'
配列集計:{c[$1]++} END {for (k in c) print k, c[k]}
-v は外部変数の受け渡し:awk -v lim=500 '$3>lim'。-v はプログラムより前に置く
-F と -v は awk 自身が解釈するので、引用符で囲んだプログラムより前に必須
例の sort -rn は数値の降順、uniq -c は件数集計、ss -tn は TCP 接続(各オプションは該当項目を参照)
awk -F: '$3>=1000 {print $1, $3}' /etc/passwd
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
awk -v lim=500 '{ if ($9+0 >= lim) print NR": "$7 }' access.log
ss -tn | awk 'NR>1 {c[$1]++} END {for (k in c) print k, c[k]}'sort -n は数値順(既定の辞書順では 10 が 9 より前に来る)
sort -h は 1K/2M/3G の単位を解釈する。du / df の出力には必須
sort -r は降順、-u は並べ替えと同時に重複除去
-k2 は 2 列目で並べ替え、-k3 も同様。複数列は -k1,1 -k2,2
-t: は区切り文字をコロンに指定(既定は空白区切り)
uniq -c は連続する重複の件数 — だから先に sort が必須。でないと同じ値が複数ブロックに分かれる
uniq -d は重複行のみ、uniq -u は一意行のみ
Top N の定石:sort | uniq -c | sort -rn | head
例の du -sh の -s は合計のみで子項目を出さない(du / df の項目を参照)
du -sh /var/log/* | sort -h | tail -5
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
sort -t: -k3 -n /etc/passwd | headcut -d: -f1,7 は区切り文字で列を取る。-d が区切り、-f が列番号(複数可)
cut -c1-19 は文字位置で切る。固定長ログのタイムスタンプ部分の取得に向く。1〜19 文字目という意味
cut は単一の区切り文字しか扱えない。空白で桁揃えされた出力は誤って切れるので awk を使う
tr 'a-z' 'A-Z' は文字単位のマッピング。tr は 1 対 1 の置換のみで、文字列置換は sed を使う
tr -d は文字削除。最頻出は tr -d '\r' で Windows の改行を除去
tr -s は連続する同一文字を 1 つに圧縮。複数空白を 1 つに畳むのによく使う
tr は標準入力からしか読めず、ファイル名は直接渡せない。< でリダイレクトする
例の df -h は人間が読める単位、cut -f5 は 5 列目(Use%)の取得、sort -u は重複除去。詳細は各項目
cut -d: -f1,7 /etc/passwd | head cut -c1-19 /var/log/nginx/access.log | head -3 tr -d '\r' < crlf.txt > lf.txt df -h | tr -s ' ' | cut -d' ' -f5 | sort -u
xargs は標準入力の各行を後続コマンドの引数にする。find | xargs rm はこれ
-0 は find -print0 と併用。ファイル名に空白や改行があっても分割されない(ユーザー投稿の処理では必須)
find -print0 は \0 終端で出力する。xargs -0 専用
-n1 は 1 回に 1 引数だけ渡す。1 件ずつ実行するので安全で、どれが失敗したかも分かる
-I {} はプレースホルダ。引数をコマンドの途中に置ける:xargs -I {} cp {} /backup
-p は実行前に確認、-t は実行前にコマンドを表示 — 何が組み立てられるか不明なときは -t で一度見る
-r は入力が空なら実行しない(GNU 拡張)。でないと空入力でも rm が 1 回走る
-P4 は 4 並列。一括圧縮やダウンロードで数倍速くなる
cp --parents はディレクトリ構造ごとコピーする。バックアップの復元で役立つ
例の find の -name / -mtime / -type は絞り込み条件、rm -f の -f は強制でプロンプトなし
find /var/log -name '*.log.*' -mtime +7 -print0 | xargs -0 -r rm -f
find /srv/uploads -type f -print0 | xargs -0 -I {} cp --parents {} /backup
printf '%s\n' a b c | xargs -n1 -t echotee は入力をファイルと標準出力の両方に書く。設定変更前に証跡を残すのに最適
-a は上書きではなく追記。ログ類は必ずこれ
sudo tee が必要な理由:sudo echo x > /etc/f のリダイレクトは現在のシェルが行うため権限で失敗する
-i は割り込み信号を無視。長い出力の取得中に Ctrl-C で止められなくなる
パイプに渡す前に 2>&1 で stderr をまとめる。でないとエラーが tee に届かない
コマンドにタイムスタンプを付ける:... | ts または | awk '{print strftime("%F %T"), $0}'
./deploy.sh 2>&1 | tee -a /var/log/deploy-$(date +%F).log echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
jq は API レスポンスの JSON を扱う。grep よりはるかに確実 — 行一致ではなく構造で取り出す
. はフィールド取得:.data .items[0].name。.[] は配列の走査
-r は生の文字列を出力(二重引用符を外す)。変数や後続パイプに渡すときは必須
-c は各結果を 1 行に圧縮。sort / uniq での集計に渡しやすい
'keys' はオブジェクトのフィールド一覧、'length' は配列長、'..' は全パスの列挙(構造不明時の形の把握)
select() で絞る:'.[] | select(.status != "ok")'
-C は色付き(人が見る用)、-M は単色(ファイルやスクリプト用)
jq は既定で整形出力。-e は「結果が真か」を終了コードで表現し、スクリプトの判定に使う
例の curl -s は静音。でないとプログレスバーが JSON 解析に混ざる(curl の項目を参照)
curl -s https://api.example.com/v1/tools | jq -r '.data[].name'
curl -s 'https://api.example.com/v1/list?page=1' | jq -c '.items[] | {id, name}'
jq -r '.[] | select(.status != "ok") | .id' resp.jsondiff -u は unified 形式で、そのままパッチ形式。patch が直接適用できる
diff -r は 2 つのディレクトリを再帰比較、-q は「どのファイルが違うか」だけを報告
diff -y は左右並列表示、-W 200 は幅の指定。設定を目視比較するときに便利
wc -l は行数(ログ件数の集計で最頻出)、-c はバイト数、-w は単語数
wc の出力にはファイル名が付く。スクリプトで値を使うときは列が増えるので awk '{print $1}'
comm は「2 つの整列済みリストの差集合」に向くが、両方とも先に sort が必要
comm -13 a b は b にしかない行を出力。-12 との列の意味の違いに注意
比較前に両側を sort して重複除去する。でないと「全部違う」は順序違いなだけ
diff -u /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf | head -40 diff -rq /srv/app/releases/20261028 /srv/app/releases/20261029 wc -l /var/log/nginx/access.log sort a.txt > a.s; sort b.txt > b.s; comm -13 a.s b.s # b にしかない行
ログイン・鍵認証・転送・レジューム — 日常で最も時間を食うのは「どうやってファイルを送るか」の部分
ssh ユーザー@ホスト が基本のログイン。既定は 22 番ポート
-p 2222 はポート指定(注意:scp/sftp は大文字 -P。混同しやすい)
-i は秘密鍵の指定。既定では ~/.ssh/id_ed25519、id_rsa を探す
-t は疑似端末を強制割り当て — リモートで sudo や top などの対話コマンドを動かすなら必須
-v はハンドシェイクと認証の過程を出力。繋がらないとき「ネットワーク不通」か「認証失敗」かが分かる。-vvv はさらに詳細
-o は設定項目の透過指定:-o ConnectTimeout=5 でハング防止、-o BatchMode=yes で対話を全面禁止(スクリプトでは必須)
リモートで 1 コマンドだけ実行:ssh ホスト 'コマンド'。端末を割り当てないので一括点検に向く
-J は踏み台:ssh -J bastion@1.2.3.4 deploy@10.0.0.5(旧記法は -o ProxyCommand)
例の df -h の -h は人間が読める単位(du / df の項目を参照)
ssh -p 2222 -i ~/.ssh/id_ed25519 deploy@10.0.0.5 ssh -o ConnectTimeout=5 -o BatchMode=yes 10.0.0.5 'uptime; df -h /' ssh -v -t 10.0.0.5 'sudo systemctl restart app' ssh -J bastion@1.2.3.4 deploy@10.0.0.5
ssh-keygen -t ed25519 は鍵ペアの生成(rsa より短く速く、現行システムはすべて対応)
-t は種類:ed25519 が第一選択、rsa は古いシステムでの止むを得ない選択で -b 4096 と併用
-C はコメント。用途やメールを書いておくと、鍵が複数あるときに区別できる
-f は出力ファイル名。既定鍵の上書きを避ける(上書きすると既存の鍵認証が壊れる)
-N '' はパスフレーズなし。自動化用。対話ならそのまま Enter でもよい
ssh-copy-id は公開鍵を相手の ~/.ssh/authorized_keys に追記し、ディレクトリとファイルのパーミッションも直す
-i(ssh-copy-id の -i)はコピーする公開鍵の指定。既定では id_*.pub のみ送る
-R は known_hosts から該当ホストの古い指紋を削除 — 再インストール後は必ず実行。でないと REMOTE HOST IDENTIFICATION HAS CHANGED になる
-lf は公開鍵の指紋と種類を表示。2 台のマシンが同じ鍵かを確認する
秘密鍵は 600、ディレクトリは 700 必須。でないと ssh が使用を拒否する
ssh-keygen -t ed25519 -C 'deploy@icetools' -f ~/.ssh/id_deploy -N '' ssh-copy-id -i ~/.ssh/id_deploy.pub deploy@10.0.0.5 ssh-keygen -R 10.0.0.5 ssh-keygen -lf ~/.ssh/id_deploy.pub
設定後は ssh app が ssh -p 2222 deploy@10.0.0.5 と同じになる。毎回長い文字列を打たなくて済む
Host は別名であり、以降の設定が有効になる範囲
HostName は実アドレス、User はログインユーザー、Port はポート、IdentityFile は秘密鍵のパス
IdentitiesOnly yes は指定した鍵だけを使う。agent に大量の鍵があると順に試してロックされるのを防ぐ
ProxyJump は踏み台。コマンドラインの -J と等価で、scp / rsync にも自動で継承される
ServerAliveInterval 30 は 30 秒ごとに heartbeat。NAT やクラウド LB はアイドル接続を切るので長い処理では必須
ServerAliveCountMax 3 は heartbeat が 3 回失敗したら切断。上の間隔と併用
Host * はファイル末尾に置き、グローバル既定(heartbeat、タイムアウト)を書く
パーミッション:config は 600、~/.ssh は 700。でないと Bad owner or permissions になる
# ~/.ssh/config (chmod 600)
Host app
HostName 10.0.0.5
User deploy
Port 2222
IdentityFile ~/.ssh/id_deploy
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 3
Host db
HostName 10.0.0.6
ProxyJump app
Host *
ServerAliveInterval 30-L はローカル転送:ssh -L 8080:127.0.0.1:80 ホスト でリモートの 80 をローカルの 8080 に写す
-R はリバース転送:ローカルポートをリモート側に公開し、外部から内部サービスへ到達させる
-D は動的転送。SOCKS プロキシを立てる(curl --socks5 やブラウザと組み合わせる)
-N は転送のみでシェルを開かない、-f はバックグラウンド化。ほぼ常にセットで使う
-g は他のマシンからローカル転送ポートへの接続を許可(既定は 127.0.0.1 のみ待ち受け)
-L の中間アドレスは「リモート側の視点」で解決される。127.0.0.1 はリモート自身を指す
「DB に直接繋がせない」場面:まず -L でポートをローカルへ引き、127.0.0.1 に接続する
ローカルポートが埋まっていたら別のポートにする。lsof -i :8080 で誰が使っているか確認
ssh -N -f -L 15432:127.0.0.1:5432 deploy@10.0.0.5 ssh -N -L 8080:10.0.0.6:80 app ssh -D 1080 -N -f app ssh -g -L 8080:127.0.0.1:80 app
scp コピー元 コピー先 — 向きでアップロードかダウンロードが決まる。ローカルが先ならアップロード、リモートが先ならダウンロード
-P は大文字でポート指定(ssh は小文字 -p。最も混同しやすい箇所)
-r はディレクトリ全体を再帰。-r なしでディレクトリを渡すと即エラー
-i は秘密鍵、-o は ssh オプションの透過(例:-o ConnectTimeout=5)
-C は転送時圧縮。テキストやログでは効果が大きい。既に tar.gz のものには無意味
-l 8000 は速度制限。単位は Kbit/s(8000 ≈ 1MB/s)。本番帯域を埋めない
-p は小文字でタイムスタンプとパーミッションを保持。-P とは無関係なので混同しない
scp にレジュームはない。大容量は 1 回切れると最初から。数 GB なら rsync -P を使う
scp -P 2222 app.tar.gz deploy@10.0.0.5:/srv/ scp -r -i ~/.ssh/id_deploy ./dist deploy@10.0.0.5:/srv/app/ scp -C -l 8000 backup.tar.gz deploy@10.0.0.5:/backup/ scp -p deploy@10.0.0.5:/var/log/app/error.log ./
sftp ユーザー@ホスト で対話セッションに入る — 「ls しながら転送」は盲目的な scp より制御しやすい
-P は大文字でポート、-i は秘密鍵。scp と同じ
get リモートファイル はダウンロード、put ローカルファイル はアップロード。get -r はディレクトリ全体
reget はダウンロードのレジューム、reput はアップロード側 — ここが sftp の scp に対する強み
-p(get/put の -p)はパーミッションとタイムスタンプを保持
mget / mput はワイルドカードで一括転送できるが、既定では各ファイルで確認が入る。先に prompt off
ls / lls はリモートとローカルの一覧、cd / lcd はそれぞれのディレクトリ移動(l 付きがローカル)
!df -h は ! で始めてローカルのシェルコマンドを実行。大きいファイルを取る前に空きを確認
bye / exit で終了。heredoc でスクリプト化すれば非対話で一括転送できる
sftp -P 2222 -i ~/.ssh/id_deploy deploy@10.0.0.5 <<'EOF' get -r /srv/app/logs ./ reget backup.tar.gz put ./dist/app.tar.gz /srv/ EOF
-a はアーカイブモード(= -rlptgoD)。パーミッション・時刻・シンボリックリンク・グループを保持し、日常の第一選択
-v は経過表示、-z は転送時圧縮。公網越しなら必須
-P は --partial --progress と等価:途中ファイルを保持し進捗を表示 — これが「レジューム」のスイッチ
--partial は中断時に転送済み部分を保持し、次回続きから。なしだと毎回ゼロから
--append は追記のみ。増えるだけのログに向き、内容が変わるファイルには不適
--delete は転送先を転送元と完全一致させる(余分なファイルは削除)— まず -n で試す
-n は --dry-run と同義:何をするか表示するだけでファイルは触らない
-e 'ssh -p 2222' はリモートシェルとポートの指定。rsync 自身にポート引数はない
--exclude は 1 条件の除外、--exclude-from はファイルから複数条件を読む
--bwlimit=2000 は速度制限(KB/s)。--checksum はサイズと時刻でなく内容で差分判定
--info=progress2 は全体進捗を 1 行で表示。-P のファイル単位より大きいディレクトリに向く
【最大の罠】コピー元のパス末尾に / があるとディレクトリの中身をコピー、ないとディレクトリ自体をコピー
rsync -avzP ./dist/ deploy@10.0.0.5:/srv/app/dist/ rsync -avzP -e 'ssh -p 2222' deploy@10.0.0.5:/var/log/app/ ./logs/ rsync -avn --delete /srv/data/ /backup/data/ # ドライラン、内容を確認してから本番実行 rsync -avz --bwlimit=2000 --exclude='*.tmp' ./ /mnt/nas/app/
wget URL はカレントディレクトリへ直接ダウンロード。-O は出力ファイル名、-O - は標準出力へ
-c はレジューム(curl では -C -)。大容量が落ちたら同じコマンドを再実行すれば続きから
-q は静音モード。--progress=dot:giga は進捗表示を簡潔にする
--limit-rate=1m は速度制限。k/m 接尾辞に対応。--tries=3 はリトライ回数
-T 30 は接続タイムアウト秒。ハング防止。長い記法は --timeout
-r はサイト全体の再帰取得、-np は親ディレクトリへ遡らない(うっかりドメイン全体を落とすのを防ぐ)
--no-check-certificate は証明書検証を飛ばす。内部サイトの診断時のみ。スクリプトに書かない
独自ヘッダ、Cookie、ログイン状態が必要なら wget より curl が柔軟
wget -c https://example.com/big.iso wget -O - https://example.com/install.sh | head -40 # 中身を確認してから実行するか判断 wget --limit-rate=1m --tries=3 -c -O app.tar.gz https://example.com/app.tar.gz
数値モード・特殊ビット・sudo — 設定ミスの典型症状は「パスワードは合っているのに入れない」と「サービスが起動しない」
数値:4 読取 / 2 書込 / 1 実行。3 桁は順に 所有者 / グループ / その他
755 はディレクトリの定番(rwxr-xr-x)、644 はファイルの定番(rw-r--r--)
600 は所有者のみ読み書き。秘密鍵は必ずこれ
シンボル:u/g/o/a が対象、+/-/= が動作、X は「ディレクトリにのみ実行ビット」
-R は再帰。ディレクトリへ再帰的に x を付けるなら a+X が安全。通常ファイルに実行ビットを付けない
go-w はグループとその他の書込権を外す。a+X と同時に指定すれば 2 種類を一度に変更
再帰変更後は ls -l でファイルとディレクトリを 1 つずつ抜き取り確認。ミスの多くは再帰で変えすぎ
chmod 600 ~/.ssh/id_rsa chmod -R a+X,go-w /srv/app/public ls -l /srv/app/public | head # 再帰変更後にスポットチェック
chown ユーザー:グループ パス。区切りはコロンの代わりにドットも可
グループだけ変えるなら chown :グループ または chgrp
-R は再帰。-h を足すとリンク自体だけを変更しターゲットは変えない
--reference=元ファイル は所有者とパーミッションを丸ごと複写。一括同期に便利
所有者を変えられるのは root のみ。一般ユーザーは自分のファイルのグループのみ(かつ所属グループ内)
コンテナのボリュームで Permission denied が出たら、まずホスト側ディレクトリの uid がコンテナ内ユーザーと一致するか確認
chown -R www-data:www-data /var/www/app chown --reference=/srv/app/a /srv/app/b
4000 SUID:実行時にファイル所有者の権限で動く(/usr/bin/passwd がこれ)
2000 SGID:ファイルはグループ権限で動く。ディレクトリでは新規ファイルがそのグループを継承
1000 Sticky:ディレクトリ内のファイルは所有者だけが削除できる(/tmp がこれ)
ls に s や t が出れば該当ビットが設定済み。大文字 S/T は「設定済みだが x がない」
-perm -4000 は「パーミッションに SUID を含む」。-perm は find のパーミッション条件
-xdev はファイルシステムを跨がない — 付けないと NFS やコンテナのマウントを延々辿って激遅になる
-ls はヒットしたファイルを ls -dils 形式で出力。サイズと時刻が同時に見える
-type f は通常ファイルのみ。ディレクトリとシンボリックリンクを除外
権限昇格の調査はまず SUID を探す:find / -xdev -perm -4000 -type f
# 監査:すべての SUID プログラムを列挙 find / -xdev -perm -4000 -type f -ls 2>/dev/null # 共有ディレクトリ:グループ内で共同作業 + 削除は本人のみ chmod 2770 /srv/share
umask は「差し引くビット」であり、パーミッションそのものではない
022 → 新規ディレクトリ 755、新規ファイル 644(ファイルは元々実行ビットを持たない)
077 → 新規ディレクトリ 700、新規ファイル 600。鍵を置くディレクトリ向き
-S は実際の既定パーミッションをシンボル形式(rwxr-xr-x)で表示。数字より直感的
一時変更:umask 077 は現在のシェルにのみ有効
永続化:/etc/profile かユーザーの ~/.bashrc に書く
「新規作成した鍵が他人に読める」を調べるときはまず umask を見る
umask # 現在値を確認 umask 077 # 現在のセッションで有効 umask -S # シンボル形式で実際の既定パーミッションを表示
sudo -i は新しい root ログインシェルを得る(root の環境変数を読む)
sudo -s は現在のユーザーの環境変数のまま root シェルを起動。PATH は変わらない
sudo -u ユーザー は指定ユーザーとして実行。サービスの動作確認でよく使う
sudo -l は自分に許可されたコマンドの一覧。「なぜ実行できないか」の調査はここから
su - は root へ切り替え環境も読み込む。ハイフンを忘れると現在の環境を引き継ぎ PATH が狂う
visudo で sudoers を編集する。構文チェック付き。-c は保存せず構文チェックのみ。変更後に必ず 1 回実行
/etc/sudoers を直接編集して壊すと自分が締め出される。運用で最も典型的な事故の 1 つ
sudo -l sudo -u www-data /usr/bin/env # サービスアカウントの環境を確認 visudo -c # 構文チェックのみ
useradd -m はホームディレクトリ作成、-s はシェル指定、-G は補助グループへ追加
-m がないとホームディレクトリが作られず、ログイン後に多くのツールが不可解に失敗する
usermod -aG グループ ユーザー — -a を忘れると他の補助グループから外れる(-a は append)
-G は補助グループのリストで、-aG と連記できる
id ユーザー は uid/gid と全グループ、groups ユーザー はグループのみ
userdel -r はホームディレクトリも削除。-r なしだと孤立ファイルが残る
グループ変更後も既存のログインセッションには反映されない。再ログインが必要
useradd -m -s /bin/bash deploy usermod -aG docker deploy id deploy
誰が CPU を食い、誰がファイルを掴み、どう穏当に終わらせるか — ここでの誤りはたいてい kill -9 を早く打ちすぎること
ps aux は BSD 形式:%CPU、%MEM、STAT、COMMAND。日常で最も使う
ps -ef は System V 形式:UID、PID、PPID、C、STIME、CMD。親プロセスが見える
-e は全プロセス表示(System V 形式)、-o は出力列の指定
混ぜて ps -aux と書いても動くが意味が変わる。習慣にしない
--sort=-%cpu は CPU 降順。負号が降順、カンマで複数キーを連ねられる
--sort=-rss は実メモリ降順。STAT の Z はゾンビ、D は中断不可、R は実行中
RSS は実使用量、VSZ は仮想アドレス空間でほぼ参考にならない
ps aux --sort=-%cpu | head ps -eo pid,ppid,rss,pcpu,cmd --sort=-rss | head
top で P は CPU 順、M はメモリ順、T は累積時間順
1 は CPU コアごとに展開。単一コア飽和か全体高負荷かが分かる
c はコマンドライン全体を表示。既定では切り詰められる
H はスレッド表示、u は特定ユーザーのみ、k はプロセスを kill
-b はバッチモード、-n1 は 1 回だけ出力。スクリプトやログ向き(-n は回数)
-p は指定 PID のみ、複数はカンマ区切り。pgrep の -d, は複数 PID をカンマで連結する
先頭行の load average は 1/5/15 分の平均。コア数を超えていれば待ちが発生している
top -b -n1 | head -15 # スナップショットを1回取得 top -p $(pgrep -d, nginx) # 指定プロセスのみ表示
kill は既定で TERM(15) を送る。プロセスは後処理と書き込みを済ませて終了できる
-15 と -TERM は等価。名前で書くほうが読みやすく、まずこちらを打つ
-9 は KILL。カーネルが即座に回収し、後始末の機会はない — DB やメッセージキューはデータ破損の恐れ
-1 または -HUP は「設定の再読み込み」によく使う(nginx、sshd が対応)
-0 は信号を送らず存在確認のみ。終了コードをそのまま判定に使える
kill が受けるのは PID でプロセス名ではない。名前で指定するなら pkill か killall
pkill -HUP nginx は名前で全 nginx プロセスに HUP を送る
まず TERM で数秒待ち、まだ居たら KILL。この順序は運用ルールにほぼ必ず書かれている
kill -TERM 1234 && sleep 3 kill -0 1234 && kill -9 1234 pkill -HUP nginx # 設定を再読み込み
& はバックグラウンドに置くだけで、端末を閉じると SIGHUP で一緒に終了する
nohup は SIGHUP を無視し、出力は既定で nohup.out へ(明示的なリダイレクトを推奨)
2>&1 は stderr を stdout にまとめる。でないとエラーがどこへ行くか分からない
< /dev/null は標準入力を空デバイスへ繋ぐ。端末読み取りでバックグラウンドプロセスが停止するのを防ぐ
setsid は現在のセッションから切り離す。nohup より徹底している
disown は既にバックグラウンドにあるジョブをシェルのジョブテーブルから外す
長期運用のサービスは systemd を使う。nohup は一時的な検証向き
nohup ./run.sh > /var/log/run.log 2>&1 & setsid ./worker.sh < /dev/null > /var/log/worker.log 2>&1 &
lsof -i :8080 は 8080 番ポートを掴んでいる者を表示(-i はネットワークアドレスでの絞り込み)
-p PID はそのプロセスが開いている全ファイルのみ
-u ユーザー はユーザーで絞る。-n はホスト名を引かないので速く、DNS で止まらない
+D /path はディレクトリ配下を開いているプロセスを再帰的に調べる(遅いので大きいディレクトリでは注意)
+L1 は「リンク数 1 未満」=削除済みだが開かれたままのファイル。ディスクが解放されない元凶
後ろの sort -k7 -n は並べ替えの定石:-k7 は 7 列目(SIZE/OFF)、-n は数値比較
lsof がない環境では fuser -v か /proc/PID/fd で代替する
lsof -i :8080 lsof -n +L1 | sort -k7 -n | tail # サイズ順に、掴まれている削除済みファイルを確認
nice 値は -20(最高優先)から 19(最低)。一般ユーザーは値を上げる方向のみ可能
nice -n 19 コマンド は起動時から低優先で動かす。バックアップや圧縮などのバッチ処理向き
-n の後ろは nice 値そのもので「回数」ではない
renice -n 10 -p PID は実行中のプロセスを変更。-p は PID 指定、-u ユーザー単位
taskset -c 0-3 はコア固定:-c は CPU リスト。複数インスタンスが同じコアを取り合うのを緩和
taskset -cp 0-3 1234 の -p は「既存プロセスを操作」の意味で、後ろに PID を取る
優先度を下げてもリソース不足は解決しない。重要業務の邪魔を減らすだけ
例の tar -czf は -c 作成、-z gzip、-f ファイル名指定(tar の項目を参照)
nice -n 19 tar -czf backup.tar.gz /srv/data renice -n 10 -p 1234 taskset -cp 0-3 1234
watch は既定で 2 秒ごとにコマンドを再実行し画面を消す。「一瞬のスナップショット」ではなく「変化」を見る
-n 1 は 1 秒ごとの更新。-n の後ろは秒数(0.5 も書ける)
-d は 2 回の出力の差分を強調。どこが動いているか一目で分かる
-d=cumulative は差分の強調を累積。変化した位置に印が残る — 散発的な変動の調査に有効
-t はヘッダ行(先頭のコマンドと間隔)を非表示。出力がすっきりする
コマンドは単一引用符で囲む。でないとパイプが現在のシェルに分解され、watch は前半だけ受け取る
高頻度の指標(接続数、QPS)の監視には使わない。vmstat 1 や sar を使う。watch は途中を取りこぼす
例の ss -s は接続の集計、wc -l は行数カウント(-l が行数)。詳細は各項目
watch -n 1 -d 'ss -tn state established | wc -l' watch -d=cumulative -t 'ss -s'
screen -S deploy は名前付きセッションを開始。端末を閉じても回線が切れてもタスクは動き続ける
-list は全セッションの一覧(-ls でも可)。まず Attached 状態の古いセッションがないか見る
-r deploy は指定セッションへ再接続。Attached と出たら他所が掴んでいるので -d -r で強制奪取
Ctrl-a d は screen のデタッチキー。Ctrl-a " はウィンドウ一覧、Ctrl-a k は現在のウィンドウを kill
tmux new -s deploy で新規作成、tmux attach -t deploy で再接続(-t は名前指定)
tmux では Ctrl-b d でデタッチ、Ctrl-b [ でコピーモードに入り過去の出力を遡れる
移行・バックアップ・大容量転送の前にセッションを立てる — 回線断はタスクを止めないが、出力が見えなくなる
screen -S deploy # その後 Ctrl-a d でデタッチ screen -list screen -r deploy tmux new -s deploy
サービスが応答しないとき、まず「待ち受けていない」「許可されていない」「到達できない」を切り分ける — 順序を誤ると長時間の空振りになる
-t は TCP、-u は UDP、-l は待ち受けのみ、-n はサービス名を引かない(数値のみで速い)、-p はプロセス表示
-tulnp は日常で最も使う組み合わせ。5 文字がそれぞれ 1 つの役割
-l を除いた -tnp は「確立済み接続のプロセスも見る」。接続数調査はこれ
-a は確立済み接続も含めて列挙、-s は集計。接続数が急増したらまずこれ
state established は状態で絞る。grep ESTAB より正確(ESTAB は列の略号にすぎない)
netstat は非推奨で、新しいシステムにはほぼ入っていない。ss を直接使う
127.0.0.1 と 0.0.0.0 の待ち受けは別物。前者は自ホストからのみ。「ポートは開いているのに外から繋がらない」の最大要因
ss -tulnp ss -tnp state established | head ss -s
-s は静音:プログレスバーとエラー表示を出さない。スクリプトではほぼ必須
-S は -s と併用しエラーだけ再表示 — つまり -sS は「静かに、ただし失敗時は喋る」
-i はレスポンスヘッダも出力、-I はヘッダのみ(HEAD を送り body は読まない)
-v はハンドシェイクとリクエストの全過程を出力。どこで詰まるか一目で分かる
-L はリダイレクトを追う。301/302 の連鎖を調べるなら必須
-X はメソッド指定、-d は body 送信(既定は POST)、-H はヘッダ追加
-o /dev/null は body を捨て、-w と組み合わせてステータスと所要時間だけ出す
-w '%{http_code} %{time_total}' は独自出力。目視より速い
-m 10 はリクエスト全体を最大 10 秒。--connect-timeout 3 は接続段階のみ制限
--resolve ホスト:443:IP は IP を直接固定して DNS を迂回。名前解決が原因かの判定に使う
-x http://proxy:8080 はプロキシ経由、--noproxy '*' は強制直結(ローカル調査ではほぼ必要)
-k は証明書検証を飛ばす — 診断時のみ。スクリプトに書かない
-w 内の \n は改行。付けないと全部 1 行に詰まる
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://example.com/
curl -m 10 --connect-timeout 3 -sS -o /dev/null \
-w 'dns:%{time_namelookup} conn:%{time_connect} ttfb:%{time_starttransfer} total:%{time_total}\n' \
https://example.com/api
curl -iL --resolve example.com:443:1.2.3.4 https://example.com/
curl -v -X POST -H 'Content-Type: application/json' -d '{"a":1}' http://127.0.0.1:8080/apinc -zv ホスト ポート — -z はデータを送らず探るだけ、-v は結果表示。最も速い
-w3 は接続タイムアウト 3 秒。コマンドが固まるのを防ぐ(未指定だと長く待つ)
telnet ホスト ポート は古いシステムに必ずある。Connected が出れば疎通
bash 内蔵の /dev/tcp は何もインストール不要:echo > /dev/tcp/ホスト/ポート
refused はホストまで届いて誰も待ち受けていない、タイムアウトはファイアウォールかセキュリティグループで落ちている — この 2 つは意味が全く違う
マシンを跨ぐ調査は常に「ローカル ss → ローカル curl 127.0.0.1 → 同一セグメント nc → 外部 nc」と層で絞る
nc -zv -w3 10.0.0.5 5432 echo > /dev/tcp/10.0.0.5/5432 && echo open || echo closed
ip a は全 NIC のアドレス(旧 ifconfig 相当)。-4 は IPv4 のみ、-6 は IPv6 のみ
ip r はルーティングテーブル。default の行が既定ゲートウェイ
ip neigh は ARP 隣接テーブル。同一セグメントで不通ならここにエントリがあるか見る
ip -s link は送受信とエラー数。-s は statistics。errors/dropped が 0 でなければリンク層の問題
show eth0 は特定 NIC のみに限定。仮想 NIC の洪水に埋もれない
add / del は一時的なアドレス追加削除。再起動で消える
永続設定はディストリビューション次第:Debian 系は netplan か /etc/network/interfaces、RHEL 系は nmcli
ip -4 a ip r ip -s link show eth0
+short は結果のみ出力。スクリプト向き
+A / +AAAA / +MX はレコード種別の指定(未指定は A)
@8.8.8.8 は DNS サーバーの指定。「ローカル解決 vs 公共解決」の比較に使う
+trace はルートから順に追跡し、どの委譲が誤っているかを特定する
引けないときはまず status:NXDOMAIN はレコード不存在、SERVFAIL はサーバー側の問題
TTL はレコード変更が反映されるまでの時間。ANSWER SECTION の残 TTL がキャッシュの残り
dig が見るのは DNS 層のみ。解決はできるのに繋がらないなら、問題は DNS より後ろの経路かポート
dig +short example.com dig @8.8.8.8 example.com A dig +trace example.com
-i any は全 NIC(具体名でも可)、-n はドメイン名とポート名を引かない(速く、DNS で止まらない)
-nn は「IP もポートも数値」の意味。調査時は -n より分かりやすい
port 443 は該当ポートのみ、host 1.2.3.4 は該当ホストのみ。and / or で組み合わせ可能
-c 100 は 100 パケットで停止。画面の洪水を防ぐ
-w file.pcap は保存して Wireshark で見る。端末で読むより圧倒的に良い
-A は ASCII でペイロード表示、-X は 16 進 + ASCII(平文プロトコルでのみ意味がある)
tcp[tcpflags] & tcp-rst != 0 は RST のみ取得。「どちらが先に切ったか」の判定に使う
root が必要。本番では必ず -c とフィルタを付ける
tcpdump -i any -nn -c 50 'port 443 and host 1.2.3.4' tcpdump -i eth0 -nn -w /tmp/cap.pcap 'tcp[tcpflags] & tcp-rst != 0'
-connect ホスト:443 は相手の証明書を見る。HTTP は見ず TLS 層のみ
-servername ホスト は必須(SNI)。でないと同一 IP の複数ドメインで誤った証明書が返る
-showcerts は証明書チェーン全体を出力。中間証明書の欠落が分かる
-noout は証明書本体を出さず、後続で要求した項目だけを出力
-dates は notBefore / notAfter。期限調査の第一歩
-subject は誰に発行されたか、-ext subjectAltName は対象ドメインの一覧
-checkend 2592000 は 30 日(秒)以内に失効するかの判定。終了コードを監視に使える
</dev/null を付けると読み終わりで終了する。でないと入力待ちで止まり続ける
openssl s_client -connect www.icetools.cn:443 -servername www.icetools.cn </dev/null 2>/dev/null \ | openssl x509 -noout -dates -subject openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null | head -30 openssl s_client -connect example.com:443 -servername example.com -checkend 2592000 </dev/null >/dev/null \ && echo '30 天内不过期'
firewall-cmd --list-all は現在の zone で何が許可されているか(RHEL 系)
--add-port=8080/tcp はルール追加。--permanent を付けないと再起動で消える
--reload は永続ルールの再読み込み。追加後は必須。でないと実行状態に反映されない
ufw status verbose はルール表示(Ubuntu)
iptables -L はフィルタテーブルの一覧。-n は名前解決しない、-v はパケットカウンタ表示(ルールが当たったか分かる)
--line-numbers はルール番号の表示。削除時はこれを指定して iptables -D INPUT 3 と書く
-I INPUT 1 は先頭へ挿入、-A は末尾へ追加 — 順序が結果を決める
ルールで許可しても繋がらないなら、まずクラウドのセキュリティグループを疑う。それはこのマシンの外にある
firewall-cmd --list-all firewall-cmd --add-port=8080/tcp --permanent && firewall-cmd --reload iptables -L INPUT -n -v --line-numbers
「ディスクが満杯」には 4 つの全く異なる原因がある:ファイル数、単一の巨大ファイル、inode 枯渇、削除済みファイルの掴み
第 1 段階:df -h でどのマウントポイントが満杯か確認。すぐサービスを再起動しない
第 2 段階:df -i で inode を見る。inode 枯渇は「デバイスに空きがありません」と出るのに df -h では余裕がある
第 3 段階:du -h --max-depth=1 で階層を下りながら大きいディレクトリを探す(--max-depth は階層数)
第 4 段階:両者の数字が合わないなら lsof +L1 で削除済みだが掴まれているファイルを探す。再起動か kill で解放される
awk の行は 7 列目(バイト数)を合計し MB に換算する。目視で見積もるより速い
ログの急増はたいてい第 1 段階で分かる。すぐ消さず、まず何かのエラーが連発していないか見る
df -h; df -i
du -h --max-depth=1 / | sort -h | tail
lsof +L1 2>/dev/null | awk '{s+=$7} END {print s/1024/1024" MB held"}'既定はツリー表示。LVM、パーティション、マウントポイントの従属関係が分かる
-f はファイルシステム種別、UUID、マウントポイントを表示 — fstab を書く前に必ず見る
-p は完全なデバイスパスを表示。/dev/sda と /dev/sda1 の取り違えを防ぐ
-d は子デバイスを展開せず、ディスク全体のみ表示
-o は出力列の指定。NAME,SIZE,FSTYPE,MOUNTPOINT,UUID が最も実用的な組
拡張後に lsblk は新容量なのに df が変わらないなら、ファイルシステムがまだ resize されていない
lsblk -f lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,UUID
クラウドディスクの拡張は 2 段階:まずパーティション、次にファイルシステム。片方だけでは df が変わらない
growpart /dev/vda 1 はパーティション拡張(cloud-utils-growpart パッケージ)。第 2 引数はパーティション番号
xfs_growfs / は XFS の拡張(拡大のみ、縮小不可)
resize2fs /dev/vda1 は ext4 の拡張(拡大も縮小も可。縮小前に umount と検査が必須)
LVM は lvextend がもう 1 段必要で、その後にファイルシステムを resize する
最後に df -h で容量が実際に変わったか確認(-h は human-readable で人間が読める単位)
作業前にスナップショットを取る。resize 途中の電源断はデータ消失の恐れがある
growpart /dev/vda 1 xfs_growfs / # XFS resize2fs /dev/vda1 # ext4 df -h /
mount -o のよく使う指定:ro は読み取り専用、remount は再マウント、noatime は書き込み増幅の抑制
remount が最頻出:マウントオプションを変えるのにアンマウント不要で、業務を止めない
umount が device is busy と言うなら、lsof +D か fuser -vm で占有者を探す
fstab では /dev/sdb より UUID を推奨。デバイス名は変わりうる
fstab の 4 列目は defaults,noatime。5・6 列目は dump と fsck の順序で、通常のデータディスクは 0 0
fstab を編集したら必ず mount -a で検証する。書き間違えると次回起動でシステムが上がらない
mount -a は fstab にあり未マウントのものをすべてマウントし、エラー時は該当行を報告する
mount -o remount,rw / fuser -vm /mnt/data mount -a # fstab の記述を検証
ファイル 1 つが inode を 1 つ消費し、総数はフォーマット時に決まる
症状は df -h に空きがあるのに書き込みで No space left on device
df -i で IUse% を見る。100% に近ければこれ
-xdev はファイルシステムを跨がない。集計が他のマウントへ流れるのを防ぐ
-type f はファイルのみ数える(ディレクトリも inode を食うが、まずファイル分布を見るほうが有効)
sed はパスの最後の / 以降を削ってディレクトリ名にする。sort | uniq -c でディレクトリごとのファイル数を集計
よくある元凶:セッションファイル、アップロードの一時ファイル、mail spool、数十万の小ファイルを持つキャッシュディレクトリ
根本対応は再フォーマットして -i パラメータを大きくする。短期は小ファイルの削除のみ
最後の sort -rn は「数値の降順」:-r が降順、-n が数値比較
df -i find /var -xdev -type f | sed 's#/[^/]*$##' | sort | uniq -c | sort -rn | head
cron が動かない原因は八割が PATH と出力リダイレクト。時刻が狂うと証明書・JWT・ログがまとめて壊れる
crontab -l は現在ユーザーのジョブ一覧、-e は編集、-r は全削除(確認なしなので誤爆注意)
-u ユーザー は指定ユーザーの crontab を操作(root で実行する)
5 つのフィールドは 分 時 日 月 曜日:0 3 * * * は毎日 3:00
*/5 は 5 分ごと、1-5 は月〜金、0,12 は 0 時と 12 時のみ
cron の環境はログインシェルと全く違い PATH が極端に短い — スクリプトでは必ず絶対パスを書く
cron は ~/.bashrc を読まない。環境変数が必要なら crontab の先頭に書くか、スクリプト内で source する
出力は必ずリダイレクトする:>> /var/log/x.log 2>&1。でないとエラーはメールにしか行かず目に触れない
動かない原因の調査:Debian 系は journalctl -u cron、RHEL 系は /var/log/cron
例の curl -sS は静音だがエラーは喋る(-s 静音、-S エラー表示)、-o /dev/null は body を捨てる
crontab -e crontab -l # 毎日 3:30 にバックアップ、出力はログへ 30 3 * * * /srv/app/backup.sh >> /var/log/backup.log 2>&1 # 5分ごとにヘルスチェックを実行 */5 * * * * /usr/bin/curl -sS -o /dev/null http://127.0.0.1:8080/health
利点は具体的:ログが journalctl に入る、次回実行時刻が見える、取りこぼしを補える
OnCalendar はカレンダー形式:*-*-* 03:30:00 は毎日 3:30、OnUnitActiveSec=5min は 5 分ごと
Persistent=true は停止期間中に逃したジョブを起動後に 1 回補う — cron にはできない
Unit= は実行する service の指定。省略すると同名の .service が使われる
systemctl list-timers は全タイマーと次回起動時刻の一覧
enable --now が「起動時有効 + 即時実行」。enable だけではすぐには動かない
テストでは .timer ではなく .service を start する。timer を start してもジョブは即実行されない
最後の行は journalctl -u で unit 指定、-n 30 で直近 30 行、--no-pager でページャなし
# /etc/systemd/system/backup.timer [Unit] Description=Daily backup [Timer] OnCalendar=*-*-* 03:30:00 Persistent=true [Install] WantedBy=timers.target # 有効化と確認 systemctl list-timers systemctl enable --now backup.timer journalctl -u backup -n 30 --no-pager
date は現在時刻とタイムゾーン。+%F は 2026-10-29、+%T は時分秒、+'%F %T' が最頻出
-d '-1 hour' は 1 時間前の計算、-d 'next monday' も可 — スクリプトの時間窓はすべて -d
-s はシステム時刻の直接変更。本番では手で変えない。ログ順序と証明書検証が崩れる
timedatectl はタイムゾーンと NTP 同期状態を表示。date より情報が多い
set-timezone Asia/Shanghai はタイムゾーン変更(国内でよくあるのが UTC のまま)
set-ntp true は自動時刻合わせを有効化。コンテナの時刻はホストを継承する
時刻ずれの影響は具体的:証明書が not yet valid、JWT が即失効、ログの時刻が合わない、定時ジョブがずれる
date '+%F %T' date -d '-1 hour' '+%F %T' timedatectl timedatectl set-timezone Asia/Shanghai
chronyc tracking は現在の偏差と階層を表示。System time の行が「実時刻との差」そのもの
偏差が数百ミリ秒を超えたら調査:ソース不達、UDP 123 の遮断、仮想マシンのクロックドリフト
chronyc sources -v は各時刻源を表示。-v で詳細(遅延、ジッタ、選択状態)
行頭の ^* が使用中のソース、^? は到達不可
chronyc makestep は即時に時刻を跳ばす。偏差が大きいときのみ手動実行。ログは自動で残る
ntpq -p は旧 ntpd 系の等価コマンド。-p はピア一覧の表示
クラウドではベンダー内網の NTP を優先。公開 NTP は制限や拒否が多い
chronyc tracking chronyc sources -v chronyc makestep ntpq -p # 旧システム(ntpd)
調査はコマンドより順序が重要:まず時間で絞り、次にキーワードで収束させ、最後に前後を読む
-u サービス名 は特定 unit のみ
-f は継続追跡。--since '10 min ago' や --since '2026-10-29 09:00' で範囲を区切る
-p err は error 以上のみ。優先度は emerg から debug まで
-n 100 は最後の 100 行、-r は逆順(新しいものが上)
--no-pager はページャを使わない。ファイルへのリダイレクトやパイプに便利
-o short-iso はタイムゾーンオフセット付きの時刻。タイムゾーンを跨ぐ調査では必須
--disk-usage はログが使う容量、--vacuum-time=7d は 7 日分だけ残す
--vacuum-size=500M は容量で削除。--vacuum-time とどちらか一方
journalctl -u nginx -p err --since '30 min ago' --no-pager journalctl -u app -f -o short-iso journalctl --disk-usage
-f はファイルディスクリプタで追うので、ログがローテート(rename)されると古いファイルを追い続け「更新が止まった」ように見える
-F はファイル名で追い、ローテート後は自動で新しいファイルへ切り替わる — 長時間の監視は必ずこれ
-n 100 は最後の 100 行を出してから追う。-n0 は履歴を一切見ず新規のみ
-Fn0 は -F と -n0 の連記。リアルタイム監視で最も使う組み合わせ
--pid=PID はそのプロセスが終了したら追跡を止める
grep --line-buffered は grep を行ごとに即出力させる。付けないとバッファされ、パイプでなかなか出てこない
組み合わせ:tail -Fn0 app.log | grep --line-buffered -i error
複数ファイル:tail -F a.log b.log は ==> で区切って出る。1 つだけ追っていないか確認
tail -F /var/log/app/error.log tail -Fn0 /var/log/nginx/access.log | grep --line-buffered -i 500
OOM、ディスク IO エラー、NIC の link down、ドライバ異常はすべてここに出る
-T は人が読める時刻、-w は継続追跡
-l err,warn は指定レベルのみ(-l の後ろはカンマ区切りのレベル一覧)
dmesg のリングバッファは上書きされるので重要情報は早めに取得する。journalctl -k は同じデータの永続版
「Out of memory: Killed process」は OOM killer に殺されたという意味で、プログラム自身のクラッシュではない
grep -iE と繋ぐとき:-i は大文字小文字無視、-E は拡張正規表現(| が効く。grep の項目を参照)
dmesg -T -l err,warn | tail -30 dmesg -T | grep -iE 'oom|killed process'
確認:dmesg -T | grep -i -A2 'killed process'。-A2 は一致行の後ろ 2 行も出す(殺されたプロセスのメモリ情報がある)
journalctl -k のほうが情報が多く、当時のメモリ状態も含まれる
systemctl status に status=9/KILL や signal=KILL が出れば、ほぼ OOM による kill
--no-pager はページャに奪われないようにする。スクリプトやスクリーンショットで必要
クラッシュではない:アプリのログは唐突に途切れ、例外スタックトレースは残らない
原因を探す:free -h で空きメモリ(-h は人間が読める単位)、次に RSS 順で誰が増えているか見る
コンテナでは cgroup 制限も見る:/sys/fs/cgroup/memory.max
dmesg -T | grep -i -A2 'killed process' systemctl status app --no-pager | head -20 free -h
設定は /etc/logrotate.d/ に置き、サービスごとに 1 ファイル
daily / weekly はローテート周期、rotate 7 は 7 世代保持
compress は古いログを圧縮、delaycompress は最新の 1 世代を圧縮しない(書き込み中のファイルを圧縮しない)
copytruncate はコピー後に空にする。ファイルを開き直せないプログラム向けだが、ログ欠落の窓がある
postrotate でシグナルを送りプロセスにファイルを開き直させる。copytruncate より筋が良い
-d は実行せず試算のみ。編集後は必ず 1 回実行して何をするか確認する
-f は 1 回だけ強制実行。周期を待たずに設定の有効性を確認する
missingok はファイル不在でエラーにしない、notifempty は空ファイルをローテートしない — ほぼすべての設定に入れる
# /etc/logrotate.d/app
/var/log/app/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
postrotate
systemctl reload app > /dev/null 2>&1 || true
endscript
}
# まずドライラン、その後1回強制実行して検証
logrotate -d /etc/logrotate.d/app
logrotate -f /etc/logrotate.d/appサービスが起動しない原因はほぼ 3 つ:パスが違う、ユーザーに権限がない、依存が起動していない — status にすべて書いてある
start / stop / restart / reload — reload はプロセスを再起動せず設定を読み直す。プログラムが対応している場合のみ意味がある
status は直近のログ数行と終了コードを出す。推測より有用。--no-pager でページャを回避
enable は起動時有効化、--now は同時に即起動
disable は自動起動を外すが停止はしない。止めるなら別途 stop
list-units --failed は壊れている unit が一目で分かる。--all は未起動も含める
is-active / is-enabled の終了コードはスクリプトの判定にそのまま使える
cat は systemd が実際に読んでいる unit(drop-in の上書き込み)を出力。編集後はまずこれを見る
systemctl status app --no-pager systemctl enable --now app systemctl list-units --failed
/etc/systemd/system/名前.service に置き、書いたら必ず daemon-reload
ExecStart は絶対パス必須で、プログラム自身が fork してはいけない(するなら Type=forking)
Restart=on-failure は再起動ポリシー。RestartSec=5 と併用して再起動の暴走を防ぐ
User / Group は権限を決める。非 root に変えるならホームディレクトリとファイル所有者も合わせる
EnvironmentFile で環境変数を別ファイルに置く。ExecStart に直書きしない
WorkingDirectory は相対パスに影響する。これがないと自分の設定ファイルを見つけられないプログラムが多い
StandardOutput=journal でログを journalctl に入れ、調査を一本化する
After は起動順序を並べるだけで成功は保証しない。ネットワーク準備には Wants=network-online.target を併用
# /etc/systemd/system/app.service [Unit] Description=App After=network-online.target Wants=network-online.target [Service] Type=simple User=deploy WorkingDirectory=/srv/app EnvironmentFile=/etc/app/env ExecStart=/srv/app/bin/app Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
第 1 段階:systemctl status で Active と最後の数行のエラーを見る。多くの問題はここで分かる
第 2 段階:journalctl -u サービス -n 50 --no-pager で完全なスタックトレースを見る(-n は行数)
第 3 段階:ExecStart のパスが存在し実行可能か確認:ls -l そのパス
第 4 段階:手で動かす:sudo -u サービスユーザー で ExecStart のコマンドを実行。たいてい本当の原因が即座に出る
第 5 段階:権限を見る:SELinux は ausearch -m avc -ts recent(-m はレコード種別 avc、-ts は開始時刻)
AppArmor の拒否は dmesg に出る。コンテナでは docker logs も見る
status=203/EXEC はパスか権限の問題、status=1 はプログラム自身の終了コード 1
systemctl status app --no-pager journalctl -u app -n 50 --no-pager sudo -u deploy /srv/app/bin/app # 手動実行して実際のエラーを確認 ausearch -m avc -ts recent # SELinux の拒否記録
.service を変更した → 必ず daemon-reload。でないと systemd は古い定義を使い続ける
EnvironmentFile の中身だけ変えた → reload 不要だが、反映には restart が必要
unit を新規作成 → reload してからでないと enable できない
start 済みのサービスは reload では新しい設定にならない。restart が必要
reload を忘れた典型症状:ExecStart を変えたのに何も変わらず、status に古いコマンドが出る
cat は systemd から見たこのファイルの姿を確認する(drop-in で上書きされた結果が出る)
systemctl daemon-reload && systemctl restart app systemctl cat app # systemd から見たこのファイルの内容を確認
After は起動順序のみで、相手の起動成功は保証しない
Requires はハード依存。相手が失敗すればこのサービスも失敗する
Wants はソフト依存。相手が失敗しても本サービスは起動する — 多くの場面ではこれ
ネットワーク関連:network.target はスタック初期化完了の意味だけで、アドレスの利用可否は別
After=network-online.target は Wants=network-online.target と併用して初めて本当に待つ
起動が早すぎて「アドレス解決失敗」になるのは十中八九これ
依存の充足を確認:systemctl list-dependencies サービス名 でツリーを見る
[Unit] Wants=network-online.target After=network-online.target # 確認 systemctl list-dependencies app
入れる前に出自を確認し、消す前に何が一緒に消えるかを見る — 本番機では特に
apt update はインデックスを更新するだけで、パッケージは 1 つも更新しない — 最も覚え違いしやすい
apt upgrade はインデックスに従い導入済みパッケージを更新。full-upgrade は依存解決のために削除も行うので慎重に
install -y は確認なし。スクリプト向き。-y なしだと入力待ちで止まる
apt purge は設定ファイルも削除。remove だけでは設定が残る
apt list --installed は導入済み、apt list --upgradable(--upgradable が絞り込み条件)は更新可能なもの
apt-cache show パッケージ名 でバージョンと説明
apt-mark hold はバージョンを固定し、一括更新で上げられないようにする
apt update && apt install -y nginx apt list --upgradable apt purge nginx-common
dnf は yum の後継。新しいシステムの yum は通常シンボリックリンク
install / remove / update の 3 動詞は apt と対応しない。混ぜて打たない
install -y は確認なし。apt と同じ
provides はファイルやコマンドからパッケージ名を逆引き。「このコマンドはどのパッケージか」を調べる
history は各トランザクションの一覧、history undo last は直前の操作を取り消す — 更新で壊したときに効く
--enablerepo は一時的にリポジトリを有効化、--nogpgcheck は他に手段がないときだけ
モジュール機構(dnf module list)は同一ソフトの複数バージョンが併存する場合に必要
dnf install -y nginx dnf provides /usr/sbin/ss dnf history undo last
RHEL 系:rpm -qf /usr/bin/ss — -q はクエリモード、-f はファイルからパッケージ名を逆引き
Debian 系:dpkg -S /usr/bin/ss。見つからないときは apt-file update の後に apt-file search
フルパスが不明ならコマンド名で逆引き:dnf provides '*/ss' または apt-file search bin/ss
dpkg -L パッケージ名 はそのパッケージが入れたファイル一覧。設定を触る前に役立つ
rpm -qc パッケージ名 は設定ファイルのみ(-qc は -q に -c(config)を足したもの)
コマンドが見つからないときは、まず本当に入っていないかを確認する。PATH のせいと決めつけない
rpm -qf /usr/bin/ss dpkg -S /usr/bin/ss dpkg -L nginx-common | head -20
apt clean は .deb キャッシュを全削除、apt autoclean は期限切れのみ
dnf clean all はキャッシュとメタデータを削除
apt autoremove は「依存として入り、今は不要」なパッケージを削除。--purge を付けると設定も削除
autoremove でも -y は必要。でないと確認プロンプトで止まる
削除前に du -sh で本当に容量を食っているか確認(-s は合計、-h は人間が読める単位)
コンテナイメージで apt install の後に clean しないと、数十 MB が無駄に積み上がる
du -sh /var/cache/apt /var/cache/dnf 2>/dev/null apt clean && apt autoremove --purge -y dnf clean all
コンテナの問題は九割が「思っている設定」と「実際に効いている設定」の不一致 — まず inspect で事実を揃え、それからログを見る
-a は終了済みコンテナも含めて一覧 — 実行中だけ見ると、たった今落ちたものが見えない
-q はコンテナ ID のみ。docker rm や docker logs へのパイプに便利
-s は書き込み可能レイヤの使用量。「コンテナがディスクを食い尽くす」調査に使う
--format は列のカスタム指定。'{{.Names}} {{.Status}}' は既定の表より読みやすく、スクリプトでも扱いやすい
--filter は条件で絞る:status=exited は終了済み、name=web は名前で検索
--no-trunc は長い ID と完全なコマンドを切り詰めない
-n 5 は直近作成の 5 個のみ(--last の短縮形)
Status に Up 3 seconds (restarting) が出たら無限再起動。docker logs --tail で理由を見る
# すべてのコンテナ(終了済みを含む)
docker ps -a --format '{{.Names}} {{.Status}}'
# 終了済み/再起動を繰り返すものを探す
docker ps -a --filter status=exited -n 5
# ディスクを圧迫しているのはどれか
docker ps -s --format '{{.Names}} {{.Size}}'-f は継続追跡。tail -f と等価
--tail 200 は最後の 200 行のみ — 長時間動くコンテナは数万行あり、付けないと問題部分が流れて見えない
-t は各行にタイムスタンプ。ホスト側ログと時系列を合わせるのに必須
--since 10m は直近 10 分のみ。2026-10-29T10:00:00 のような絶対時刻も可
--until は --since と組み合わせて期間を切り出す
grep 側の -i は大文字小文字を無視。error / ERROR / Error を一度に拾う
ログは /var/lib/docker/containers/<ID>/<ID>-json.log にあり、コンテナを消すと消える —
重要なサービスは journald や外部収集に流す。docker の中だけに残さない
docker logs -f --tail 200 -t myapp docker logs --since 10m myapp | grep -i error
-it は対話端末。-t がないとプロンプトが出ず、-i がないと入力できず、両方必要
-u root は root で入る。イメージ既定ユーザーが設定ファイルを読めないときに使う
-w /app は作業ディレクトリ指定。入ってから cd しなくて済む
-e KEY=val は一時的な環境変数注入。「環境変数が渡っていないのでは」の検証に使う
-- 以降は docker が解釈しない。コマンド自体が - で始まるときは必須
bash の -c は後続の文字列全体をコマンドとして実行。bash -c 'a && b' で複数手順を一度に動かす
ls の -l は長形式。パーミッションと所有者を確認する
入れないときはまずコンテナが running か見る:docker ps -a で Exited のものへの exec は必ず失敗する
それは exec の問題ではなく、メインプロセスが既に終了している
イメージに bash がなければ sh を試す。alpine ベースは通常 sh だけ
distroless イメージには sh すらない。docker cp でファイルを出すか、一時的なデバッグコンテナを立てる
docker exec -it -u root myapp sh docker exec myapp env | grep -i database docker exec -it myapp bash -c 'ls -l /app'
--format は Go テンプレートでフィールドを取る。-f は短縮形
よく使うパス:.State.Status 実行状態、.State.ExitCode 終了コード、
.NetworkSettings.Networks でコンテナ IP、.Mounts でマウント、.Config.Env で環境変数、
.HostConfig.RestartPolicy で再起動ポリシー
{{json .}} は生の JSON を出力。jq に渡すと扱いやすい
--type image はコンテナではなくイメージを調べる
compose の値はコマンドラインの -e で上書きされうる。実際に効いているのは inspect でしか分からない
.Config.Env にはパスワードや token が入りがち。他人に貼る前にフィルタする
docker inspect -f '{{.State.Status}} exit={{.State.ExitCode}}' myapp
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' myapp
docker inspect --format '{{json .Mounts}}' myapp | jq--no-stream は 1 回だけスナップショットを取って終了。スクリプトや定期記録向き
--format は出力列のカスタム指定。grep や DB 投入に便利
-a は停止済みコンテナも含めて集計(0 と表示されるが一覧には出る)
MEM LIMIT はそのコンテナの上限。MEM % が 100 に近ければまもなく OOM
CPU % はコア数で按分される。マルチコア機で単一コンテナが 100% を超えるのは正常
コンテナが OOMKilled されたとき stats には何も出ない(プロセスが既にいない)。docker inspect の ExitCode 137 と
dmesg の oom-kill 記録を見る
docker stats --no-stream --format '{{.Name}} {{.CPUPerc}} {{.MemUsage}}'
docker stats --no-stream -a-t はタグ付け。慣例は 名前:バージョン。latest だけにしない
-f は Dockerfile のパス指定(ビルドディレクトリにない場合)
--no-cache はキャッシュを一切使わない — 「コードを変えたのにイメージが変わらない」の切り分けはまずこれ
--build-arg はビルド引数。Dockerfile で ARG 宣言されている場合のみ有効
--target は指定ステージまでビルド。マルチステージの中間層のデバッグに使う
--progress=plain はビルドログを完全出力。既定の TTY モードは並行ログを 1 行に畳み、エラー時に詳細が見えない
最後の . はビルドコンテキストで Dockerfile のパスではない — 大きすぎると明らかに遅くなる。.dockerignore で node_modules と .git を除外する
docker build -t myapp:1.2.3 . docker build -f docker/Dockerfile.prod --build-arg NODE_ENV=prod --progress=plain -t myapp:prod .
-f は compose ファイル指定。複数環境は -f を 2 つ重ね、後者が前者を上書きする
-p はプロジェクト名 — 既定はディレクトリ名。同一ホストで複数環境を動かすなら明示必須。でないとコンテナとネットワークが衝突する
-d はバックグラウンド実行。付けないと前景に張り付き、Ctrl+C で停止する
--build は起動前にイメージを再ビルド
up は作成と起動、down は停止してコンテナとネットワークを削除
logs -f --tail 100 はログ追跡。末尾にサービス名を書くとその 1 つだけ追う
config は構文検証とマージ後の最終設定の出力 — compose を編集したらまずこれを実行。直接 up するより安全
compose v2 は docker compose(空白)、v1 は docker-compose(ハイフン)。引数が微妙に違うのでスクリプトではどちらか確認する
docker compose -f docker-compose.yml -f docker-compose.prod.yml -p shop up -d --build docker compose -p shop logs -f --tail 100 api docker compose -p shop config
system df はイメージ / コンテナ / ボリューム / ビルドキャッシュの 4 分類で使用量を集計。-v は各オブジェクトまで展開
prune は未使用オブジェクトを削除。既定は dangling イメージ(<none>)のみで、-a でコンテナから参照されていない全イメージが対象
--volumes はボリュームも削除 — 中にデータベースがあるかもしれず、消すと復旧できない
--filter until=24h は 24 時間より古いものだけ削除。使用中の誤削除を避ける
推奨手順:まず system df -v で誰が食っているか見てから狙い撃ちで prune。いきなり system prune -a を打たない
コンテナログもディスクを食う:/var/lib/docker/containers/*/*-json.log
根本対応は daemon.json の log-opts に max-size と max-file を設定する
docker system df -v docker image prune -a --filter until=24h docker system prune --volumes
K8s の障害調査はほぼ固定の順序:状態 → イベント → ログ → コンテナに入る。どれかを飛ばすのは当てずっぽう
-A は全ネームスペース — リソースが見つからないときはまずこれを付ける。多くは ns の指定ミス
-n はネームスペース指定。省略すると現在の context の既定 ns
-o wide は IP と配置ノードの 2 列が増える。ネットワークとスケジューリングの調査では必見
-o yaml は完全な定義を出力。設定を変える前に書き出して突き合わせる
--show-labels はラベル表示 — Service が Pod を選べないのは、ほぼラベルの不一致
-l app=api はラベルで絞る。名前の grep より確実
--sort-by=.status.startTime は起動時刻順。新しいものが最後
リソース名には短縮形がある:pod/po、svc、deploy、rs、no、cm、secret
kubectl get pod -A -o wide kubectl get pod -n shop -l app=api --show-labels kubectl get pod -A --sort-by=.status.startTime
末尾の Events は K8s 調査で最も情報量が多い場所 —
ImagePullBackOff、FailedScheduling、Unhealthy、OOMKilled の理由はすべてここに書かれている
-n を付けずにネームスペースを間違えると、NotFound だけで有用なヒントは得られない
Events は既定で直近 1 時間しか残らない。事後分析はログ収集に頼る。後から見られると思わない
describe node の下部の Allocated resources は request / limit の割当比率を表示し、
Pod が Pending のままなら、ノードが request で埋まっていないかを見る(CPU が本当に使われているかではない)
describe は設定とイベントのみ。アプリログは出ない。それは kubectl logs の役割
kubectl describe pod -n shop api-7d9f-abc12 kubectl describe node k8s-node-2 | tail -30
-f は継続追跡
-n はネームスペース指定。省略すると現在の context の既定 ns
--tail 200 は最後の 200 行。長時間動く Pod では付けないと画面が埋まる
-c はコンテナ指定 — sidecar が同居する Pod では必須。でないと ambiguous と利用可能なコンテナ一覧が出る
--previous は前のコンテナのログを見る:CrashLoopBackOff はコンテナを繰り返し再起動するので、
現在のコンテナのログは「起動直後に落ちた」断片だけで、本当の死因は前のコンテナにある
--since 10m は直近 10 分のみ
--timestamps は各行にタイムスタンプ
リソースは deploy/api のような「種類/名前」形式で書け、Deployment の Pod ログを直接見られる
kubectl logs -f --tail 200 -n shop deploy/api kubectl logs -n shop api-7d9f-abc12 --previous kubectl logs -n shop api-7d9f-abc12 -c istio-proxy --since 10m
-it は対話端末。2 つセットで指定する
-n はネームスペース指定
-c はコンテナ指定。マルチコンテナ Pod では必須
-- 以降は kubectl が解釈しない。コマンド自体が - で始まるときは必須
cp の書式は コピー元 コピー先。Pod 側は pod:パス と書く
cp はコンテナ内の tar に依存するので distroless イメージでは即失敗する
kubectl debug はデバッグ用コンテナを一時的に注入しネットワークネームスペースを共有する。
--image はデバッグイメージ(busybox で十分)、--target はデバッグ対象コンテナの指定 —
本番イメージにシェルがない場合の唯一の正道で、イメージを作り直して再リリースするより遥かに速い
kubectl exec -it -n shop api-7d9f-abc12 -- sh kubectl cp -n shop api-7d9f-abc12:/app/logs/app.log ./app.log kubectl debug -it -n shop api-7d9f-abc12 --image=busybox --target=api
describe 末尾の Events と同源だが、リソースを跨いで一度に見られる。「どのオブジェクトが壊れているか分からない」ときに向く
-n はネームスペース指定
--sort-by=.lastTimestamp は時刻順。新しいものが最後
--field-selector type=Warning は警告のみ。ノイズが一桁減る
--field-selector involvedObject.name=api-7d9f-abc12 は特定オブジェクトに限定
-A は全ネームスペース。クラスタレベルの問題(ノード NotReady、エビクション)は付けて初めて見える
Warning の FailedScheduling にはスケジューラの生の言葉が入る。例えば
insufficient cpu、node(s) had taint — なぜ配置できないかが直接分かるので、
リソースかテイントかを推測する必要がない
イベントの TTL は約 1 時間。Pod が再起動を繰り返すときは早めに控える
kubectl get events -A --sort-by=.lastTimestamp | tail -20 kubectl get events -n shop --field-selector type=Warning kubectl get events -n shop --field-selector involvedObject.name=api-7d9f-abc12
-f は manifest ファイルかディレクトリ指定。-f - は標準入力から読む
--dry-run=client は送信せず最終 YAML を出力するだけ — テンプレート生成に使い、手書きを省く
--dry-run=server はサーバー側で検証(admission webhook 含む)。client より遥かに厳格
-o yaml は dry-run と組み合わせて初めて意味がある
rollout status はローリング更新の完了を待ち、詰まったらどの段階か教えてくれる
rollout undo は前のリビジョンへ戻す。--to-revision=3 は指定リビジョンへ。history で番号を調べる
rollout restart は設定を変えずに Pod を再起動。ConfigMap や Secret を変更した後によく使う
-n はネームスペース指定
apply は宣言的で冪等。create は命令的で、2 回目は AlreadyExists になる
kubectl apply -f deploy/api.yaml --dry-run=client -o yaml > api.gen.yaml kubectl rollout status deploy/api -n shop kubectl rollout undo deploy/api -n shop --to-revision=3 kubectl rollout restart deploy/api -n shop
書式は リソース/名前 ローカルポート:リモートポート。NodePort や Ingress は不要
-n はネームスペース指定
--address 0.0.0.0 は同一セグメントの他マシンからも接続可能にする。既定は 127.0.0.1 のみで他者は繋がらない
kube-apiserver のトンネル経由で転送し、Service を通らない —
つまり「サービス自体が正常か」と「ネットワークポリシーで遮断されているか」は別問題で、まず前者を切り分けられる
端末を切ると停止する。長期利用は tmux か screen に入れる
調査順序:port-forward が通る → サービスは正常で、問題は Ingress / Service / NetworkPolicy;
port-forward が通らない → Pod かポート自体に問題がある
kubectl port-forward -n shop svc/api 8080:80 kubectl port-forward -n shop --address 0.0.0.0 svc/api 8080:80 kubectl port-forward -n shop pod/api-7d9f-abc12 5432:5432
top node と top pod は metrics-server に依存。未導入なら Metrics API not available になる
--sort-by=cpu か --sort-by=memory で列を基準に並べ、資源の大食いを探す
--containers は Pod をコンテナ単位に分解。どの sidecar がメモリを食うか分かる
-A は全ネームスペース
describe node の Allocated resources は request / limit の割当比率 —
スケジューリング失敗時はまずここを見る:request を大きく書きすぎてノードが「帳簿上」埋まり、実 CPU は暇というのが定番
cordon はノードをスケジュール不可にする。稼働中の Pod には影響しない
drain はメンテナンスのためノード上の Pod を退去させる。--ignore-daemonsets は DaemonSet を無視し
(そもそも退去させるべきでない)、--delete-emptydir-data は emptyDir を使う Pod の削除を許可する(一時データは失われる)
メンテナンス後は uncordon でスケジューリングを再開する
kubectl top node kubectl top pod -A --sort-by=memory --containers kubectl drain k8s-node-2 --ignore-daemonsets --delete-emptydir-data
top は「どのプロセスが CPU を食っているか」しか分からず、「何を待っているか」には答えられない。この一群は後者に答える
-p は稼働中のプロセスにアタッチ。コマンドの前に付ければ起動から追える
-f は fork した子プロセスも追う — 付けないと親が子を待つだけの姿しか見えず、実際に働くプロセスが見えない
-T は各呼び出しの所要時間を表示(<0.000012>)。どこが遅いかの特定はこれ
-tt はマイクロ秒精度のタイムスタンプ
-s 200 は文字列を最大 200 文字まで表示。既定は 32 で、重要なパスが ... に切られる
-o out.txt はファイルへ出力。数万行を端末に出すと解析できない
-c は終了時にシステムコール種別ごとの回数と合計時間を集計 — まずこれでどの種別が高コストか見て、
次に -T で個別の遅い呼び出しを特定する
-p でアタッチして出力が全くないなら、中断不可の IO かカーネルロックで固まっている。
次は /proc/PID/stack と /proc/PID/wchan を見る
本番では慎重に:-f で全呼び出しを追うと数倍遅くなる。-c かフィルタで軽減する
strace -p 1234 -T -tt -s 200 strace -f -p 1234 -o /tmp/st.txt strace -c -p 1234
-e trace= は strace で最も有用なオプション。例外なく
-e trace=file はファイル関連のみ(openat、stat、unlink)。「ファイルが見つからない / 権限不足」の調査
-e trace=network はネットワークのみ(connect、sendto、recvfrom)。「繋がらない / タイムアウト」の調査
-e trace=openat,connect はカンマ区切りで特定の呼び出しだけ指定
-e trace=desc はファイルディスクリプタ操作のみ。どの fd で詰まっているか判断する
-e signal=SIGSEGV はシグナルのみ追う
フィルタで数万行が数十行になる — 正しい順序は:まず -c でどの種別が最も時間を食うか見て、
次に -e trace= でその種別だけ追う
openat が ENOENT ならパス不在、EACCES は権限不足、EAGAIN は非ブロッキングでデータなし。
3 つのエラーコードは対処方向が全く違う。error を見て一律に権限を疑わない
その他のオプションは前述の strace -p と同一:-f は子プロセス追跡、-p はプロセス指定、-T は単回所要時間、-tt はマイクロ秒時刻、-o はファイル出力
strace -f -e trace=file -p 1234 -T strace -f -e trace=network -p 1234 -tt strace -o net.txt -e trace=openat,connect -f ./app
perf top はリアルタイムのホット関数ランキング。PID を知らなくてもマシン全体を見られる
-p は特定プロセスのみ
-F 99 は 99Hz のサンプリング。100Hz の整数を避けてタイマー共鳴による偽のホットスポットを防ぐ
-g はコールグラフ表示。ここでは視野が限られるので、本当の呼び出し鎖は record を使う
関数名が unknown なら debuginfo 不足かシンボルが strip されている —
perf が壊れたのではなく、そのバイナリがシンボルなしで出荷されている
root か kernel.perf_event_paranoid の緩和が必要。コンテナでは通常 --privileged か SYS_ADMIN を追加
top との違い:top は「どのプロセス」、perf top は「どの関数」に答える。最適化の起点は後者
perf top perf top -p 1234 -F 99 perf top -g
record はサンプル記録、report は対話閲覧、script はテキスト出力 — 3 点セット
-F 99 は毎秒 99 回サンプリング
-g は呼び出し鎖を記録。フレームグラフには必須で、ないと孤立した関数名しか出ない
-p はプロセス指定、-a は全 CPU(システム全体)を採取
--call-graph dwarf は DWARF 情報でスタックを展開し、既定の fp より正確。
代償としてデータファイルが一桁大きくなる。-fomit-frame-pointer でビルドされたバイナリでは必須
-o は出力ファイル指定。既定はカレントディレクトリの perf.data
-i は report / script が読むデータファイルの指定
-- の後ろにコマンドを置けば、perf から起動して起動段階を採取できる
フレームグラフには script の折り畳み出力が必要で、それを FlameGraph の stackcollapse スクリプトに渡す
コンテナでは既定で perf_event_paranoid が 2 のことが多く、カーネルシンボルが見えない。
sysctl kernel.perf_event_paranoid=-1 で完全に見えるようになる
perf record -F 99 -g -p 1234 -o /tmp/perf.data perf report -i /tmp/perf.data perf script -i /tmp/perf.data > /tmp/perf.txt
pidstat 1 は 1 秒ごとに出力し更新し続ける。後ろにもう 1 つ数値を書くとその回数で停止
-p ALL は全プロセス、-p 1234 は 1 つだけ
-d はディスク IO(kB_rd/s 読取、kB_wr/s 書込、iodelay は待ち時間ミリ秒)
-r はメモリ(ページフォールト、RSS)
-u は CPU 使用率(既定がこれ)
-t はスレッド単位に分解。どのスレッドが CPU を食うか特定する — マルチスレッドプログラムでは必須
-w はコンテキストスイッチ。cswch/s は自発(資源待ち)、nvcswch/s は非自発(横取りされた)
sysstat パッケージに含まれる。最小インストールのシステムではまず sysstat を入れる
top より優れる点は、一定間隔で長期サンプリングしてファイルに書けること。後から平均やグラフが作れる
pidstat -p 1234 1 10 pidstat -d -p ALL 1 5 pidstat -t -p 1234 1
iostat -x 1 は拡張統計を毎秒更新
-x は拡張指標の出力。付けないと別の簡易表になる
%util が 100% に近ければディスクは飽和。await は 1 回の IO の平均待ちミリ秒
rkB/s と wkB/s は毎秒の読み書き量 — await が大きくスループットが小さいならディスク自体が遅い
await が大きくスループットも大きいなら本当に忙しい。増強の方向が異なる
vmstat 1 は毎秒 1 行:r は実行待ちキュー長(継続的にコア数を超えるなら CPU 不足)
b はブロック中のプロセス数、si / so はスワップイン/アウト(0 でなければメモリ不足で swap を使い始めている)
wa は IO 待ちが CPU 時間に占める割合。高いなら CPU ではなくディスクで詰まっている
vmstat の -d はディスク統計で、ディスク関連の列だけを表示
最初の出力は起動からの平均値。リアルタイム値は 2 行目から
1 行目で結論を出さない
iostat -x 1 5 vmstat 1 5 vmstat -d 1
プロセスがハングし、strace に何も出ない(中断不可状態)ときは、ユーザー空間のスタックがどこで止まっているかを見る
gdb -p はアタッチ中プロセスを停止させる — 取得後は必ず detach してから終了する。でないと止まったままになり、事実上サービス停止
-ex は gdb に 1 コマンド実行させて継続する。複数連記でき「アタッチ → スタック出力 → デタッチ」を一括で行える
thread apply all bt は全スレッドの完全なスタックを出力。シングルスレッドなら bt で足りる
gcore はコアファイルを生成し、後からゆっくり解析できる。オンラインデバッグより安全
-o はコアの出力パス指定(.PID の接尾辞は付かない)
Java プロセスに gdb を使わない — JVM のスタックは gdb では読めない。jstack を使う
perf の --call-graph はサンプリング点、ハングは完全なスタックが必要。互いに代替できない
gdb -p 1234 -ex 'thread apply all bt' -ex detach gcore -o /tmp/core.1234 1234
サーバーには vi しか入っていないことが多いので、設定変更は避けられない。20 個のキーで足りる。全部覚える必要はない
vim +120 ファイル は 120 行目へ直接ジャンプ — エラーログが示す行番号を追う最速の方法
-u NONE は設定もプラグインも読み込まない。vimrc が壊れていないかの切り分けに使う
-w /tmp/keys.log は全キー入力を記録。「自分が何を押したか」の調査に
:w は保存、:w! は強制保存(読み取り専用や権限不足のとき)
:q は終了。未保存の変更があると拒否される。:q! は変更を捨てて強制終了
:wq か :x は保存して終了。違いは :x が変更なしのときタイムスタンプを更新しないこと
:e! は現在の変更を捨てて再読み込み — 壊してしまってやり直したいときに
:saveas /tmp/a.conf は別名保存。設定変更前のバックアップの定石
読み取り専用ファイルをうっかり編集したら :q! が最速の挽回。保存ボタンを探さない
vim +120 /etc/nginx/nginx.conf vim -u NONE /etc/nginx/nginx.conf :w 保存 :q! 変更を破棄して終了 :wq 保存退出 :x 変更があるときだけ保存 :e! 重新加载 :saveas /tmp/a.conf 名前を付けて保存
gg は先頭行、G は最終行、42G は 42 行目へ
0 は行頭、^ は行頭の最初の非空白文字(インデントを揃えるときはこちら)、$ は行末
w は次の単語の先頭、b は前の単語の先頭、e は現在位置の単語の末尾
Ctrl+f は次ページ、Ctrl+b は前ページ、Ctrl+d は半ページ下、Ctrl+u は半ページ上
% は対応する括弧へジャンプ — 設定ブロックの閉じ位置を探すのが特に速い
'' は前回位置へ戻る(シングルクォート 2 つ)。Enter でもよい
大きいファイルで矢印キーを使わない。hjkl と上記の組み合わせのほうが遥かに速い
編集前に :set nu で行番号を表示。エラーの行番号が意味を持つ
gg 文件首行 G ファイル最終行 42G 第 42 行 0 行頭 ^ 行首首个非空白 $ 行末 w / b / e 下词首 / 上词首 / 词尾 Ctrl+f 下一页 Ctrl+b 前ページ % 跳到匹配的括号 '' 前回の位置へ戻る
dd は行削除 — 実体はカットなので p で貼り戻せる
yy は行コピー、5yy は 5 行まとめてコピー
p は現在行の下に貼り付け、P は上に貼り付け
u はアンドゥ、Ctrl+r はリドゥ(戻しすぎたときに使う)
x は 1 文字削除、r はカーソル位置の文字を 1 文字で置換(挿入モードに入らない)
cc は行全体を書き換え。内容は消えるがインデントは保たれる
d$ は行末まで削除、d0 は行頭まで削除、dw は 1 単語削除
. は直前の変更を繰り返す — Vim で最も価値のあるキー。20 行削除は dd の後に . を 19 回
i はカーソル前に挿入、a は後ろに挿入、o は下に新しい行、O は上に新しい行
Esc でノーマルモードへ戻る。忘れると文字の羅列を打ち込むことになる
dd / yy / p 行削除 / 行コピー / 貼り付け 5yy 5行コピー u / Ctrl+r アンドゥ / リドゥ x / r 文字削除 / 1文字置換 cc 行全体を書き換え(インデント保持) . 直前の変更を繰り返す(最も価値のあるキー) i / a / o / O 行頭挿入 / 行末挿入 / 下に新規行 / 上に新規行
/pattern は下方向検索、?pattern は上方向検索
n は次の一致、N は前の一致
* はカーソル下の単語を入力せずに検索、# は逆方向
:set hlsearch は全一致をハイライト、:noh はハイライト解除(画面が黄色だらけになったとき)
:%s/old/new/g は全文置換。g は各行のすべての一致、なしは各行の最初のみ
:%s/old/new/gc は置換ごとに確認 — 設定ファイルでは必ず c を付ける。1 回の誤置換でサービスが上がらなくなる
:10,20s/old/new/g は 10〜20 行目のみ置換
:.,$s/old/new/g は現在行からファイル末尾まで
区切り文字は変更できる::s#/usr/local/old#/usr/local/new#g なら、パス中の / をエスケープしなくて済む
置換前に /pattern で一致数を数え、見当をつけてから実行する
:%s/old/new/g 全文置換(各行のすべての一致) :%s/old/new/gc 置換のたびに確認(設定変更では c 必須) :10,20s/old/new/g 10〜20行目だけ置換 :.,$s/old/new/g 現在行からファイル末尾まで :s#/usr/local/old#/usr/local/new#g 区切り文字を変更し、パスのエスケープを不要に :noh 検索ハイライトを解除
Ctrl+v でブロック選択に入り、カーソル移動で 1 列を矩形選択 — 一括コメントや桁揃えはこれ
選択後に I で各行の行頭に同時挿入。入力後に Esc を押して初めて確定(押さないと反応がないように見える)
選択後に A で各行の行末に同時追記
選択後に c でブロック全体を置換、d でブロック全体を削除
:g/pattern/d は一致する行をすべて削除
:g/pattern/s/old/new/g は一致行の中だけ置換。全体置換より安全
:sort は選択行を並べ替え、:sort u は並べ替えて重複除去
:normal @a は選択行ごとにレジスタ a のマクロを 1 回実行。反復作業に使う
:set nu を先に有効化。矩形操作で範囲を見誤りにくくなる
Ctrl+v I Esc ブロック選択後、各行の行頭に挿入(一括コメント化) Ctrl+v A Esc ブロック選択後、各行の行末に追加 :g/^#/d # で始まる行をすべて削除 :g/ERROR/s/old/new/g ERROR を含む行だけ置換 :sort u 選択行をソートして重複除去
Web や Windows からコードを貼るとインデントが右へずれていく::set paste してから貼り、貼ったら :set nopaste
(:set pastetoggle=<F2> で F2 を切り替えキーにもできる)
Windows で編集されたファイルは行末に ^M が出る::set ff=unix してから :w で保存
:set ff を単独で実行すると現在が dos か unix か確認できる
中国語が文字化けする::set fileencoding=utf-8 で保存。表示だけ直したいならファイルは変えず
:e ++enc=gbk で GBK として読み直す
:w !sudo tee % は「sudo を付け忘れて保存できない」を解決する —
内容を sudo 経由で現在のファイル名に書き戻す。開き直して編集し直すより速い
これらの設定は一時的。永続化するには ~/.vimrc に書く
:set paste 貼り付け前に自動インデントを無効化 :set nopaste 貼り付け後に戻す :set ff=unix Windows の ^M を除去 :set fileencoding=utf-8 エンコーディングを修正して保存 :w !sudo tee % sudo を忘れたとき強制書き込み
「サーバーが遅い」は 1 つの問題ではなく 4 つ:CPU、メモリ、IO、ネットワーク。まず 1 コマンドでどれかを見分ける
uptime で load average の 3 値(1/5/15 分)を見る。コア数を超えていれば待ちが発生
load が高いのに CPU が暇(top の %id が高い)なら、ほぼ IO 待ち。wa 列を見る
free -h は free 列ではなく available 列を見る(-h は人間が読める単位)
df -h と df -i を一緒に見る:-h は容量、-i は inode。ディスク満杯は様々な不可解な障害を起こす
vmstat 1 5 は毎秒 1 回を 5 回。r(待ち行列)、b(ブロック)、wa(IO 待ち)、si/so(スワップ)を見る
層を切り分けてから動く。いきなりサービスを再起動するより遥かに有効
uptime free -h df -h; df -i vmstat 1 5
まず vmstat の wa 列:継続的に 10 を超えるなら IO が足を引っ張っている
次に b 列(ブロック中のプロセス数)。継続的にコア数を超えるなら多くのプロセスが資源待ちで詰まっている
どのプロセスか特定:pidstat -d 1 5(-d は IO 統計のみ、毎秒 1 回で計 5 回)
ps -eo の -e は全プロセス、-o は列指定。state 列で D(中断不可スリープ)が見える
awk '$1=="D"' で D 状態のプロセスを抽出 — これらが IO で詰まっている者
よくある原因:DB の全表走査、ログの同期書き込み、バックアップの実行中、NFS やクラウドディスクの揺らぎ
D 状態のプロセスは kill できない。IO の返却を待つかマシンを再起動するしかない
vmstat 1 5 pidstat -d 1 5 ps -eo state,pid,cmd | awk '$1=="D"'
free -h の available が「新しいプロセスにまだ渡せる量」。free が少ないのは正常で、Linux は空きメモリをキャッシュに使う
buff/cache が多いのは問題ではない。必要になれば自動で回収される
swap を多用しているなら本当に厳しい。vmstat の si/so が継続的に 0 でなければスラッシング
メモリ順に並べる:ps -eo pid,rss,comm --sort=-rss(-e は全部、-o は列、先頭の負号は降順)
RSS は実使用量、VSZ は仮想アドレス空間でほぼ参考にならない
コンテナで見るのは cgroup 制限(/sys/fs/cgroup/memory.max)で、ホストの total ではない
メモリリークを疑うなら RSS を 2 回比較:10 分間隔で取り、一方向に増え続けるものを探す
free -h vmstat 1 5 # si / so を確認 ps -eo pid,rss,comm --sort=-rss | head
第 1 層:curl -w で DNS / 接続 / 初回バイト / 総時間の 4 項目に分解
curl -o /dev/null は body を捨て、-s は静音でプログレスバーを消し、-w は独自出力(3 つ合わせて数字だけ見る)
第 2 層:サーバー上で curl 127.0.0.1。ネットワークとゲートウェイを除外する
第 3 層:アプリログのリクエスト所要時間と、nginx の $request_time / $upstream_response_time を比較
両者の差が大きければ中間層(ゲートウェイ、プロキシ、コネクションプール)で詰まっている
DB のスロークエリログも忘れずに有効化する。「API が遅い」の多くは実は SQL が遅い
総時間だけ高く上流時間が低いなら、通常はレート制限かキュー待ち
curl -o /dev/null -s -w 'dns:%{time_namelookup} conn:%{time_connect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://example.com/api
# nginx log_format に追加:
# $request_time $upstream_response_timeping -c 50 は 50 パケット送って loss% と mdev(ジッタ)を見る。1 回の ping では何も証明できない
ping -M do -s 1472 は経路 MTU の測定:-M do は分割禁止、-s はパケットサイズ。通らなければ MTU 問題
mtr -r -c 50:-r はレポート出力で画面分割なし、-c は送信数。各ホップの損失率が見える点で traceroute より強力
ICMP がレート制限されると損失に見えることがある。TCP での探査(nc -z や curl)のほうが正確
ethtool -S NIC は rx/tx のエラーとドロップのカウンタ(-S は statistics)
grep -iE は大文字小文字無視 + 拡張正規表現
同一ラック内の損失はまず NIC やケーブルを疑い、地域を跨ぐならキャリアの回線を疑う
ping -c 50 10.0.0.5 mtr -r -c 50 10.0.0.5 ethtool -S eth0 | grep -iE 'err|drop'
初めて入るマシンではまずこの一連を流して全体像を取り、それから何を調べるか決める
各行が「何に答えているか」を書いてある。ブラックボックスとして丸写ししない
オプション早見(すべて上の項目で説明済み):--sort=-%cpu は CPU 降順、-h は人間が読める単位、-i は inode
grep -vE は「除外 + 拡張正規表現」。ここでは tmpfs のような仮想ファイルシステムを除くのに使う
ss -tulnp の分解は t/TCP、u/UDP、l/待ち受け、n/数値、p/プロセス
systemctl の --failed は失敗 unit のみ、--no-pager はページャなし
dmesg の -T は読める時刻、-l は指定レベルのみ
本番機では書き込み操作を避ける — この一式はすべて読み取り専用で、出力はそのままチケットに貼れる
echo "== 负载 =="; uptime echo "== CPU 前 5 =="; ps aux --sort=-%cpu | head -6 echo "== 内存前 5 =="; ps aux --sort=-%mem | head -6 echo "== 内存 =="; free -h echo "== 磁盘 =="; df -h | grep -vE 'tmpfs|overlay' echo "== inode =="; df -i | grep -vE 'tmpfs|overlay' echo "== 监听端口 =="; ss -tulnp echo "== 失败服务 =="; systemctl list-units --failed --no-pager echo "== 内核错误 =="; dmesg -T -l err | tail -10