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ファイルもビルドの残骸も存在しません。
なぜ気づきにくいか
zip -rがエラーを出さない。正常終了して exit code 0 を返す- 新しいファイルの追加・更新は正しく動いている。壊れているのは「削除の反映」だけ
- CI/CDのクリーン環境では問題が顕在化しない(前回のzipが残っていないから)
- ローカルで手動デプロイしているときや、ビルドキャッシュが効いている環境で初めて踏む
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ベースのデプロイ全般で同じ問題が起きます。
参考
- zip(1) man page —
-FS(File Sync) オプションの説明 - Working with .zip file archives for Python Lambda functions - AWS Lambda — AWS公式のzipパッケージング手順
- Deploying Lambda functions as .zip file archives - AWS Lambda — zipデプロイの公式ガイド