AWS中級 #3 S3 — 静的ホスティング、presigned URL
EC2 (#1 ~ #2) が コンピューティング の場だとすると、S3 (Simple Storage Service) は AWS の オブジェクトストレージ の場。2006 年に AWS の最初のサービスとしてリリースされた、最も古く、いまも最も広く使われているサービスの 1 つです。
S3 は実質 「無限容量のグローバルファイルシステム (ただしディレクトリは偽物)」。11 9’s (99.999999999%) の耐久性、GB あたり ~$0.023 の価格、他のすべての AWS サービスのデータハブ — この 3 点が S3 の正体。
この記事では S3 の形 → ポリシーとセキュリティ → 静的ホスティング → presigned URL → ストレージクラスを 1 本の線で通していきます。
バケットとオブジェクト #
S3 には 2 つの構成要素しかありません:
- バケット (Bucket) — オブジェクトを格納するコンテナ。アカウント / リージョン単位で作成
- オブジェクト (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 (ソウル) で作成すればオブジェクトデータはソウルのデータセンター内。コンソールに入ればどのリージョンにいるかが見えます。
リージョン間レプリケーションは 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 | バージョニング有効時のバージョン識別子 |
| 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 のセキュリティは 4 つの層 が重なって動きます。優先度 (上が強い):
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 つすべて有効化 がデフォルト。静的ホスティングのように意図的に 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 と組み合わさって効果を生みます — 両方を通過してこそ Allow が適用 (cross-account の場合)。
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/uploads/*"
}]
}詳しい IAM の場は 基礎 #2。
静的サイトホスティング #
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 パターン — #7 CloudFront で扱います。そのときは PAB も再び有効化し、OAC で CloudFront のみアクセス許可するパターン。
Presigned URL — 一時的な権限 #
Presigned URL は「このオブジェクトを N 分間だけ誰でもダウンロード / アップロードできる」URL を生成する仕組みです。権限のないユーザーに 権限を一時委任 するパターン。
最も一般的な用途:
- ユーザープロフィール画像のアップロード — クライアントが直接 S3 に PUT
- 決済領収書のダウンロード — 5 分の URL
- 非公開動画ストリーミング — 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 を有効化すると、同じキーに複数回 PUT したとき以前のバージョンが自動保存。
aws s3api put-bucket-versioning \
--bucket my-bucket \
--versioning-configuration Status=Enabled有効化後:
- 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 }
}]
}運用ではライフサイクルがほぼ必須 — ないと 6 か月後の請求書が怖くなります。
ストレージクラス — 費用の場 #
同じデータでも どれだけ頻繁に / 早く取り出すか によって異なるクラスに置けば費用が大きく節約できます。
| クラス | 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 キャッシュ + 自前ドメイン (#7) |
| Lambda | S3 PUT トリガーで画像変換 / インデックス (上級 #3) |
| Athena | S3 の CSV / Parquet / JSON を SQL で |
| Glue | S3 のデータカタログ / ETL |
| CloudTrail / VPC Flow Logs / ALB Logs | すべて S3 に保存 |
よくハマる罠 #
1) バケットが意図せず public #
ニュースに出るデータ漏洩の半分は S3。新バケットは PAB 4 つすべて有効 で開始、静的ホスティングのように意図された場のみ明示解除。
2) 費用爆弾 #
- GB あたり保存 + リクエスト数 + データ転送 の 3 重課金
- 特に インターネット向け転送 (Egress) が GB あたり ~$0.09 — 人気の静的サイトはここが大きい
- CloudFront とセット にして Egress 削減 + Edge キャッシュで高速化 (#7)
3) 小さなファイルが数百万個 #
オブジェクトごとに GET / PUT 費用がかかるため、小さなファイル数百万個 パターンは費用が意外に高い。まとめて (tar.gz、Parquet) 保存するか DynamoDB などへ移すのが正解。
4) Lifecycle なしで 1 年 #
ログ / 一時ファイルが Standard のまま — 6 か月後に請求が暴騰。ライフサイクルは作成初日に同時に 設定。
5) Versioning を有効化して放置 #
Versioning + lifecycle なし = 保存費用が無限に増加。有効化したらライフサイクルで古い非カレントバージョンを整理。
6) presigned URL の有効期限が長すぎる #
24 時間の presigned URL は実質的に永続アクセス。通常は 5~15 分、長くて 1 時間。
7) s3:* ワイルドカード IAM
#
Action: "s3:*" ポリシーは 危険。少なくとも GetObject / PutObject / ListBucket のように明示。
まとめ #
今回押さえたこと:
- S3 = 無限オブジェクトストレージ。バケット (グローバル一意の名前) + オブジェクト (key) の 2 つの構成要素のみ
- ディレクトリは偽物 —
keyの一部にすぎない - PAB → IAM Policy → Bucket Policy → ACL の順でセキュリティ評価
- 新バケットは PAB 4 つすべて有効 がデフォルト
- 静的ホスティング = バケット + website endpoint + public read ポリシー。HTTPS / Edge は #7 CloudFront へ
- Presigned URL = 一時的な権限委任。5~15 分、HTTPS のみ、ContentType を埋め込む
- Versioning + Lifecycle は対。ライフサイクルなしの versioning だけだと請求暴騰
- ストレージクラス — Standard / IA / One Zone-IA / Intelligent-Tiering / Glacier 3 種
- 罠 — public 漏れ、Egress 費用、小ファイル、ライフサイクル抜け、versioning 費用、presigned 期限、ワイルドカード IAM
次回 — RDS #
オブジェクトの場は掴みました。次は リレーショナル DB の場へ。
#4 RDS — マネージド DB、バックアップ、パラメータグループ では、RDS のマネージドモデル、自動バックアップと PITR、Multi-AZ、パラメータ / オプショングループ、そして運用時のマイナー vs メジャーアップグレードをどう扱うかを整理します。