
你的資料該放哪?EC2 的本地磁碟重開機就沒了、S3 便宜但不能當硬碟掛、EFS 方便但貴——三種儲存各有各的坑。但在你選之前,有一個問題要先答:這份資料需不需要活過容器重啟?
結論:先問一題,再選三兄弟
先問:這份資料需不需要活過 task 重啟?不需要 → 留在容器裡沒關係。需要 → 一定要外置,而且多數情況外置到 S3,不是 EFS。
再選:
| 一句話 | 什麼時候 | |
|---|---|---|
| S3 | 物件儲存,PUT / GET 整個檔案,幾乎無限 | 上傳的檔案、報表、備份、log、靜態資源——九成的情況 |
| EBS | 掛在單台 EC2 上的硬碟 | 只有在 EC2 上跑資料庫時;用 RDS 就不用管它 |
| EFS | 多台機器同時掛的 NFS | 真的需要多台共用同一個檔案系統時,而且要先確認不是該改用 S3 |
一、容器磁碟是暫時的,這件事比選型重要
官方文件:Fargate task 預設 20 GiB 暫存空間,最多加到 200 GiB,而且——拉下來的 container image 也算在這 20 GiB 裡。task 停止,暫存空間就沒了。
從 EC2 搬進容器時,卡住的通常不是 Dockerfile,是程式裡藏著的「寫本地檔」:
- 框架的上傳暫存目錄(Laravel 的
storage/、Django 的MEDIA_ROOT)→ S3 - 寫在磁碟上的 log → 標準輸出,讓 CloudWatch 收(第 7 篇)
- 用檔案做的排程鎖、PID 檔 → Redis 鎖(第 6 篇)
- session 寫本地檔 → ElastiCache
- 產生的報表先寫本地再上傳 → 直接串流到 S3,或至少確認 20 GiB 夠
Redis 鎖有一個坑要先知道:A 拿到鎖,但工作做太久,鎖先過期了;這時 B 拿到同一把鎖開始做;A 做完照常 DEL,刪掉的其實是 B 的鎖。
所以取鎖用 SET key token NX PX <ttl>。NX 是「key 不存在才寫入」,有人拿著鎖時你就寫不進去;PX 是「幾毫秒後自動過期」,拿鎖的程式當掉了,鎖也不會永遠卡著。token 每次取一個唯一值,用來認出「這把鎖是我拿的」。
釋放時要先比對 token 是自己的,才刪。比對和刪除要包在同一段 Lua 腳本裡,因為 Redis 會把整段腳本一次跑完、中間不插別的指令。分成兩個指令的話,你比對完、還沒刪之前,鎖可能剛好過期又被別人拿走,你還是會刪到別人的鎖。
這是程式碼問題,不是設定問題——看架構圖看不出來,只能翻 code。一份架構裡還留著「一台 EC2 跑排程」沒進容器,多半就是卡在這裡。
二、S3:兩個預設已經幫你做好,三個要自己做
已經做好的(不少教學還在教你手動開,其實不用了):
- 預設擋公開:官方——新 bucket 預設不允許公開存取。Block Public Access 有四個設定,建議帳號層級全開,這樣之後任何人建的 bucket 都擋。
- 預設加密:官方——2023 年 1 月 5 日起所有新物件自動用 SSE-S3(金鑰由 S3 代管)加密,不另收費。要自己管金鑰輪替才需要換 SSE-KMS(金鑰改放 KMS 管理)。
# 帳號層級全擋,一次做完
aws s3control put-public-access-block --account-id 123456789012 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true要自己做的:
- 版本控制——開了之後誤刪、誤覆蓋都找得回來。備份 bucket 一定開。
- Lifecycle——我自己的做法是:log 先丟 Standard、30 天轉 IA(不常讀的檔案用,存放比較便宜、讀取另收費)、90 天轉 Glacier(封存用,更便宜、取回要等)、一年後刪。光這一條就省下大半的 log 儲存費。
aws s3api put-bucket-lifecycle-configuration --bucket my-app-logs --lifecycle-configuration '{
"Rules": [{"ID":"archive-logs","Status":"Enabled","Filter":{"Prefix":"logs/"},
"Transitions":[{"Days":30,"StorageClass":"STANDARD_IA"},{"Days":90,"StorageClass":"GLACIER"}],
"Expiration":{"Days":365}}]}'- 備份 bucket 加 Object Lock——勒索軟體的第一步是刪備份。Object Lock 讓物件在保留期內連 root 都刪不掉。備份沒鎖等於沒備份。
版本控制開了之後,三種東西會默默算錢——lifecycle 官方範例每一種都有對應的動作,不設就一直存、一直收:
- 舊版本——每次覆蓋,舊的那份變成「非當前版本」留著。要用
NoncurrentVersionExpiration設「變成非當前幾天後刪」,可以再加NewerNoncurrentVersions保留最近幾版(1 到 100)。沒設等於每個檔案的歷史全部永久保存。 - 刪除標記——版本控制下的「刪除」只是加一個標記,物件還在。舊版本清完之後,標記自己會留下來,要用
ExpiredObjectDeleteMarker掃掉。 - 沒完成的分段上傳——大檔上傳到一半斷線,已經傳上去的分段會一直佔空間,任何列表都看不到它。用
AbortIncompleteMultipartUpload設幾天後自動清。這條跟版本控制無關,每個 bucket 都該有。
還有一條會影響上面那條 log 規則的:2024 年 9 月起,小於 128 KB 的物件預設不會被轉到任何儲存等級。log 檔如果切得很細,lifecycle 看起來設了、實際上沒動。要轉就在 filter 加 ObjectSizeGreaterThan。
aws s3api put-bucket-lifecycle-configuration --bucket my-app-uploads --lifecycle-configuration '{
"Rules": [{"ID":"housekeeping","Status":"Enabled","Filter":{"Prefix":""},
"NoncurrentVersionExpiration":{"NoncurrentDays":30,"NewerNoncurrentVersions":5},
"Expiration":{"ExpiredObjectDeleteMarker":true},
"AbortIncompleteMultipartUpload":{"DaysAfterInitiation":7}}]}'儲存等級的價格看定價頁,這篇不列單價——那些數字會變。
三、EBS:只在 EC2 上跑資料庫時
EBS 是掛在 EC2 上的虛擬硬碟。EC2 的 instance store(實體主機上直接附的本地磁碟)在 stop 或 terminate 後會消失,重要資料一定掛 EBS。
但用 RDS 的話你根本不會碰 EBS——RDS 幫你管了。這節只給「有理由在 EC2 上自己跑資料庫」的人。
官方:gp3 基準 3,000 IOPS 和 125 MiB/s 含在儲存價格裡,IOPS 和吞吐可以獨立加、跟容量脫鉤,而且每 GiB 比 gp2 便宜 20%。沒理由用 gp2。
aws ec2 create-volume --volume-type gp3 --size 100 --availability-zone ap-northeast-1a
aws ec2 attach-volume --volume-id vol-xxx --instance-id i-xxx --device /dev/xvdf
# 機器內:mkfs -t xfs /dev/xvdf && mount /dev/xvdf /data
echo '/dev/xvdf /data xfs defaults,nofail 0 2' | sudo tee -a /etc/fstab我踩過的坑:nofail 很重要——萬一 volume 沒掛上,加了這個機器還是能開機,不加的話整台卡在開機。
刪機器時的預設行為要知道:根磁碟預設跟著機器一起刪,另外掛的資料磁碟預設留下。前者的後果是「機器 terminate 了資料也沒了」,後者的後果是 第 11 篇講的沒掛載 EBS 一直算錢。兩個方向都是坑,建機器時用 DeleteOnTermination 明確指定,別靠預設。
兩件 EBS 特有的:volume 綁 AZ,AZ-a 的 volume 掛不到 AZ-c 的機器,換 AZ 要靠 snapshot;快照用 Data Lifecycle Manager 自動排程,但快照存著就收錢,保留數要設(第 11 篇)。
四、EFS:真的要共用才用,而且常常是誤用
EFS 是託管的 NFS,多台 EC2 或 ECS task 同時讀寫同一個檔案系統。
最常見的誤用:程式在容器裡寫檔、容器重啟檔案不見,於是掛個 EFS 讓檔案活下來。這是把「狀態外置」做成「狀態放共享磁碟」——比不做好,但代價是 NFS 的延遲、EFS 的價格、以及多個 task 同時寫同一檔的鎖問題。先問能不能改成 S3,多數上傳、報表、匯出都可以。
真的需要 EFS 的:共用的設定目錄、需要 POSIX 檔案語意的舊程式、多個 task 要讀同一批大檔。
aws efs create-file-system --performance-mode generalPurpose --throughput-mode elastic
# 每個 AZ 一個 mount target,不然那個 AZ 的 task 掛不到
aws efs create-mount-target --file-system-id fs-xxx --subnet-id subnet-private-1a --security-groups sg-efs
aws efs create-mount-target --file-system-id fs-xxx --subnet-id subnet-private-1c --security-groups sg-efs這篇的驗證標準
| 主張 | ❌ 不算 | ✅ 算 |
|---|---|---|
| 狀態外置了 | code 裡有 S3 SDK | 殺掉一個 task 再起來,上傳的檔案還在、使用者還登入著 |
| 沒有公開的 bucket | 「我們沒開公開」 | 帳號層級 Block Public Access 四項全 true;Access Analyzer for S3 零 finding |
| 備份還原得回來 | 有 snapshot | 實際還原到另一個實例,比對資料,計時 |
| 備份刪不掉 | 開了版本控制 | 用 root 試刪一個 Object Lock 的物件,被拒 |
實戰 Checklist
- 翻過 code:本地檔、log、鎖、session 全部外置;Fargate 暫存夠不夠含 image
- 帳號層級 Block Public Access 全開;備份 bucket 開版本控制和 Object Lock
- log 有 lifecycle 且物件大於 128 KB;版本控制的 bucket 有
NoncurrentVersionExpiration和AbortIncompleteMultipartUpload;快照有保留數 - EBS 一律 gp3;
fstab加nofail - 要掛 EFS 之前先問:能不能改 S3
從 K8s 過來的話
EBS 對 PersistentVolume(EBS CSI Driver)、EFS 對 ReadWriteMany 的 PV、S3 沒有直接對應。EBS 綁 AZ 這件事在 EKS 會咬人:Pod 被排到別的 AZ,PV 掛不上,Pod 卡 Pending——要用 WaitForFirstConsumer 的 StorageClass 讓 volume 跟著 Pod 建。細節在 K8s Storage。
「狀態外置」在 K8s 是同一個問題:emptyDir 也是 Pod 重啟就沒。你在 K8s 上已經學過的紀律,搬到 ECS 一字不改。
相關文章
- 上一篇 Route 53,下一篇 DynamoDB
- 狀態外置的另一半(session、鎖):資料庫;快照和 EFS 的帳單:成本
- 概念層:Storage Management(MinIO)
儲存的選擇不是「哪個最好」,是「這份資料要不要活過重啟、多常被讀、能不能丟」。
