<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://your-docusaurus-site.example.com/blog</id>
    <title>Qingshiyuu Blog</title>
    <updated>2026-05-13T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://your-docusaurus-site.example.com/blog"/>
    <subtitle>Qingshiyuu Blog</subtitle>
    <icon>https://your-docusaurus-site.example.com/img/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[Docker多階段構建]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn-2</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn-2"/>
        <updated>2026-05-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[從文章Docker鏡像分層與層共享中我們知道，Docker鏡像是由一層一層疊起來的，每一層對應Dockerfile中的一條指令。這些層都是唯讀的，構建完成後不可以修改。]]></summary>
        <content type="html"><![CDATA[<p>從文章Docker鏡像分層與層共享中我們知道，Docker鏡像是由一層一層疊起來的，每一層對應Dockerfile中的一條指令。這些層都是唯讀的，構建完成後不可以修改。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="問題">問題<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn-2#%E5%95%8F%E9%A1%8C" class="hash-link" aria-label="Direct link to 問題" title="Direct link to 問題" translate="no">​</a></h2>
<p>構建時候需要用到的東西，運行時候不需要，一個典型的python應用，構建需要</p>
<ul>
<li class="">gcc/build-essential</li>
<li class="">pip</li>
<li class="">.h文件</li>
<li class="">測試依賴</li>
</ul>
<p>但是運行的時候只需要</p>
<ul>
<li class="">python解釋器</li>
<li class="">裝好的包</li>
<li class="">你的代碼</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="先看沒有優化的鏡像有多大">先看沒有優化的鏡像有多大<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn-2#%E5%85%88%E7%9C%8B%E6%B2%92%E6%9C%89%E5%84%AA%E5%8C%96%E7%9A%84%E9%8F%A1%E5%83%8F%E6%9C%89%E5%A4%9A%E5%A4%A7" class="hash-link" aria-label="Direct link to 先看沒有優化的鏡像有多大" title="Direct link to 先看沒有優化的鏡像有多大" translate="no">​</a></h2>
<p>假設現在有一個FastAPI應用:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">myapp/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── main.py</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── requirements.txt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└── Dockerfile</span><br></span></code></pre></div></div>
<p>requirements</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">fastapi==0.110.0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">uvicorn==0.29.0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">pandas==2.2.0       #觸發了gcc的安裝</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">numpy==1.26.4</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">scikit-learn==1.4.0</span><br></span></code></pre></div></div>
<p>最常見的寫法，能跑就行</p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM python:3.11</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY requirements.txt .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN pip install -r requirements.txt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]</span><br></span></code></pre></div></div>
<p>這是本項目構建完後可能出現的大小</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$ docker build -t myapp:naive .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$ docker images myapp:naive</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">REPOSITORY   TAG     IMAGE ID       SIZE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">myapp        naive   a1b2c3d4e5f6   1.08GB</span><br></span></code></pre></div></div>
<p>1GB 多，原因：</p>
<p>python:3.11 基础镜像本身 900MB+
pip 安装时下载的 wheel 缓存没清
编译 numpy/pandas 留下的编译工具</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pip的緩存是什麼">pip的緩存是什麼<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn-2#pip%E7%9A%84%E7%B7%A9%E5%AD%98%E6%98%AF%E4%BB%80%E9%BA%BC" class="hash-link" aria-label="Direct link to pip的緩存是什麼" title="Direct link to pip的緩存是什麼" translate="no">​</a></h3>
<p>pip 安装包时，会先把 .whl 文件下载到本地缓存目录，装完之后缓存文件还留着，下次装同一个包可以不用重新下载。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">安裝 numpy 的過程：</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">pip 下載 numpy-1.26.4-cp311-linux_x86_64.whl</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">存一份到緩存目錄 ~/.cache/pip/      ← 這份沒用了，但還占著空間</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">解壓安裝到 site-packages/           ← 這才是真正要用的</span><br></span></code></pre></div></div>
<p>所以鏡像中存了兩份 numpy，一份是 pip 的緩存，一份是安裝好的包。</p>
<p>加入 <code>--no-cache-dir</code> 參數，pip 就不會把下載的包存到緩存目錄了：</p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">RUN pip install --no-cache-dir -r requirements.txt</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="apt安裝包">apt安裝包<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn-2#apt%E5%AE%89%E8%A3%9D%E5%8C%85" class="hash-link" aria-label="Direct link to apt安裝包" title="Direct link to apt安裝包" translate="no">​</a></h3>
<p>安裝系統工具時，apt也會留下緩存</p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">RUN apt-get update &amp;&amp; \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    apt-get install -y build-essential &amp;&amp; \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rm -rf /var/lib/apt/lists/*</span><br></span></code></pre></div></div>
<p>/var/lib/apt/lists/ 裡面存的是軟件包的索引列表，之後不會用到了。</p>
<p>為什麼分開RUN清理不掉，</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">RUN apt-get install -y gcc        # 第N層，安裝了gcc，留下了緩存</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN rm -rf /var/lib/apt/lists/*   # 第N+1層：只是標記刪除</span><br></span></code></pre></div></div>
<p>在同一層產生，在同一層清理，才會真的消失。</p>
<p>即使用了緩存之後，鏡像大小可能並沒有降低多少。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$ docker images myapp:no-cache</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">REPOSITORY   TAG        SIZE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">myapp        no-cache   870MB</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="多階段構建">多階段構建<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn-2#%E5%A4%9A%E9%9A%8E%E6%AE%B5%E6%A7%8B%E5%BB%BA" class="hash-link" aria-label="Direct link to 多階段構建" title="Direct link to 多階段構建" translate="no">​</a></h2>
<p>用一個階段來構建，安裝所有依賴，編譯好包，然後把需要的東西複製到另一個乾淨的階段，這樣就不會把構建過程中產生的垃圾帶到最終鏡像裡了。</p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM python:3.11 AS builder</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY requirements.txt .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN pip install --no-cache-dir \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    --prefix=/install \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    -r requirements.txt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FROM python:3.11-slim AS runtime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WORKDIR /app</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY --from=builder /install /usr/local</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN useradd -m appuser</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">USER appuser</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]</span><br></span></code></pre></div></div>
<p>如果現在構建：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$ docker build -t myapp:multistage .</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">$ docker images myapp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">REPOSITORY   TAG          SIZE</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">myapp        naive        1.08GB</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">myapp        no-cache     870MB</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">myapp        multistage   195MB   </span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn-2#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="Direct link to 總結" title="Direct link to 總結" translate="no">​</a></h2>
<p>多階段構建的本質是：</p>
<p>構建環境和運行環境分離。</p>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="Docker" term="Docker"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Docker鏡像分層與層共享]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn"/>
        <updated>2026-05-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[第一個問題]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一個問題">第一個問題<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn#%E7%AC%AC%E4%B8%80%E5%80%8B%E5%95%8F%E9%A1%8C" class="hash-link" aria-label="Direct link to 第一個問題" title="Direct link to 第一個問題" translate="no">​</a></h2>
