<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Alb on Tarragon</title><link>https://tarrragon.github.io/blog/tags/alb/</link><description>Recent content in Alb on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/alb/index.xml" rel="self" type="application/rss+xml"/><item><title>入口上 IaC — ALB、TLS 與健康檢查</title><link>https://tarrragon.github.io/blog/infra/05-core-services/loadbalancer-alb/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/05-core-services/loadbalancer-alb/</guid><description>&lt;p>ALB（Application Load Balancer）描述流量進入系統的第一站。它在 IaC 裡的接線責任是把三個層次釘清楚：listener 決定監聽哪些 port 與協定、target group 決定流量導向哪些運算後端、health check 決定後端是否健康到可以接流量。ALB 本身是 stateless 的 — 重建不會遺失資料，但會換掉它的 DNS 名稱，所以對外服務通常在它前面再掛一層穩定的 DNS 記錄（Route 53 alias 或 CNAME），讓使用者看到的網域不隨 ALB 重建而改變。&lt;/p>
&lt;p>ALB 掛在 public subnet、引用專屬的 security group，security group 的入站通常只開 80 和 443 對 &lt;code>0.0.0.0/0&lt;/code>（這是少數合理出現全開的位置，因為 ALB 的工作本來就是接收公開流量）。後端運算節點住在 private subnet，它們的 security group 入站只允許來自 ALB security group 的流量 — 這個 group-to-group 引用讓規則跟著成員身分走，不跟著 IP 走（見&lt;a href="https://tarrragon.github.io/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三：網路地基&lt;/a>）。&lt;/p>
&lt;h2 id="alb-與-listener-設定">ALB 與 listener 設定&lt;/h2>
&lt;p>ALB 資源本身描述的是它掛在哪些 subnet、用哪個 security group、是對外（&lt;code>internal = false&lt;/code>）還是內部。Listener 則是掛在 ALB 上的監聽端點，每個 listener 綁定一個 port + protocol 的組合。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_lb&amp;#34; &amp;#34;api&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">&lt;span class="n"> name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;api-${var.env}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="n"> internal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="n"> load_balancer_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;application&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="n"> security_groups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="k">aws_security_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">alb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">id&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="n"> subnets&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="k">for&lt;/span> &lt;span class="k">s&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="k">aws_subnet&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">public&lt;/span> &lt;span class="err">:&lt;/span> &lt;span class="k">s&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">id&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="http-到-https-的強制跳轉">HTTP 到 HTTPS 的強制跳轉&lt;/h3>
&lt;p>正式服務通常同時建兩個 listener：port 443 接受 HTTPS 流量並轉發到後端，port 80 接收 HTTP 流量後直接回一個 301 redirect 到 HTTPS — 確保使用者即使用 &lt;code>http://&lt;/code> 開頭訪問也會被導到加密連線。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_lb_listener&amp;#34; &amp;#34;https&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="n"> load_balancer_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_lb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">arn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="n"> port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">443&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="n"> protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;HTTPS&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="n"> ssl_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;ELBSecurityPolicy-TLS13-1-2-2021-06&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="n"> certificate_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_acm_certificate&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">arn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl"> &lt;span class="k">default_action&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="n"> type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;forward&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="n"> target_group_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_lb_target_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">arn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_lb_listener&amp;#34; &amp;#34;http_redirect&amp;#34;&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">15&lt;/span>&lt;span class="cl">&lt;span class="n"> load_balancer_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_lb&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">arn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">16&lt;/span>&lt;span class="cl">&lt;span class="n"> port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">80&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">17&lt;/span>&lt;span class="cl">&lt;span class="n"> protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;HTTP&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">18&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">19&lt;/span>&lt;span class="cl"> &lt;span class="k">default_action&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">20&lt;/span>&lt;span class="cl">&lt;span class="n"> type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;redirect&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">21&lt;/span>&lt;span class="cl"> &lt;span class="k">redirect&lt;/span> {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">22&lt;/span>&lt;span class="cl">&lt;span class="n"> port&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;443&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">23&lt;/span>&lt;span class="cl">&lt;span class="n"> protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;HTTPS&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">24&lt;/span>&lt;span class="cl">&lt;span class="n"> status_code&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;HTTP_301&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">25&lt;/span>&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">26&lt;/span>&lt;span class="cl"> }
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">27&lt;/span>&lt;span class="cl">}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>ssl_policy&lt;/code> 決定 ALB 接受哪些 TLS 版本與密碼套件。選擇以安全與相容性為取捨 — &lt;code>ELBSecurityPolicy-TLS13-1-2-2021-06&lt;/code> 只接受 TLS 1.2 和 1.3，能阻擋過時協定的降級攻擊，但會拒絕仍在使用 TLS 1.0/1.1 的極舊用戶端。對面向公眾的 API 或網站，TLS 1.2 以上是合理的底線；如果有明確的舊用戶端需求（例如嵌入式設備），再往下調但要知道代價。&lt;/p></description><content:encoded><![CDATA[<p>ALB（Application Load Balancer）描述流量進入系統的第一站。它在 IaC 裡的接線責任是把三個層次釘清楚：listener 決定監聽哪些 port 與協定、target group 決定流量導向哪些運算後端、health check 決定後端是否健康到可以接流量。ALB 本身是 stateless 的 — 重建不會遺失資料，但會換掉它的 DNS 名稱，所以對外服務通常在它前面再掛一層穩定的 DNS 記錄（Route 53 alias 或 CNAME），讓使用者看到的網域不隨 ALB 重建而改變。</p>
<p>ALB 掛在 public subnet、引用專屬的 security group，security group 的入站通常只開 80 和 443 對 <code>0.0.0.0/0</code>（這是少數合理出現全開的位置，因為 ALB 的工作本來就是接收公開流量）。後端運算節點住在 private subnet，它們的 security group 入站只允許來自 ALB security group 的流量 — 這個 group-to-group 引用讓規則跟著成員身分走，不跟著 IP 走（見<a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三：網路地基</a>）。</p>
<h2 id="alb-與-listener-設定">ALB 與 listener 設定</h2>
<p>ALB 資源本身描述的是它掛在哪些 subnet、用哪個 security group、是對外（<code>internal = false</code>）還是內部。Listener 則是掛在 ALB 上的監聽端點，每個 listener 綁定一個 port + protocol 的組合。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_lb&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  name</span>               <span class="o">=</span> <span class="s2">&#34;api-${var.env}&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  internal</span>           <span class="o">=</span> <span class="kt">false</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">  load_balancer_type</span> <span class="o">=</span> <span class="s2">&#34;application&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="n">  security_groups</span>    <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">alb</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">  subnets</span>            <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="k">s</span> <span class="k">in</span> <span class="k">aws_subnet</span><span class="p">.</span><span class="k">public</span> <span class="err">:</span> <span class="k">s</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">}</span></span></code></pre></div><h3 id="http-到-https-的強制跳轉">HTTP 到 HTTPS 的強制跳轉</h3>
<p>正式服務通常同時建兩個 listener：port 443 接受 HTTPS 流量並轉發到後端，port 80 接收 HTTP 流量後直接回一個 301 redirect 到 HTTPS — 確保使用者即使用 <code>http://</code> 開頭訪問也會被導到加密連線。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_lb_listener&#34; &#34;https&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  load_balancer_arn</span> <span class="o">=</span> <span class="k">aws_lb</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  port</span>              <span class="o">=</span> <span class="m">443</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  protocol</span>          <span class="o">=</span> <span class="s2">&#34;HTTPS&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  ssl_policy</span>        <span class="o">=</span> <span class="s2">&#34;ELBSecurityPolicy-TLS13-1-2-2021-06&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  certificate_arn</span>   <span class="o">=</span> <span class="k">aws_acm_certificate</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">default_action</span> {
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">    type</span>             <span class="o">=</span> <span class="s2">&#34;forward&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">    target_group_arn</span> <span class="o">=</span> <span class="k">aws_lb_target_group</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  }
</span></span><span class="line"><span class="ln">12</span><span class="cl">}
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_lb_listener&#34; &#34;http_redirect&#34;</span> {
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">  load_balancer_arn</span> <span class="o">=</span> <span class="k">aws_lb</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">  port</span>              <span class="o">=</span> <span class="m">80</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="n">  protocol</span>          <span class="o">=</span> <span class="s2">&#34;HTTP&#34;</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="k">default_action</span> {
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">    type</span> <span class="o">=</span> <span class="s2">&#34;redirect&#34;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="k">redirect</span> {
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="n">      port</span>        <span class="o">=</span> <span class="s2">&#34;443&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">      protocol</span>    <span class="o">=</span> <span class="s2">&#34;HTTPS&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="n">      status_code</span> <span class="o">=</span> <span class="s2">&#34;HTTP_301&#34;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    }
</span></span><span class="line"><span class="ln">26</span><span class="cl">  }
</span></span><span class="line"><span class="ln">27</span><span class="cl">}</span></span></code></pre></div><p><code>ssl_policy</code> 決定 ALB 接受哪些 TLS 版本與密碼套件。選擇以安全與相容性為取捨 — <code>ELBSecurityPolicy-TLS13-1-2-2021-06</code> 只接受 TLS 1.2 和 1.3，能阻擋過時協定的降級攻擊，但會拒絕仍在使用 TLS 1.0/1.1 的極舊用戶端。對面向公眾的 API 或網站，TLS 1.2 以上是合理的底線；如果有明確的舊用戶端需求（例如嵌入式設備），再往下調但要知道代價。</p>
<h3 id="多服務共用-alb">多服務共用 ALB</h3>
<p>一個 ALB 可以掛多個 listener rule，用 host header 或 path 把流量分到不同的 target group。這讓多個微服務共用一個 ALB（省成本），而不需要每個服務各開一個：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_lb_listener_rule&#34; &#34;auth&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  listener_arn</span> <span class="o">=</span> <span class="k">aws_lb_listener</span><span class="p">.</span><span class="k">https</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  priority</span>     <span class="o">=</span> <span class="m">10</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">  <span class="k">condition</span> {
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">    path_pattern { values</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;/auth/*&#34;</span><span class="p">]</span> }
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">  }
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">  <span class="k">action</span> {
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">    type</span>             <span class="o">=</span> <span class="s2">&#34;forward&#34;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">    target_group_arn</span> <span class="o">=</span> <span class="k">aws_lb_target_group</span><span class="p">.</span><span class="k">auth</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  }
</span></span><span class="line"><span class="ln">13</span><span class="cl">}</span></span></code></pre></div><p>一個常見的收斂機會：如果每個服務都各自開了一個 ALB，但流量都從同一個入口進來、只是路徑不同，可以收斂成一個 ALB 加 listener rule。每個 ALB 有固定的小時費，少開幾個月費就少幾筆。反過來，當不同服務的安全等級或流量特性差異大到需要獨立的 security group 和 WAF 規則時，分開 ALB 才合理。</p>
<h2 id="target-group-與健康檢查">target group 與健康檢查</h2>
<p>Target group 定義一組接收流量的後端（ECS task、EC2 instance 或 IP），以及判斷這些後端是否健康的檢查邏輯。它是 ALB 和實際運算之間的橋樑。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_lb_target_group&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  name</span>        <span class="o">=</span> <span class="s2">&#34;api-${var.env}-tg&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  port</span>        <span class="o">=</span> <span class="m">8080</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  protocol</span>    <span class="o">=</span> <span class="s2">&#34;HTTP&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  vpc_id</span>      <span class="o">=</span> <span class="k">aws_vpc</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">id</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  target_type</span> <span class="o">=</span> <span class="s2">&#34;ip&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="k">health_check</span> {
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">    path</span>                <span class="o">=</span> <span class="s2">&#34;/healthz&#34;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">    interval</span>            <span class="o">=</span> <span class="m">15</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">    healthy_threshold</span>   <span class="o">=</span> <span class="m">2</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">    unhealthy_threshold</span> <span class="o">=</span> <span class="m">3</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">    timeout</span>             <span class="o">=</span> <span class="m">5</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">    matcher</span>             <span class="o">=</span> <span class="s2">&#34;200&#34;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">  }
</span></span><span class="line"><span class="ln">16</span><span class="cl">}</span></span></code></pre></div><h3 id="健康檢查的閾值設計">健康檢查的閾值設計</h3>
<p>健康檢查的路徑與閾值是最常被忽略的判讀點。各參數之間的交互作用決定了兩個時間窗口：新後端多久後開始接流量、壞後端多久後被移出。</p>
<p><code>healthy_threshold = 2</code> 配 <code>interval = 15</code> 代表一個新啟動的後端要等 30 秒（兩次通過）才開始接流量。<code>unhealthy_threshold = 3</code> 代表連續三次失敗（45 秒）才被移出。閾值太寬鬆會把壞掉的後端留在輪替裡，讓部分使用者持續收到錯誤；太嚴格會在部署瞬間 — 新容器啟動、應用還在初始化 — 就判定不健康，反覆移出移入，使用者看到間歇性失敗。</p>
<table>
  <thead>
      <tr>
          <th>參數</th>
          <th>過小的風險</th>
          <th>過大的風險</th>
          <th>起點建議</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>interval</code></td>
          <td>ALB 對後端造成額外負擔</td>
          <td>壞後端被偵測到的延遲增加</td>
          <td>15-30 秒</td>
      </tr>
      <tr>
          <td><code>healthy_threshold</code></td>
          <td>還沒完全就緒就接流量</td>
          <td>部署後等太久才開始分流</td>
          <td>2-3 次</td>
      </tr>
      <tr>
          <td><code>unhealthy_threshold</code></td>
          <td>暫時性波動導致健康的後端被移出</td>
          <td>壞後端繼續收流量太久</td>
          <td>2-3 次</td>
      </tr>
      <tr>
          <td><code>timeout</code></td>
          <td>正常但偏慢的回應被誤判為失敗</td>
          <td>確實掛了卻要等很久才確認</td>
          <td>5 秒</td>
      </tr>
  </tbody>
