S3 — 静的ホスティング, presigned URL
AWS で最も古いオブジェクトストレージ S3。バケットの形と名前のグローバル一意性、ポリシーと Public Access Block、静的サイトホスティング、presigned URL、そしてストレージクラスで費用を下げるパターンまで整理します。
第8章 ~ 第9章 の EC2 がコンピューティング領域なら、S3 (Simple Storage Service) は AWS のオブジェクトストレージ領域です。2006年に AWS の最初のサービスとしてリリースされた最も古いサービスであり、今も最もよく使われるサービスの1つです。
S3 は事実上、無限大容量のグローバルファイルシステムです(ただしディレクトリは偽物です)。11 9’s (99.999999999%) の耐久性、GB あたり約 $0.023 の価格、他のすべての AWS サービスのデータハブ — この3つが S3 の正体です。
本章では S3 の構造から始め、ポリシーとセキュリティ、静的ホスティング、presigned URL、ストレージクラスまで一つの流れで整理します。ここで扱う静的ホスティングは 第14章 CloudFront で HTTPS と Edge キャッシュを加えて完成し、S3 PUT トリガーは 第17章 Lambda 基礎 のイベント処理へつながります。
バケットとオブジェクト #
S3 は覚えるべき核心が2つだけです。
- バケット (Bucket) — オブジェクトを入れるコンテナです。1アカウント / リージョン単位で作ります。
- オブジェクト (Object) — 実際のファイルです。キー(key)で識別します。
my-bucket/ ← バケット (名前はグローバル一意)
images/
profile/2026/avatar-001.jpg ← オブジェクト (key = 全体パス)
profile/2026/avatar-002.jpg
videos/
intro.mp4
index.htmlディレクトリは実はありません。上の図の / はキーの一部です。images/profile/2026/avatar-001.jpg がオブジェクト1つの全体キー(key)です。コンソールが / を基準にフォルダのように見せているだけです。
バケット名のグローバル一意性 #
バケット名は全世界の AWS アカウントすべてを横断して一意でなければなりません。my-bucket のような平凡な名前は、すでに誰かが取っています。
my-company-dev-uploads-2026
acme-prod-static-ap-northeast-2ルールは次のとおりです。
- 3 ~ 63 文字、小文字 / 数字 /
-/.を使います。 - ドット(
.)は可能ですが、SSL 証明書のワイルドカードで問題になるため、通常は-だけを使います。 - IP アドレスの形は不可で、
xn--で始められません(Punycode)。 - 大文字とアンダースコアは不可です。
名前に環境 / 用途 / リージョン / 会社名を書いておくと、請求書と検索が楽になります。
バケットはリージョン単位 #
バケット名はグローバル一意ですが、データは1つのリージョンに住んでいます。ap-northeast-2 (ソウル) に作れば、オブジェクトデータはソウルのデータセンターの中にあります。コンソールに入るとどのリージョンにあるかが見えます。このグローバル名 / リージョンデータの区別は、第1章 AWS 入門 のグローバルサービス vs リージョンサービスの議論と同じ文脈です。
リージョン間の複製は S3 Replication (CRR, Cross Region Replication) で明示的に設定します。
オブジェクトの核心属性 #
オブジェクト1つは次を持ちます。
| 属性 | 役割 |
|---|---|
| Key | オブジェクトの全体パス。バケットの中で一意 |
| Body | 実際のデータ (最大 5TB) |
| Content-Type | ブラウザがどう処理するか (image/jpeg, application/json) |
| Metadata | ユーザー定義ヘッダー (x-amz-meta-*) |
| ACL | オブジェクト単位の権限 (最近はほぼ使わず、バケットポリシーで代替) |
| Storage Class | ストレージクラス (Standard, IA, Glacier など) |
| Version ID | バージョニングを ON にしていればバージョン識別子 |
| ETag | コンテンツハッシュ (大部分は MD5) |
コンソール / CLI / SDK でアップロード #
# 単一ファイル
aws s3 cp ./image.jpg s3://my-bucket/images/profile/avatar.jpg
# フォルダ全体を同期
aws s3 sync ./public s3://my-bucket --delete
# Content-Type を明示
aws s3 cp ./index.html s3://my-bucket/ --content-type "text/html; charset=utf-8"
# ダウンロード
aws s3 cp s3://my-bucket/data.json ./import boto3
s3 = boto3.client("s3")
s3.upload_file("image.jpg", "my-bucket", "images/avatar.jpg")
s3.download_file("my-bucket", "data.json", "data.json")セキュリティの4つの構成 #
S3 のセキュリティは複数の層が重なって動きます。評価順は上から下へ行くほど弱くなります。
1. Public Access Block ← 最も強力。遮断の決定がすべての上に
2. SCP (Organizations) ← アカウント単位のガード
3. IAM Policy ← ユーザー / ロール単位
4. Bucket Policy ← バケット単位
5. Object ACL ← オブジェクト単位 (旧方式、ほぼ使わない)Public Access Block — 最初に #
Public Access Block (PAB) はバケットが誤って公開されるのを防ぐ安全装置です。4つのオプションがあります。
| オプション | 意味 |
|---|---|
| BlockPublicAcls | 新しい ACL が public になれないように |
| IgnorePublicAcls | 既存の public ACL を無視 |
| BlockPublicPolicy | 新しいバケットポリシーが public になれないように |
| RestrictPublicBuckets | すでに public のバケットも IAM Principal だけがアクセス |
最近はすべての新しいバケットがアカウントレベルで4つすべてを ON にするのがデフォルトです。静的ホスティングのように、わざと public なバケットだけを明示的に解除します。
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=trueBucket Policy — JSON ポリシー #
Bucket Policy はバケットに直接付ける JSON ポリシーです。誰が(Principal)何を(Action)どこに(Resource)できるかを定義します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "logdelivery.elasticloadbalancing.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-alb-logs/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/MyAppRole"
},
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}IAM Policy #
IAM ユーザー / ロール に付けるポリシーです。Bucket Policy と結合して効果を生みます。cross-account の場合は両方を通過してこそ Allow が適用されます。
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/uploads/*"
}]
}詳しい IAM 設定は 第2章 IAM で扱います。
静的サイトホスティング #
S3 は静的な HTML / CSS / JS をそのままホスティングできます。最も簡単な静的サイトホスティング方式です。
aws s3 website s3://my-static-site/ \
--index-document index.html \
--error-document 404.htmlこのコマンドの後、バケットが次の URL で応答します。
http://my-static-site.s3-website-ap-northeast-2.amazonaws.com公開アクセスの許可 #
デフォルト値では PAB が塞ぎます。静的ホスティングは意図された public なので、次を行います。
- バケット PAB で BlockPublicPolicy の2項目を解除します。
- Bucket Policy で全員に GetObject を許可します。
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-static-site/*"
}]
}S3 静的ホスティングの限界 #
S3 だけでは次ができません。
- HTTPS — S3 website endpoint は HTTP です。
- カスタムドメイン + SSL 証明書 — 直接はできません。
- Edge キャッシュ — 全世界の速い応答ができません。
そのため運用ではほぼ常に S3 + CloudFront パターンを使います(第14章 CloudFront で扱います)。そのときは PAB も再び ON にして、OAC で CloudFront だけにアクセスを許可するパターンになります。
Presigned URL — 一時的な権限 #
Presigned URL は、このオブジェクトを N 分間だけ誰でもダウンロードまたはアップロードできるという一時的な権限を作る方式です。権限のないユーザーに権限を一時的に委任するパターンです。
最もよくある使いどころは次のとおりです。
- ユーザープロフィール画像のアップロード — クライアントが直接 S3 へ PUT します。
- 決済領収書のダウンロード — 5分のリンクです。
- 非公開ビデオストリーミング — 1時間のトークンです。
import boto3
s3 = boto3.client("s3")
url = s3.generate_presigned_url(
"put_object",
Params={
"Bucket": "my-bucket",
"Key": f"uploads/user-123/{filename}",
"ContentType": "image/jpeg",
},
ExpiresIn=600, # 10 分
)
# クライアントはこの URL で PUT リクエストcurl -X PUT --upload-file ./photo.jpg "<presigned-url>"presigned URL のセキュリティ #
- URL 自体に一時的な資格情報が含まれています。その URL さえあれば誰でも使えます。
- 有効期限が過ぎると自動で無効になります。
- HTTPS だけを使います。HTTP で漏れると露出します。
- ContentType / Content-Length のような条件を一緒に埋め込めます。
POST フォーム vs PUT URL #
アップロード方式は2つです。
- PUT URL — 単純です。ヘッダーでメタデータを渡し、ContentType を1つ固定します。
- POST フォーム (presigned post) — 複雑です。多重条件(
content-length-range、starts-withなど)でより安全です。
大規模だったり重要なアップロードは POST フォームが推奨されます。単純な場合は PUT URL で十分です。
バージョニングとライフサイクル #
Versioning — オブジェクトの履歴 #
バケットに Versioning を ON にすると、同じキーに何度も PUT したときに以前のバージョンが自動で保存されます。
aws s3api put-bucket-versioning \
--bucket my-bucket \
--versioning-configuration Status=EnabledON にした後は次のとおりです。
- Delete も実際には消しません。Delete Marker だけが追加されます。
- 以前のバージョンは
--version-idで復旧できます。 - 保存費用はすべてのバージョンを合算します(落とし穴)。
Lifecycle — 自動整理 / 移行 #
古いオブジェクトを自動で安いクラスへ移すか削除するルールです。
{
"Rules": [{
"ID": "ArchiveOldLogs",
"Status": "Enabled",
"Filter": { "Prefix": "logs/" },
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 365 }
}]
}運用にライフサイクルはほぼ必須です。なければ数か月後に請求書が怖くなります。
ストレージクラス — 費用の項目 #
同じデータをどれだけ頻繁に、どれだけ速く取り出すかによって別のクラスに置けば、費用が大きく節約できます。
| クラス | GB/月 | 頻繁にアクセス | 取り出し時間 | 役割 |
|---|---|---|---|---|
| Standard | $0.023 | 毎日 | 即時 | デフォルト。ホットデータ |
| Standard-IA | $0.0125 | たまに | 即時 | バックアップ、分析データ |
| One Zone-IA | $0.01 | たまに、再生成可能 | 即時 | 1 AZ のみ — 重要度低い |
| Intelligent-Tiering | 自動 | パターン不明 | 即時 | アクセス頻度がばらつく |
| Glacier Instant Retrieval | $0.004 | 四半期に1回 | 即時 | アーカイブ + たまに即時必要 |
| Glacier Flexible Retrieval | $0.0036 | 年に1~2回 | 分~時間 | 一般的なアーカイブ |
| Glacier Deep Archive | $0.00099 | ほぼなし | 12 時間 | 長期コンプライアンス |
数字は
ap-northeast-2基準のおおよその値です。詳しくは 公式の価格表 を見ます。
クラス決定ガイド #
このデータ、毎日 / 毎週見るか?
├── YES → Standard
└── NO →
たまに (月1回以下) ?
├── YES → Standard-IA (再生成可能なら One Zone-IA)
└── NO →
パターンを予測できるか ?
├── YES → Glacier 系
└── NO → Intelligent-Tiering落とし穴 — クラス移行の費用 #
Standard から IA のような移行ごとに、オブジェクトあたり約 $0.01 の小さな費用がかかります。オブジェクトが1億個なら、この費用が大きくなります。ライフサイクルで頻繁に移してはいけません。オブジェクトのサイズと頻度を見て決めます。
S3 の一貫性 #
昔は read-after-write の一貫性が弱かったです。2020年12月からすべてのリージョンで strong consistency が保証されます。
- PUT 後の GET が即時に可能です。
- DELETE 後の LIST が即時に反映されます。
ただしバージョンオブジェクトやメタデータの変更は、依然として若干の時間差があり得ます。
S3 と他のサービスの役割 #
| 一緒によく使うサービス | パターン |
|---|---|
| CloudFront | S3 + Edge キャッシュ + ユーザードメイン (第14章) |
| Lambda | S3 PUT トリガーで画像変換 / インデックス (第17章) |
| Athena | S3 の CSV / Parquet / JSON を SQL で |
| Glue | S3 のデータカタログ / ETL |
| CloudTrail / VPC Flow Logs / ALB Logs | すべて S3 に保存 |
よく出会う落とし穴 #
- バケットが意図せず public — ニュースに出るデータ漏洩の半分は S3 です。新しいバケットは PAB 4つをすべて ON にした状態で始め、静的ホスティングのように意図された場合だけ明示解除します。
- 費用爆弾 — GB あたりの保存 + リクエスト数 + データ転送の三重課金です。特にインターネットへ出るトラフィック(Egress)が GB あたり約 $0.09 なので、人気の静的サイトはこれが大きいです。CloudFront と束ねて Egress を削減し、Edge キャッシュで加速します(第14章)。
- 小さいファイルが数百万個 — オブジェクト1つ1つに GET / PUT 費用があるため、小さいファイル数百万個のパターンは費用が意外に大きいです。束ねて(
tar.gz、Parquet)保存するか、別のストレージへ移すのが答えです。 - Lifecycle なしで1年 — ログや一時ファイルが Standard にそのまま残ると、数か月後に請求書が暴増します。ライフサイクルはバケットを作る初日に一緒に設定します。
- Versioning を ON にして忘れる — Versioning を ON にして lifecycle がなければ、保存費用が無限に増加します。ON にしたら、ライフサイクルで古い非現行バージョンを整理します。
- presigned URL の有効期限が長すぎる — 24時間の presigned URL は事実上、永久アクセスです。通常 5 ~ 15 分、長くても1時間に設定します。
s3:*ワイルドカード IAM —Action: "s3:*"ポリシーは危険です。少なくとも GetObject / PutObject / ListBucket のように明示します。
練習問題 #
- §「S3 権限の評価順」の5つの層のうち、静的ホスティングバケットをわざと公開するにはどの層をどう解除すべきかを書いてみてください。そして 第14章 CloudFront の OAC パターンでは同じバケットの PAB をなぜ再び ON にするのか、一文で対比して説明してみてください。
- presigned PUT URL の
ExpiresInを 600 秒に設定したコードを見て、有効期限が長すぎるときと短すぎるときにそれぞれどんな問題が生じるかを、§「presigned URL のセキュリティ」を根拠に書いてみてください。 - ログバケットを1つ想定し、30日後 Standard-IA、90日後 Glacier、365日後に削除する Lifecycle ルールを自分で作成してみてください。このルールが §「費用爆弾」のどの項目を減らしてくれるかを、第27章 コスト最適化 と結びつけてメモしておいてください。
一行まとめ: S3 はバケット(グローバル一意の名前)とオブジェクト(key)の2概念だけがある無限オブジェクトストレージで、ディレクトリは偽物。セキュリティは PAB が最上位で、新しいバケットは PAB 4つをすべて ON にする。静的ホスティングは HTTPS と Edge がないので CloudFront と束ね、presigned URL は 5~15分の一時的な権限委任、Versioning は必ず Lifecycle と一対で置く。
次の章 #
オブジェクト領域はつかめました。次の 第11章 RDS では、リレーショナル DB の番に移ります。RDS のマネージドモデル、自動バックアップと PITR、Multi-AZ、パラメータ / オプショングループ、そしてマイナー vs メジャーアップグレードをどう扱うかを整理します。