<p>假設你的伺服器上跑了兩個容器:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">container1: Ubuntu(200MB) + Nginx(50MB) = 250MB</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">container2: Ubuntu(200MB) + MySQL(100MB) = 300MB</span><br></span></code></pre></div></div>
<p>按照常識來說，磁碟總共應該花了200+200+200+50+100=750MB，但是實際上只有200+50+100=350MB。這是為什麼呢？</p>
<p>因為Ubuntu那200MB的鏡像只存了一份，這就是Docker分層架構要做的事情。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="docker鏡像的本質一疊唯讀的層">Docker鏡像的本質：一疊唯讀的層<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn#docker%E9%8F%A1%E5%83%8F%E7%9A%84%E6%9C%AC%E8%B3%AA%E4%B8%80%E7%96%8A%E5%94%AF%E8%AE%80%E7%9A%84%E5%B1%A4" class="hash-link" aria-label="Direct link to Docker鏡像的本質：一疊唯讀的層" title="Direct link to Docker鏡像的本質：一疊唯讀的層" translate="no">​</a></h2>
<p>Docker鏡像不是一個整體的大檔案，而是一層一層疊起來的，每一層對應Dockerfile中的一條指令。</p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FROM ubuntu:22.04 # 這一層包含了ubuntu的檔案</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN apt-get install -y nginx # 這一層包含了安裝nginx的檔案</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN apt-get install -y mysql-server # 這一層包含了安裝mysql的檔案</span><br></span></code></pre></div></div>
<p>每一層只記錄相對上一層的改變</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">┌─────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  第3層: config檔案   │  1MB（只有這層的新內容）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─────────────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  第2層: Nginx       │  30MB</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─────────────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  第1層: Ubuntu      │  200MB</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────────────┘</span><br></span></code></pre></div></div>
<p>所有層都是唯讀的，構建完成後不可以修改。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三層共享">三：層共享<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn#%E4%B8%89%E5%B1%A4%E5%85%B1%E4%BA%AB" class="hash-link" aria-label="Direct link to 三：層共享" title="Direct link to 三：層共享" translate="no">​</a></h2>
<p>當你基於同一個 Ubuntu 構建兩個不同鏡像時：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">image1 (nginx):   [Ubuntu 200MB] → [Nginx 50MB]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">image2 (mysql):   [Ubuntu 200MB] → [MySQL 100MB]</span><br></span></code></pre></div></div>
<p>Docker 透過每層的 Content Hash（內容哈希）來識別層是否相同。Ubuntu 層的 hash 完全一致，所以磁碟上只存一份：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">磁碟實際儲存：</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">┌─────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  MySQL層            │  100MB  ← image2 獨有</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─────────────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  Nginx層            │  50MB   ← image1 獨有</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─────────────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  Ubuntu層           │  200MB  ← 兩個鏡像共享</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">總計：350MB，而不是 750MB</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四容器執行時">四：容器執行時<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn#%E5%9B%9B%E5%AE%B9%E5%99%A8%E5%9F%B7%E8%A1%8C%E6%99%82" class="hash-link" aria-label="Direct link to 四：容器執行時" title="Direct link to 四：容器執行時" translate="no">​</a></h2>
<p>鏡像層是唯讀的，那容器如果在執行的時候寫檔案怎麼辦？</p>
<p>Docker在唯讀鏡像層上面再加了一層可寫的容器層</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">┌─────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  容器層（可寫）      │  ← 容器執行時產生的檔案寫在這裡</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─────────────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  Nginx層（唯讀）     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─────────────────────┤</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  Ubuntu層（唯讀）    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────────────┘</span><br></span></code></pre></div></div>
<p>這就是 Copy-on-Write（寫時複製）機制：</p>
<p>讀檔案：從上往下找，找到就返回<br>
<!-- -->寫檔案：先把檔案從唯讀層複製到容器層，再修改</p>
<p>所以即使 100 個容器基於同一個鏡像，鏡像層依然只有一份，每個容器只多出自己那個很薄的容器層。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">container1  container2  container3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  [寫層1]     [寫層2]     [寫層3]   ← 各自獨立，很小</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ↘          ↓         ↙</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       [共享的唯讀鏡像層]            ← 只有一份</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五這對dockerfile寫法的啟示">五：這對Dockerfile寫法的啟示<a href="https://your-docusaurus-site.example.com/blog/2026/05/13/docker-learn#%E4%BA%94%E9%80%99%E5%B0%8Ddockerfile%E5%AF%AB%E6%B3%95%E7%9A%84%E5%95%9F%E7%A4%BA" class="hash-link" aria-label="Direct link to 五：這對Dockerfile寫法的啟示" title="Direct link to 五：這對Dockerfile寫法的啟示" translate="no">​</a></h2>
<p>利用層快取：把不常變的指令放前面，常變的指令放後面，這樣修改後只需要重建後面的層，前面的層可以複用。</p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># ✅ 好的寫法</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY requirements.txt .       # 依賴檔案變化少 → 快取穩定</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN pip install -r requirements.txt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .                      # 程式碼經常變 → 放最後</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ❌ 差的寫法</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">COPY . .                      # 程式碼一變，下面的依賴安裝就要重跑</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN pip install -r requirements.txt</span><br></span></code></pre></div></div>
<p>合併RUN減少層數</p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># ❌ 產生3層，中間層的快取檔案仍佔空間</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN apt-get update</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN apt-get install -y curl</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN rm -rf /var/lib/apt/lists/*</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># ✅ 一層搞定，最終只保留有用的檔案</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RUN apt-get update &amp;&amp; \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    apt-get install -y curl &amp;&amp; \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rm -rf /var/lib/apt/lists/*</span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="Docker" term="Docker"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[結束了碩士生涯]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/05/11/cityu</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/05/11/cityu"/>
        <updated>2026-05-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[碩士生涯結束了]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="碩士生涯結束了">碩士生涯結束了<a href="https://your-docusaurus-site.example.com/blog/2026/05/11/cityu#%E7%A2%A9%E5%A3%AB%E7%94%9F%E6%B6%AF%E7%B5%90%E6%9D%9F%E4%BA%86" class="hash-link" aria-label="Direct link to 碩士生涯結束了" title="Direct link to 碩士生涯結束了" translate="no">​</a></h2>
<p>隨著最後一門考試結束，我長達17年的學生生涯正式畫上句號。從小學到大學，再到碩士，每一個階段都充滿了挑戰和成長。回首過去，感謝一路上支持我的家人、朋友和老師們，是你們的鼓勵讓我堅持走到今天。</p>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="CityU" term="CityU"/>
        <category label="碩士" term="碩士"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[設計Redis鎖的思路]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn"/>
        <updated>2026-04-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Redis鎖]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis鎖">Redis鎖<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#redis%E9%8E%96" class="hash-link" aria-label="Direct link to Redis鎖" title="Direct link to Redis鎖" translate="no">​</a></h2>
