/var/www/yatta47.log

/var/www/yatta47.log

やったのログ置場です。スクラップみたいな短編が多いかと。

zip -r で削除したファイルがLambdaに残る原因と対策

zip -r でLambdaのデプロイパッケージを作ると、ソースから削除したファイルがzip内に残り続ける。原因は zip -r が既存アーカイブに対して「追加・上書き」する仕様。

対策は rm -f してから zip -r するか、-FS オプションを使う。

何が起きたか

Lambdaのコードを整理して不要なモジュールを削除して、zip -r でパッケージングしてデプロイ。いつもの手順です。ところがデプロイ後に動作確認すると、削除したはずの古いモジュールがまだ動いていた。

ファイルシステム上には存在しないのに、Lambda上では存在する。

原因がわかるとシンプルなんですが、気づくまでが厄介です。

原因

zip -r は既存のzipファイルがある場合、「追加・上書き」として動作します。同名ファイルは上書きされますが、ソースから消えたファイルをアーカイブから削除する処理は行いません。

1回目: ソースに A.py, B.py, C.py がある
┌──────────────┐      zip -r       ┌──────────────┐
│ src/         │  ──────────────→  │ deploy.zip   │
│  A.py        │                   │  A.py        │
│  B.py        │                   │  B.py        │
│  C.py        │                   │  C.py        │
└──────────────┘                   └──────────────┘

2回目: B.py を削除してから zip -r
┌──────────────┐      zip -r       ┌──────────────┐
│ src/         │  ──────────────→  │ deploy.zip   │
│  A.py (更新) │                   │  A.py (更新) │
│  C.py (更新) │                   │  B.py ← 残る │
└──────────────┘                   │  C.py (更新) │
                                   └──────────────┘

zip(1)のmanページにも「zipは既存のアーカイブがあればそれを更新し、なければ新規作成する」と書いてあります。

「更新」であって「再作成」ではないのがポイントです。

対策

対策1: zip作成前に既存ファイルを削除する(推奨)

rm -f deploy.zip
zip -r deploy.zip .

最もシンプルで確実。rm -f は対象が存在しなくてもエラーにならないので安全です。

対策2: zip -FS でフレッシュ同期する

zip -r -FS deploy.zip .

-FS(File Sync)オプションの動作: - mtime または size が変わっていれば → アーカイブ内のエントリを更新(内容ハッシュ比較ではない) - 新しいファイルがあれば → アーカイブに追加 - ソースに存在しないファイルがアーカイブにあれば → アーカイブから削除

差分だけ処理するので大きなアーカイブでは速い場合があります。ただし Info-ZIP 系では利用できますが、実装差や古い環境では未対応の可能性があります。

また、TZ がずれると全件更新扱いになる副作用があるので、Docker ビルド等では注意が必要です。

対策3: ビルドディレクトリを毎回クリーンにする

BUILD_DIR=$(mktemp -d)
cp -r src/. "$BUILD_DIR"/
cd "$BUILD_DIR" && zip -r /path/to/deploy.zip .
rm -rf "$BUILD_DIR"

一時ディレクトリを使えば、前回のzipファイルもビルドの残骸も存在しません。

なぜ気づきにくいか

  1. zip -r がエラーを出さない。正常終了して exit code 0 を返す
  2. 新しいファイルの追加・更新は正しく動いている。壊れているのは「削除の反映」だけ
  3. CI/CDのクリーン環境では問題が顕在化しない(前回のzipが残っていないから)
  4. ローカルで手動デプロイしているときや、ビルドキャッシュが効いている環境で初めて踏む

Lambdaの場合、デプロイしたzipの中身を直接確認する機会が少ないです。

コンソールのコードエディタで見えるファイル一覧と、ローカルのソースディレクトリを見比べて初めて気づくパターンが多い。

CI/CDでの防御策

Makefileで強制する

package:
    rm -f deploy.zip
    cd src && zip -r ../deploy.zip .

zipの中身を検証するステップを入れる

# パッケージング後にzipの中身を出力して差分を確認
zipinfo -1 deploy.zip | sed '/\/$/d' | sort > /tmp/zip-contents.txt
(cd src && find . -type f -printf '%P\n' | sort) > /tmp/src-contents.txt
diff -u /tmp/src-contents.txt /tmp/zip-contents.txt

zipinfo -1 でファイル名のみ出力し、-printf '%P' でパス形式を揃えるのがポイントです。

差分があればCIを止める、という防御ができます。

まとめ

zip -r は既存のzipファイルがあると「追加・上書き」する。削除されたファイルはアーカイブに残る。

対策は rm -f してから zip -r するのが最もシンプルで確実。Lambdaに限らず、zipベースのデプロイ全般で同じ問題が起きます。

参考