</table>
<h3 id="健康檢查路徑的選擇">健康檢查路徑的選擇</h3>
<p><code>path</code> 指向的端點應該能反映應用是否確實能服務請求，而不只是 process 還活著。一個只回 200 的空端點（所謂 liveness check）證明 HTTP server 在跑，但不代表它能連到資料庫、能讀到必要的 config。較合理的做法是讓 <code>/healthz</code> 至少檢查核心依賴的連線（例如 ping 一下 DB），失敗時回 503。代價是健康檢查會跟著核心依賴一起報不健康 — 如果 DB 暫時斷了，所有後端都會被判定不健康，ALB 會回 503 給使用者。這是正確的行為：如果應用確實無法服務請求，把它標成不健康比假裝健康好。</p>
<p>判讀方式：部署後觀察 target group 裡的 healthy / unhealthy 轉換次數。如果每次部署都看到新 target 在 healthy 與 unhealthy 之間跳動，代表初始等待不夠 — 應用的啟動時間超出 <code>healthy_threshold * interval</code>，考慮加大 <code>healthy_threshold</code> 或設定 ECS 的 <code>startPeriod</code>（啟動寬限期）讓健康檢查在應用初始化期間暫停。</p>
<h2 id="tls-憑證acm-簽發dns-驗證與自動續期">TLS 憑證：ACM 簽發、DNS 驗證與自動續期</h2>
<p>HTTPS listener 引用的 TLS 憑證也屬於 ALB 的接線。用 ACM（AWS Certificate Manager）簽發的憑證在 IaC 裡完整描述 — 涵蓋網域與 DNS 驗證方式 — 讓「憑證存在、驗證、掛載」整條鏈都進版本控制，而非在 Console 手動上傳一份會過期沒人盯的憑證。</p>
<p>ACM 簽發的憑證使用 DNS 驗證時，ACM 要求在指定的 DNS 記錄上放一段驗證值。Terraform 可以自動建立這段記錄並等待驗證通過：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_acm_certificate&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  domain_name</span>       <span class="o">=</span> <span class="s2">&#34;api.${var.domain}&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  validation_method</span> <span class="o">=</span> <span class="s2">&#34;DNS&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">  lifecycle { create_before_destroy</span> <span class="o">=</span> <span class="kt">true</span> }
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_route53_record&#34; &#34;cert_validation&#34;</span> {
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">  for_each</span> <span class="o">=</span> {
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">    for dvo in aws_acm_certificate.api.domain_validation_options : dvo.domain_name</span> <span class="o">=</span><span class="err">&gt;</span> <span class="k">dvo</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  }
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">  zone_id</span> <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="k">aws_route53_zone</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">zone_id</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">  name</span>    <span class="o">=</span> <span class="k">each</span><span class="p">.</span><span class="k">value</span><span class="p">.</span><span class="k">resource_record_name</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">  type</span>    <span class="o">=</span> <span class="k">each</span><span class="p">.</span><span class="k">value</span><span class="p">.</span><span class="k">resource_record_type</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">  records</span> <span class="o">=</span> <span class="p">[</span><span class="k">each</span><span class="p">.</span><span class="k">value</span><span class="p">.</span><span class="k">resource_record_value</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">  ttl</span>     <span class="o">=</span> <span class="m">60</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">}
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_acm_certificate_validation&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">  certificate_arn</span>         <span class="o">=</span> <span class="k">aws_acm_certificate</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="n">  validation_record_fqdns</span> <span class="o">=</span> <span class="p">[</span><span class="k">for</span> <span class="k">r</span> <span class="k">in</span> <span class="k">aws_route53_record</span><span class="p">.</span><span class="k">cert_validation</span> <span class="err">:</span> <span class="k">r</span><span class="p">.</span><span class="k">fqdn</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">}</span></span></code></pre></div><h3 id="create_before_destroy-的必要性">create_before_destroy 的必要性</h3>
<p><code>create_before_destroy = true</code> 確保憑證更新（例如加 SAN 或續期觸發重建）時先建新的再刪舊的，避免 listener 在交接期間沒有可用憑證。Terraform 預設行為是先刪後建，會造成一個短暫的 HTTPS 中斷窗口 — listener 找不到憑證、所有 HTTPS 連線失敗直到新憑證簽發並驗證完畢。</p>
<p>ACM 簽發的憑證自動續期：只要 DNS 驗證記錄還在（由 Terraform 管理，所以會一直在），ACM 在到期前 60 天自動續期。這是把憑證管理成本降到接近零的做法 — 不需要排程提醒、不需要手動下載上傳。判讀訊號：如果 CloudWatch 出現 <code>DaysToExpiry</code> 降到 30 以下的 alarm，代表自動續期失敗，通常是 DNS 驗證記錄被手動刪了或 Route 53 zone 變了。</p>
<h3 id="多網域憑證san">多網域憑證（SAN）</h3>
<p>一張 ACM 憑證可以涵蓋多個網域（Subject Alternative Names），例如 <code>api.example.com</code> 和 <code>admin.example.com</code> 共用一張。在 IaC 裡用 <code>subject_alternative_names</code> 列舉：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_acm_certificate&#34; &#34;multi&#34;</span> {
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">  domain_name</span>               <span class="o">=</span> <span class="s2">&#34;api.${var.domain}&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">  subject_alternative_names</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;admin.${var.domain}&#34;, &#34;*.internal.${var.domain}&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">  validation_method</span>         <span class="o">=</span> <span class="s2">&#34;DNS&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="n">  lifecycle { create_before_destroy</span> <span class="o">=</span> <span class="kt">true</span> }
</span></span><span class="line"><span class="ln">7</span><span class="cl">}</span></span></code></pre></div><p>共用一張還是分開簽取決於生命週期：如果這幾個網域總是一起上下線、一起變更，共用一張省維護；如果各自獨立演進，分開簽讓變更範圍更小。</p>
<h2 id="dns-zone-管理與-alb-的銜接">DNS zone 管理與 ALB 的銜接</h2>
<h3 id="hosted-zonedns-記錄的容器">Hosted zone：DNS 記錄的容器</h3>
<p>Route 53 的 hosted zone 是一個網域下所有 DNS 記錄的容器。public hosted zone 管理對外可見的網域（如 <code>example.com</code>），private hosted zone 管理只在 VPC 內可解析的內部網域（如 <code>internal.example.com</code>），讓服務之間用 DNS 名稱互連而不靠 IP。</p>
<p>多環境的 DNS 管理常用子網域 delegation：production 用 <code>example.com</code>（主 zone），dev 和 staging 各用 <code>dev.example.com</code> 和 <code>staging.example.com</code>（子 zone）。子 zone 可以放在不同帳號、由不同團隊管理，主 zone 只需要一組 NS 記錄指向子 zone。這讓環境之間的 DNS 邊界跟帳號邊界對齊。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_route53_zone&#34; &#34;main&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  name</span> <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">domain</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_route53_zone&#34; &#34;staging&#34;</span> {
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">  name</span> <span class="o">=</span> <span class="s2">&#34;staging.${var.domain}&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_route53_record&#34; &#34;staging_ns&#34;</span> {
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">  zone_id</span> <span class="o">=</span> <span class="k">aws_route53_zone</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">zone_id</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">  name</span>    <span class="o">=</span> <span class="s2">&#34;staging.${var.domain}&#34;</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">  type</span>    <span class="o">=</span> <span class="s2">&#34;NS&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">  ttl</span>     <span class="o">=</span> <span class="m">300</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">  records</span> <span class="o">=</span> <span class="k">aws_route53_zone</span><span class="p">.</span><span class="k">staging</span><span class="p">.</span><span class="k">name_servers</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">}</span></span></code></pre></div><p>hosted zone 也是 ACM 憑證 DNS 驗證的依賴 — ACM 簽發憑證時需要在對應的 zone 寫入一條驗證記錄，zone 不存在或不在同帳號就接不上。把 zone 的建立排在 ACM 之前，讓依賴圖自然正確。</p>
<h3 id="alb-的穩定-dns-記錄">ALB 的穩定 DNS 記錄</h3>
<p>ALB 重建後 DNS 名稱會改變。穩定對外的方式是在 Route 53 建一條 alias 記錄指向 ALB，使用者連的是 <code>api.example.com</code>，DNS 自動解析到 ALB 目前的位址：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_route53_record&#34; &#34;api&#34;</span> {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">  zone_id</span> <span class="o">=</span> <span class="k">data</span><span class="p">.</span><span class="k">aws_route53_zone</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">zone_id</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="n">  name</span>    <span class="o">=</span> <span class="s2">&#34;api.${var.domain}&#34;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">  type</span>    <span class="o">=</span> <span class="s2">&#34;A&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">  <span class="k">alias</span> {
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">    name</span>                   <span class="o">=</span> <span class="k">aws_lb</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">dns_name</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">    zone_id</span>                <span class="o">=</span> <span class="k">aws_lb</span><span class="p">.</span><span class="k">api</span><span class="p">.</span><span class="k">zone_id</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">    evaluate_target_health</span> <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">  }
</span></span><span class="line"><span class="ln">11</span><span class="cl">}</span></span></code></pre></div><p><code>evaluate_target_health = true</code> 讓 Route 53 在 ALB 所有 target 都不健康時把這條記錄標為不健康。如果有多個 region 的 ALB 做了 failover routing，這個設定能讓 DNS 層自動切換到健康的 region — 屬於跨區域容災的地基，在 devops 模組展開。</p>
<h2 id="waf-與下一步">WAF 與下一步</h2>
<p>ALB 支援掛載 AWS WAF（Web Application Firewall），在流量進到應用之前先過一層規則 — 擋已知惡意 IP、防 SQL injection / XSS 的常見模式、限制單一 IP 的請求速率。WAF 的規則也可以寫進 IaC，讓「哪些流量被擋」成為可審查的程式碼而非 Console 上的設定。WAF 的詳細設計屬於安全層的範圍（見 <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">backend 模組七：資安與資料保護</a>），這裡只確認它的掛載點是 ALB。</p>
<p>四類核心服務的 IaC 描述到此完成。下一步是讓這些服務可被觀測——log、metric、alarm 跟資源同生命週期建立，見<a href="/blog/infra/06-observability-logging/" data-link-title="模組六：可觀測性與 log 一併寫進 code" data-link-desc="log group、metric、alarm 跟基礎設施同生命週期管理，出事時追得到查得到">模組六：可觀測性與 log</a>。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/03-network-foundation/" data-link-title="模組三：網路地基 — VPC 與分層" data-link-desc="VPC、public / private subnet 切分、route table、NAT、security group 設計">模組三：網路地基</a>：ALB 的 security group 設計，group-to-group 引用</li>
<li>→ <a href="/blog/infra/05-core-services/stateful-protection-dependency/" data-link-title="Stateful 資源保護與跨服務依賴表達" data-link-desc="stateful 資源的保護策略（multi-AZ、備份、刪除保護）、stateful 與 stateless 的操作差異，以及用 output 與 data source 表達服務間依賴">模組五：stateful 資源的保護策略</a>：ALB 是 stateless，但它引用的 ACM 憑證和 DNS 記錄有自己的生命週期考量</li>
<li>→ <a href="/blog/devops/01-load-balancing/" data-link-title="模組一：負載平衡與反向代理" data-link-desc="流量進來怎麼分給多個服務實例 — nginx / HAProxy / DNS round-robin 的選型和健康檢查路由設計">devops 模組一：負載平衡</a>：ALB 的運行期調校 — 跨 AZ 流量分配、connection draining、sticky session</li>
<li>→ <a href="/blog/backend/07-security-data-protection/" data-link-title="模組七：資安與資料保護" data-link-desc="以問題驅動方式擴充資安知識網：先定義服務環節問題，再以案例作為觸發式參考">backend 模組七：資安與資料保護</a>：WAF 規則設計</li>
</ul>
]]></content:encoded></item><item><title>ALB</title><link>https://tarrragon.github.io/blog/infra/knowledge-cards/alb/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/knowledge-cards/alb/</guid><description>&lt;p>ALB（Application Load Balancer）的核心職責是接收外部流量、根據規則（path、host header）把請求路由到後端的 target group，並用健康檢查持續驗證後端是否能服務。它是系統對外的第一個接觸點，跑在 public subnet 裡。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>ALB 在核心服務層裡的角色是「入口設施」。它掛在 public subnet 的 security group 上（入站允許 80/443），把流量導向 private subnet 裡的 ECS task 或 EC2 instance。ALB 本身是 stateless 的 — 重建一個 ALB 不會遺失資料，但會換掉它的 DNS 名稱，所以對外服務通常在 ALB 前面掛一個穩定的 Route 53 alias record。&lt;/p>
&lt;p>TLS 終結是 ALB 的標準職責：HTTPS listener 引用 ACM（AWS Certificate Manager）簽發的憑證，ALB 處理加解密，後端收到的是 HTTP 明文。憑證由 ACM 自動續期，IaC 用 DNS 驗證方式描述憑證 — 讓「憑證存在、續期、掛載」整條鏈都進版本控制。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>以下狀況指向 ALB 相關問題：&lt;/p>
&lt;ul>
&lt;li>使用者看到 502 — ALB 轉發請求但後端回應異常（健康檢查可能通過但實際請求處理失敗），查 target group 的健康狀態和後端 log&lt;/li>
&lt;li>使用者看到 503 — target group 裡沒有健康的後端，通常是部署期間所有舊 task 停了但新 task 還沒通過健康檢查&lt;/li>
&lt;li>HTTPS 憑證過期警告 — 如果用 ACM 搭配 DNS 驗證，憑證自動續期；看到過期警告代表 DNS 驗證記錄被刪了或 ACM 服務異常&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>使用 ALB 時要決定：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>健康檢查參數&lt;/strong>：檢查路徑（用應用層的 health endpoint、不用根路徑）、間隔、閾值。閾值太寬鬆會把壞掉的後端留在輪替裡，太嚴格會在部署瞬間誤判&lt;/li>
&lt;li>&lt;strong>HTTP → HTTPS redirect&lt;/strong>：port 80 的 listener 設定固定回應 301 redirect 到 443，確保所有流量走加密&lt;/li>
&lt;li>&lt;strong>TLS 憑證&lt;/strong>：用 ACM 搭配 DNS 驗證，讓憑證的簽發和續期自動化&lt;/li>
&lt;li>&lt;strong>穩定 DNS&lt;/strong>：ALB 前面掛 Route 53 alias record，對外暴露的是自己的 domain name 而非 ALB 的隨機 hostname&lt;/li>
&lt;/ul>
&lt;h2 id="鄰卡">鄰卡&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet&lt;/a> — ALB 跑在 public subnet，後端跑在 private subnet&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group&lt;/a> — ALB 的 security group 是系統對外唯一合理開放 0.0.0.0/0 的位置（僅限 80/443）&lt;/li>
&lt;li>&lt;a href="https://tarrragon.github.io/blog/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS&lt;/a> — ALB 透過 target group 把流量導向 ECS task&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>ALB（Application Load Balancer）的核心職責是接收外部流量、根據規則（path、host header）把請求路由到後端的 target group，並用健康檢查持續驗證後端是否能服務。它是系統對外的第一個接觸點，跑在 public subnet 裡。</p>
<h2 id="概念位置">概念位置</h2>
<p>ALB 在核心服務層裡的角色是「入口設施」。它掛在 public subnet 的 security group 上（入站允許 80/443），把流量導向 private subnet 裡的 ECS task 或 EC2 instance。ALB 本身是 stateless 的 — 重建一個 ALB 不會遺失資料，但會換掉它的 DNS 名稱，所以對外服務通常在 ALB 前面掛一個穩定的 Route 53 alias record。</p>
<p>TLS 終結是 ALB 的標準職責：HTTPS listener 引用 ACM（AWS Certificate Manager）簽發的憑證，ALB 處理加解密，後端收到的是 HTTP 明文。憑證由 ACM 自動續期，IaC 用 DNS 驗證方式描述憑證 — 讓「憑證存在、續期、掛載」整條鏈都進版本控制。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>以下狀況指向 ALB 相關問題：</p>
<ul>
<li>使用者看到 502 — ALB 轉發請求但後端回應異常（健康檢查可能通過但實際請求處理失敗），查 target group 的健康狀態和後端 log</li>
<li>使用者看到 503 — target group 裡沒有健康的後端，通常是部署期間所有舊 task 停了但新 task 還沒通過健康檢查</li>
<li>HTTPS 憑證過期警告 — 如果用 ACM 搭配 DNS 驗證，憑證自動續期；看到過期警告代表 DNS 驗證記錄被刪了或 ACM 服務異常</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>使用 ALB 時要決定：</p>
<ul>
<li><strong>健康檢查參數</strong>：檢查路徑（用應用層的 health endpoint、不用根路徑）、間隔、閾值。閾值太寬鬆會把壞掉的後端留在輪替裡，太嚴格會在部署瞬間誤判</li>
<li><strong>HTTP → HTTPS redirect</strong>：port 80 的 listener 設定固定回應 301 redirect 到 443，確保所有流量走加密</li>
<li><strong>TLS 憑證</strong>：用 ACM 搭配 DNS 驗證，讓憑證的簽發和續期自動化</li>
<li><strong>穩定 DNS</strong>：ALB 前面掛 Route 53 alias record，對外暴露的是自己的 domain name 而非 ALB 的隨機 hostname</li>
</ul>
<h2 id="鄰卡">鄰卡</h2>
<ul>
<li><a href="/blog/infra/knowledge-cards/subnet/" data-link-title="Subnet（子網路）" data-link-desc="VPC 內按可用區與暴露程度切出的子網段，決定資源有沒有一條通往網際網路的路徑">Subnet</a> — ALB 跑在 public subnet，後端跑在 private subnet</li>
<li><a href="/blog/infra/knowledge-cards/security-group/" data-link-title="Security Group" data-link-desc="掛在資源網卡層級的有狀態防火牆，逐埠決定哪些來源能連進這個資源">Security Group</a> — ALB 的 security group 是系統對外唯一合理開放 0.0.0.0/0 的位置（僅限 80/443）</li>
<li><a href="/blog/infra/knowledge-cards/ecs/" data-link-title="ECS" data-link-desc="AWS Elastic Container Service — 受管的容器編排服務，用 task definition 描述容器配置、由平台負責排程與健康管理">ECS</a> — ALB 透過 target group 把流量導向 ECS task</li>
</ul>
]]></content:encoded></item></channel></rss>