<p>當Redis被用來做分布式鎖、限流、去重的時候，key應該如何設計？</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="建立模型">建立模型<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E5%BB%BA%E7%AB%8B%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="Direct link to 建立模型" title="Direct link to 建立模型" translate="no">​</a></h2>
<p>在建立鎖之前，先根據我們要解決的問題想一下以下幾個點：</p>
<ul>
<li class="">什麼東西會被加鎖？</li>
<li class="">在做什麼的時候需要加鎖？</li>
<li class="">在什麼時間窗口內需要加鎖？</li>
<li class="">在什麼環境下鎖生效？</li>
</ul>
<p>可以把key想像為一個業務範圍的名字。</p>
<p>例如: rate_limit:prod:payment:user:12345:create_order:2026-04-13T12:00</p>
<p>這串key的意思是在 prod 環境，payment 業務裡，user 123 在 2026-04-13 12:00 這一分鐘內 create_order 的次數。</p>
<p>在比如說: lock:prod:order_id:0519<!-- -->:pay</p>
<p>表示在prod環境裡，對 order_id 0519 的 pay 操作加鎖。</p>
<p>所以 key 設計的本質是：把抽象的控制範圍變成一個穩定、唯一、可過期、可觀察的名字。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一個好的key">一個好的key<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E4%B8%80%E5%80%8B%E5%A5%BD%E7%9A%84key" class="hash-link" aria-label="Direct link to 一個好的key" title="Direct link to 一個好的key" translate="no">​</a></h2>
<p>應該設計有如下結構的鎖：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">{purpose}:{env}:{domain}:{scope_type}:{scope_id}:{action}:{window / version}</span><br></span></code></pre></div></div>
<p><code>purpose:</code> lock / rate_limit / deduplication 表示key做什麼？</p>
<p><code>env</code>: prod / staging / dev 表示在哪個環境？</p>
<p><code>domain</code>: payment / user / inventory 表示業務範圍？</p>
<p><code>scope_type</code>: user_id / order_id / ip 表示鎖的範圍類型？</p>
<p><code>scope_id</code>: 12345 / 0519</p>
<p><code>action</code>: create_order / pay 表示鎖的具體行為？</p>
<p><code>window / version</code>: 2026-04-13T12:00 / v1 表示鎖的時間窗口或者版本？</p>
<div class="language-eg codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-eg codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">rate_limit:prod:auth:user:123:login:202604211035</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">rate_limit:prod:auth:ip:1.2.3.4:login:202604211035</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">lock:prod:payment:order:98765:capture</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">dedupe:prod:notification:user:123:send_email:template_abc</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一些設計">一些設計<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E4%B8%80%E4%BA%9B%E8%A8%AD%E8%A8%88" class="hash-link" aria-label="Direct link to 一些設計" title="Direct link to 一些設計" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="鎖的粒度">鎖的粒度<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E9%8E%96%E7%9A%84%E7%B2%92%E5%BA%A6" class="hash-link" aria-label="Direct link to 鎖的粒度" title="Direct link to 鎖的粒度" translate="no">​</a></h3>
<p>lock:prod:order<!-- -->:pay</p>
<p>表示所有訂單使用一把鎖，會導致大量的業務邏輯阻塞。</p>
<p>lock:prod:order:0519:pay:request_id<!-- -->:abc</p>
<p>每個請求都有不同的<code>request_id</code>，那就失去了鎖的意義，因為每次都會拿到不同的鎖。</p>
<p>鎖的粒度應該有如下的考慮：</p>
<p>哪些請求同時執行，會互相破環狀態？</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis鎖-1">Redis鎖<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#redis%E9%8E%96-1" class="hash-link" aria-label="Direct link to Redis鎖" title="Direct link to Redis鎖" translate="no">​</a></h3>
<p>Redis做鎖的時候，常見的命令是</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">SET key value NX PX</span><br></span></code></pre></div></div>
<p>其中：</p>
<ul>
<li class=""><code>key</code> 是鎖的名字，根據上面的設計原則來定義。</li>
<li class=""><code>value</code> 是鎖的持有者，可以是請求ID、主機ID等唯一標識，方便後續的解鎖和觀察。</li>
<li class=""><code>NX</code> 表示只有當鍵不存在時才會設置成功，適合用於鎖的場景。</li>
<li class=""><code>PX</code> 表示設置鍵的過期時間，適合用於自動解鎖。</li>
</ul>
<p>刪除的時候</p>
<div class="language-del codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-del codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEL key</span><br></span></code></pre></div></div>
<p>這樣會出現問題，因為會發生：</p>
<ul>
<li class="">A請求獲取鎖成功，設置過期時間為10秒。</li>
<li class="">A請求處理業務邏輯，可能需要5秒。</li>
<li class="">A請求完成業務邏輯，準備釋放鎖，</li>
<li class="">此時鎖已經過期了，A請求刪除的其實是別人的鎖，導致其他請求的鎖被誤刪。</li>
</ul>
<p>正確的做法應該是<code>compare token and delete</code>，確保只有鎖的持有者才能釋放鎖。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dynamodb鎖">DynamoDB鎖<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#dynamodb%E9%8E%96" class="hash-link" aria-label="Direct link to DynamoDB鎖" title="Direct link to DynamoDB鎖" translate="no">​</a></h3>
<p>DynamoDB做鎖的時候，通常一個lock key就是一條item。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">PK = LOCK#prod#order#order_id#123#pay</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">{</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "PK": "LOCK#prod#order#order#98765#pay",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "owner_token": "uuid",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "lease_until": 1776748500,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "created_at": 1776748470,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  "fencing_token": 1024</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>獲取鎖的時候用：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">attribute_not_exists(PK) OR lease_until &lt; :now</span><br></span></code></pre></div></div>
<p>如果item不存在，可以拿鎖。</p>
<p>如果item存在但是過期了，也可以拿鎖。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="比較">比較<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E6%AF%94%E8%BC%83" class="hash-link" aria-label="Direct link to 比較" title="Direct link to 比較" translate="no">​</a></h3>
<p>Redis 很適合：</p>
<ul>
<li class="">秒級 / 分鐘級限流。</li>
<li class="">短租約分布式鎖。</li>
<li class="">高頻快速計數。</li>
<li class="">需要低延遲的控制面。</li>
</ul>
<p>Redis key 設計時特別注意：</p>
<ul>
<li class="">key 數量會不會爆炸。</li>
<li class="">每個 key 是否都有合理 TTL。</li>
<li class="">熱 key 是否會集中在少數幾個 key 上。</li>
<li class="">Redis Cluster 中多 key Lua 操作是否落在同一 hash slot。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis實戰">Redis實戰<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#redis%E5%AF%A6%E6%88%B0" class="hash-link" aria-label="Direct link to Redis實戰" title="Direct link to Redis實戰" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="第一步">第一步<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E7%AC%AC%E4%B8%80%E6%AD%A5" class="hash-link" aria-label="Direct link to 第一步" title="Direct link to 第一步" translate="no">​</a></h3>
<p>在本地docker中開啟一個Redis容器</p>
<p><img decoding="async" loading="lazy" alt="image.png" src="https://your-docusaurus-site.example.com/assets/images/redis-docker-b6e7882a961f6262a84724c6946c1565.png" width="1922" height="106" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="第二步">第二步<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E7%AC%AC%E4%BA%8C%E6%AD%A5" class="hash-link" aria-label="Direct link to 第二步" title="Direct link to 第二步" translate="no">​</a></h3>
<p>開啟兩個終端，在終端中分別輸入</p>
<div class="language-docker codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-docker codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">docker exec -it redis-1 redis-cli</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="第三步">第三步<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E7%AC%AC%E4%B8%89%E6%AD%A5" class="hash-link" aria-label="Direct link to 第三步" title="Direct link to 第三步" translate="no">​</a></h3>
<p>在第一個終端中輸入</p>
<div class="language-redis codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-redis codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">SET lock:prod:order:order_id:0001:pay owner-A NX PX 10000</span><br></span></code></pre></div></div>
<p>返回OK，表示鎖獲取成功。</p>
<p>同時在第二個終端中輸入</p>
<div class="language-redis codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-redis codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">SET lock:prod:order:order_id:0001:pay owner-B NX PX 10000</span><br></span></code></pre></div></div>
<p>返回(nil)，表示鎖已經被A持有，B獲取鎖失敗。</p>
<p><img decoding="async" loading="lazy" alt="redis" src="https://your-docusaurus-site.example.com/assets/images/redis-set-lock-bbb6f7f1e44caf8f6a891571a651da51.png" width="1924" height="160" class="img_ev3q"></p>
<p>十秒後，鎖過期了，B再次嘗試獲取鎖：</p>
<div class="language-redis codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-redis codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">SET lock:prod:order:order_id:0001:pay owner-B NX PX 10000</span><br></span></code></pre></div></div>
<p>返回OK，表示B獲取鎖成功。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="錯誤的刪除鎖的方式">錯誤的刪除鎖的方式<a href="https://your-docusaurus-site.example.com/blog/2026/04/13/redis-learn#%E9%8C%AF%E8%AA%A4%E7%9A%84%E5%88%AA%E9%99%A4%E9%8E%96%E7%9A%84%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="Direct link to 錯誤的刪除鎖的方式" title="Direct link to 錯誤的刪除鎖的方式" translate="no">​</a></h3>
<p>如果B現在持有鎖，A嘗試刪除鎖：</p>
<div class="language-redis codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-redis codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEL lock:prod:order:order_id:0001:pay</span><br></span></code></pre></div></div>
<p>這樣會導致B的鎖被誤刪，A應該先檢查鎖的持有者是否是自己：</p>
<div class="language-redis codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-redis codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 lock:demo:payment:order:1001:pay owner-A</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="Redis" term="Redis"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[什么是BGP]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn"/>
        <updated>2026-04-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[什么是BGP？]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="什么是bgp">什么是BGP？<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#%E4%BB%80%E4%B9%88%E6%98%AFbgp" class="hash-link" aria-label="Direct link to 什么是BGP？" title="Direct link to 什么是BGP？" translate="no">​</a></h2>
<p>BGP(Border Gateway Protocol)是互聯網的路由大腦，負責告訴全球所有的路由器某個IP地址在哪裡，怎麼去。</p>
<p>當你在瀏覽器輸入google.com，你的ISP路由器查詢BGP路由表，找到去往google.com的最佳路徑，封包才能跨越數十甚至數百個自治系統(AS)到達Google的服務器。</p>
<p><strong>BGP是唯一在互聯網規模下工作的協議</strong>，管理全球九十萬以上的路由前綴</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="as自治系統">AS自治系統<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#as%E8%87%AA%E6%B2%BB%E7%B3%BB%E7%B5%B1" class="hash-link" aria-label="Direct link to AS自治系統" title="Direct link to AS自治系統" translate="no">​</a></h2>
<p>AS(Autonomous System)是BGP的基本單位，代表一個由單一機構管理的網路。</p>
<p>每個AS都有一個唯一的編號ASN，例如Google是ASN15196，AWS是ASN16509。 AS之間通過eBGP建立Peering，互相交換我能連結到哪些IP地址的路由資訊。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bgp路由機制">BGP路由機制<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#bgp%E8%B7%AF%E7%94%B1%E6%A9%9F%E5%88%B6" class="hash-link" aria-label="Direct link to BGP路由機制" title="Direct link to BGP路由機制" translate="no">​</a></h2>
<p>BGP的路由通知被叫做Update消息，每當有一個AS有新路由或者路由失效，就發送Update給所有的BGP鄰居。</p>
<p><strong>關鍵機制</strong>：AS_PATH，每當一個AS宣告一個路由，它會把自己的ASN添加到AS_PATH中。這樣其他AS就知道這條路由經過了哪些AS，避免路由循環。</p>
<p>AS_PATH Prepend 就是利用這個機制：把自己的 ASN 重複追加幾次，讓路徑看起來更長，其他 AS 就會優先選更短的路徑（即 DX）</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bgp路徑屬性">BGP路徑屬性<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#bgp%E8%B7%AF%E5%BE%91%E5%B1%AC%E6%80%A7" class="hash-link" aria-label="Direct link to BGP路徑屬性" title="Direct link to BGP路徑屬性" translate="no">​</a></h2>
<p>BGP 路徑屬性決定了當有多條路徑到達同一目的地時，路由器選哪條。理解這四個屬性是掌握混合雲路由控制的關鍵：</p>
<ul>
<li class="">LOCAL_PREF：只在同一 AS 內有效。值越高，優先級越高。用來控制「本 AS 的流量從哪個出口出去」。</li>
<li class="">AS_PATH：路徑所經過的 AS 序列。越短越優先。跨 AS 可見，是影響對端選路的主要手段。</li>
<li class="">Community：可自定義的標籤。AWS 預定義了一些 Community 值，可以讓你告訴 AWS「這條路由請低優先/不要傳播」。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="決策過程">決策過程<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#%E6%B1%BA%E7%AD%96%E9%81%8E%E7%A8%8B" class="hash-link" aria-label="Direct link to 決策過程" title="Direct link to 決策過程" translate="no">​</a></h2>
<p>當路由器收到多條去往同一目的地的路由時，按照嚴格的優先級順序逐條比較，找到第一個能分出勝負的屬性就停止。</p>
<p>生產中最常用的兩個手段：LOCAL_PREF 控制本端選路（出方向），AS_PATH Prepend 影響對端選路（入方向）。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ibgp和ebgp">iBGP和eBGP<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#ibgp%E5%92%8Cebgp" class="hash-link" aria-label="Direct link to iBGP和eBGP" title="Direct link to iBGP和eBGP" translate="no">​</a></h2>
<p>eBGP（external BGP）：不同 AS 之間的 BGP 會話。會自動把自己的 ASN 追加到 AS_PATH，修改 NEXT_HOP 為自己的地址，LOCAL_PREF 不會傳遞給對方。</p>
<p>iBGP（internal BGP）：同一 AS 內部的 BGP 會話。不修改 AS_PATH，LOCAL_PREF 可以傳遞。但有個重要限制：iBGP 收到的路由不能再傳給其他 iBGP 鄰居（防環路），所以 AS 內部要麼全互聯，要麼用 Route Reflector 解決。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bgp在aws中的應用">BGP在AWS中的應用<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#bgp%E5%9C%A8aws%E4%B8%AD%E7%9A%84%E6%87%89%E7%94%A8" class="hash-link" aria-label="Direct link to BGP在AWS中的應用" title="Direct link to BGP在AWS中的應用" translate="no">​</a></h2>
<p>在 AWS 混合雲場景中，BGP 扮演三個角色：</p>
<ul>
<li class="">路由自動發現：機房路由器把機房網段（如 192.168.0.0/16）通過 BGP 告訴 AWS VGW，VGW 自動把這條路由注入 VPC Route Table，無需手動配置靜態路由。</li>
<li class="">故障自動切換：DX 斷開時，BGP 會話超時，VGW 刪除從 DX 學到的路由，自動轉為使用從 VPN 學到的備份路由。</li>
<li class="">路由策略控制：通過 LOCAL_PREF 和 AS_PATH Prepend 控制哪條路徑優先，實現精細的流量工程。</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bgp為什麼是tcp連線">BGP為什麼是TCP連線<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#bgp%E7%82%BA%E4%BB%80%E9%BA%BC%E6%98%AFtcp%E9%80%A3%E7%B7%9A" class="hash-link" aria-label="Direct link to BGP為什麼是TCP連線" title="Direct link to BGP為什麼是TCP連線" translate="no">​</a></h2>
<p>BGP工作在TCP 179端口，選擇TCP是因為BGP要保證路由更新消息一條不漏地傳遞，丟包就意味著路由表不一致，後果很嚴重。TCP 的可靠傳輸幫 BGP 省去了自己實現重傳的麻煩。這也意味著 BGP Peering 建立需要 TCP 三次握手，所以 Security Group 要放行雙向的 TCP 179。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="aws-as-7224">AWS AS 7224<a href="https://your-docusaurus-site.example.com/blog/2026/04/11/aws-daily-learn#aws-as-7224" class="hash-link" aria-label="Direct link to AWS AS 7224" title="Direct link to AWS AS 7224" translate="no">​</a></h2>
<p>這是AWS為Direct Connect和VPN分配的公有ASN，代表了AWS這一側的網路身分。當你的機房路由器和 AWS VGW 建立 BGP 會話，對端 ASN 就填 7224。有趣的是，AWS 在不同服務裡有不同的 ASN：DX 和 VPN 用 7224，AWS Transit Gateway 可以自定義 ASN（預設 64512），而面向公網的 AWS IP 地址段則用 AS16509。</p>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="AWS" term="AWS"/>
        <category label="BGP" term="BGP"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[一條奇怪的CNAME記錄]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn"/>
        <updated>2026-04-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[一條奇怪的CNAME記錄]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一條奇怪的cname記錄">一條奇怪的CNAME記錄<a href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn#%E4%B8%80%E6%A2%9D%E5%A5%87%E6%80%AA%E7%9A%84cname%E8%A8%98%E9%8C%84" class="hash-link" aria-label="Direct link to 一條奇怪的CNAME記錄" title="Direct link to 一條奇怪的CNAME記錄" translate="no">​</a></h2>
<p>在CloudFlare的DNS管理界面上，有一條奇怪的CNAME記錄：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Type: CNAME</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Name:_222ec04618be45...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Content: _b8c270d13e0adf5a673...</span><br></span></code></pre></div></div>
<p>另外一條則是由qingshiyuu.com指向cloudfront.net的CNAME記錄。</p>
<p>這條CNAME有什麼用呢？</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ca與ssl證書">CA與SSL證書<a href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn#ca%E8%88%87ssl%E8%AD%89%E6%9B%B8" class="hash-link" aria-label="Direct link to CA與SSL證書" title="Direct link to CA與SSL證書" translate="no">​</a></h2>
<p>當你在瀏覽器地址欄看到qingshiyuu.com那把綠色鎖頭，你其實在信任一個承諾。</p>
<p>這個承諾是：有一個受信任的第三方替這個網站擔保，確認你現在連接的 qingshiyuu.com，確實是 qingshiyuu.com 的主人運營的，不是別人偽裝的。</p>
<p>這個第三方就是CA（Certificate Authority），AWS的ACM（AWS Certificate Manager）就是一個CA。</p>
<p>此時的問題是如果任何人都有資格申請任何域名的證書，我就能申請一張<code>google.com</code>的證書，然後冒充google.com來騙取用戶的信任。</p>
<p>所以，在CA頒布證書值錢，必須先問一個問題，你能證明你是這個域名的主人嗎？</p>
<p>驗證的邏輯非常簡單，只有能修改DNS記錄的人才能證明他是域名的主人。</p>
<p>道理很簡單——DNS 記錄只有登錄域名管理後台才能修改，這個後台只有域名的購買者才有訪問權限。所以如果你能在 DNS 裡加一條特定的記錄，就等於你在向全世界說：我能控制這個域名，這個後台在我手上。</p>
<p>ACM 用的就是這個邏輯：它給你一個只有它自己知道的隨機字符串，讓你把它加進 DNS。它去查，查到了，就知道你是域名的主人，然後才頒發證書。</p>
<p>這條記錄，就是在 Cloudflare 後台看到的那條奇怪的 CNAME。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="申請的四個階段">申請的四個階段<a href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn#%E7%94%B3%E8%AB%8B%E7%9A%84%E5%9B%9B%E5%80%8B%E9%9A%8E%E6%AE%B5" class="hash-link" aria-label="Direct link to 申請的四個階段" title="Direct link to 申請的四個階段" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="階段一申請證書">階段一：申請證書<a href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn#%E9%9A%8E%E6%AE%B5%E4%B8%80%E7%94%B3%E8%AB%8B%E8%AD%89%E6%9B%B8" class="hash-link" aria-label="Direct link to 階段一：申請證書" title="Direct link to 階段一：申請證書" translate="no">​</a></h3>
<p>ACM生成一對全局唯一的隨機字符串，告訴我把其中一個字符串（_222ec04618be45...）加到DNS裡，然後它會去查這個記錄的值。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="階段二dns驗證">階段二：DNS驗證<a href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn#%E9%9A%8E%E6%AE%B5%E4%BA%8Cdns%E9%A9%97%E8%AD%89" class="hash-link" aria-label="Direct link to 階段二：DNS驗證" title="Direct link to 階段二：DNS驗證" translate="no">​</a></h3>
<p>我把這個字符串加到DNS裡，ACM去查這個記錄的值，查到了，就知道我能控制這個域名，然後才頒發證書。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="階段三acm掃描">階段三：ACM掃描<a href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn#%E9%9A%8E%E6%AE%B5%E4%B8%89acm%E6%8E%83%E6%8F%8F" class="hash-link" aria-label="Direct link to 階段三：ACM掃描" title="Direct link to 階段三：ACM掃描" translate="no">​</a></h3>
<p>DNS查詢那條字符串。</p>
<p>返回的值和ACM生成的值一樣。</p>
<p>驗證通過，證書狀態從Pending變為Issued。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="階段四證書頒發">階段四：證書頒發。<a href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn#%E9%9A%8E%E6%AE%B5%E5%9B%9B%E8%AD%89%E6%9B%B8%E9%A0%92%E7%99%BC" class="hash-link" aria-label="Direct link to 階段四：證書頒發。" title="Direct link to 階段四：證書頒發。" translate="no">​</a></h3>
<p>可以在cloudfront中使用證書了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://your-docusaurus-site.example.com/blog/2026/04/20/aws-daily-learn#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="Direct link to 總結" title="Direct link to 總結" translate="no">​</a></h2>
<p>這整個過程展示了如何通過DNS記錄來驗證域名所有權，並最終獲得SSL證書。這不僅保護了用戶的數據安全，也增強了網站的可信度。</p>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="AWS" term="AWS"/>
        <category label="DNS" term="DNS"/>
        <category label="CloudFlare" term="CloudFlare"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Dockerfile怎麽寫？]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/04/23/daily-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/04/23/daily-learn"/>
        <updated>2026-04-10T00:00:00.000Z</updated>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="Docker" term="Docker"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[為什麼我的網站qingyuu刷新後後會出現403錯誤？]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/04/07/aws-daily-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/04/07/aws-daily-learn"/>
        <updated>2026-04-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[起因]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="起因">起因<a href="https://your-docusaurus-site.example.com/blog/2026/04/07/aws-daily-learn#%E8%B5%B7%E5%9B%A0" class="hash-link" aria-label="Direct link to 起因" title="Direct link to 起因" translate="no">​</a></h2>
