<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>インフラ &#8211; エンジニア見習い</title>
	<atom:link href="https://otonan-syusyoku.work/archives/category/learning/infra/feed" rel="self" type="application/rss+xml" />
	<link>https://otonan-syusyoku.work</link>
	<description>三流プログラマー</description>
	<lastBuildDate>Wed, 25 Feb 2026 08:34:10 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://otonan-syusyoku.work/wp-content/uploads/2023/10/cropped-名称未設定のデザイン-16-32x32.png</url>
	<title>インフラ &#8211; エンジニア見習い</title>
	<link>https://otonan-syusyoku.work</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>TerraformとCDK のロールバックについて</title>
		<link>https://otonan-syusyoku.work/archives/2193</link>
					<comments>https://otonan-syusyoku.work/archives/2193#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Thu, 12 Feb 2026 08:49:22 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[ポエム]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2193</guid>

					<description><![CDATA[どうも、PHPerです。 先日、友人のWebアプリケーションエンジニアとIaC（Infrastructure as Code）について話す機会がありました。 話の発端は「Terraform と CDK、どっちを採用すべき [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">どうも、PHPerです。</p>



<p class="wp-block-paragraph">先日、友人のWebアプリケーションエンジニアとIaC（Infrastructure as Code）について話す機会がありました。 話の発端は「Terraform と CDK、どっちを採用すべきか？」というよくあるテーマだったのですが、友人からこんな強烈な一言をもらいました。</p>



<p class="wp-block-paragraph"><strong>「CDK一択でしょ。だってCDK（CloudFormation）はデプロイ失敗時に自動ロールバックしてくれるけど、Terraformは作りかけで止まるでしょ？怖くて使えないよ」</strong></p>



<p class="wp-block-paragraph">なるほど、確かにその視点はあります。DBのトランザクションに慣れ親しんだエンジニアからすれば、「失敗したらAtomicに元に戻る」挙動こそが正義でしょう。</p>



<p class="wp-block-paragraph">しかし、普段Terraformを触っている身からすると「うーん、それは『ロールバック』の定義や運用思想が違うだけでは？」というモヤモヤが残りました。</p>



<p class="wp-block-paragraph">今回は、この「IaCにおけるロールバックと復旧戦略」について、公式ドキュメント等の情報を交えながら整理してみたいと思います。</p>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2193" data-theme="BlogArise">
			<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>Contents</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">そもそも「ロールバック」とは何を指しているのか？</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-2">CDK の場合</a></li><li class="rtoc-item"><a href="#rtoc-3">Terraform の場合</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-4">なぜTerraformには自動ロールバックがないのか？</a></li><li class="rtoc-item"><a href="#rtoc-5">ロールバックというバージョン切り戻し</a></li><li class="rtoc-item"><a href="#rtoc-6">結論：どっちが良いのか？</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-7">まとめ</a></li></ul></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">そもそも「ロールバック」とは何を指しているのか？</h2>



<p class="wp-block-paragraph">友人が言っていた「ロールバック」は、主に <strong>「デプロイ（Apply）が途中でコケた時の挙動」</strong> を指しています。</p>



<h3 id="rtoc-2"  class="wp-block-heading">CDK の場合</h3>



<p class="wp-block-paragraph">CDKは、コンパイルされると最終的に <strong>AWS CloudFormation テンプレート</strong> になり、デプロイもCFnを通じて行われます。</p>



<ul class="wp-block-list">
<li><strong>挙動</strong><br>スタックの更新中にエラーが発生すると、CFnは <strong>「自動的に直前の正常な状態に戻そう」</strong> とします。</li>



<li><strong>ステータス</strong><br><code>UPDATE_ROLLBACK_IN_PROGRESS</code> → <code>UPDATE_ROLLBACK_COMPLETE</code></li>
</ul>



<p class="wp-block-paragraph">これはCloudFormationの基本機能として提供されています。<br> <a href="https://www.google.com/search?q=https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-monitor-rollback.html" target="_blank" rel="noreferrer noopener">スタックの更新のロールバック &#8211; AWS CloudFormation</a></p>



<p class="wp-block-paragraph"><strong>メリット</strong><br>何もしなくても「壊れた状態」で放置されることが少ないです。<br>「オール・オア・ナッシング」に近い挙動をするため、整合性が保たれやすい安心感があります。</p>



<p class="wp-block-paragraph"><strong>デメリット</strong><br>ロールバックには時間がかかります。<br>リソース作成に30分かかって失敗した場合、ロールバックにも同等の時間がかかることがあります。 <br>また、ロールバック自体が失敗する<strong>UPDATE_ROLLBACK_FAILED</strong>という状態に陥ると、手動での介入が必要になり、復旧難易度が跳ね上がります。 <br><a href="https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-continueupdaterollback.html" target="_blank" rel="noreferrer noopener">参考: 更新のロールバックに失敗したスタックを更新できるようにする &#8211; AWS CloudFormation</a></p>



<h3 id="rtoc-3"  class="wp-block-heading">Terraform の場合</h3>



<p class="wp-block-paragraph">Terraformは、AWSのAPIを直接叩いてリソースを操作します。</p>



<ul class="wp-block-list">
<li><strong>挙動</strong><br><code>terraform apply</code> 中にエラーが発生すると、<strong>その時点で処理が停止します</strong>。</li>



<li><strong>ステータス</strong><br>成功したリソースは作成済み、失敗したリソースは未作成（または中途半端）な状態で残ります。</li>
</ul>



<p class="wp-block-paragraph">Terraformには「自動ロールバック」という機能自体が存在しません。これはバグではなく仕様（思想）です。 <br><a href="https://developer.hashicorp.com/terraform/cli/commands/apply" target="_blank" rel="noreferrer noopener">HashiCorp Terraform: Command: apply</a></p>



<p class="wp-block-paragraph"><strong>メリット</strong><br>エラーの原因が明確で、<strong>「ここまでは成功した」という状態がStateファイルに保存されます</strong>。 <br>コードを修正して再実行（<code>terraform apply</code>）すれば、<strong>差分のみ</strong>を適用し直すため、復旧までの時間が圧倒的に短いです。</p>



<p class="wp-block-paragraph"><strong>デメリット</strong><br>「中途半端な状態」が一時的に発生するため、手動でのリカバリ（State操作やTaint）が必要になる場合があります。</p>



<h2 id="rtoc-4"  class="wp-block-heading">なぜTerraformには自動ロールバックがないのか？</h2>



<p class="wp-block-paragraph">Terraform派の意見として、「自動ロールバックがないこと」は <strong>「Fail Fast（早く失敗して早く直す）」</strong> という設計思想に基づいています。</p>



<ol start="1" class="wp-block-list">
<li><strong>Stateの一貫性重視</strong><br>Terraformは「現在のインフラの状態」をtfstateファイルで厳密に管理します。<br>勝手にロールバック（過去の状態に戻すAPIコール）を行うと、ローカルのコードと現実のリソースの乖離が複雑化するリスクがあります。 <a href="https://developer.hashicorp.com/terraform/language/state" target="_blank" rel="noreferrer noopener">State &#8211; Terraform by HashiCorp</a></li>



<li><strong>Roll Forward（前進による修復）の推奨</strong><br>インフラ変更において、失敗した場合は「元に戻す」よりも「修正して適用し直す」方が早いケースが多いです。<br>Terraformは <code>plan</code> コマンドで事前に変更内容を詳細に確認できるため、実行時エラーは「権限不足」や「パラメータ不正」などが主であり、コードを直して再Applyすれば済むことがほとんどです。</li>
</ol>



<h2 id="rtoc-5"  class="wp-block-heading">ロールバックというバージョン切り戻し</h2>



<p class="wp-block-paragraph">友人の言う「デプロイ失敗時の自動復旧」ではなく、<strong>「正常にデプロイしたけどアプリが動かないから昨日の構成に戻したい」</strong> という場合のロールバックはどうでしょうか。</p>



<ul class="wp-block-list">
<li><strong>CDK (CloudFormation)</strong>: Gitで以前のコードに戻し、再デプロイします。 ※ CFnの自動ロールバックはあくまで「デプロイエラー時」の挙動であり、論理的なバグまでは戻してくれません。</li>



<li><strong>Terraform</strong>: Gitで以前のコードに戻し、<code>terraform apply</code> します。</li>
</ul>



<p class="wp-block-paragraph">つまり、<strong>「正常デプロイ後の切り戻し（Revert）」に関しては、両者の手間に大きな差はありません。</strong> どちらも「Gitを正」として、過去のコミットの状態へインフラを収束させる作業になります。</p>



<h2 id="rtoc-6"  class="wp-block-heading">結論：どっちが良いのか？</h2>



<p class="wp-block-paragraph">友人の「CDKの方が優秀」という意見は、<strong>「デプロイ作業中の安全性」</strong> に重きを置いた意見と言えます。特にアプリケーションエンジニアにとって、デプロイ失敗時に環境が散らかったままになるのは恐怖でしょう。</p>



<p class="wp-block-paragraph">一方でTerraformは、<strong>「状態の透明性と制御」</strong> に重きを置いています。「勝手に戻るより、どこで止まったか教えてくれ。俺が直すから」というスタンスです。</p>



<h3 id="rtoc-7"  class="wp-block-heading">まとめ</h3>



<ul class="wp-block-list">
<li><strong>CDK (CloudFormation)</strong>
<ul class="wp-block-list">
<li><strong>特徴</strong>: 失敗時は自動で元通り（トランザクション的）。</li>



<li><strong>向いているケース</strong><br>インフラの詳細なステータス管理より、デプロイパイプラインの安定性を重視したい場合。ロールバック待ち時間が許容できる場合。</li>
</ul>
</li>



<li><strong>Terraform</strong>
<ul class="wp-block-list">
<li><strong>特徴</strong><br>失敗時はそこでストップ（即時停止）。修正して再実行（ロールフォワード）。</li>



<li><strong>向いているケース</strong><br><code>terraform plan</code> で事前に結果を予測し、失敗時も自分の手ですぐに復旧させたい場合。APIコールの速さを求める場合。</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">「ロールバックがあるからCDK」ではなく、「失敗時のリカバリ戦略として、自動復帰（CDK）と即時停止（Terraform）のどちらが自分たちのチームに合っているか」で選ぶのが正解ではないでしょうか。</p>



<p class="wp-block-paragraph">私は……やっぱり <code>terraform plan</code> で「何が起きるか完全に把握してから実行する」安心感が好きですね（笑）</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2193/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CDKで管理しているRDSを暗号化したい</title>
		<link>https://otonan-syusyoku.work/archives/2198</link>
					<comments>https://otonan-syusyoku.work/archives/2198#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Thu, 12 Feb 2026 06:29:36 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[生涯独学]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[CDK]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[RDS]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2198</guid>

					<description><![CDATA[どうも、PHPerです。 先日、業務にて「既存のRDSのストレージを暗号化したい」という、シンプルかつ重たい要件が降ってきました。 これの何が困るかというと、皆様ご存知の通りRDSの仕様です。 RDSは作成後の暗号化ステ [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">どうも、PHPerです。</p>



<p class="wp-block-paragraph">先日、業務にて<strong>「既存のRDSのストレージを暗号化したい」</strong>という、シンプルかつ重たい要件が降ってきました。</p>



<p class="wp-block-paragraph">これの何が困るかというと、皆様ご存知の通りRDSの仕様です。 RDSは<strong>作成後の暗号化ステータスの変更ができません</strong>。つまり、<strong>既存のRDSを暗号化するためには、「暗号化有効」設定で作り直す（再作成する）必要</strong>があります。 </p>



<p class="wp-block-paragraph"><a href="https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/patterns/automatically-remediate-unencrypted-amazon-rds-db-instances-and-clusters.html" target="_blank" rel="noreferrer noopener">参考: Amazon RDS DB インスタンスおよびクラスターの暗号化 &#8211; AWS Prescriptive Guidance</a></p>



<p class="wp-block-paragraph">さらに今回はインフラを <strong>AWS CDK</strong> で管理しています。<br> 単純にCDKのコードを書き換えてデプロイすると、CloudFormationの仕様により「リソースの置換」が発生し、予期せぬダウンタイムやデータ損失のリスクがあります。<br>また、よくある「スナップショットから復元して差し替え」という手法も、CDKのState管理との兼ね合いで今回は採用できませんでした。</p>



<p class="wp-block-paragraph">様々な制約がある中、「既存データを維持しつつ」「CDK管理のまま」暗号化リソースへ移行できたので、その手法を共有します。</p>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2198" data-theme="BlogArise">
			<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>Contents</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">今回の要件と制約</a></li><li class="rtoc-item"><a href="#rtoc-2">結論：CDKデプロイを3回行う</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-3">なぜこの手順が必要なのか？（ハマりポイント）</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-4">手順詳細</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-5">1. 削除ポリシーの変更</a></li><li class="rtoc-item"><a href="#rtoc-6">2. instanceIdentifier の変更</a></li><li class="rtoc-item"><a href="#rtoc-7">3. いよいよ暗号化RDSの構築</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-8">データ移行</a></li><li class="rtoc-item"><a href="#rtoc-9">後片付け</a></li><li class="rtoc-item"><a href="#rtoc-10">(おまけ) 試したけどうまくいかなかったこと</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-11"><code>cdk import</code> での既存リソース取り込み</a></li><li class="rtoc-item"><a href="#rtoc-12"><code>DatabaseInstanceFromSnapshot</code> の利用</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-13">まとめ</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">今回の要件と制約</h2>



<p class="wp-block-paragraph">今回のミッションにおける前提条件は以下の通りです。</p>



<ul class="wp-block-list">
<li><strong>既存RDSの暗号化</strong>（最重要）</li>



<li>既存データをそのまま移行できること</li>



<li>アプリ側の変更を避けるため、<strong>エンドポイント（またはDNS）の変更がないこと</strong></li>



<li><strong>ダウンタイムは許容</strong>（ALBでメンテナンス画面を挟む前提）</li>



<li>バッチ処理が稼働していない夜間帯での作業</li>



<li><strong>CDKコードの変更は最小限</strong>に留め、今後もCDKで管理し続けること</li>
</ul>



<h2 id="rtoc-2"  class="wp-block-heading">結論：CDKデプロイを3回行う</h2>



<p class="wp-block-paragraph">結論から言うと、<strong>「削除ポリシーを変更して既存RDSをCDK管理から切り離し、新規で暗号化RDSを作成する」</strong>という力技で解決しました。</p>



<p class="wp-block-paragraph">具体的には以下の3ステップでデプロイを行います。</p>



<ol start="1" class="wp-block-list">
<li><code>RemovalPolicy.RETAIN</code> を設定（既存保護）</li>



<li><code>instanceIdentifier</code> を変更（既存切り離し）</li>



<li><code>storageEncrypted: true</code> を指定（新規作成）</li>
</ol>



<h3 id="rtoc-3"  class="wp-block-heading">なぜこの手順が必要なのか？（ハマりポイント）</h3>



<p class="wp-block-paragraph">通常、CDKで管理されているリソースに対し、<code>instanceIdentifier</code>（物理ID）を固定したまま <code>storageEncrypted: true</code> に変更してデプロイしようとすると、以下のエラーが発生して失敗します。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>CloudFormation cannot update a stack when a custom-named resource requires replacing</strong></p>
</blockquote>



<p class="wp-block-paragraph">これは、「カスタム名（明示的な名前）がついているリソースを置換（削除＆作成）することはできない」というCloudFormationの安全装置です。これを回避するために、あえて識別子を変更して別リソースとして認識させる必要があります。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id="rtoc-4"  class="wp-block-heading">手順詳細</h2>



<h3 id="rtoc-5"  class="wp-block-heading">1. 削除ポリシーの変更</h3>



<p class="wp-block-paragraph">まず、既存のRDSがCDKのスタック操作によって削除されないようにします。 これをやらないと、次の手順で「既存RDSの削除」が走ってしまい、データが消えます（超重要）。</p>



<p class="wp-block-paragraph">コード スニペット</p>



<pre class="wp-block-code"><code>new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db', // 現在の識別子
  // ...
  removalPolicy: cdk.RemovalPolicy.RETAIN, // ここを追加！
});
</code></pre>



<p class="wp-block-paragraph">この状態で一度 <strong><code>cdk deploy</code></strong> します。 これで、このRDSはスタックから削除される際も、AWS上にはリソースが残るようになります。</p>



<h3 id="rtoc-6"  class="wp-block-heading">2. instanceIdentifier の変更</h3>



<p class="wp-block-paragraph">次に、既存のRDSをCDKの管理下から外す（Orphanにする）作業です。 CDK上の識別子を、既存のものとは別の名前に書き換えます。</p>



<p class="wp-block-paragraph">コード スニペット</p>



<pre class="wp-block-code"><code>new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db-temp', // 3と被らない一時的な名前に変更
  // ...
  removalPolicy: cdk.RemovalPolicy.RETAIN,
});
</code></pre>



<p class="wp-block-paragraph">この状態で <strong><code>cdk deploy</code></strong> します。</p>



<p class="wp-block-paragraph"><strong>何が起きるか：</strong><br> CloudFormationは「<code>my-prod-db</code> を管理から外し、新しく <code>my-prod-db-temp</code> を作る」という挙動をします。 ステップ1で <code>RETAIN</code> を設定しているため、<strong>旧RDS（データ入り）は削除されずにそのまま残ります。</strong> <br>※この時点でアプリはまだ旧RDSを見ています。</p>



<h3 id="rtoc-7"  class="wp-block-heading">3. いよいよ暗号化RDSの構築</h3>



<p class="wp-block-paragraph">最後に、本命の「暗号化されたRDS」を作成します。</p>



<p class="wp-block-paragraph">コード スニペット</p>



<pre class="wp-block-code"><code>new rds.DatabaseInstance(this, 'RdsInstance', {
  instanceIdentifier: 'my-prod-db', // 元の名前に戻す（ここが変わるとエンドポイント変わるので注意）
  storageEncrypted: true,           // 暗号化をON
  removalPolicy: cdk.RemovalPolicy.RETAIN,
  // ...
});
</code></pre>



<p class="wp-block-paragraph">この状態で <strong><code>cdk deploy</code></strong> します。</p>



<p class="wp-block-paragraph"><strong>注意点：</strong><br>もし <code>instanceIdentifier</code> を手順1と同じ名前（<code>my-prod-db</code>）に戻したい場合、手順2の時点でAWS上に古い <code>my-prod-db</code> が残っているため、名前重複でエラーになる可能性があります。<br>その場合は、<strong>AWSマネジメントコンソールまたはCLIから、古いRDS（手順2で切り離されたもの）の識別子を手動で <code>my-prod-db-old</code> などにリネーム</strong> してからデプロイしてください。</p>



<p class="wp-block-paragraph">これで、暗号化された空っぽのRDSが立ち上がりました。</p>



<h2 id="rtoc-8"  class="wp-block-heading">データ移行</h2>



<p class="wp-block-paragraph">新旧2つのRDSが存在している状態になりました。</p>



<ul class="wp-block-list">
<li><strong>旧RDS</strong>
<ul class="wp-block-list">
<li>データあり</li>



<li>非暗号化</li>



<li>CDK管理外</li>
</ul>
</li>



<li><strong>新RDS</strong>
<ul class="wp-block-list">
<li>データなし</li>



<li>暗号化済み</li>



<li>CDK管理下</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">データ量がそれほど多くない（数GB〜数十GB程度）ため、今回はシンプルに <code>mysqldump</code> で移行を行いました。TB級の場合は AWS DMS (Database Migration Service) の利用を検討してください。</p>



<pre class="wp-block-code"><code># SSM Session Manager 等で踏み台サーバーに接続

# 1. 旧RDSからデータをエクスポート（パイプで圧縮して転送時間を短縮）
mysqldump -h &#91;旧RDSエンドポイント] -u &#91;ユーザー名] -p \
  --single-transaction --routines --triggers \
  --databases &#91;対象のスキーマ] \
  | gzip &gt; dump.sql.gz

# 2. 新RDSへインポート
zcat dump.sql.gz | mysqldump -h &#91;新RDSエンドポイント] -u &#91;ユーザー名] -p
</code></pre>



<p class="wp-block-paragraph">データの移行が完了したら、アプリケーションの接続先が新RDSに向いていることを確認し、メンテナンスを解除します。</p>



<h2 id="rtoc-9"  class="wp-block-heading">後片付け</h2>



<p class="wp-block-paragraph">無事稼働確認が取れたら、不要なリソースを削除してお財布を守りましょう。</p>



<ol start="1" class="wp-block-list">
<li><strong>手順2で立ち上がった一時的なRDS</strong>（CDKの管理からは外れているはずですが、念の為確認して削除）</li>



<li><strong>手順1以前から存在していた旧RDS</strong>（リネームした <code>my-prod-db-old</code> など）</li>
</ol>



<p class="wp-block-paragraph">スナップショットが取れていることを確認した上で、マネジメントコンソールから削除しました。</p>



<h2 id="rtoc-10"  class="wp-block-heading">(おまけ) 試したけどうまくいかなかったこと</h2>



<h3 id="rtoc-11"  class="wp-block-heading"><code>cdk import</code> での既存リソース取り込み</h3>



<p class="wp-block-paragraph">既存のリソースをリネームして、新しいスタックに取り込もうと <code>cdk import</code> を試みましたが、以下のようなメッセージが出てスキップされました。</p>



<pre class="wp-block-code"><code>TmpStack: no new resources compared to the currently deployed stack, skipping import.
</code></pre>



<p class="wp-block-paragraph">また、定義と実リソースのプロパティ（暗号化の有無）が食い違っているため、インポート自体が整合性エラーになる可能性が高いです。</p>



<h3 id="rtoc-12"  class="wp-block-heading"><code>DatabaseInstanceFromSnapshot</code> の利用</h3>



<p class="wp-block-paragraph">スナップショットから復元するCDKのクラスですが、これをメインの定義にしてしまうと、常に「スナップショットから作られた状態」が正となり、パラメータグループやバージョン更新などの運用時にドリフト（設定乖離）の管理が面倒になると判断し、今回は見送りました。</p>



<h2 id="rtoc-13"  class="wp-block-heading">まとめ</h2>



<p class="wp-block-paragraph">CDKで管理しているステートフルなリソース（RDSなど）の「再作成必須な変更」は非常に神経を使います。 <strong>「Retainで保護」→「Identifier変更で管理から切り離し」→「新規作成」</strong> というフローは、RDSに限らず他のリソースでも応用が効くテクニックなので、覚えておいて損はないはずです。</p>



<p class="wp-block-paragraph">なにはともあれですが、RDSは最初から暗号化しましょう。これだけは覚えて頂けると。。。</p>



<p class="wp-block-paragraph">同じ悩みを抱えるPHPer（およびAWS使い）の助けになれば幸いです！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2198/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【CDK】LAMP環境を題材に考えるスタック間のリソース共有が難しい問題を解決する</title>
		<link>https://otonan-syusyoku.work/archives/2194</link>
					<comments>https://otonan-syusyoku.work/archives/2194#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Wed, 14 Jan 2026 06:26:23 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[LAMP]]></category>
		<category><![CDATA[Typescript]]></category>
		<category><![CDATA[実務]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2194</guid>

					<description><![CDATA[こんちゃーす。最近 CDK と格闘している PHPer です。 CDK、楽しいですよね。Typescript でインフラがかけるなんて最高です。でも、学習を始めていくとある壁にぶつかりました。 ネット上の「CDKでECS [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">こんちゃーす。最近 CDK と格闘している PHPer です。</p>



<p class="wp-block-paragraph">CDK、楽しいですよね。Typescript でインフラがかけるなんて最高です。でも、学習を始めていくとある壁にぶつかりました。</p>



<p class="wp-block-paragraph">ネット上の「CDKでECS構築してみた！」系の記事を参考にすると、大抵の場合 <strong>VPCもECSもRDSも全部一つの</strong> <code>lib/my-stack.ts</code> <strong>に処理が書かれている」</strong>のです。サンプルコードとしてはわかりやすいのですが、いざ実務でLMAP環境を作ろうとすると⋯…</p>



<ul class="wp-block-list">
<li>VPC は更新頻度が低いからスタックを分けたい</li>



<li>RDS はステートフルだから独立させたい</li>



<li>ECS はアプリケーションコードのデプロイで頻繁に更新が入るかも</li>
</ul>



<p class="wp-block-paragraph">とスタックを分割したくなりませんか？<br>でも、分割した途端に「StackA で作ったVPCのIDをどうやってStackBにわたすの？」という問題が発生します。</p>



<p class="wp-block-paragraph">当記事では、現時点での私の持論である <strong>スタック間のリソース共有</strong>を紹介します。</p>



<p class="wp-block-paragraph"></p>



<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2194" data-theme="BlogArise">
			<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button>
			<span>Contents</span>
			</div><ol class="rtoc-mokuji decimal_ol level-1"><li class="rtoc-item"><a href="#rtoc-1">IDではなくオブジェクトを渡す</a></li><li class="rtoc-item"><a href="#rtoc-2">型安全の恩恵を受ける</a><ul class="rtoc-mokuji mokuji_ul level-2"><li class="rtoc-item"><a href="#rtoc-3">VPCStack ⇒ 渡す側</a></li><li class="rtoc-item"><a href="#rtoc-4">EcsStack ⇒ 受け取る側</a></li><li class="rtoc-item"><a href="#rtoc-5">bin/app.ts ⇒ 繋ぐ場所</a></li><li class="rtoc-item"><a href="#rtoc-6">この方法の何が良いの？</a></li></ul></li><li class="rtoc-item"><a href="#rtoc-7">議論したいポイント</a></li></ol></div><h2 id="rtoc-1"  class="wp-block-heading">IDではなくオブジェクトを渡す</h2>



<p class="wp-block-paragraph">VPC スタックとECSスタックを分けるときに、ついやってしまいがちなのが <strong>VPC IDを Props で渡す</strong> という手法です。<br>Terraform に慣れていると、この発想になりがちですよね。</p>



<p class="wp-block-paragraph">この手法では CDK の 型安全を活かすことが出来ないので、 <strong>VPCオブジェクトそのもの</strong> を渡すのがベストだと考えています。</p>



<p class="wp-block-paragraph">私の構成案：<br>司令塔となる エントリーポイント <code>app</code> がバケツリレーのようにオブジェクトを渡していくイメージです。</p>



<ol class="wp-block-list">
<li>VPCStack： VPCを作る。 <code>public readonly vpc</code>でVPCオブジェクトを公開する</li>



<li>APP： VpcStack からVPCオブジェクトをもらい、 ECSStackに渡す</li>



<li>EcsStack： 受け取ったVPC オブジェクトを使ってクラスターを作る</li>
</ol>



<p class="wp-block-paragraph">実際にコードを見ていきましょう。</p>



<h2 id="rtoc-2"  class="wp-block-heading">型安全の恩恵を受ける</h2>



<h3 id="rtoc-3"  class="wp-block-heading">VPCStack ⇒ 渡す側</h3>



<p class="wp-block-paragraph">ここでは <code>this.vpc</code>をクラスのメンバ変数として公開しておきます。</p>



<pre class="wp-block-code"><code>// lib/vpc-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';

export class VpcStack extends cdk.Stack {
  // 外部に公開するプロパティ（これが重要！）
  public readonly vpc: ec2.IVpc;

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    this.vpc = new ec2.Vpc(this, 'LampVpc', {
      maxAzs: 2,
    });
  }
}</code></pre>



<p class="wp-block-paragraph"></p>



<h3 id="rtoc-4"  class="wp-block-heading">EcsStack ⇒ 受け取る側</h3>



<p class="wp-block-paragraph">ここがポイントデス。 <code>vpcId: string</code>ではなく、 <code>vpc: ec2.IVpc</code> というインターフェース型で受け取ります。</p>



<pre class="wp-block-code"><code>// lib/ecs-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import { Construct } from 'constructs';

// Propsの定義：文字列ではなく「VPCの型」を指定する
interface EcsStackProps extends cdk.StackProps {
  vpc: ec2.IVpc;
}

export class EcsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: EcsStackProps) {
    super(scope, id, props);

    // 文字列から検索する必要なし！そのまま使える
    const cluster = new ecs.Cluster(this, 'LampCluster', {
      vpc: props.vpc, 
    });
  }
}</code></pre>



<p class="wp-block-paragraph"></p>



<h3 id="rtoc-5"  class="wp-block-heading">bin/app.ts ⇒ 繋ぐ場所</h3>



<p class="wp-block-paragraph">最後にエントリーポイントで紐づけます。</p>



<pre class="wp-block-code"><code>// bin/app.ts
import * as cdk from 'aws-cdk-lib';
import { VpcStack } from '../lib/vpc-stack';
import { EcsStack } from '../lib/ecs-stack';

const app = new cdk.App();

// 1. VPCを作る
const vpcStack = new VpcStack(app, 'VpcStack');

// 2. VPCオブジェクトをECSスタックに渡す
const ecsStack = new EcsStack(app, 'EcsStack', {
  vpc: vpcStack.vpc, // ここでバケツリレー
});</code></pre>



<p class="wp-block-paragraph"></p>



<h3 id="rtoc-6"  class="wp-block-heading">この方法の何が良いの？</h3>



<p class="wp-block-paragraph">この方法のメリットは2つあると思っています。<br>（一言で言うとプログラミング言語による抽象化です。）</p>



<ol class="wp-block-list">
<li>圧倒的な型安全性<br>もし間違えてS3のオブジェクトを渡そうとすると、エディタがその場でエラーを吐いてくれます。<br>「デプロイしてみたらIDが違ってコケた」という悲劇が未然に防げます</li>



<li>記述がメチャ楽<br>受け取る側で <code>ec2.Vpc.fromLookup()</code> のようなインポート処理を書く必要がありません。渡された瞬間から <code>props.vpc.addInterfaceEndpoint</code> のようにメソッドが使えます</li>
</ol>



<p class="wp-block-paragraph"></p>



<h2 id="rtoc-7"  class="wp-block-heading">議論したいポイント</h2>



<p class="wp-block-paragraph">ここまで「これが正解だ」という顔で書いてきましたが、実はこの構成には明確なデミリットもあります。それは、<strong> スタック同士が密結合</strong> になることです。</p>



<p class="wp-block-paragraph">Cloudformation の <code>Export/Import</code> 機能でガッチリ紐づいてしまうため、以下のような問題が置きます。</p>



<ul class="wp-block-list">
<li>スタック間の参照があるため、「VPCだけを作り直したい」が出来ない</li>
</ul>



<p class="wp-block-paragraph">ただし、私は上記のような問題が発生したとしても、<br>LAMP 環境という一つのアプリケーションにおいて、VPCとECSは「運命共同体」にあたるため問題ないと考えています。</p>



<p class="wp-block-paragraph">「ECSが生きているのにVPCだけ消したい」という状況は稀ではないでしょうか。そのためこの<strong>密結合はあえて受け入れるべき仕様</strong>だと割り切っています。</p>



<p class="wp-block-paragraph">ただ、もし組織の基盤となる共通のVPCを作る場合は、ライフサイクルが異なるため、疎結合なID渡しにするのが正解なのかもしれません。</p>



<p class="wp-block-paragraph">皆さんの構成おしてほしいっす。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2194/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【CDKで構築】AWS ECS/RDSの監視・オートスケール基盤を自動構築しSlack通知する方法</title>
		<link>https://otonan-syusyoku.work/archives/2168</link>
					<comments>https://otonan-syusyoku.work/archives/2168#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Wed, 19 Nov 2025 04:44:12 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[業務]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[RDS]]></category>
		<category><![CDATA[監視]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2168</guid>

					<description><![CDATA[ECS FargateとRDSでPHPアプリケーションを運用する際、リソースの異常検知や負荷に応じたスケール管理は必須です。 しかし、これらの仕組みを個別に手動で設定するのは手間がかかります。 本記事では、AWS CDK [&#8230;]]]></description>
										<content:encoded><![CDATA[<p><span class="">ECS FargateとRDSでPHPアプリケーションを運用する際、</span><span class="">リソースの異常検知や負荷に応じたスケール管理は必須です。<br />
</span><span class="">しかし、</span><span class="">これらの仕組みを個別に手動で設定するのは手間がかかります。</span></p>
<p><span class="">本記事では、</span><b class="">AWS CDK</b><span class="">（Cloud Development Kit）を用いて、</span><span class="">ECSとRDSの</span><b class="">監視アラーム</b><span class="">、</span><span class="">ECSの</span><b class="">オートスケーリング</b><span class="">、</span><span class="">そしてそのイベントを</span><b class="">Slack</b><span class="">に集約する通知基盤を<strong>IaC</strong>として一括で構築する方法を解説します。</span></p>
<p>&nbsp;</p>
<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2168" data-theme="BlogArise">
<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button><br />
			<span>Contents</span>
			</div>
<ol class="rtoc-mokuji decimal_ol level-1">
<li class="rtoc-item"><a href="#rtoc-1">監視・通知アーキテクチャの全体像</a></li>
<li class="rtoc-item"><a href="#rtoc-2">事前準備: AWS ChatbotとSlackの連携</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-3">1. 外部サービスとの認証（OAuthプロセス）が必要</a></li>
<li class="rtoc-item"><a href="#rtoc-4">2. AWS Chatbot Clientの事前設定が必要</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-5">監視とオートスケール基盤の構築</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-6">CDKスタックのセットアップ</a></li>
<li class="rtoc-item"><a href="#rtoc-7">通知用SNSトピックとChatbotの設定</a></li>
<li class="rtoc-item"><a href="#rtoc-8">ECSとRDSの監視アラーム設定</a></li>
<li class="rtoc-item"><a href="#rtoc-9">ECSサービスのオートスケーリング設定</a></li>
<li class="rtoc-item"><a href="#rtoc-10">オートスケールイベントのSlack通知設定</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-11">まとめ</a></li>
</ol>
</div>
<h2 id="rtoc-1" >監視・通知アーキテクチャの全体像</h2>
<p>採用するアーキテクチャは以下の通りです。</p>
<div class="table-block-component"></div>
<div>
<table style="border-collapse: collapse; width: 100%;">
<tbody>
<tr style="background-color: #5aabe8;">
<td style="width: 33.3333%;"><span style="color: #ffffff;">機能</span></td>
<td style="width: 33.3333%;"><span style="color: #ffffff;">AWSサービス</span></td>
<td style="width: 33.3333%;"><span style="color: #ffffff;">役割</span></td>
</tr>
<tr>
<td style="width: 33.3333%;"><b>監視</b></td>
<td style="width: 33.3333%;">CloudWatch</td>
<td style="width: 33.3333%;">メトリクスを収集・アラーム発報</td>
</tr>
<tr>
<td style="width: 33.3333%;"><b>自動応答</b></td>
<td style="width: 33.3333%;">Application Auto Scaling</td>
<td style="width: 33.3333%;">ECSの負荷に応じてタスク数を自動調整</td>
</tr>
<tr>
<td style="width: 33.3333%;"><b>通知ルーティング</b></td>
<td style="width: 33.3333%;">Amazon SNS</td>
<td style="width: 33.3333%;">アラームやスケーリングイベントを中継</td>
</tr>
<tr>
<td style="width: 33.3333%;"><b>通知先</b></td>
<td style="width: 33.3333%;">AWS Chatbot <span class="math-inline" data-math="\rightarrow">$\rightarrow$</span> Slack</td>
<td style="width: 33.3333%;">SNSからの通知を整形しSlackへ送信</td>
</tr>
</tbody>
</table>
</div>
<div></div>
<h2 id="rtoc-2" >事前準備: AWS ChatbotとSlackの連携</h2>
<p>CDKデプロイの前に、AWSアカウントとSlackを連携させるための<b>AWS Chatbotクライアント</b>を作成しておく必要があります。</p>
<ol start="1">
<li>AWS Chatbotコンソールで、<b>Slackワークスペース</b>と<b>チャンネル</b>を連携させます。</li>
<li>この設定により、通知先の<b>SlackチャンネルID</b>と<b>ワークスペースID</b>が取得できます。これらはCDKコード内で使用します。</li>
</ol>
<div class="sc_toggle_box">
<div class="sc_toggle_title">CDKでAWS Chatbot連携が完結しない理由</div>
<div class="sc_toggle_content">
<h3 id="rtoc-3" >1. 外部サービスとの認証（OAuthプロセス）が必要</h3>
<p>AWS Chatbotは、AWSアカウントと外部サービス（SlackやMicrosoft Teams）の連携を提供します。この連携は、Slack側でAWS Chatbotアプリをインストールし、AWSアカウントへのアクセスを許可するOAuth認証プロセスをユーザー（管理者）がブラウザ上で行う必要があります。</p>
<p>CDKはAWSアカウント内のリソースをプログラマブルにデプロイしますが、Slackワークスペース側の認証フローをプログラムで自動実行することは、セキュリティ上の理由からできません。</p>
<h3 id="rtoc-4" >2. AWS Chatbot Clientの事前設定が必要</h3>
<p>CDKのaws-chatbotモジュールで定義できるのは、連携が完了したワークスペースを参照し、その上でチャンネル設定（どのSNSトピックの通知をどのSlackチャンネルに送るか）を行う部分のみです。</p>
<p>CDKでnew chatbot.SlackChannelConfiguration(&#8230;)を作成する前に、通知先のSlackワークスペースID (slackWorkspaceId) を特定する必要があります。このワークスペースIDは、手動でChatbotコンソールからSlackとの連携を完了させた際に初めてAWS側に登録される情報です。</p>
</div>
</div>
<p>&nbsp;</p>
<h2 id="rtoc-5" >監視とオートスケール基盤の構築</h2>
<p>今回は、ECSサービスとRDSインスタンスが既に存在することを前提に、その監視・スケール設定を定義するCDKスタックを作成します。</p>
<h3 id="rtoc-6" >CDKスタックのセットアップ</h3>
<p>必要なCDKライブラリをインポートし、スタックを定義します。</p>
<p>&nbsp;</p>
<div>
<pre class="line-numbers"><code class="language-js">// lib/monitoring-stack.ts

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as actions from 'aws-cdk-lib/aws-cloudwatch-actions';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as autoscaling from 'aws-cdk-lib/aws-applicationautoscaling';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as chatbot from 'aws-cdk-lib/aws-chatbot';

// 環境依存の定数を定義 (適宜置き換えてください)
const CLUSTER_NAME = 'your-ecs-cluster-name';
const SERVICE_NAME = 'your-ecs-service-name';
const RDS_IDENTIFIER = 'your-rds-instance-identifier';
const SLACK_CHANNEL_ID = 'C01234567'; // 取得したチャンネルID
const SLACK_WORKSPACE_ID = 'T01234567'; // 取得したワークスペースID

export class MonitoringStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 既存のECSサービスを参照
    const ecsService = ecs.FargateService.fromFargateServiceAttributes(this, 'ExistingEcsService', {
      serviceArn: cdk.Stack.of(this).formatArn({
        service: 'ecs',
        resource: 'service',
        resourceName: `${CLUSTER_NAME}/${SERVICE_NAME}`,
        sep: '/',
      }),
      cluster: ecs.Cluster.fromClusterAttributes(this, 'ExistingCluster', {
        clusterName: CLUSTER_NAME,
        vpc: ({} as any), // 参照のため空オブジェクト
        securityGroups: [],
      }),
    });
  }
}</code></pre>
<p>&nbsp;</p>
</div>
<h3 id="rtoc-7" >通知用SNSトピックとChatbotの設定</h3>
<p>アラームやイベントが通知を送るSNSトピックを作成し、それをAWS Chatbotに連携させます。</p>
<p><response-element class="" ng-version="0.0.0-PLACEHOLDER"><code-block _nghost-ng-c519592647="" class="ng-tns-c519592647-2197 ng-star-inserted"></code-block></response-element></p>
<div _ngcontent-ng-c519592647="" class="code-block ng-tns-c519592647-2197 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_a2c461939f765a9a&quot;,&quot;c_86e1e1d55e25872e&quot;,null,&quot;rc_330331dc660dc0f2&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c519592647="" class="code-block-decoration header-formatted gds-title-s ng-tns-c519592647-2197 ng-star-inserted"><span _ngcontent-ng-c519592647="" class="ng-tns-c519592647-2197"></span></div>
</div>
<pre class="line-numbers"><code class="language-js">// 1. 通知用SNSトピックの作成
    const alarmTopic = new sns.Topic(this, 'AlarmTopic', {
      displayName: 'System-Monitoring-Notifications',
    });

    // 2. AWS ChatbotとSNSトピックの連携 (Slackへのルーティング)
    new chatbot.SlackChannelConfiguration(this, 'ChatbotConfig', {
      slackChannelConfigurationName: 'EcsRdsMonitoring',
      slackWorkspaceId: SLACK_WORKSPACE_ID,
      slackChannelId: SLACK_CHANNEL_ID,
      notificationTopics: [alarmTopic], // このトピックへの通知がSlackに送られる
    });</code></pre>
<h3 id="rtoc-8" >ECSとRDSの監視アラーム設定</h3>
<p>主要なメトリクスに対してCloudWatchアラームを作成し、アクションとして上記SNSトピックを設定します。</p>
<pre class="line-numbers"><code class="language-js">// --- ECS Fargate アラーム ---
    // ECS CPU利用率が80%を超えた場合にアラーム
    const ecsCpuAlarm = new cloudwatch.Alarm(this, 'EcsCpuHighAlarm', {
      metric: cloudwatch.Metric.fromNamespaceAndName('AWS/ECS', 'CPUUtilization').with({
        dimensionsMap: { ClusterName: CLUSTER_NAME, ServiceName: SERVICE_NAME },
      }),
      threshold: 80, 
      evaluationPeriods: 2,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
      alarmDescription: 'ECS Service CPU utilization is too high.',
    });
    ecsCpuAlarm.addAlarmAction(new actions.SnsAction(alarmTopic));
    // ecsCpuAlarm.addOkAction(new actions.SnsAction(alarmTopic)); // 復旧時も通知したい場合は追加

    // --- RDS アラーム ---
    // RDS CPU利用率が70%を超えた場合にアラーム
    const rdsCpuAlarm = new cloudwatch.Alarm(this, 'RdsCpuHighAlarm', {
      metric: cloudwatch.Metric.fromNamespaceAndName('AWS/RDS', 'CPUUtilization').with({
        dimensionsMap: { DBInstanceIdentifier: RDS_IDENTIFIER },
      }),
      threshold: 70, 
      evaluationPeriods: 3,
      comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
      alarmDescription: 'RDS CPU utilization is too high.',
    });
    rdsCpuAlarm.addAlarmAction(new actions.SnsAction(alarmTopic));</code></pre>
<h3 id="rtoc-9" >ECSサービスのオートスケーリング設定</h3>
<p>ECSサービスをCPU利用率$60%$を目標に自動スケーリングするように設定します。</p>
<p><response-element class="" ng-version="0.0.0-PLACEHOLDER"><code-block _nghost-ng-c519592647="" class="ng-tns-c519592647-2199 ng-star-inserted"></code-block></response-element></p>
<div _ngcontent-ng-c519592647="" class="code-block ng-tns-c519592647-2199 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_a2c461939f765a9a&quot;,&quot;c_86e1e1d55e25872e&quot;,null,&quot;rc_330331dc660dc0f2&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c519592647="" class="code-block-decoration header-formatted gds-title-s ng-tns-c519592647-2199 ng-star-inserted">
<pre class="line-numbers"><code class="language-js">// 3. ECSサービスのオートスケーリング設定
    // サービスのDesired Countをスケーリング対象にする
    const scalableTarget = autoscaling.ScalableTarget.forEcsService(this, 'EcsScalableTarget', {
      service: ecsService,
      minCapacity: 1, // 最小タスク数
      maxCapacity: 5, // 最大タスク数
    });

    // 目標CPU利用率 60% を維持するように調整するターゲット追跡スケーリングポリシー
    scalableTarget.scaleToTrackMetric('CpuScalingPolicy', {
      metric: ecsService.metricCpuUtilization(), 
      targetValue: 60,
      scaleOutCooldown: cdk.Duration.seconds(60),
      scaleInCooldown: cdk.Duration.seconds(300),
    });</code></pre>
</div>
<h3 id="rtoc-10" >オートスケールイベントのSlack通知設定</h3>
<p>スケーリング操作の成功イベントをEventBridgeで捕捉し、Slackに通知します。</p>
<p><response-element class="" ng-version="0.0.0-PLACEHOLDER"><code-block _nghost-ng-c519592647="" class="ng-tns-c519592647-2200 ng-star-inserted"></code-block></response-element></p>
<div _ngcontent-ng-c519592647="" class="code-block ng-tns-c519592647-2200 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_a2c461939f765a9a&quot;,&quot;c_86e1e1d55e25872e&quot;,null,&quot;rc_330331dc660dc0f2&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c519592647="" class="code-block-decoration header-formatted gds-title-s ng-tns-c519592647-2200 ng-star-inserted">
<pre class="line-numbers"><code class="language-js">// 4. オートスケールイベントを捕捉しSNSトピックへルーティング
    new events.Rule(this, 'AutoScalingNotificationRule', {
      description: 'Notify Slack on successful ECS Auto Scaling events.',
      eventPattern: {
        source: ['aws.application-autoscaling'],
        detailType: ['Application Auto Scaling Policy Execution'],
        detail: {
          // 成功したスケーリング操作のみを通知
          statusCode: ['Successful'], 
        },
      },
      // ターゲットは既存のSNSトピック
      targets: [new targets.SnsTopic(alarmTopic)],
    });</code></pre>
</div>
<h2 id="rtoc-11" >まとめ</h2>
<p>CDKを使うことで、<b>TypeScriptコード</b>で以下の堅牢な運用基盤を構築できました。</p>
<ol start="1">
<li>ECS/RDSの重要なメトリクス監視</li>
<li>CloudWatch AlarmsとChatbotによるSlackへの即時通知</li>
<li>ECSサービスの負荷に応じた自動スケーリング（スケールイン/アウト）</li>
<li>スケーリング操作完了時の通知</li>
</ol>
<p>インフラをコード化することで、再現性、バージョン管理、レビューが可能になり、システムの信頼性が大きく向上します。</p>
<div _ngcontent-ng-c519592647="" class="code-block-decoration header-formatted gds-title-s ng-tns-c519592647-2200 ng-star-inserted"></div>
</div>
</div>
<p>&nbsp;</p>
<div></div>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2168/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>マルチステージビルドでイメージサイズを劇的に削減できるべ</title>
		<link>https://otonan-syusyoku.work/archives/2165</link>
					<comments>https://otonan-syusyoku.work/archives/2165#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Wed, 19 Nov 2025 04:09:20 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[業務]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2165</guid>

					<description><![CDATA[PHPアプリケーションのコンテナイメージを作成する際、ビルドに必要なツール（Composer、各種コンパイラなど）をすべて含めてしまうと、イメージが肥大化し、セキュリティリスクも増大します。 この課題を解決するのが、コン [&#8230;]]]></description>
										<content:encoded><![CDATA[<div id="model-response-message-contentr_700d418a97f56345" class="markdown markdown-main-panel stronger enable-updated-hr-color" dir="ltr" aria-live="polite" aria-busy="false">
<p>PHPアプリケーションのコンテナイメージを作成する際、ビルドに必要なツール（Composer、各種コンパイラなど）をすべて含めてしまうと、イメージが肥大化し、セキュリティリスクも増大します。</p>
<p>この課題を解決するのが、コンテナビルドの効率を最大化する技術、<b>マルチステージビルド</b>です。</p>
<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2165" data-theme="BlogArise">
<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button><br />
			<span>Contents</span>
			</div>
<ol class="rtoc-mokuji decimal_ol level-1">
<li class="rtoc-item"><a href="#rtoc-1">マルチステージビルドとは？</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-2">役割分担</a></li>
<li class="rtoc-item"><a href="#rtoc-3">メリット</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-4">PHPアプリケーションでのマルチステージビルド</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-5">Dockerfile</a></li>
<li class="rtoc-item"><a href="#rtoc-6">実行手順</a></li>
<li class="rtoc-item"><a href="#rtoc-7">PHPでのポイント</a></li>
</ul>
</li>
</ol>
</div>
<h2 id="rtoc-1" >マルチステージビルドとは？</h2>
<p>マルチステージビルドとは、<b>一つの<code>Dockerfile</code>内で複数の<code>FROM</code>命令（ステージ）を定義し、最終的なイメージにデプロイに必要なファイルだけをコピーする</b>手法です。</p>
<h3 id="rtoc-2" >役割分担</h3>
<ol start="1">
<li><b>ビルドステージ (Build Stage):</b>
<ul>
<li><b>目的:</b> アプリケーションのビルドと依存関係の解決。</li>
<li><b>使用イメージ:</b> <b>大きなイメージ</b>（例: <code>composer:latest</code>、<code>php:8.3-fpm</code>など）。</li>
<li><b>作業:</b> Composerによる依存パッケージのインストール、フロントエンドアセットのコンパイル、テストの実行などを行います。</li>
<li><b>特徴:</b> このステージは最終的なデプロイには使用されません。</li>
</ul>
</li>
<li><b>最終ステージ (Final Stage):</b>
<ul>
<li><b>目的:</b> アプリケーションの実行環境の構築。</li>
<li><b>使用イメージ:</b> <b>軽量なイメージ</b>（例: <code>php:8.3-fpm-alpine</code>、<code>distroless</code>など）。</li>
<li><b>作業:</b> ビルドステージで生成された<b>成果物</b>（例: <code>vendor</code>ディレクトリ、コンパイル済みバイナリ、PHPコード）のみをコピーします。</li>
</ul>
</li>
</ol>
<h3 id="rtoc-3" >メリット</h3>
<ul>
<li><b>イメージサイズの劇的な削減:</b> ビルドツールやキャッシュ、開発用の依存関係が最終イメージから完全に除外されます。</li>
<li><b>セキュリティ向上:</b> 最終イメージが最小限の構成になるため、攻撃対象となる領域が縮小します。</li>
<li><b>ビルド時間の短縮:</b> 軽量なイメージを使用することで、プル時間が短くなります。</li>
</ul>
<h2 id="rtoc-4" >PHPアプリケーションでのマルチステージビルド</h2>
<p>PHPアプリケーションの典型的なマルチステージビルドは、<b>Composerによる依存関係の解決</b>を分離することに焦点を当てます。</p>
<h3 id="rtoc-5" >Dockerfile</h3>
<p>以下の<code>Dockerfile</code>は、Composerで依存関係を解決するステージと、それを使ってアプリケーションを実行する最小限のステージに分離しています。</p>
<pre class="ng-tns-c519592647-2110"><code># =============================================</code>
<code># ステージ1：build_stage（依存関係の解決）</code>
<code># =============================================</code>
<code>FROM composer:latest AS build_stage 
WORKDIR /app 
# 先に依存関係ファイルだけをコピー（キャッシュ効率化のため）
COPY composer.json composer.lock ./ 
# 本番環境用に最適化してインストール 
</code><code># --no-dev: 開発用パッケージを除外</code>
<code># --optimize-autoloader: オートローダーを最適化して高速化 
</code><code>RUN composer install \</code>
<code>--no-dev \</code>
<code>--no-interaction \</code>
<code>--optimize-autoloader 
</code><code># =============================================</code>
<code># ステージ2：final_stage（最小限の実行環境）</code>
<code># =============================================</code>
<code># 軽量なAlpine LinuxベースのPHP-FPMイメージを使用</code>
<code>FROM php:8.3-fpm-alpine AS final_stage 
</code><code># セキュリティ：実行用の非特権ユーザーを作成</code>
<code>RUN adduser -D appuser</code>
<code>USER appuser
</code><code>WORKDIR /var/www/html
</code><code># ステージ1からvendorディレクトリのみをコピー</code>
<code># --chown で所有権を同時に変更するのがポイント</code>
<code>COPY --from=build_stage --chown=appuser:appuser /app/vendor ./vendor
</code><code># アプリケーションのソースコードをコピー</code>
<code>COPY --chown=appuser:appuser . .
 </code>
<code># PHP-FPMを起動 
</code><code>EXPOSE 9000</code><code>
CMD ["php-fpm"]</code><span style="font-size: 14px; background-color: #dddddd;">

</span></pre>
</div>
<h3 id="rtoc-6" >実行手順</h3>
<ol start="1">
<li><b>プロジェクトルートに配置:</b> 上記の<code>Dockerfile</code>をプロジェクトのルートディレクトリに配置します。</li>
<li><b><code>docker build</code>を実行:</b> 通常通り<code>docker build</code>コマンドを実行するだけです。Dockerエンジンが自動的にマルチステージビルドを処理します。</li>
</ol>
<pre class="line-numbers"><code class="language-php">docker build -t my-php-app:prod .</code></pre>
<h3 id="rtoc-7" >PHPでのポイント</h3>
<ol start="1">
<li><b><code>composer_stage</code>の利用:</b> Composerをインストールする手間を省くため、公式の<code>composer</code>イメージをビルダーとして使うのが最も効率的です。</li>
<li><b><code>--no-dev</code>:</b> 開発時のみ必要なパッケージ（テストフレームワークなど）は最終イメージに含めないようにします。</li>
<li><b><code>USER nonroot</code>:</b> セキュリティのため、最終ステージでは必ず<code>root</code>権限を放棄し、<b>非特権ユーザー</b>でアプリケーションを実行するように設定しましょう。（これにより、低ポート番号の使用には制約が生じますが、セキュリティが大幅に向上します）</li>
<li><b><code>COPY --from</code>の活用:</b> この命令が全てです。Composerの巨大なキャッシュやビルドツール群を最終イメージから排除し、必要な<code>vendor</code>ディレクトリだけをクリーンにコピーします。</li>
</ol>
<p>マルチステージビルドは、軽量で安全なPHPコンテナイメージを作成するための必須技術です。ぜひあなたのプロジェクトに導入してください。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2165/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>unprivileged かつ distroless なイメージ について理解しようぜ</title>
		<link>https://otonan-syusyoku.work/archives/2156</link>
					<comments>https://otonan-syusyoku.work/archives/2156#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Wed, 19 Nov 2025 03:50:41 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[生涯独学]]></category>
		<category><![CDATA[ALB]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[nginx]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2156</guid>

					<description><![CDATA[コンテナ技術は現代のアプリケーション開発・運用に欠かせないものとなりましたが、コンテナを運用する上で「セキュリティ」と「イメージサイズ」は常に大きな課題として立ちはだかります。 あなたのコンテナイメージは本当に安全で、そ [&#8230;]]]></description>
										<content:encoded><![CDATA[<div _ngcontent-ng-c2987940602="" inline-copy-host="" class="markdown markdown-main-panel stronger enable-updated-hr-color" id="model-response-message-contentr_c303978cc0204370" aria-live="polite" aria-busy="false" dir="ltr">
<p>コンテナ技術は現代のアプリケーション開発・運用に欠かせないものとなりましたが、コンテナを運用する上で「セキュリティ」と「イメージサイズ」は常に大きな課題として立ちはだかります。</p>
<p>あなたのコンテナイメージは本当に安全で、そして最小限に保たれていますか？</p>
<p>&nbsp;</p>
<p>多くの開発者は、手軽さからコンテナを<code>root</code>（管理者）権限で実行し、また開発やデバッグに不要なOSパッケージをそのまま含めてしまいがちです。</p>
<p>これが、<b>セキュリティリスクの増大</b>と、<b>イメージサイズの肥大化</b>を招く原因となります。</p>
<p>この記事では、この課題を解決するための <strong>Unprivileged（非特権ユーザー実行）</strong>と<strong>Distroless（ディストロレス）</strong>イメージについて、その概念から実践的な使い方、そして運用上の注意点までを解説します。</p>
<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2156" data-theme="BlogArise">
<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button><br />
			<span>Contents</span>
			</div>
<ol class="rtoc-mokuji decimal_ol level-1">
<li class="rtoc-item"><a href="#rtoc-1">そもそも「Unprivileged」と「Distroless」とは何か？</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-2">1. Unprivileged (非特権ユーザー実行) とは？</a></li>
<li class="rtoc-item"><a href="#rtoc-3">2. Distroless (ディストロレス) イメージとは？</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-4">Unprivileged &amp; Distroless を組み合わせるメリット</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-5">1. 圧倒的なセキュリティ向上</a></li>
<li class="rtoc-item"><a href="#rtoc-6">2. イメージサイズの劇的な削減</a></li>
<li class="rtoc-item"><a href="#rtoc-7">3. ビルド時間の短縮と効率化</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-8">Unprivileged &amp; Distroless なコンテナの使い方</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-9">1. Distroless イメージの基本構造</a></li>
<li class="rtoc-item"><a href="#rtoc-10">2. Unprivileged実行の設定</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-11">運用上の注意点：非特権ユーザーとポート番号の壁</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-12">1. ポート1024未満のバインドはroot権限が必要</a></li>
<li class="rtoc-item"><a href="#rtoc-13">2. ECS/Fargate環境での具体的な解決策</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-14">次世代コンテナの標準へ</a></li>
</ol>
</div>
<h2 id="rtoc-1" >そもそも「Unprivileged」と「Distroless」とは何か？</h2>
<h3 id="rtoc-2" >1. Unprivileged (非特権ユーザー実行) とは？</h3>
<p><b>Unprivileged実行</b>とは、「最小特権の原則」をコンテナで実践することです。</p>
<ul>
<li><b>定義<br />
</b>コンテナ内のメインプロセスを<code>root</code>ユーザー（UID 0）ではなく、一般ユーザー（非特権ユーザー）として実行すること。</li>
<li><b>メリット<br />
</b>コンテナが何らかの理由で侵害されたり、エスケープ（コンテナの外側にあるホストOSへの侵入）が試みられたりした場合でも、プロセスが持つ権限が最小限に抑えられます。<br />
これにより、ホストシステムへの影響範囲を劇的に制限できます。</li>
<li><b>実現方法<br />
</b>Dockerfile内で<code>USER</code>命令を使用して、コンテナの実行ユーザーを切り替えます。</li>
</ul>
<h3 id="rtoc-3" >2. Distroless (ディストロレス) イメージとは？</h3>
<p><b>Distrolessイメージ</b>とは、セキュリティとサイズを極限まで追求したベースイメージです。</p>
<ul>
<li><b>定義<br />
</b>OSディストリビューション（Linux）の要素、具体的には<code>bash</code>、<code>apt</code>、<code>ls</code>といった一般的なシェルやパッケージ管理ツール<b>を</b>意図的に含めない、アプリケーション実行に必要な最小限のランタイムとライブラリのみで構成されたコンテナイメージです。</li>
<li><b>「通常のイメージ」との違い<br />
</b>通常のイメージ（例: <code>ubuntu:latest</code>）には多くの汎用ツールが含まれていますが、Distrolessイメージにはそれらが一切含まれていません。デバッグツールさえないため、その名（Distro-less = ディストリビューションがない）が示す通り、極めて軽量です。</li>
</ul>
<p><a href="https://otonan-syusyoku.work/archives/1674/lets-go" rel="attachment wp-att-1680"><img fetchpriority="high" decoding="async" src="https://otonan-syusyoku.work/wp-content/uploads/2024/02/Lets-GO.png" alt="Let&#96;s GO" width="1000" height="500" class="aligncenter size-full wp-image-1680" srcset="https://otonan-syusyoku.work/wp-content/uploads/2024/02/Lets-GO.png 1000w, https://otonan-syusyoku.work/wp-content/uploads/2024/02/Lets-GO-300x150.png 300w, https://otonan-syusyoku.work/wp-content/uploads/2024/02/Lets-GO-768x384.png 768w" sizes="(max-width: 1000px) 100vw, 1000px" /></a></p>
<h2 id="rtoc-4" >Unprivileged &amp; Distroless を組み合わせるメリット</h2>
<p>この二つのアプローチを組み合わせることで、コンテナの運用は次のレベルに引き上げられます。</p>
<h3 id="rtoc-5" >1. 圧倒的なセキュリティ向上</h3>
<ul>
<li><b>攻撃対象領域の削減<br />
</b>Distroless化により、イメージに含まれるバイナリやライブラリの数が最小限になります。<br />
その結果、<b>既知の脆弱性（CVE）を持つ可能性のあるパッケージが激減</b>し、攻撃対象となる領域を大幅に縮小します。</li>
<li><b>権限昇格リスクの低減<br />
</b>Unprivileged実行により、仮にコンテナに侵入されても、攻撃者がホストシステムで<code>root</code>権限を得るのが極めて困難になります。</li>
</ul>
<h3 id="rtoc-6" >2. イメージサイズの劇的な削減</h3>
<ul>
<li>不要なOSレイヤーやツールがないため、イメージサイズは通常のベースイメージに比べて<b>数百MB単位で削減</b>されることがあるらしい</li>
<li>これにより、レジストリへのプッシュ/プル時間が短縮され、デプロイ時間の高速化、そしてストレージコストの削減につながります。</li>
</ul>
<h3 id="rtoc-7" >3. ビルド時間の短縮と効率化</h3>
<p>ベースイメージが小さくなることで、CI/CDパイプラインでのダウンロード時間が短縮され、コンテナのビルドプロセス全体が効率化されます。</p>
<h2 id="rtoc-8" >Unprivileged &amp; Distroless なコンテナの使い方</h2>
<p>Unprivileged &amp; Distrolessを実現する最も一般的な方法は、<b>マルチステージビルド</b>を利用することです。</p>
<h3 id="rtoc-9" >1. Distroless イメージの基本構造</h3>
<ol start="1">
<li><b>Build Stage<br />
</b>開発に必要なコンパイラ、パッケージマネージャなどを含む通常のイメージ（例: <code>golang:latest</code>, <code>node:latest</code>）を使い、アプリケーションのビルドや依存関係のインストールを行います。</li>
<li><b>Final Stage<br />
</b>Distrolessイメージをベースとし、Build Stageで生成された<b>最終的なバイナリ</b>や<b>必要なランタイムファイル</b>だけをコピーします。</li>
</ol>
<h3 id="rtoc-10" >2. Unprivileged実行の設定</h3>
<p>Distrolessイメージの多く（例: <code>gcr.io/distroless/...</code>）は、セキュリティのため、デフォルトで<code>非root</code>ユーザー（例: <code>nonroot</code>）が設定されています。</p>
<p>もしご自身でベースイメージから構築する場合、Dockerfileに以下のコマンドを追加してユーザーを切り替えます。</p>
<p><response-element class="" ng-version="0.0.0-PLACEHOLDER"><code-block _nghost-ng-c519592647="" class="ng-tns-c519592647-2060 ng-star-inserted"></code-block></response-element></p>
<div _ngcontent-ng-c519592647="" class="code-block ng-tns-c519592647-2060 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_c303978cc0204370&quot;,&quot;c_2e6b4137d5b58b23&quot;,null,&quot;rc_9ed9dae3948441fd&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c519592647="" class="formatted-code-block-internal-container ng-tns-c519592647-2060">
<div _ngcontent-ng-c519592647="" class="animated-opacity ng-tns-c519592647-2060">
<pre _ngcontent-ng-c519592647="" class="ng-tns-c519592647-2060"># ユーザーを作成し、パーミッションを設定
RUN adduser --disabled-password --gecos "" appuser

# 実行ユーザーを一般ユーザーに切り替える
USER appuser</pre>
</div>
</div>
</div>
<p><a href="https://otonan-syusyoku.work/archives/1519/%e3%81%ae" rel="attachment wp-att-1540"><img decoding="async" src="https://otonan-syusyoku.work/wp-content/uploads/2023/12/の.png" alt="title" width="1000" height="500" class="aligncenter size-full wp-image-1540" srcset="https://otonan-syusyoku.work/wp-content/uploads/2023/12/の.png 1000w, https://otonan-syusyoku.work/wp-content/uploads/2023/12/の-300x150.png 300w, https://otonan-syusyoku.work/wp-content/uploads/2023/12/の-768x384.png 768w" sizes="(max-width: 1000px) 100vw, 1000px" /></a></p>
<h2 id="rtoc-11" >運用上の注意点：非特権ユーザーとポート番号の壁</h2>
<p>UnprivilegedコンテナをECS（または任意のLinuxホスト）で運用する際、セキュリティの恩恵と引き換えに、ある<b>実用上の課題</b>に直面します。</p>
<h3 id="rtoc-12" >1. ポート1024未満のバインドはroot権限が必要</h3>
<p>Linux OSのセキュリティ原則により、Webサーバーなどで一般的に使われる<b>ポート番号1024未満</b>（ウェルノウンポート、例: HTTPの<code>80</code><b>、</b>HTTPSの<code>443</code><b>）にバインドするには、</b><code>root</code>権限（特権）が必要です。</p>
<p>あなたがECSでUnprivilegedかつDistrolessなNginxイメージを使った際、Nginxがデフォルトで<code>80</code>番ポートを使おうとすると、権限がないため「<b>Permission Denied</b>」といった<b>エラーが発生し、コンテナの起動に失敗</b>してしまいます。</p>
<h3 id="rtoc-13" >2. ECS/Fargate環境での具体的な解決策</h3>
<p>この問題を解決しつつ、Unprivilegedのセキュリティメリットを維持するには、以下の方法が最も推奨されます。</p>
<ol start="1">
<li>アプリケーション側のポート変更:Webサーバー（Nginxなど）の設定ファイルを変更し、1024番以上のポート（例: 8080、8081）で待ち受けるように設定を変更します。</li>
<li><b>ロードバランサー (ALB) でのポートマッピング:</b>
<ul>
<li>ECS（Fargate）の<b>タスク定義</b>で、コンテナが使用するポートを<code>8080</code>などに設定します。</li>
<li><b>Application Load Balancer (ALB)</b> を利用し、ALBは外部からのアクセスを<code>80</code>番（標準HTTP）で受けます。</li>
<li>ALBは、内部のECSタスクグループに対しては<code>8080</code>番でルーティングします。</li>
</ul>
</li>
</ol>
<p>これにより、コンテナ内部は非特権ユーザーで安全に運用され、外部ユーザーはポート番号を意識することなくWebサイトにアクセスできるようになります。</p>
<h2 id="rtoc-14" >次世代コンテナの標準へ</h2>
<p>UnprivilegedとDistrolessの組み合わせは、<b>コンテナのセキュリティ基準</b>と<b>デプロイ効率</b>を大きく引き上げる、次世代コンテナの標準パターンです。</p>
<ul>
<li><b>Distroless:</b> 攻撃対象領域とイメージサイズを最小化。</li>
<li><b>Unprivileged:</b> 権限昇格リスクを最小化。</li>
</ul>
<p>特にECSなどの本番環境で運用する際は、<b>ポート1024未満の制限</b>に注意を払い、<b>ロードバランサー</b>を活用して安全かつ効率的な構成を構築しましょう。</p>
<p>あなたのプロジェクトも、今日からこの強力なセキュリティパターンに移行してみませんか？</p>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2156/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Laravel+Inertia+ReactをECSで動かしたい！ NginxとPHP-FPMのコンテナ分離</title>
		<link>https://otonan-syusyoku.work/archives/2153</link>
					<comments>https://otonan-syusyoku.work/archives/2153#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Tue, 28 Oct 2025 05:24:52 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[インフラ]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[LAMP]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[ネットワーク]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2153</guid>

					<description><![CDATA[Laravel + Inertia (React/Vue) の組み合わせは、SPA（シングルページアプリケーション）の体験とサーバーサイド（Laravel）の書きやすさを両立できる、非常に強力な構成です。 しかし、ローカ [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Laravel + Inertia (React/Vue) の組み合わせは、SPA（シングルページアプリケーション）の体験とサーバーサイド（Laravel）の書きやすさを両立できる、非常に強力な構成です。</p>
<p>しかし、ローカルのDocker Compose環境では問題なく動作しても、AWSのECS Fargateのような本番環境にデプロイしようとすると、いくつかの疑問に直面します。</p>
<ul>
<li>「NginxとPHP-FPMコンテナはどう連携させるのがベスト？」</li>
<li>「<code>fastcgi_pass</code> ってローカルと設定を変える必要ある？」</li>
<li>「InertiaでビルドしたReactのJS/CSSファイルは、結局どのコンテナに置くのが正解？」</li>
</ul>
<p>この記事では、<code>fastcgi_pass</code> の基本から、ECS Fargateでの最適なコンテナ構成、そしてInertia/Reactプロジェクト特有の「静的ファイルの配置場所」問題までを、順を追って解説します。</p>
<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2153" data-theme="BlogArise">
<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button><br />
			<span>Contents</span>
			</div>
<ol class="rtoc-mokuji decimal_ol level-1">
<li class="rtoc-item"><a href="#rtoc-1">fastcgi_pass とは？</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-2">NginxとPHP-FPMの「橋渡し」</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-3">ローカル開発 (Docker Compose) での構成</a></li>
<li class="rtoc-item"><a href="#rtoc-4">【1タスク2コンテナ】本番 (ECS Fargate) での構成：</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-5">ECS Fargateでのfastcgi_passはどうなる？</a></li>
<li class="rtoc-item"><a href="#rtoc-6">タスク定義のイメージ</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-7">Inertia/Reactのビルドファイルはどこに置く？</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-8">ステップ1: HTMLシェルのリクエスト (PHP-FPMが処理)</a></li>
<li class="rtoc-item"><a href="#rtoc-9">ステップ2: 静的アセット(JS/CSS)のリクエスト (Nginxが処理)</a></li>
<li class="rtoc-item"><a href="#rtoc-10">CI/CDでのベストプラクティス</a></li>
<li class="rtoc-item"><a href="#rtoc-11"><b>結論</b></a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-12">まとめ</a></li>
</ol>
</div>
<h2 id="rtoc-1" >fastcgi_pass とは？</h2>
<h3 id="rtoc-2" >NginxとPHP-FPMの「橋渡し」</h3>
<p>まず基本のおさらいです。Nginxは高性能なWebサーバーですが、それ自体はPHPコードを実行できません。<br />
一方、PHP-FPMはPHPコードを実行することに特化したプロセス（サーバー）です。</p>
<p>Nginxがクライアント（ブラウザ）から <code>.php</code> ファイルへのリクエストを受け取ったとき、そのリクエストをPHP-FPMに処理してもらう必要があります。</p>
<p>このとき、Nginxの設定ファイル（<code>nginx.conf</code>）で、<b>「どのPHP-FPMに処理を依頼するか」の宛先を指定する</b>のが <code>fastcgi_pass</code> ディレクティブです。</p>
<div _ngcontent-ng-c535465467="" class="code-block ng-tns-c535465467-470 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_4fea32b244968dc8&quot;,&quot;c_103da6cb53c10bc2&quot;,null,&quot;rc_8751fdc7f6beda59&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c535465467="" class="formatted-code-block-internal-container ng-tns-c535465467-470">
<div _ngcontent-ng-c535465467="" class="animated-opacity ng-tns-c535465467-470">
<pre _ngcontent-ng-c535465467="" class="ng-tns-c535465467-470"><code _ngcontent-ng-c535465467="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c535465467-470"><span class="hljs-attribute">location</span> <span class="hljs-regexp">~ \.php$</span> {
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># ↓ この一行が「橋渡し」の指定</span>
    <span class="hljs-attribute">fastcgi_pass</span> php-fpm-server:<span class="hljs-number">9000</span>; 
}
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<h2 id="rtoc-3" >ローカル開発 (Docker Compose) での構成</h2>
<p>ローカル開発では、「1コンテナ1責務」の原則に従い、NginxとPHP-FPMを別々のコンテナとして <code>docker-compose.yml</code> で定義するのが一般的です。</p>
<div _ngcontent-ng-c535465467="" class="code-block ng-tns-c535465467-471 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_4fea32b244968dc8&quot;,&quot;c_103da6cb53c10bc2&quot;,null,&quot;rc_8751fdc7f6beda59&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c535465467="" class="formatted-code-block-internal-container ng-tns-c535465467-471">
<div _ngcontent-ng-c535465467="" class="animated-opacity ng-tns-c535465467-471">
<pre _ngcontent-ng-c535465467="" class="ng-tns-c535465467-471"><code _ngcontent-ng-c535465467="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c535465467-471"><span class="hljs-comment"># docker-compose.yml (抜粋)</span>
<span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-comment"># Nginxコンテナ</span>
  <span class="hljs-attr">nginx:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:alpine</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"80:80"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx.conf:/etc/nginx/conf.d/default.conf</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./laravel-project:/var/www/html</span>

  <span class="hljs-comment"># PHP-FPMコンテナ</span>
  <span class="hljs-attr">php:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span> <span class="hljs-comment"># PHP-FPMとLaravelコードを含むDockerfile</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./laravel-project:/var/www/html</span>
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>この構成では、NginxコンテナとPHPコンテナは <b>別々のネットワーク空間</b> に存在します。Docker Composeが提供する内部ネットワークを介して、サービス名で通信します。</p>
<p>したがって、NginxコンテナからPHPコンテナへは、<code>php</code> というサービス名（ホスト名）を使ってアクセスします。</p>
<div _ngcontent-ng-c535465467="" class="code-block ng-tns-c535465467-472 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_4fea32b244968dc8&quot;,&quot;c_103da6cb53c10bc2&quot;,null,&quot;rc_8751fdc7f6beda59&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c535465467="" class="formatted-code-block-internal-container ng-tns-c535465467-472">
<div _ngcontent-ng-c535465467="" class="animated-opacity ng-tns-c535465467-472">
<pre _ngcontent-ng-c535465467="" class="ng-tns-c535465467-472"><code _ngcontent-ng-c535465467="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c535465467-472"><span class="hljs-comment"># nginx.conf (Docker Compose用)</span>
<span class="hljs-attribute">location</span> <span class="hljs-regexp">~ \.php$</span> {
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># 'php' サービス（コンテナ）の 9000番ポートに転送</span>
    <span class="hljs-attribute">fastcgi_pass</span> php:<span class="hljs-number">9000</span>;
}
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<p>（※ このTCP/IP通信によるパフォーマンスやセキュリティの懸念は、コンテナ間通信がDockerの内部ネットワークに限定されていれば、実用上ほとんど問題になりません。）</p>
<h2 id="rtoc-4" >【1タスク2コンテナ】本番 (ECS Fargate) での構成：</h2>
<p>さて、本題のECS Fargateです。<br />
ECSでは、<code>docker-compose.yml</code> に相当するものとして「タスク定義 (Task Definition)」を使います。</p>
<p>ここで重要なキーポイントは、<strong>「1つのタスク定義の中で、NginxコンテナとPHP-FPMコンテナの2つを定義する」</strong>ことです。</p>
<p><strong>これら2つのコンテナは、1セットで「Webアプリケーションサーバー」</strong>として機能します。</p>
<h3 id="rtoc-5" >ECS Fargateでのfastcgi_passはどうなる？</h3>
<p>ECSの同一タスク内で実行されるコンテナは、<b>同じネットワーク空間（ネットワークモード <code>awsvpc</code>）を共有します</b>。</p>
<p>これは、Nginxコンテナから見ると、PHP-FPMコンテナが「他人」ではなく、「自分自身（localhost）」として見えることを意味します。</p>
<p>したがって、ECS Fargate用のNginxイメージに含める <code>nginx.conf</code> の設定は、以下のようになります。</p>
<div _ngcontent-ng-c535465467="" class="code-block ng-tns-c535465467-473 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_4fea32b244968dc8&quot;,&quot;c_103da6cb53c10bc2&quot;,null,&quot;rc_8751fdc7f6beda59&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c535465467="" class="formatted-code-block-internal-container ng-tns-c535465467-473">
<div _ngcontent-ng-c535465467="" class="animated-opacity ng-tns-c535465467-473">
<pre _ngcontent-ng-c535465467="" class="ng-tns-c535465467-473"><code _ngcontent-ng-c535465467="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c535465467-473"><span class="hljs-comment"># nginx.conf (ECS Fargate用)</span>
<span class="hljs-attribute">location</span> <span class="hljs-regexp">~ \.php$</span> {
    <span class="hljs-comment"># ...</span>
    <span class="hljs-comment"># サービス名ではなく、localhost (127.0.0.1) を指定する</span>
    <span class="hljs-attribute">fastcgi_pass</span> <span class="hljs-number">127.0.0.1:9000</span>;
}
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<h3 id="rtoc-6" >タスク定義のイメージ</h3>
<p>タスク定義では、以下のように設定します。</p>
<ul>
<li><b>コンテナ-A: <code>php-fpm-container</code></b>
<ul>
<li>イメージ: （Laravelコードを含むPHP-FPMイメージ）</li>
<li>ポートマッピング: <b>なし</b> (外部に公開する必要はないため)</li>
</ul>
</li>
<li><b>コンテナ-B: <code>nginx-container</code></b>
<ul>
<li>イメージ: （設定済みのnginx.confと静的ファイルを含むNginxイメージ）</li>
<li>ポートマッピング: <b><code>80:80</code></b> (ALBからのトラフィックを受け取るため)</li>
</ul>
</li>
</ul>
<p>ALB（ロードバランサー）からのトラフィックはNginxコンテナが受け取り、必要に応じて <code>localhost:9000</code> を介してPHP-FPMコンテナに処理を渡します。</p>
<h2 id="rtoc-7" >Inertia/Reactのビルドファイルはどこに置く？</h2>
<p>ここで、Inertia/React構成特有の問題に直面します。</p>
<p>「InertiaはLaravel（PHP）がビューを配信する仕組みだから、<code>npm run build</code> で生成された <code>public/build</code> フォルダは、PHP-FPMコンテナにだけ置けば良いのでは？」</p>
<p>これはよくある誤解ですが、<b>パフォーマンスと責務分離の観点から、ビルドした静的ファイル（JS/CSS）はNginxコンテナに配置するのが正解</b>です。</p>
<p>この理由を理解するために、Inertiaのページが読み込まれる2段階のフローを見てみましょう。</p>
<h3 id="rtoc-8" >ステップ1: HTMLシェルのリクエスト (PHP-FPMが処理)</h3>
<ol start="1">
<li>ブラウザが <code>https://example.com/dashboard</code> にアクセスします。</li>
<li>ALB → Nginxコンテナがリクエストを受け取ります。</li>
<li>NginxはこれがPHPのリクエストだと判断し、<code>fastcgi_pass 127.0.0.1:9000</code> を使って<b>PHP-FPMコンテナ</b>に転送します。</li>
<li>PHP (Laravel) が起動し、Inertiaは <code>app.blade.php</code> をレンダリングしようとします。</li>
<li>このとき、PHPは <b><code>public/build/manifest.json</code></b> を読み取り、HTMLに含めるべきJS/CSSのファイル名を特定します。</li>
<li>PHPは以下のようなHTMLの「ガワ」を<b>生成</b>し、Nginx経由でブラウザに返します。
<p><response-element class="" ng-version="0.0.0-PLACEHOLDER"><code-block _nghost-ng-c535465467="" class="ng-tns-c535465467-474 ng-star-inserted"></p>
<div _ngcontent-ng-c535465467="" class="code-block ng-tns-c535465467-474 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression,attention;BardVeMetadataKey:[[&quot;r_4fea32b244968dc8&quot;,&quot;c_103da6cb53c10bc2&quot;,null,&quot;rc_8751fdc7f6beda59&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c535465467="" class="formatted-code-block-internal-container ng-tns-c535465467-474">
<div _ngcontent-ng-c535465467="" class="animated-opacity ng-tns-c535465467-474">
<pre _ngcontent-ng-c535465467="" class="ng-tns-c535465467-474"><code _ngcontent-ng-c535465467="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c535465467-474"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/build/assets/app.12345.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/build/assets/app.67890.css"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span> <span class="hljs-attr">data-page</span>=<span class="hljs-string">"..."</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
</div>
</div>
</div>
<p></code-block></response-element></li>
</ol>
<h3 id="rtoc-9" >ステップ2: 静的アセット(JS/CSS)のリクエスト (Nginxが処理)</h3>
<ol start="1">
<li>ブラウザは、ステップ1で受け取ったHTMLを解析します。</li>
<li>「<code>/build/assets/app.12345.js</code> と <code>/build/assets/app.67890.css</code> が必要だ」と判断し、ブラウザは<b>別途2回のリクエスト</b>をサーバーに送信します。</li>
<li><b>このリクエストは、もはやPHPとは全く関係ありません。</b></li>
<li>ALB → <b>Nginxコンテナ</b>がこの2つのリクエストを受け取ります。</li>
<li>Nginxは「自分の管理下（<code>public</code> フォルダ）にそのファイルがあるか？」を探します。</li>
<li><b>Nginxコンテナに <code>public/build/</code> 以下の実体ファイルが存在するため</b>、NginxはPHP-FPMを起動することなく、それらの静的ファイルを<b>超高速で</b>ブラウザに直接返します。</li>
</ol>
<p>もし、NginxコンテナにJS/CSSファイルがなければ、このリクエストもPHP-FPMに転送されてしまい、「JSファイルを取得するためだけにLaravelを起動する」という深刻なパフォーマンスボトルネックが発生します。</p>
<h3 id="rtoc-10" >CI/CDでのベストプラクティス</h3>
<p>上記2ステップから、CI/CDパイプラインでDockerイメージをビルドする際は、以下の構成が最適です。</p>
<ol start="1">
<li><code>npm run build</code> を実行し、<code>public/build</code> ディレクトリを生成します。</li>
<li><b>PHP-FPMイメージのビルド:</b>
<ul>
<li>Laravelのコード（<code>app/</code>, <code>routes/</code> など）をコピーします。</li>
<li>ステップ1のために <b><code>public/build/manifest.json</code></b> をコピーします。（リンク生成に必要）</li>
</ul>
</li>
<li><b>Nginxイメージのビルド:</b>
<ul>
<li><code>nginx.conf</code> をコピーします。</li>
<li>ステップ2のために <b><code>public</code> フォルダ全体</b>（<code>index.php</code> と、<code>public/build</code> 以下の <b>全てのJS/CSSファイル</b> を含む）をコピーします。（静的アセットの配信用）</li>
</ul>
</li>
</ol>
<h3 id="rtoc-11" ><b>結論</b></h3>
<p><code>manifest.json</code> はPHP-FPMコンテナに、JS/CSSの実体ファイルはNginxコンテナに必要です。</p>
<p>両方のイメージに <code>public</code> フォルダ（または <code>public/build</code>）全体をコピーするのが、最もシンプルで確実な方法です。</p>
<h2 id="rtoc-12" >まとめ</h2>
<p>ECS Fargateで Laravel + Inertia + React 構成を動かすための要点をまとめます。</p>
<ol start="1">
<li><b>構成</b>: 「1タスク2コンテナ（Nginx + PHP-FPM）」構成を採用します。</li>
<li><b><code>fastcgi_pass</code></b>: NginxからPHP-FPMへの通信は、同一タスク内のため <code>fastcgi_pass 127.0.0.1:9000;</code> を指定します。</li>
<li><b>静的ファイル</b>: ビルドしたJS/CSSは、パフォーマンス最適化のためNginxコンテナに配置し、Nginxから直接配信させます。</li>
<li><b><code>manifest.json</code></b>: HTMLシェル生成のため、PHP-FPMコンテナにも <code>manifest.json</code> が必要です。</li>
</ol>
<p>この構成により、PHP-FPMは動的処理に専念し、Nginxは静的ファイルの高速配信に専念するという、「1コンテナ1責務」のメリットを最大限に活かした、スケーラブルな本番環境を構築できます。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2153/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【AWS解説】ALBのHTTPSリダイレクト、コンテナはポート80でなぜOK？</title>
		<link>https://otonan-syusyoku.work/archives/2147</link>
					<comments>https://otonan-syusyoku.work/archives/2147#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Fri, 17 Oct 2025 01:45:08 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[ALB]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[HTTP]]></category>
		<category><![CDATA[セキュリティ]]></category>
		<category><![CDATA[ネットワーク]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2147</guid>

					<description><![CDATA[AWSでWebアプリケーションを構築する際、ALB (Application Load Balancer) を使ってHTTPS化するのはもはや常識です。しかし、多くの開発者が一度は疑問に思う点があります。 「ALBでHT [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>AWSでWebアプリケーションを構築する際、ALB (Application Load Balancer) を使ってHTTPS化するのはもはや常識です。しかし、多くの開発者が一度は疑問に思う点があります。</p>
<p><b>「ALBでHTTPS化したのに、なぜ後ろにいるECSコンテナはポート80（HTTP）のままで通信できるんだろう？」</b></p>
<p>この一見不思議な挙動は、ALBが持つ2つの重要な役割を理解することで、スッキリと解決します。今回は、このALBの「魔法」の仕組みを、セキュリティグループの設定から紐解いていきましょう。</p>
<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2147" data-theme="BlogArise">
<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button><br />
			<span>Contents</span>
			</div>
<ol class="rtoc-mokuji decimal_ol level-1">
<li class="rtoc-item"><a href="#rtoc-1">最終的な構成：安全な3層アーキテクチャ</a></li>
<li class="rtoc-item"><a href="#rtoc-2">門番の役割を担う「セキュリティグループ」</a></li>
<li class="rtoc-item"><a href="#rtoc-3">ALBがこなす2つの全く異なる仕事</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-4">ブラウザへの「リダイレクト指示」</a></li>
<li class="rtoc-item"><a href="#rtoc-5">ステップ2：コンテナへの「リクエスト転送」</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-6">CDKの実装</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-7">SecurityGroup の実装</a></li>
<li class="rtoc-item"><a href="#rtoc-8">ALBリスナー</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-9">まとめ</a></li>
<li class="rtoc-item"><a href="#rtoc-10">参考記事</a></li>
</ol>
</div>
<h2 id="rtoc-1" >最終的な構成：安全な3層アーキテクチャ</h2>
<p>まず、私たちが目指すのは、セキュリティと可用性のベストプラクティスに基づいた、以下のような3層アーキテクチャです。</p>
<ul>
<li><b>Web層</b>: ALBがインターネットからのリクエストをすべて受け付けます。</li>
<li><b>AP層</b>: ECSコンテナがプライベートな領域でアプリケーションを動かします。</li>
<li><b>DB層</b>: RDSがさらに内側のプライベートな領域でデータを安全に保管します。</li>
</ul>
<p>この構成のセキュリティの肝は<strong>「ECSはALBからの通信しか受け付けない」</strong>というルールです。これを実現するのが、次に説明するセキュリティグループです。</p>
<p>&nbsp;</p>
<h2 id="rtoc-2" >門番の役割を担う「セキュリティグループ」</h2>
<p>セキュリティグループは、リソースのドアを守る「バーチャルな門番」です。今回は3人の門番を配置します。</p>
<ol start="1">
<li><b>ALBの門番</b>: インターネットからの訪問者（ポート80, 443）を全員受け入れます。</li>
<li><b>ECSの門番</b>: <b>ALBの門番から紹介された人</b>（ALBからのトラフィック）<b>だけ</b>を通します。それ以外の人は全員お断りです。</li>
<li><b>RDSの門番</b>: <b>ECSの門番から紹介された人</b>（ECSからのトラフィック）<b>だけ</b>を通します。</li>
</ol>
<p>この連携により、たとえ攻撃者がECSに直接アクセスしようとしても、門前払いされる仕組みが完成します。</p>
<h2 id="rtoc-3" >ALBがこなす2つの全く異なる仕事</h2>
<p>さて、ここからが本題です。ユーザーが<code>http://</code>でアクセスしてきた時に、ALBとコンテナの間で何が起きているのでしょうか。実は、これは<b>2段階の独立した処理</b>になっています。</p>
<p>ポイントは役割が2つ存在するということです。</p>
<h3 id="rtoc-4" >ブラウザへの「リダイレクト指示」</h3>
<p>最初のやり取りは、ユーザーのブラウザとALBの間だけで完結します。</p>
<ol start="1">
<li><b>ユーザー</b>: ブラウザで <code>http://example.com</code> (ポート80) にアクセス。</li>
<li><b>ALB (ポート80の受付係)</b>: リクエストを受け取ると、中身は見ずにこう応答します。「<b>すみません、こちらの窓口は安全ではありません。向かいにある安全な443番の窓口へ行き直してください</b>」。これが<b>HTTP 301リダイレクト</b>です。</li>
</ol>
<p><b>この時点では、リクエストはまだECSコンテナには一切届いていません。</b></p>
<h3 id="rtoc-5" >ステップ2：コンテナへの「リクエスト転送」</h3>
<p>ブラウザはALBからの指示に従い、今度は最初から安全な窓口へ向かいます。</p>
<ol start="1">
<li><b>ユーザー</b>: ブラウザが自動的に <code>https://example.com</code> (ポート443) に<b>新しいリクエスト</b>を送信します。</li>
<li><b>ALB (ポート443の受付係)</b>:
<ul>
<li>暗号化されたリクエストを受け取り、持っているSSL証明書を使って<b>通信を復号</b>します（SSL/TLS終端）。</li>
<li>安全な平文になったリクエストを、宛先である<b>ECSコンテナのポート80</b>へ転送（フォワード）します。</li>
</ul>
</li>
<li><b>ECSコンテナ (ポート80)</b>: ALBから来た通常のHTTPリクエストを受け取り、処理を実行します。</li>
</ol>
<p>このように、ALBは<strong>ユーザーを正しい入口へ案内する「案内係」</strong>の仕事と、<strong>リクエストを内部の担当者へ届ける「取次係」</strong>の仕事という、2つの全く異なる役割をこなしているのです。</p>
<p>コンテナからすれば、常にALBという信頼できる取次係から、暗号化が解かれた分かりやすいリクエストが届くだけなので、ポート80で待ち受けていれば良い、というわけです。</p>
<p>非常に便利ですね。。</p>
<h2 id="rtoc-6" >CDKの実装</h2>
<h3 id="rtoc-7" >SecurityGroup の実装</h3>
<pre class="line-numbers"><code class="language-php">// ALB用セキュリティグループ
const albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSg', { vpc });
albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP');
albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS');

// ECS用セキュリティグループ
const ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSg', { vpc });
// ALBからの通信のみを、コンテナのポート80で許可
ecsSecurityGroup.addIngressRule(
    albSecurityGroup,
    ec2.Port.tcp(80),
    'Allow traffic only from ALB'
);</code></pre>
<h3 id="rtoc-8" >ALBリスナー</h3>
<pre class="line-numbers"><code class="language-js">// ALB本体を作成
const alb = new elbv2.ApplicationLoadBalancer(this, 'MyAlb', {
    vpc,
    internetFacing: true,
    securityGroup: albSecurityGroup,
});

// HTTPリスナー (ポート80): HTTPSへリダイレクトするだけ
alb.addListener('HttpListener', {
    port: 80,
    defaultAction: elbv2.ListenerAction.redirect({
        protocol: 'HTTPS',
        port: '443',
        permanent: true,
    }),
});

// HTTPSリスナー (ポート443): ECSへリクエストを転送する
alb.addListener('HttpsListener', {
    port: 443,
    certificates: [certificate], // ACMで発行した証明書
    defaultAction: elbv2.ListenerAction.forward([ecsTargetGroup]), // ECSのターゲットグループ
});</code></pre>
<h2 id="rtoc-9" >まとめ</h2>
<ul>
<li>ALBのHTTP→HTTPSリダイレクトは、「リダイレクト指示」<b>と</b>「リクエスト転送」の2段階で行われる。</li>
<li>SSL/TLSによる暗号化・復号の処理はALBがすべて肩代わりしてくれる（<b>SSL/TLS終端</b>）。</li>
<li>コンテナは、ALBから転送されてくる<b>暗号化されていないHTTPリクエスト</b>を受け取るだけで良い。</li>
<li><b>セキュリティグループ</b>を正しく設定することで、この安全な通信経路が完成する。</li>
</ul>
<p>役割が2つあることが分からなければ理解できなかったなぁ。</p>
<p>&nbsp;</p>
<h2 id="rtoc-10" >参考記事</h2>
<div class="awsui_grid-column_14yj0_16am7_186 awsui_colspan-12_14yj0_16am7_307">
<div class="awsui_restore-pointer-events_14yj0_16am7_357">
<ol>
<li><a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html">What is an Application Load Balancer?</a></li>
<li><a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html">Listeners for your Application Load Balancers</a></li>
</ol>
</div>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2147/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>クラウドメタデータ攻撃とは？ Nginx設定で防ぐサイバー脅威</title>
		<link>https://otonan-syusyoku.work/archives/2131</link>
					<comments>https://otonan-syusyoku.work/archives/2131#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Thu, 26 Jun 2025 04:38:30 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[nginx]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2131</guid>

					<description><![CDATA[近年、企業のITインフラをクラウドへ移行する動きが加速しています。AWS、GCP、Azureといったクラウドサービスは非常に便利ですが、同時に新たなセキュリティリスクも生み出しています。その一つが「クラウドメタデータ攻撃 [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>近年、企業のITインフラをクラウドへ移行する動きが加速しています。AWS、GCP、Azureといったクラウドサービスは非常に便利ですが、同時に新たなセキュリティリスクも生み出しています。その一つが「<b>クラウドメタデータ攻撃</b>」です。</p>
<p>この記事では、この攻撃の仕組みと、<b>特にNginxを使っている場合にどのように対策すべきか</b>を分かりやすく解説します。</p>
<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2131" data-theme="BlogArise">
<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button><br />
			<span>Contents</span>
			</div>
<ol class="rtoc-mokuji decimal_ol level-1">
<li class="rtoc-item"><a href="#rtoc-1">そもそも「クラウドメタデータ」って何？</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-2">169.254.169.254ってどんなIP？</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-3">クラウドメタデータ攻撃の仕組み</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-4">攻撃の手順</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-5">3. Nginxでクラウドメタデータ攻撃を防ぐ方法</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-6">設定のポイント</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-7">まとめ</a></li>
</ol>
</div>
<h2 id="rtoc-1" >そもそも「クラウドメタデータ」って何？</h2>
<p>クラウド環境でサーバー（インスタンス）を動かしていると、「<b>メタデータ</b>」というものが存在します。これは、そのサーバー自身の「身分証明書」のような情報です。</p>
<p>具体的には、以下のような情報が含まれます。</p>
<ul>
<li><b>インスタンスID:</b> そのサーバー固有の識別番号</li>
<li><b>ロール・認証情報:</b> そのサーバーが他のクラウドサービスにアクセスするための権限や秘密鍵など</li>
<li><b>ネットワーク情報:</b> サーバーのIPアドレスなど</li>
<li><b>起動スクリプト:</b> サーバーが起動時に実行するコマンド</li>
</ul>
<p>これらの情報は、サーバーが自分自身を識別したり、他のクラウドサービスと安全に連携したりするために非常に重要です。そして、これらのメタデータには通常、**<code>169.254.169.254</code>**という特別なIPアドレスを介して、サーバー内部からのみアクセスできるようになっています。</p>
<h3 id="rtoc-2" >169.254.169.254ってどんなIP？</h3>
<p>この<code>169.254.169.254</code>というIPアドレスは、「<b>リンクローカルアドレス</b>」と呼ばれる特殊な範囲のIPアドレスです。これは、インターネットのような外部ネットワークからはアクセスできない、サーバー自身の内部ネットワークでのみ有効なIPアドレスです。</p>
<p>つまり、通常は外部からこのIPアドレスにアクセスすることはできず、サーバー内部のアプリケーションだけが、自分自身の情報を安全に取得するために使います。</p>
<h2 id="rtoc-3" >クラウドメタデータ攻撃の仕組み</h2>
<p>「クラウドメタデータ攻撃」は、この本来アクセスできないはずのメタデータに、<b>不適切な設定を悪用してアクセスしようとする攻撃</b>です。</p>
<p>攻撃者が狙うのは、ウェブサーバーとしてよく使われる「<b>Nginx（エンジンエックス）</b>」の設定ミスです。</p>
<pre class="line-numbers"><code class="language-php">curl -H "X-aws-ec2-metadata-token: $(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")" http://169.254.169.254/latest/meta-data/</code></pre>
<p>&nbsp;</p>
<h3 id="rtoc-4" >攻撃の手順</h3>
<ol start="1">
<li><b>Nginxの誤設定:</b> ウェブサーバーのNginxが、特定のルールなしに外部からのリクエストを内部のIPアドレスに転送してしまうような、<b>不適切な設定になっている</b>のが攻撃の前提です。</li>
<li><b>特別なリクエストの送信:</b> 攻撃者は、ウェブブラウザなどから、悪意のあるHTTPリクエストをNginxサーバーに送ります。このとき、リクエストの「<b>Hostヘッダー</b>」という部分に、先ほどのメタデータ用IPアドレスである<code>169.254.169.254</code>を意図的に含めます。</li>
<li><b>Nginxの転送:</b> 誤設定されたNginxは、この<code>Host</code>ヘッダーに<code>169.254.169.254</code>が含まれているリクエストを、本来ルーティング不可能なはずの内部メタデータサービスへ転送してしまいます。</li>
<li><b>メタデータの取得:</b> 結果として、外部の攻撃者が、サーバーの機密性の高いメタデータ（認証情報など）を不正に取得できてしまうのです。</li>
</ol>
<p>この攻撃が成功すると、攻撃者はサーバーになりすまして他のクラウドサービスへアクセスしたり、機密情報を盗み取ったりするなど、さらに深刻な被害につながる可能性があります。</p>
<h2 id="rtoc-5" >3. Nginxでクラウドメタデータ攻撃を防ぐ方法</h2>
<p>この攻撃を防ぐための最も効果的で直接的な方法は、<b>Nginxの設定を正しく行うこと</b>です。</p>
<p>Nginxの設定ファイル（通常は<code>/etc/nginx/nginx.conf</code>や<code>/etc/nginx/conf.d/</code>内のサイト設定ファイル）に、以下のルールを追加しましょう。</p>
<div _ngcontent-ng-c3273690373="" class="code-block ng-tns-c3273690373-284 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression;BardVeMetadataKey:[[&quot;r_a0b4d50376cf47d8&quot;,&quot;c_c62c50919a81b22d&quot;,null,&quot;rc_a2eeb02e335308f6&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c3273690373="" class="formatted-code-block-internal-container ng-tns-c3273690373-284">
<div _ngcontent-ng-c3273690373="" class="animated-opacity ng-tns-c3273690373-284">
<pre _ngcontent-ng-c3273690373="" class="ng-tns-c3273690373-284"><code _ngcontent-ng-c3273690373="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c3273690373-284"><span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
    <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl; <span class="hljs-comment"># HTTPSを使用している場合</span>
    <span class="hljs-attribute">server_name</span> your-domain.com; <span class="hljs-comment"># 実際のドメイン名に置き換えてください</span>

    <span class="hljs-attribute">location</span> / {
        <span class="hljs-comment"># HostヘッダーがメタデータIPアドレス（169.254.169.254）の場合、</span>
        <span class="hljs-comment"># アクセスを拒否し、403 Forbiddenエラーを返す</span>
        <span class="hljs-attribute">if</span> ($http_host = <span class="hljs-string">"169.254.169.254"</span>) {
            <span class="hljs-attribute">return</span> <span class="hljs-number">403</span>;
        }
    }

    <span class="hljs-comment"># その他のNginx設定</span>
    <span class="hljs-comment"># ...</span>
}
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<h3 id="rtoc-6" >設定のポイント</h3>
<ul>
<li><code>if ($http_host = "169.254.169.254")</code>: これが核心的な部分です。受信したリクエストの<code>Host</code>ヘッダーが<code>169.254.169.254</code>であるかをチェックします。</li>
<li><code>return 403;</code>: もし一致した場合、そのリクエストをサーバーの内部処理に進ませず、すぐに「403 Forbidden（アクセス禁止）」のエラーを返します。これにより、攻撃者がメタデータにアクセスするのを防ぎます。</li>
</ul>
<p>この設定を追加したら、必ずNginxの設定をテストし、再読み込み（または再起動）するのを忘れないでください。</p>
<div _ngcontent-ng-c3273690373="" class="code-block ng-tns-c3273690373-285 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression;BardVeMetadataKey:[[&quot;r_a0b4d50376cf47d8&quot;,&quot;c_c62c50919a81b22d&quot;,null,&quot;rc_a2eeb02e335308f6&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c3273690373="" class="formatted-code-block-internal-container ng-tns-c3273690373-285">
<div _ngcontent-ng-c3273690373="" class="animated-opacity ng-tns-c3273690373-285">
<pre _ngcontent-ng-c3273690373="" class="ng-tns-c3273690373-285"><code _ngcontent-ng-c3273690373="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c3273690373-285">sudo nginx -t <span class="hljs-comment"># 設定ファイルの文法チェック</span>
sudo systemctl reload nginx <span class="hljs-comment"># Nginxの設定を再読み込み</span>
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<h2 id="rtoc-7" >まとめ</h2>
<p>クラウドメタデータ攻撃は、クラウド環境を利用する上で注意すべきサイバー脅威の一つです。しかし、適切なNginxの設定を行うことで、このリスクを効果的に軽減できます。</p>
<p>あなたのウェブサーバーが安全に稼働しているか、今一度Nginxの設定を見直してみてはいかがでしょうか？</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2131/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Nginxの「upstream sent too big header」エラーを解決する：LaravelとPHP-FPM環境での対処法</title>
		<link>https://otonan-syusyoku.work/archives/2128</link>
					<comments>https://otonan-syusyoku.work/archives/2128#respond</comments>
		
		<dc:creator><![CDATA[hrokig2]]></dc:creator>
		<pubDate>Mon, 23 Jun 2025 15:04:51 +0000</pubDate>
				<category><![CDATA[インフラ]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[LAMP]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[PHP]]></category>
		<guid isPermaLink="false">https://otonan-syusyoku.work/?p=2128</guid>

					<description><![CDATA[Contents 環境 起こったこと エラーが起きる原因は？ 解決策：Nginxの設定を調整する ディレクティブの解説 設定変更後の手順 根本的な解決策：Laravelアプリケーション側の最適化 環境 Ubuntu：24 [&#8230;]]]></description>
										<content:encoded><![CDATA[<div id="rtoc-mokuji-wrapper" class="rtoc-mokuji-content frame4 preset3 animation-fade rtoc_open noto-sans" data-id="2128" data-theme="BlogArise">
<div id="rtoc-mokuji-title" class=" rtoc_center">
			<button class="rtoc_open_close rtoc_open"></button><br />
			<span>Contents</span>
			</div>
<ol class="rtoc-mokuji decimal_ol level-1">
<li class="rtoc-item"><a href="#rtoc-1">環境</a></li>
<li class="rtoc-item"><a href="#rtoc-2">起こったこと</a></li>
<li class="rtoc-item"><a href="#rtoc-3">エラーが起きる原因は？</a></li>
<li class="rtoc-item"><a href="#rtoc-4">解決策：Nginxの設定を調整する</a>
<ul class="rtoc-mokuji mokuji_ul level-2">
<li class="rtoc-item"><a href="#rtoc-5"><strong>ディレクティブの解説</strong></a></li>
<li class="rtoc-item"><a href="#rtoc-6">設定変更後の手順</a></li>
</ul>
</li>
<li class="rtoc-item"><a href="#rtoc-7">根本的な解決策：Laravelアプリケーション側の最適化</a></li>
</ol>
</div>
<h2 id="rtoc-1"  data-sourcepos="7:1-7:154">環境</h2>
<p>Ubuntu：24.04<br />
PHP：8.2<br />
Laravel：11.x</p>
<h2 id="rtoc-2"  data-sourcepos="5:1-5:183">起こったこと</h2>
<p data-sourcepos="5:1-5:183">NginxをWebサーバーとして、LaravelアプリケーションとPHP-FPMを連携させている環境で、ある日突然 <code>upstream sent too big header while reading response header from upstream</code> というエラーがNginxのエラーログに表示され、Webサイトが正常に表示されなくなリマした…</p>
<p data-sourcepos="7:1-7:154">調べていくと、このエラーは、<span class="sc_marker blue"><strong>PHP-FPM（アップストリームサーバー）がNginxに返そうとしたHTTPレスポンスヘッダーのサイズが、Nginxが受け入れられる設定値の上限を超えてしまった場合に発生</strong></span>します。</p>
<p data-sourcepos="7:1-7:154">特にLaravelのような動的なアプリケーションでは、セッション情報やクッキーの肥大化が原因で発生しやすい問題とのことでやんす。</p>
<h2 id="rtoc-3"  data-sourcepos="9:1-9:15">エラーが起きる原因は？</h2>
<p data-sourcepos="11:1-11:182">NginxがWebサーバーとしてクライアントからのリクエストを受け取り、それをバックエンドのPHP-FPM（FastCGIプロセス）に渡して処理してもらいます。</p>
<p data-sourcepos="11:1-11:182">PHP-FPMが処理を終えてNginxに応答を返す際、そのHTTPレスポンスにはヘッダー情報が含まれます。このヘッダー情報がNginxが設定しているバッファのサイズを超えると、エラーが発生してしまいます。</p>
<p data-sourcepos="13:1-13:24">主な原因として、以下のようなものが挙げられます。</p>
<ul data-sourcepos="15:3-18:0">
<li data-sourcepos="15:3-15:177"><strong>セッションデータの肥大化:</strong> Laravelなどのフレームワークでセッションをクッキーベースで管理している場合、ユーザーが多くの情報をセッションに保存したり、ショッピングカートに大量のアイテムを追加したりすると、クッキーのサイズが大きくなります。この大きなクッキー情報がレスポンスヘッダーに含まれることで、上限を超えてしまうことがあります。</li>
<li data-sourcepos="16:3-16:100"><strong>多数のクッキーやカスタムヘッダー:</strong> アプリケーションが非常に多くのクッキーを設定している、または独自のカスタムヘッダーを大量に追加している場合も、ヘッダー全体のサイズが大きくなります。</li>
<li data-sourcepos="17:3-18:0"><strong>リダイレクトループ:</strong> 不適切なリダイレクト設定により、ブラウザが何度もリダイレクトを繰り返す際、そのたびに新しいクッキーやヘッダーが付与され、最終的にヘッダーが大きくなってしまうケースも考えられます。</li>
</ul>
<h2 id="rtoc-4"  data-sourcepos="19:1-19:21">解決策：Nginxの設定を調整する</h2>
<p data-sourcepos="21:1-21:48">最も手軽で直接的な解決策は、Nginxが受け入れられるヘッダーバッファのサイズを増やすことです。</p>
<p data-sourcepos="23:1-23:162">Nginxの設定ファイル（通常は <code>/etc/nginx/nginx.conf</code> か、サイト固有の <code>.conf</code> ファイル）を開き、<code>http</code> ブロック内、または対象の <code>server</code> ブロック内に以下のディレクティブを追加または調整します。<code>http</code> ブロックに記述すると、すべてのバーチャルホストに適用されます。</p>
<div _ngcontent-ng-c1213168243="" class="code-block ng-tns-c1213168243-479 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression;BardVeMetadataKey:[[&quot;r_7b21b8e99d7c089f&quot;,&quot;c_6fface8cac75276a&quot;,null,&quot;rc_b9532f80be7d9863&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c1213168243="" class="formatted-code-block-internal-container ng-tns-c1213168243-479">
<div _ngcontent-ng-c1213168243="" class="animated-opacity ng-tns-c1213168243-479">
<pre _ngcontent-ng-c1213168243="" class="ng-tns-c1213168243-479"><code _ngcontent-ng-c1213168243="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c1213168243-479" data-sourcepos="25:1-42:1"><span class="hljs-section">http</span> {
    <span class="hljs-comment"># ... 他の http ブロック内の設定 ...</span>

    <span class="hljs-comment"># upstream sent too big header エラー対策</span>
    <span class="hljs-comment"># PHP-FPM（FastCGI）からのレスポンスヘッダーに対応するため、fastcgi_ のディレクティブを設定します。</span>
    <span class="hljs-comment"># 必要に応じて proxy_ のディレクティブも設定しますが、PHP-FPMの場合はfastcgi_を優先します。</span>
    <span class="hljs-attribute">fastcgi_buffer_size</span> <span class="hljs-number">128k</span>;   <span class="hljs-comment"># FastCGIヘッダーバッファの初期サイズ。デフォルトは4k/8k。</span>
    <span class="hljs-attribute">fastcgi_buffers</span>     <span class="hljs-number">4</span> <span class="hljs-number">256k</span>; <span class="hljs-comment"># 256KBのバッファを4つ用意。合計1MB。</span>

    <span class="hljs-comment"># 必要に応じて、もしプロキシとして使用している場合も考慮するなら、こちらも設定</span>
    <span class="hljs-comment"># proxy_buffer_size   128k;</span>
    <span class="hljs-comment"># proxy_buffers       4 256k;</span>
    <span class="hljs-comment"># proxy_busy_buffers_size 256k;</span>

    <span class="hljs-comment"># ... 他の http ブロック内の設定 ...</span>
}
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<h3 id="rtoc-5"  data-sourcepos="44:1-44:15"><strong>ディレクティブの解説</strong></h3>
<ul data-sourcepos="46:3-48:0">
<li data-sourcepos="46:3-46:150"><code>fastcgi_buffer_size</code>: NginxがFastCGIからのレスポンスヘッダーを読み込むための最初のバッファサイズを指定します。デフォルト値（通常は <code>4k</code> または <code>8k</code>）よりも大きな値（例：<code>128k</code>）を設定することで、大きなヘッダーにも対応できるようになります。</li>
<li data-sourcepos="47:3-48:0"><code>fastcgi_buffers</code>: ヘッダーを含むレスポンス全体をバッファリングするためのバッファの数とサイズを設定します。上記の例では、<code>256KB</code>のバッファを<code>4</code>つ使用し、合計で<code>1MB</code>のバッファスペースを確保します。</li>
</ul>
<p data-sourcepos="49:1-50:107"><strong>注意点:</strong> これらの値を必要以上に大きくしすぎると、Nginxが使用するメモリ量が増加し、サーバーリソースを圧迫する可能性があります。まずはエラーが出なくなる最小限の増加から試し、サーバーの負荷状況を見ながら調整してください。</p>
<h3 id="rtoc-6"  data-sourcepos="52:1-52:12">設定変更後の手順</h3>
<ol data-sourcepos="54:1-65:0">
<li data-sourcepos="54:1-54:46"><strong>Nginx設定ファイルを保存:</strong> 変更したNginx設定ファイルを保存します。</li>
<li data-sourcepos="55:1-59:60"><strong>設定ファイルのテスト:</strong> <response-element class="" ng-version="0.0.0-PLACEHOLDER"><code-block _nghost-ng-c1213168243="" class="ng-tns-c1213168243-480 ng-star-inserted">
<div _ngcontent-ng-c1213168243="" class="code-block ng-tns-c1213168243-480 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression;BardVeMetadataKey:[[&quot;r_7b21b8e99d7c089f&quot;,&quot;c_6fface8cac75276a&quot;,null,&quot;rc_b9532f80be7d9863&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c1213168243="" class="code-block-decoration header-formatted gds-title-s ng-tns-c1213168243-480 ng-star-inserted"></div>
<div _ngcontent-ng-c1213168243="" class="formatted-code-block-internal-container ng-tns-c1213168243-480">
<div _ngcontent-ng-c1213168243="" class="animated-opacity ng-tns-c1213168243-480">
<pre _ngcontent-ng-c1213168243="" class="ng-tns-c1213168243-480"><code _ngcontent-ng-c1213168243="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c1213168243-480" data-sourcepos="56:5-58:17">sudo nginx -t</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<p></code-block></response-element>このコマンドで構文エラーがないかを確認します。<code>test is successful</code> と表示されればOKです。</li>
<li data-sourcepos="60:1-65:0"><strong>Nginxのリロード:</strong> <response-element class="" ng-version="0.0.0-PLACEHOLDER"><code-block _nghost-ng-c1213168243="" class="ng-tns-c1213168243-481 ng-star-inserted">
<div _ngcontent-ng-c1213168243="" class="code-block ng-tns-c1213168243-481 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" jslog="223238;track:impression;BardVeMetadataKey:[[&quot;r_7b21b8e99d7c089f&quot;,&quot;c_6fface8cac75276a&quot;,null,&quot;rc_b9532f80be7d9863&quot;,null,null,&quot;ja&quot;,null,1,null,null,1,0]]">
<div _ngcontent-ng-c1213168243="" class="formatted-code-block-internal-container ng-tns-c1213168243-481">
<div _ngcontent-ng-c1213168243="" class="animated-opacity ng-tns-c1213168243-481">
<pre _ngcontent-ng-c1213168243="" class="ng-tns-c1213168243-481"><code _ngcontent-ng-c1213168243="" role="text" data-test-id="code-content" class="code-container formatted ng-tns-c1213168243-481" data-sourcepos="61:5-63:31">sudo systemctl reload nginx
</code></pre>
</div>
</div>
</div>
<p>&nbsp;</p>
<p></code-block></response-element> Nginxサービスをリロードして、新しい設定を適用します。</li>
</ol>
<p data-sourcepos="66:1-66:87">これで、Nginxが大きなHTTPレスポンスヘッダーを適切に処理できるようになり、「<code>upstream sent too big header</code>」エラーは解消されるはずです。</p>
<h2 id="rtoc-7" >根本的な解決策：Laravelアプリケーション側の最適化</h2>
<p data-sourcepos="70:1-70:87">Nginxの設定でエラーを抑制できますが、<strong>Laravelアプリケーション側で不必要に大きなヘッダーが生成されている</strong>場合は、そちらの最適化も検討することをお勧めします。</p>
<ul data-sourcepos="72:3-78:0">
<li data-sourcepos="72:3-73:202"><strong>セッションデータの見直し:</strong>
<ul data-sourcepos="73:7-73:202">
<li data-sourcepos="73:7-73:202">クッキーに保存しているセッションデータが大きすぎる場合、データベースやRedisなどの<strong>サーバーサイドストレージにセッションを保存する</strong>ようにLaravelの設定を変更することを検討してください。これにより、クッキーにセッションIDのみが保存されるため、クッキーのサイズを大幅に削減できます。Laravelのセッション設定は <code>config/session.php</code> で変更可能です。</li>
</ul>
</li>
<li data-sourcepos="74:3-75:73"><strong>クッキーの管理:</strong>
<ul data-sourcepos="75:7-75:73">
<li data-sourcepos="75:7-75:73">アプリケーションが設定しているクッキーの数や、個々のクッキーのサイズを減らせないかレビューします。不要なクッキーは削除しましょう。</li>
</ul>
</li>
<li data-sourcepos="76:3-78:0"><strong>カスタムヘッダーの確認:</strong>
<ul data-sourcepos="77:7-78:0">
<li data-sourcepos="77:7-78:0">アプリケーションがレスポンスに含めているカスタムHTTPヘッダーが多すぎないか、または内容が大きすぎないかを確認します。</li>
</ul>
</li>
</ul>
<p data-sourcepos="79:1-79:84">Nginxの設定調整で当面のエラーは解消されますが、アプリケーションの効率性を高めるためにも、Laravel側のヘッダー生成ロジックを見直すことは良いプラクティスです。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://otonan-syusyoku.work/archives/2128/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