<p>近日我在瀏覽我的個人網站qingshiyuu.com時候，發現每次刷新頁面都會出現403 Access Denied錯誤，在搜索了相關資料後，發現這是因為CloudFront的配置問題。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="原因">原因<a href="https://your-docusaurus-site.example.com/blog/2026/04/07/aws-daily-learn#%E5%8E%9F%E5%9B%A0" class="hash-link" aria-label="Direct link to 原因" title="Direct link to 原因" translate="no">​</a></h2>
<p>這是我Docusaurus Build出來後的文件結構：</p>
<p><img decoding="async" loading="lazy" alt="錯誤" src="https://your-docusaurus-site.example.com/assets/images/403-55715a09557259a316c3b8123099bb01.png" width="434" height="576" class="img_ev3q"></p>
<p>docs/intro 這個路由對應的實際文件是 docs/intro/index.html，當我在首頁點擊intro鏈接之後，Docusaurus的React路由在前端接管了導航，沒有發送HTTP請求給CloudFront。</p>
<p>但是當我刷新頁面之後，頁面會發送實際的HTTP請求到CloudFront，請求的路徑是 /docs/intro，CloudFront會去S3桶裡尋找這個路徑對應的文件，但是S3桶裡沒有 /docs/intro 這個文件，只有 /docs/intro/index.html，所以CloudFront返回403 Access Denied錯誤。</p>
<p>在使用 OAC（Origin Access Control）私有訪問的情況下，S3 對於找不到的對象返回的是 403 Access Denied，而不是直觀的 404 Not Found。這是因為從 S3 的角度看，它無法區分「對象不存在」和「你沒權限訪問」——統一都用 403 來回應，避免洩露 bucket 裡的對象信息。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="解決方案一">解決方案一<a href="https://your-docusaurus-site.example.com/blog/2026/04/07/aws-daily-learn#%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88%E4%B8%80" class="hash-link" aria-label="Direct link to 解決方案一" title="Direct link to 解決方案一" translate="no">​</a></h2>
<p>最簡單的方案是告訴 CloudFront：遇到 403/404 時，把 /index.html 返回給用戶，讓前端路由接管。</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">custom_error_response {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  error_code         = 403</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  response_code      = 200</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  response_page_path = "/index.html"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">custom_error_response {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  error_code         = 404</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  response_code      = 200</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  response_page_path = "/index.html"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="解決方案二">解決方案二<a href="https://your-docusaurus-site.example.com/blog/2026/04/07/aws-daily-learn#%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%A1%88%E4%BA%8C" class="hash-link" aria-label="Direct link to 解決方案二" title="Direct link to 解決方案二" translate="no">​</a></h2>
<p>更好的做法是在請求到達S3之前，就把URL自動改寫為正確的格式。CloudFront Function可以在邊緣節點執行輕量級的JavaScript代碼，適合做這件事。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource </span><span class="token string" style="color:#e3116c">"aws_cloudfront_function"</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"rewrite_uri"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name    </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"rewrite-uri"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  runtime </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cloudfront-js-2.0"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  publish </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  code    </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&lt;&lt;</span><span class="token operator" style="color:#393A34">-</span><span class="token constant" style="color:#36acaa">EOT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">handler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token parameter">event</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> request </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> event</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> uri </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">uri</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">uri</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">endsWith</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'/'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">uri</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'index.html'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">else</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">uri</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">includes</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'.'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        request</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">uri</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'/index.html'</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token keyword control-flow" style="color:#00009f">return</span><span class="token plain"> request</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token constant" style="color:#36acaa">EOT</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>邏輯很簡單，如果請求的路徑是以 /docs/intro 結尾的話，就自動加上 /index.html，這樣就能正確地找到對應的文件了。</p>
<p>然後在cahce_behavior裡面把這個function掛上去：</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">function_association {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  event_type   = "viewer-request"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  function_arn = aws_cloudfront_function.rewrite_uri.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>配置完後，清除一下CloudFront的緩存，刷新頁面就不會再出現403錯誤了。</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">aws cloudfront create-invalidation \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --distribution-id YOUR_DISTRIBUTION_ID \</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  --paths "/*"</span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="AWS" term="AWS"/>
        <category label="CloudFront" term="CloudFront"/>
        <category label="S3" term="S3"/>
        <category label="WebSite" term="WebSite"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Lambda Snapshot機制：冷啟動黑科技]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn"/>
        <updated>2026-04-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Lambda Snapshot機制]]></summary>
        <content type="html"><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lambda-snapshot機制">Lambda Snapshot機制<a href="https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn#lambda-snapshot%E6%A9%9F%E5%88%B6" class="hash-link" aria-label="Direct link to Lambda Snapshot機制" title="Direct link to Lambda Snapshot機制" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="目前的問題">目前的問題<a href="https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn#%E7%9B%AE%E5%89%8D%E7%9A%84%E5%95%8F%E9%A1%8C" class="hash-link" aria-label="Direct link to 目前的問題" title="Direct link to 目前的問題" translate="no">​</a></h3>
<p>Lambda冷啟動的延遲主要來自Init階段，下載程式碼、啟動Runtime、執行初始化邏輯。對Node.js/python來說可能只需要幾百毫秒，但是對於java來說，JVM需要啟動，類加載器需要跑、Spring Boot要掃描一堆Bean、反射、DI容器初始化。</p>
<p>一個典型的Spring Boot Lambda，Init Duration會達到5-10秒。用戶第一個請求等待7秒，這在API場景下完全不可以接受。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="snapstart的核心機制-firecracker-snapshotrestore">Snapstart的核心機制: Firecracker Snapshot/Restore<a href="https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn#snapstart%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A9%9F%E5%88%B6-firecracker-snapshotrestore" class="hash-link" aria-label="Direct link to Snapstart的核心機制: Firecracker Snapshot/Restore" title="Direct link to Snapstart的核心機制: Firecracker Snapshot/Restore" translate="no">​</a></h3>
<p>SnapStart的本質就是一句話：把Init從每次冷啟動做變成發版時候只做一次，之後從快照恢復。</p>
<p>具體流程：</p>
<ul>
<li class="">當你啟用SnapStart並且發布一個新的函數版本時候，Lambda會觸法快照流程：先運行函數的完整初始化階段。</li>
<li class="">然後對已經初始化的執行環境拍攝一個不可變的、加密的Firecracker microVM快照，包含記憶體和磁碟的完整狀態，再將快照分快緩存以供快速取回。</li>
<li class="">之後冷啟動就不需要再走Init了，Lambda直接從緩存的快照恢復新的執行環境，而不是從頭初始化，用一個更快的resume階段取代了原本最耗時的初始化階段。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="快照的存儲機制">快照的存儲機制<a href="https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn#%E5%BF%AB%E7%85%A7%E7%9A%84%E5%AD%98%E5%84%B2%E6%A9%9F%E5%88%B6" class="hash-link" aria-label="Direct link to 快照的存儲機制" title="Direct link to 快照的存儲機制" translate="no">​</a></h3>
<p>Lambda將快照存儲在S3中，分割成512KB的Chunk以優化取回延遲。由於S3的取回需要數百毫秒，Lambda使用兩層緩存加速：L2是專門構建的分布式緩存艦隊，每個AZ都有一份獨立副本，Chunk從L2取回的性能通常在個位數毫秒級別。</p>
<p>這意味著恢復一個快照並不是去S3拉一整個大文件，而是按需、分塊、多層緩存地載入。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="crac-讓應用層也能配合快照">CRaC 讓應用層也能配合快照<a href="https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn#crac-%E8%AE%93%E6%87%89%E7%94%A8%E5%B1%A4%E4%B9%9F%E8%83%BD%E9%85%8D%E5%90%88%E5%BF%AB%E7%85%A7" class="hash-link" aria-label="Direct link to CRaC 讓應用層也能配合快照" title="Direct link to CRaC 讓應用層也能配合快照" translate="no">​</a></h2>
<p>Firecracker層面的snapshot/restore解決了VM狀態保存和恢復，但應用層也有自己的問題：數據庫連線、打開的socket、隨機數種子、臨時憑證等，這些東西被放進快照之後，恢復出來可能就已經失效了。</p>
<p>這就是CRaC發揮作用的地方。</p>
<p>CRaC在Java應用生命週期引入了新的before-checkpoint和after-restore階段。與非協調式的checkpoint/restore不同，協調式的做法允許被恢復的Java應用根據執行環境的變化做出不同的反應。</p>
<p>它提供兩個核心hook：</p>
<ul>
<li class="">beforeCheckpoint() --快照拍攝前調用，你在這裡關閉數據庫連線、釋放文件句柄、清除臨時狀態</li>
<li class="">afterRestore() --快照恢復時候調用，你在這裡重新建立連線、刷新憑證、重新播種隨機數生成器</li>
</ul>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">public class MyHandler implements RequestHandler&lt;Event, Response&gt;, Resource {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    private Connection dbConn;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    public MyHandler() {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 這段在 Init 階段執行，會被快照捕獲</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Core.getGlobalContext().register(this);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        dbConn = DriverManager.getConnection(DB_URL);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @Override</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    public void beforeCheckpoint(Context&lt;? extends Resource&gt; context) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 快照前：關閉連線（因為恢復後可能已過期）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        dbConn.close();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    @Override</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    public void afterRestore(Context&lt;? extends Resource&gt; context) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        // 恢復後：重新建立連線</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        dbConn = DriverManager.getConnection(DB_URL);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>Spring Boot、Quarkus、Micronaut 等主流框架已經內建了 CRaC 支援，多數情況下開發者不需要手動改程式碼就能使用。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="類加載時機對快照的影響---priming">類加載時機對快照的影響 - Priming<a href="https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn#%E9%A1%9E%E5%8A%A0%E8%BC%89%E6%99%82%E6%A9%9F%E5%B0%8D%E5%BF%AB%E7%85%A7%E7%9A%84%E5%BD%B1%E9%9F%BF---priming" class="hash-link" aria-label="Direct link to 類加載時機對快照的影響 - Priming" title="Direct link to 類加載時機對快照的影響 - Priming" translate="no">​</a></h2>
<p>在初始化未被執行的代碼路徑--比如透過依賴注入延遲加載的類--不會被包含在快照之中。</p>
<p>這意味著假設你有一個Lambda handler裡用了10個Service類，但只有3個在Init階段被觸及，剩下7個要等到第一次請求進來時才會被JVM類加載器載入。那些7個類的加載器就會落在快照恢復後的第一次請求上。</p>
<p>解法是Priming預熱：在beforeCheckpoint階段主動觸發延遲加載的類</p>
<div class="language-java codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-java codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">@Override</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">public void beforeCheckpoint(Context&lt;? extends Resource&gt; context) {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 主動觸發類加載和 JIT，讓它們被快照捕獲</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    new ObjectMapper().readValue("{\"test\":1}", Map.class);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    // 甚至可以做一次模擬調用</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    handleRequest(dummyEvent, dummyContext);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="唯一性陷阱">唯一性陷阱<a href="https://your-docusaurus-site.example.com/blog/2026/04/06/aws-daily-learn#%E5%94%AF%E4%B8%80%E6%80%A7%E9%99%B7%E9%98%B1" class="hash-link" aria-label="Direct link to 唯一性陷阱" title="Direct link to 唯一性陷阱" translate="no">​</a></h2>
<p>因為SnapStart 用同一份快照作為多個執行環境的初始狀態，所以在初始化階段生成的「唯一」內容被快照捕獲後，恢復到多個實例中就不再唯一了。 Amazon Web Services
典型翻車場景：</p>
<ul>
<li class="">Init 時生成了一個 UUID 作為實例 ID → 所有從同一快照恢復的實例拿到相同的 UUID</li>
<li class="">Init 時用 SecureRandom 播了種 → 所有實例的隨機數序列完全一致，在加密場景下是致命漏洞</li>
<li class="">Init 時拿了一個 STS 臨時憑證 → 恢復時可能已經過期</li>
</ul>
<p>解法：所有需要唯一性的內容，必須從 Init 階段移到 handler 中，或者放到 afterRestore() hook 裡生成。</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">發版時（一次性）：</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Code Deploy → Init（JVM 啟動、類加載、DI、連線建立）</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  CRaC beforeCheckpoint() → 關閉外部資源</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Firecracker snapshot → 記憶體 + 磁碟狀態 → 加密 → 分成 512KB chunks</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  S3（持久層）→ L2 分布式緩存 → L1 Worker 本地緩存</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">每次冷啟動（毫秒級）：</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  請求進來 → 無暖實例可用</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  從緩存拉取 chunks → 恢復 Firecracker microVM</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  CRaC afterRestore() → 重建連線、刷新憑證、重新播種</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       ↓</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Handler 執行 → 回應請求</span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="AWS" term="AWS"/>
        <category label="Lambda" term="Lambda"/>
        <category label="VPC" term="VPC"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[將Lambda放到VPC中，發生了什麼？]]></title>
        <id>https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn"/>
        <updated>2026-04-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[為什麼把Lambda放進VPC後冷啟動會從毫秒級變成十幾秒？]]></summary>
        <content type="html"><![CDATA[<p>為什麼把Lambda放進VPC後冷啟動會從毫秒級變成十幾秒？</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="為什麼lambda函數需要走進vpc">為什麼Lambda函數需要走進VPC？<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E7%82%BA%E4%BB%80%E9%BA%BClambda%E5%87%BD%E6%95%B8%E9%9C%80%E8%A6%81%E8%B5%B0%E9%80%B2vpc" class="hash-link" aria-label="Direct link to 為什麼Lambda函數需要走進VPC？" title="Direct link to 為什麼Lambda函數需要走進VPC？" translate="no">​</a></h2>
<p>預設的情況下，Lambda函數運行在AWS託管的網絡空間中，可以存取公網的網際網路端點，但是無法直接存取你VPC內部的私有資源，例如RDS、ElastiCache等。如果你的Lambda函數需要訪問這些私有資源，就必須將它放入VPC中。</p>
<p>一旦你為Lambda設定中指定了VPC、子網與安全群組，AWS就會為這個函數建立Elastic Network Interface，讓它伸出一隻手伸進VPC。而這隻手就是問題的根源。</p>
<p>現在矛盾的點就是，Lambda追求毫秒級的啟動，但是建立ENI是一個VPC層級的控制平面操作，天生就非常慢。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="舊時代2019年前每次呼叫都會卡eni">舊時代（2019年前）：每次呼叫都會卡ENI<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E8%88%8A%E6%99%82%E4%BB%A32019%E5%B9%B4%E5%89%8D%E6%AF%8F%E6%AC%A1%E5%91%BC%E5%8F%AB%E9%83%BD%E6%9C%83%E5%8D%A1eni" class="hash-link" aria-label="Direct link to 舊時代（2019年前）：每次呼叫都會卡ENI" title="Direct link to 舊時代（2019年前）：每次呼叫都會卡ENI" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="原始流程">原始流程<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E5%8E%9F%E5%A7%8B%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="Direct link to 原始流程" title="Direct link to 原始流程" translate="no">​</a></h3>
<p>在2019年9月之前，VPC Lambda冷啟動流程如下：</p>
<ul>
<li class="">請求建立ENI：Lambda服務向EC2控制平台發出CreateNetworkInterface API請求，要求在指定子網中創建一個ENI。</li>
<li class="">分配私有IP：從你指定的子網CIDR中取得一個可用的私有IP地址。</li>
<li class="">附加安全組：從你指定的Security Group規則套用到這張ENI上。</li>
<li class="">Attach 到 Lambda MicroVM：ENI被連接到Firecracker MicroVM的虛擬網卡上。</li>
<li class="">函數啟動：Runtime初始化，載入你的handler，開始處理請求。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="firecracker-microvm是什麼">Firecracker MicroVM是什麼？<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#firecracker-microvm%E6%98%AF%E4%BB%80%E9%BA%BC" class="hash-link" aria-label="Direct link to Firecracker MicroVM是什麼？" title="Direct link to Firecracker MicroVM是什麼？" translate="no">​</a></h3>
<p>Firecracker是AWS開源的一套輕量級虛擬機器監控器VMM，專門為serverless場景設計。它用Rust編寫，基於Linux KVM來建立和運行所謂的microVM。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="為什麼需要它">為什麼需要它？<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E7%82%BA%E4%BB%80%E9%BA%BC%E9%9C%80%E8%A6%81%E5%AE%83" class="hash-link" aria-label="Direct link to 為什麼需要它？" title="Direct link to 為什麼需要它？" translate="no">​</a></h4>
<p>Lambda是多租戶環境，你的函數和其他人的函數跑在同一台實體服務器上。AWS需要在隔離性和啟動速度之間取得平衡：</p>
<ul>
<li class="">傳統VM（類似QEMU）隔離性強，但啟動要好幾秒，而且模擬了一堆Lambda不需要的硬體--USB、顯示卡、音效卡、PCI</li>
<li class="">容器（類似Docker）啟動快，但容器共享宿主機的kernel，在多租戶環境中安全風險較高。</li>
</ul>
<p>AWS最早採用傳統VM來跑Lambda，有安全性，但是效能和密度不好，於是開發了Firecra--結合了VM的硬體級隔離和容器的資源效率與快速啟動。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="firecracker精簡程度">Firecracker精簡程度<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#firecracker%E7%B2%BE%E7%B0%A1%E7%A8%8B%E5%BA%A6" class="hash-link" aria-label="Direct link to Firecracker精簡程度" title="Direct link to Firecracker精簡程度" translate="no">​</a></h4>
<p>Firecracker的每個microVM記憶體開銷低於5MiB，啟動時間最快只要125毫秒，單台伺服器每秒可建立最多150個microVM。之所以能這麼快，是因為完全不實作BIOS、PCI等傳統硬體模擬，只透過精簡的virtio介面和guest kernel溝通。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="和eni之間的關係">和ENI之間的關係<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E5%92%8Ceni%E4%B9%8B%E9%96%93%E7%9A%84%E9%97%9C%E4%BF%82" class="hash-link" aria-label="Direct link to 和ENI之間的關係" title="Direct link to 和ENI之間的關係" translate="no">​</a></h4>
<p>每次Lambda冷啟動，AWS會啟動一個Firecracker microVM來執行代碼，如果你的Lambda配置了VPC，這個microVM就需要一張ENI來接入你的VPC網路，在舊架構下，每啟動一個microVM就要即時建立一張ENI，這就是冷啟動卡住的原因。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="架構">架構<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E6%9E%B6%E6%A7%8B" class="hash-link" aria-label="Direct link to 架構" title="Direct link to 架構" translate="no">​</a></h3>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">┌──────────────────────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  你的 VPC ─ Subnet: 10.0.1.0/24 (可用 IP: 251)          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│                                                          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ┌──────────┐  ┌──────────┐  ┌──────────┐               │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  │  ENI-001 │  │  ENI-002 │  │  ENI-003 │  ...          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  │ 10.0.1.5 │  │ 10.0.1.6 │  │ 10.0.1.7 │               │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └────┬─────┘  └────┬─────┘  └────┬─────┘               │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       │              │              │                     │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└───────┼──────────────┼──────────────┼─────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        │              │              │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ┌─────▼─────┐  ┌─────▼─────┐  ┌─────▼─────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │ Lambda #1 │  │ Lambda #2 │  │ Lambda #3 │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  │ (MicroVM) │  │ (MicroVM) │  │ (MicroVM) │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  └───────────┘  └───────────┘  └───────────┘</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三大問題">三大問題<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E4%B8%89%E5%A4%A7%E5%95%8F%E9%A1%8C" class="hash-link" aria-label="Direct link to 三大問題" title="Direct link to 三大問題" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ip地址耗盡">IP地址耗盡<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#ip%E5%9C%B0%E5%9D%80%E8%80%97%E7%9B%A1" class="hash-link" aria-label="Direct link to IP地址耗盡" title="Direct link to IP地址耗盡" translate="no">​</a></h3>
<p>一個/24子網只有251個可用IP，扣掉AWS保留的五個，如果你有多個Lambda函數共用相同的子網，並發量大了之後，ENI佔滿IP後新的呼叫會收到ENIlimitException。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="eni數量上限">ENI數量上限<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#eni%E6%95%B8%E9%87%8F%E4%B8%8A%E9%99%90" class="hash-link" aria-label="Direct link to ENI數量上限" title="Direct link to ENI數量上限" translate="no">​</a></h3>
<p>每個Region/Account預設的ENI上限大約在5000左右，當你有多個高併發的VPC Lambda，加上其他EC2實例也在消耗ENI，很容易達到上限。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="安全組規則變化">安全組規則變化<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E5%AE%89%E5%85%A8%E7%B5%84%E8%A6%8F%E5%89%87%E8%AE%8A%E5%8C%96" class="hash-link" aria-label="Direct link to 安全組規則變化" title="Direct link to 安全組規則變化" translate="no">​</a></h3>
<p>有人修改了Lambda使用的SG，加了一條規則，Lambda下次冷啟動才會套用新規則，而已經運行的實例仍使用舊規則--導致行為不一致。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="新時代aws-hyperplan架構">新時代：AWS Hyperplan架構<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E6%96%B0%E6%99%82%E4%BB%A3aws-hyperplan%E6%9E%B6%E6%A7%8B" class="hash-link" aria-label="Direct link to 新時代：AWS Hyperplan架構" title="Direct link to 新時代：AWS Hyperplan架構" translate="no">​</a></h2>
<p>2019年9約，AWS宣佈了VPC Lambda的重大架構--引入Hyperplan ENI（也稱為VPC-to-VPC NAT）。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="核心變化eni在函數建立更新時預建">核心變化：ENI在函數建立/更新時預建<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E6%A0%B8%E5%BF%83%E8%AE%8A%E5%8C%96eni%E5%9C%A8%E5%87%BD%E6%95%B8%E5%BB%BA%E7%AB%8B%E6%9B%B4%E6%96%B0%E6%99%82%E9%A0%90%E5%BB%BA" class="hash-link" aria-label="Direct link to 核心變化：ENI在函數建立/更新時預建" title="Direct link to 核心變化：ENI在函數建立/更新時預建" translate="no">​</a></h3>
<p>新架構下，ENI不再是呼叫時才建立，而是在你Create/Update Function的時候就預先建立好，這些ENI被稱為Hyperplan ENI，它們是共享的--同一個函數的多個併發實例可以共用一張ENI。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">┌──────────────────────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  你的 VPC ─ Subnet: 10.0.1.0/24                         │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│                                                          │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  ┌──────────────────────┐                                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  │  Hyperplane ENI      │  ← 函數部署時就建好             │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  │  10.0.1.5            │  ← 多個實例共用                 │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └──────────┬───────────┘                                │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│             │                                            │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│    ┌────────┴─────────┐                                  │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│    │  VPC-to-VPC NAT  │  ← AWS Hyperplane 服務           │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│    │  (Mapping Table) │                                  │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│    └────────┬─────────┘                                  │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│             │                                            │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─────────────┼────────────────────────────────────────────┘</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ┌─────────┼─────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │         │    Lambda Service    │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │   ┌─────▼─────┐  ┌───────────┐│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │   │ Lambda #1 │  │ Lambda #2 ││</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │   │ (MicroVM) │  │ (MicroVM) ││  ... 可能上百個</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    │   └───────────┘  └───────────┘│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └───────────────────────────────┘</span><br></span></code></pre></div></div>
<p>由於ENI已經存在，冷啟動時不需要走CreateNetworkInterface，Lambda實例通過Hyperplan的NAT mapping就能進入VPC，時間大幅度下降。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="hyperplan也不是完美的">Hyperplan也不是完美的<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#hyperplan%E4%B9%9F%E4%B8%8D%E6%98%AF%E5%AE%8C%E7%BE%8E%E7%9A%84" class="hash-link" aria-label="Direct link to Hyperplan也不是完美的" title="Direct link to Hyperplan也不是完美的" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="部署時候eni建立仍然需要時間">部署時候ENI建立仍然需要時間<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E9%83%A8%E7%BD%B2%E6%99%82%E5%80%99eni%E5%BB%BA%E7%AB%8B%E4%BB%8D%E7%84%B6%E9%9C%80%E8%A6%81%E6%99%82%E9%96%93" class="hash-link" aria-label="Direct link to 部署時候ENI建立仍然需要時間" title="Direct link to 部署時候ENI建立仍然需要時間" translate="no">​</a></h3>
<p>當你第一次部署VPC Lambda或更新其子網/安全群組設定時，AWS仍然需要建立Hyperplan ENI。這個過程需要時間，這個期間函數會處於Pending狀態。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="需要足夠的iam權限">需要足夠的IAM權限<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E9%9C%80%E8%A6%81%E8%B6%B3%E5%A4%A0%E7%9A%84iam%E6%AC%8A%E9%99%90" class="hash-link" aria-label="Direct link to 需要足夠的IAM權限" title="Direct link to 需要足夠的IAM權限" translate="no">​</a></h3>
<p>Lambda執行角色需要EC2網路相關權限，如果你使用自定的IAM policy，確認包含以下權限：</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"Effect"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Allow"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"Action"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"ec2:CreateNetworkInterface"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"ec2:DescribeNetworkInterfaces"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"ec2:DeleteNetworkInterface"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"ec2:AssignPrivateIpAddresses"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">"ec2:UnassignPrivateIpAddresses"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"Resource"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"*"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre></div></div>
<p>最簡單的方式是附加AWS託管策略<code>AWSLambdaVPCAccessExecutionRole</code>，它包含了上述權限。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="子網規劃仍然重要">子網規劃仍然重要<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E5%AD%90%E7%B6%B2%E8%A6%8F%E5%8A%83%E4%BB%8D%E7%84%B6%E9%87%8D%E8%A6%81" class="hash-link" aria-label="Direct link to 子網規劃仍然重要" title="Direct link to 子網規劃仍然重要" translate="no">​</a></h3>
<p>雖然IP消耗大幅度降低，但是如果你子網仍然很少，加上其他服務也在使用，仍可能出現問題，建議至少使用/24子網。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="跨az的高可用配置">跨AZ的高可用配置<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E8%B7%A8az%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E9%85%8D%E7%BD%AE" class="hash-link" aria-label="Direct link to 跨AZ的高可用配置" title="Direct link to 跨AZ的高可用配置" translate="no">​</a></h3>
<p>設定Lambda VPC，應選擇至少兩個不同的AZ子網，如果某個AZ出問題，Lambda可以自動使用另一個AZ的ENI，確保可用性。</p>
<p>VPC Lambda要存取公網資源，需要搭配NAT Gateway（放在public subnet）Lambda自己所在的private subnet路由表要將0.0.0.0/0指向NAT Gateway，不然Lambda放進VPC就會出不去了。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文檔">參考文檔<a href="https://your-docusaurus-site.example.com/blog/2026/04/03/aws-daily-learn#%E5%8F%83%E8%80%83%E6%96%87%E6%AA%94" class="hash-link" aria-label="Direct link to 參考文檔" title="Direct link to 參考文檔" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html" target="_blank" rel="noopener noreferrer" class="">AWS官方文檔</a></li>
<li class=""><a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc-internet.html" target="_blank" rel="noopener noreferrer" class="">AWS文檔</a></li>
</ul>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="AWS" term="AWS"/>
        <category label="Lambda" term="Lambda"/>
        <category label="VPC" term="VPC"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Linux 學習筆記]]></title>
        <id>https://your-docusaurus-site.example.com/blog/linux-learn</id>
        <link href="https://your-docusaurus-site.example.com/blog/linux-learn"/>
        <updated>2026-04-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[本blog記錄筆者在AWS EC2上學習Linux的過程，主要涵蓋了Linux的基本命令、文件系統、權限管理、軟件安裝和網絡配置等方面的內容。]]></summary>
        <content type="html"><![CDATA[<p>本blog記錄筆者在AWS EC2上學習Linux的過程，主要涵蓋了Linux的基本命令、文件系統、權限管理、軟件安裝和網絡配置等方面的內容。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="day1">DAY1<a href="https://your-docusaurus-site.example.com/blog/linux-learn#day1" class="hash-link" aria-label="Direct link to DAY1" title="Direct link to DAY1" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="創建一個新的EC2實例" src="https://your-docusaurus-site.example.com/assets/images/linux-create-f9b1245601e3058f63df327346af4593.png" width="2980" height="1174" class="img_ev3q"></p>
<p>在AWS EC2上創建了一個新的實例，選擇了Amazon Linux 2作為操作系統，並配置了安全組以允許SSH訪問。</p>]]></content>
        <author>
            <name>JIN Shan</name>
            <uri>https://qingshiyuu.com</uri>
        </author>
        <category label="Linux" term="Linux"/>
        <category label="學習筆記" term="學習筆記"/>
    </entry>
</feed>