<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>AWS on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/tags/aws/</link>
    <description>Recent content in AWS on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Thu, 05 Mar 2026 09:08:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/tags/aws/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>GitHub Actions OIDCで実現する鍵レス本番デプロイ：漏えい事故を減らす実装プレイブック</title>
      <link>https://www.ai2core.com/posts/2026-03-05-github-actions-oidc-secure-deploy-playbook/</link>
      <pubDate>Thu, 05 Mar 2026 09:08:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-03-05-github-actions-oidc-secure-deploy-playbook/</guid>
      <description>長期シークレットを廃止し、GitHub Actions OIDCでAWSへ安全にデプロイするための設計・実装・監査手順を具体的に解説。</description>
      <content:encoded><![CDATA[<h1 id="github-actions-oidcで実現する鍵レス本番デプロイ漏えい事故を減らす実装プレイブック">GitHub Actions OIDCで実現する鍵レス本番デプロイ：漏えい事故を減らす実装プレイブック</h1>
<p>CI/CD の事故は、ビルドが失敗することより「漏えいしても気づけない鍵」が残り続けることのほうが深刻です。特に <code>AWS_ACCESS_KEY_ID</code> のような長期シークレットを GitHub Secrets に保存し続ける運用は、便利ですがリスクが高いです。</p>
<p>本記事では、GitHub Actions の <strong>OIDC（OpenID Connect）連携</strong>を使って、長期鍵を使わずに AWS へデプロイする実践手順をまとめます。単なる設定紹介ではなく、<strong>最小権限・ブランチ制限・監査ログ設計</strong>まで含めて、明日から本番投入できる形で説明します。</p>
<h2 id="1-まず何が危険なのか長期シークレット運用の限界">1. まず何が危険なのか：長期シークレット運用の限界</h2>
<p>従来構成では、次のような問題が起きます。</p>
<ul>
<li>Secret が漏れても検知が遅い（CIログ、誤コミット、権限の広いメンバー）</li>
<li>ローテーションが後回しになる</li>
<li>1つの鍵で複数環境へアクセスできてしまう</li>
<li>「誰のどの workflow 実行が何をしたか」が追いにくい</li>
</ul>
<p>OIDC 連携では、GitHub が発行する短命トークンを信頼し、AWS 側で一時認証情報を払い出します。つまり、<strong>保管する鍵そのものを減らす</strong>のが最大の価値です。</p>
<h2 id="2-全体アーキテクチャ">2. 全体アーキテクチャ</h2>
<p>基本フローは以下です。</p>
<ol>
<li>GitHub Actions ジョブが OIDC トークンを取得</li>
<li>AWS IAM の OIDC プロバイダとロール信頼ポリシーで検証</li>
<li>条件に一致したジョブだけ <code>AssumeRoleWithWebIdentity</code></li>
<li>一時クレデンシャルで S3/CloudFront/ECR/ECS へデプロイ</li>
</ol>
<p>ポイントは「GitHub 側の workflow 制御」だけでなく、<strong>AWS 側で repo・branch・workflow を強制する</strong>ことです。</p>
<h2 id="3-aws-側の初期設定oidc-provider--iam-role">3. AWS 側の初期設定（OIDC Provider + IAM Role）</h2>
<h3 id="31-oidc-provider-を作成">3.1 OIDC Provider を作成</h3>
<p>CLI 例（すでに存在する場合はスキップ）:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>aws iam create-open-id-connect-provider <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --url https://token.actions.githubusercontent.com <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --client-id-list sts.amazonaws.com <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="32-信頼ポリシーを厳密化する">3.2 信頼ポリシーを厳密化する</h3>
<p>以下のように <code>sub</code> と <code>aud</code> を必ず絞ります。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;Version&#34;</span>: <span style="color:#e6db74">&#34;2012-10-17&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;Statement&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;Effect&#34;</span>: <span style="color:#e6db74">&#34;Allow&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;Principal&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;Federated&#34;</span>: <span style="color:#e6db74">&#34;arn:aws:iam::&lt;ACCOUNT_ID&gt;:oidc-provider/token.actions.githubusercontent.com&#34;</span>
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;Action&#34;</span>: <span style="color:#e6db74">&#34;sts:AssumeRoleWithWebIdentity&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;Condition&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;StringEquals&#34;</span>: {
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;token.actions.githubusercontent.com:aud&#34;</span>: <span style="color:#e6db74">&#34;sts.amazonaws.com&#34;</span>
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&#34;StringLike&#34;</span>: {
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;token.actions.githubusercontent.com:sub&#34;</span>: <span style="color:#e6db74">&#34;repo:your-org/your-repo:ref:refs/heads/main&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>sub</code> を <code>repo:org/repo:*</code> のように広く取りすぎると、意図しない workflow からも引き受ける可能性があり危険です。</p>
<h3 id="33-デプロイ権限ポリシーを分離する">3.3 デプロイ権限ポリシーを分離する</h3>
<p>「ロール1個に全部盛り」は避けます。</p>
<ul>
<li><code>deploy-web-prod-role</code>: S3同期 + CloudFront invalidation</li>
<li><code>deploy-api-prod-role</code>: ECR push + ECS update</li>
<li><code>read-only-audit-role</code>: CloudWatch Logs / Describe 系のみ</li>
</ul>
<p>環境別（dev/stg/prod）にロールを分離すると、誤デプロイ時の被害半径が大きく減ります。</p>
<h2 id="4-github-actions-workflow-実装">4. GitHub Actions workflow 実装</h2>
<p>最小サンプル:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">deploy-web</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">on</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">push</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">branches</span>: [<span style="color:#e6db74">&#34;main&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">permissions</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">id-token</span>: <span style="color:#ae81ff">write</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">contents</span>: <span style="color:#ae81ff">read</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">deploy</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">environment</span>: <span style="color:#ae81ff">production</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Configure AWS credentials via OIDC</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">aws-actions/configure-aws-credentials@v4</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">role-to-assume</span>: <span style="color:#ae81ff">arn:aws:iam::&lt;ACCOUNT_ID&gt;:role/deploy-web-prod-role</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">aws-region</span>: <span style="color:#ae81ff">ap-northeast-1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          npm ci
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          npm run build</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Upload to S3</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">aws s3 sync ./dist s3://example-prod-web --delete</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Invalidate CloudFront</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: <span style="color:#ae81ff">aws cloudfront create-invalidation --distribution-id E123456 --paths &#34;/*&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>重要なのは <code>permissions.id-token: write</code> を明示する点です。これがないと OIDC トークンを取得できません。</p>
<h2 id="5-事故を防ぐための実務ルール">5. 事故を防ぐための実務ルール</h2>
<h3 id="51-branch-protection-と-environment-protection-を組み合わせる">5.1 branch protection と environment protection を組み合わせる</h3>
<ul>
<li><code>main</code> 直push禁止</li>
<li>必ず PR + 1 approval</li>
<li>production environment には Required reviewers を設定</li>
<li>夜間デプロイを禁止したい場合は手動承認ステップを入れる</li>
</ul>
<h3 id="52-workflow-ファイル改変の監査">5.2 workflow ファイル改変の監査</h3>
<p><code>.github/workflows/*.yml</code> の変更は CODEOWNERS で必ずレビュアー固定にします。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>.github/workflows/*  @platform-team
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="53-self-hosted-runner-の扱い">5.3 self-hosted runner の扱い</h3>
<p>OIDC を導入しても、runner 自体が侵害されると意味が薄れます。</p>
<ul>
<li>runner をプロジェクト共有にしない（専用化）</li>
<li>ジョブ後にワークディレクトリをクリーン</li>
<li>egress 制限をかける</li>
<li>runner グループを環境ごとに分離</li>
</ul>
<h2 id="6-トラブルシューティング">6. トラブルシューティング</h2>
<h3 id="症状1-not-authorized-to-perform-stsassumerolewithwebidentity">症状1: <code>Not authorized to perform sts:AssumeRoleWithWebIdentity</code></h3>
<p>確認ポイント:</p>
<ol>
<li><code>aud</code> が <code>sts.amazonaws.com</code> になっているか</li>
<li><code>sub</code> が実際の実行 ref と一致しているか（タグ実行でハマりやすい）</li>
<li><code>permissions.id-token: write</code> が設定されているか</li>
<li>Role ARN の typo がないか</li>
</ol>
<h3 id="症状2-main-以外で偶発的にデプロイされた">症状2: main 以外で偶発的にデプロイされた</h3>
<ul>
<li>workflow の <code>on.push.branches</code> 見直し</li>
<li>IAM 信頼ポリシー <code>sub</code> を main 固定へ</li>
<li>環境保護ルール（Required reviewers）追加</li>
</ul>
<h3 id="症状3-デプロイは通るが操作が一部失敗">症状3: デプロイは通るが操作が一部失敗</h3>
<p>これは IAM 権限不足の可能性が高いです。CloudTrail の <code>eventName</code> と <code>errorCode</code> を見て、必要最小限のアクションだけ追加します。闇雲に <code>*</code> を付けないこと。</p>
<h2 id="7-段階的な移行計画既存運用からの切替">7. 段階的な移行計画（既存運用からの切替）</h2>
<p>実務では一気に切り替えず、以下の順が安全です。</p>
<ol>
<li>OIDC ロールを作成（既存シークレットは残す）</li>
<li>staging workflow だけ OIDC に切替</li>
<li>1週間監視（失敗率・デプロイ時間・CloudTrail）</li>
<li>production を OIDC 化</li>
<li>最後に長期シークレットを削除</li>
</ol>
<p>削除前に「どの workflow がどの secret を参照しているか」を grep で確認しておくと事故が減ります。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git grep -n <span style="color:#e6db74">&#34;AWS_ACCESS_KEY_ID\|AWS_SECRET_ACCESS_KEY&#34;</span> .github/workflows
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="8-監査ログ設計何を見れば安全性が上がるか">8. 監査ログ設計：何を見れば安全性が上がるか</h2>
<p>最低限、次をダッシュボード化すると運用しやすいです。</p>
<ul>
<li><code>AssumeRoleWithWebIdentity</code> 実行回数（日次）</li>
<li>失敗イベント数（権限エラー/条件不一致）</li>
<li>production deploy 実行者（workflow + sha + actor）</li>
<li>1回のデプロイで変更された主要リソース数</li>
</ul>
<p>CloudTrail + CloudWatch Logs Insights での簡易クエリ例:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span>fields <span style="color:#f92672">@</span><span style="color:#66d9ef">timestamp</span>, userIdentity.sessionContext.sessionIssuer.userName, eventName, errorCode
</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> filter eventSource <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;sts.amazonaws.com&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> filter eventName <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;AssumeRoleWithWebIdentity&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> sort <span style="color:#f92672">@</span><span style="color:#66d9ef">timestamp</span> <span style="color:#66d9ef">desc</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">|</span> <span style="color:#66d9ef">limit</span> <span style="color:#ae81ff">50</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="まとめ">まとめ</h2>
<p>OIDC は「設定が新しいから導入する」ものではなく、<strong>長期鍵を削減して事故確率を下げるための運用設計</strong>です。導入時にやるべきことはシンプルで、次の3つに集約できます。</p>
<ol>
<li>IAM 信頼ポリシーを repo/branch 単位で厳密化する</li>
<li>workflow 側で id-token 権限と環境保護を設定する</li>
<li>CloudTrail で AssumeRole の監査を継続する</li>
</ol>
<p>ここまで実施すれば、CI/CD のセキュリティは「頑張って守る」状態から、「漏えいしにくい仕組みで守る」状態へ進化します。まずは staging 1本から置き換えるのがおすすめです。</p>
]]></content:encoded>
      <category>Tech</category>
      <category>GitHub Actions</category>
      <category>OIDC</category>
      <category>Security</category>
      <category>AWS</category>
      <category>CI/CD</category>
    </item>
    <item>
      <title>AWS Lambda SnapStartがPythonに対応！コールドスタート解消へ</title>
      <link>https://www.ai2core.com/posts/2026-02-20-aws-lambda-snapstart/</link>
      <pubDate>Fri, 20 Feb 2026 18:00:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-20-aws-lambda-snapstart/</guid>
      <description>Javaに続きPythonでも利用可能になったSnapStartの仕組みと効果。</description>
      <content:encoded><![CDATA[<h2 id="aws-lambda-snapstartがpythonに対応コールドスタート解消へ">AWS Lambda SnapStartがPythonに対応！コールドスタート解消へ</h2>
<h3 id="はじめに">はじめに</h3>
<p>AWS Lambdaを本番環境で利用している、あるいは利用を検討しているPythonデベロッパーの皆さん。「サーバーレスは便利だけど、あの&quot;最初の&quot;リクエストだけ遅いのが気になる…」と感じたことはありませんか？</p>
<p>API Gatewayと連携させたLambda関数が、しばらくアクセスがないとタイムアウトギリギリになったり、ユーザーに不快な待ち時間を与えてしまったり。この現象は「コールドスタート」と呼ばれ、多くのサーバーレス開発者を悩ませてきた根深い課題です。</p>
<p>これまで、この問題を解決するにはProvisioned Concurrency（プロビジョニングされた同時実行）という有料オプションを利用するのが一般的でしたが、コストとのトレードオフに頭を悩ませるケースも少なくありませんでした。</p>
<p>しかし、2023年末のre:Invent 2023で、ついにこの状況を打開する待望の機能がPythonランタイムにもたらされました。それが<strong>AWS Lambda SnapStart</strong>です。</p>
<p>これまでJavaランタイムでのみ利用可能だったこの機能がPythonに対応したことで、私たちのサーバーレスアプリケーション開発は新たなステージに進むことになります。本記事では、プロの技術ブロガーとして、Lambda SnapStart for Pythonの仕組みから具体的な使い方、そして現場で活かすための実践的なTipsまで、徹底的に深掘りしていきます。この記事を読み終える頃には、あなたもSnapStartを使いこなし、コールドスタートの悩みから解放されているはずです。</p>
<h3 id="なぜlambda-snapstartが重要なのか---コールドスタート問題の再確認">なぜLambda SnapStartが重要なのか？ - コールドスタート問題の再確認</h3>
<p>SnapStartの詳細に入る前に、なぜこの機能がこれほどまでに待望されていたのか、その背景にある「コールドスタート問題」を改めて整理しましょう。</p>
<h4 id="lambdaの実行モデルとライフサイクル">Lambdaの実行モデルとライフサイクル</h4>
<p>AWS Lambdaは、リクエストに応じてコンテナ（実行環境）を起動し、コードを実行するアーキテクチャです。この実行環境は、常に起動しているわけではありません。一定時間リクエストがないと、AWSはコスト効率化のために実行環境を破棄します。</p>
<p>次にリクエストが来たとき、Lambdaは以下のステップを踏んで応答します。</p>
<ol>
<li><strong>実行環境の確保</strong>: 新しい実行環境（マイクロVM）をプロビジョニングします。</li>
<li><strong>コードのダウンロード</strong>: S3などから関数のコード（デプロイパッケージ）をダウンロードし、展開します。</li>
<li><strong>ランタイムの初期化</strong>: Pythonのランタイム（インタプリタ）を起動します。</li>
<li><strong>関数の初期化 (Initフェーズ)</strong>: ハンドラ関数の<strong>外</strong>で定義されたグローバルなコードを実行します。ライブラリのインポート、DBコネクションプールの作成、機械学習モデルのロードなど、比較的時間のかかる処理がここに含まれます。</li>
<li><strong>関数の実行 (Invokeフェーズ)</strong>: 実際にハンドラ関数を実行し、リクエストを処理します。</li>
</ol>
<p>このうち、ステップ1から4までを含む最初の呼び出しを<strong>コールドスタート</strong>と呼びます。一度起動した実行環境は再利用されるため、2回目以降の呼び出し（<strong>ウォームスタート</strong>）ではステップ5のInvokeフェーズのみが実行され、非常に高速に応答できます。</p>
<p><img alt="Lambda Lifecycle" loading="lazy" src="https-://mermaid.live/edit#pako:eNqNVMtqwzAQ_Jd85Si1Bw77AocuQ6GUQunRw2YpYcuSXYhtSZXkv-dsEmk3Pe7uzs7s6q1qYgGcwD8x1Qp7GvFhRz6d85sOa26bE_QJ2qA6B2L1vC3zCgqJzB80b8bXjWp6L0S1Wq8G8lXvJbK7lK2Oal8i6xJ34Bv2sB4M6nB3d2C2J06X1h8Iu5T7L_f8jY8f4L-30s0c_w-m2TqFjD2WfK2M3a1v97oOqJ-tWvKk_aW4_V0-264L5gOQYJ8Qj8mHj8q9b9g_rW9-Uj_uT-J35_qS_m71Dq35W6v-a22nE3T732QWl5Baqn0sQ8fR2b8v0-c9aGvH8p32V3-iS8HwA_6x4QvH164r3l-CqQY_0M1c60HwP8C8Y_iXh5vB8D7A6b3jG-L_x_s_0d_0HwKcJv9P-3yX_v3d-3_g-f4e9U1T_J9D_lH9zB3968"></p>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    subgraph &#34;Cold Start&#34;
        A[Request] --&gt; B(Allocate MicroVM)
        B --&gt; C(Download Code)
        C --&gt; D(Initialize Runtime)
        D --&gt; E(Run Init Code)
        E --&gt; F(Run Handler)
    end
    F --&gt; G[Response]

    subgraph &#34;Warm Start&#34;
        H[Subsequent Request] --&gt; I{Environment Ready?}
        I -- Yes --&gt; J(Run Handler)
        I -- No --&gt; B
        J --&gt; K[Response]
    end
</code></pre><h4 id="コールドスタートが引き起こす問題">コールドスタートが引き起こす問題</h4>
<p>コールドスタートによる遅延は、数十ミリ秒から、場合によっては10秒以上にも及ぶことがあります。特に以下のようなケースで顕著になります。</p>
<ul>
<li><strong>大規模なフレームワークの利用</strong>: DjangoやFlaskなど、多くの依存関係を持つフレームワークは初期化に時間がかかります。</li>
<li><strong>機械学習モデルのロード</strong>: 数百MBから数GBにもなるモデルファイルをロードする処理は非常に重たいです。</li>
<li><strong>多くのライブラリのインポート</strong>: <code>pandas</code>, <code>NumPy</code>, <code>scikit-learn</code> といった大規模なライブラリはインポートだけでも時間がかかります。</li>
<li><strong>VPC内での実行</strong>: VPC LambdaはENI（Elastic Network Interface）のアタッチが必要なため、追加の起動時間が発生します。</li>
</ul>
<p>この遅延は、特にユーザーとの対話が求められるWeb APIやチャットボットなどのアプリケーションにおいて、ユーザー体験を著しく損なう原因となります。</p>
<h4 id="既存の対策-provisioned-concurrency">既存の対策: Provisioned Concurrency</h4>
<p>この問題を解決するためにAWSが提供してきたのが<strong>Provisioned Concurrency</strong>です。これは、あらかじめ指定した数の実行環境を常にウォーム状態（Initフェーズ完了後）で待機させておく機能です。</p>
<p>これによりコールドスタートを完全に排除できますが、リクエストがないアイドル時間も実行環境を維持し続けるため、<strong>追加のコストが発生します</strong>。トラフィックの予測が難しく、スパイクが発生するようなユースケースでは、コスト効率が悪化するという課題がありました。</p>
<p>そこで登場したのが、<strong>追加コストなしでコールドスタートを劇的に改善する</strong> Lambda SnapStartなのです。</p>
<h3 id="lambda-snapstart-for-python-の仕組みと詳細解説">Lambda SnapStart for Python の仕組みと詳細解説</h3>
<p>Lambda SnapStartは、コールドスタート問題に対する革新的なアプローチです。一言で言えば、**「関数の初期化が完了した状態の実行環境全体を丸ごとスナップショット（凍結保存）し、リクエストが来た際にそのスナップショットから高速に復元する」**技術です。</p>
<p>PCのハイバネーション（休止状態）をイメージすると分かりやすいかもしれません。メモリの状態をディスクに保存しておき、次回起動時にその状態を読み込むことで、OSやアプリケーションの起動時間を大幅に短縮するのと同じ考え方です。</p>
<h4 id="snapstartのライフサイクル">SnapStartのライフサイクル</h4>
<p>SnapStartを有効にすると、Lambda関数のライフサイクルが以下のように変わります。</p>
<ol>
<li>
<p><strong>バージョンの発行 (Publish)</strong>: SnapStartはLambdaのバージョンに対して有効になります（<code>$LATEST</code>では利用できません）。新しいコードをデプロイし、バージョンを発行すると、SnapStartのプロセスが開始されます。</p>
</li>
<li>
<p><strong>初期化 (Init) とスナップショット作成 (Snapshot)</strong>: AWSはバックグラウンドで、通常のコールドスタートと同じように実行環境を起動し、Initフェーズ（ハンドラ外のコード）を実行します。<strong>Initフェーズが完了した直後</strong>、実行環境のメモリとディスクの状態全体を暗号化されたスナップショットとしてS3に保存します。このスナップショットが、今後の呼び出しの「原本」となります。</p>
</li>
<li>
<p><strong>復元 (Restore) と実行 (Invoke)</strong>: 実際にリクエストが来ると、Lambdaは新しい実行環境を起動する代わりに、保存しておいたスナップショットを読み込んで状態を<strong>復元</strong>します。この復元処理は、ゼロから初期化するよりも遥かに高速です。復元後、すぐにハンドラ関数（Invokeフェーズ）が実行されます。</p>
</li>
</ol>
<p>この流れを図で見てみましょう。</p>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">sequenceDiagram
    participant Developer
    participant AWS Lambda
    participant S3

    Developer-&gt;&gt;AWS Lambda: Deploy &amp; Publish New Version (SnapStart Enabled)
    
    box &#34;Background Process (at Publish Time)&#34;
        AWS Lambda-&gt;&gt;AWS Lambda: 1. Start MicroVM
        AWS Lambda-&gt;&gt;AWS Lambda: 2. Download Code
        AWS Lambda-&gt;&gt;AWS Lambda: 3. Initialize Runtime
        AWS Lambda-&gt;&gt;AWS Lambda: 4. Run Init Code (Global Scope)
        Note right of AWS Lambda: Heavy initialization, DB connection pools, etc.
        AWS Lambda-&gt;&gt;S3: 5. Take Encrypted Snapshot of Memory &amp; Disk
    end

    participant User
    User-&gt;&gt;AWS Lambda: Invoke Function (First Request)
    
    box &#34;Foreground Process (at Invoke Time)&#34;
        AWS Lambda-&gt;&gt;AWS Lambda: 1. Start MicroVM
        AWS Lambda-&gt;&gt;S3: 2. Restore State from Snapshot
        Note right of AWS Lambda: This is the &#34;Restore&#34; phase. Much faster than full Init.
        AWS Lambda-&gt;&gt;AWS Lambda: 3. Run Handler (Invoke Code)
    end
    AWS Lambda--&gt;&gt;User: Response
</code></pre><p>最大のポイントは、時間のかかる<strong>Initフェーズを、リクエストのクリティカルパスから完全に分離した</strong>点です。これにより、ユーザーが体感するレイテンシを劇的に削減できるのです。公式の発表では、<strong>最大90%の起動時間短縮</strong>が報告されています。</p>
<h4 id="snapstartの有効化方法">SnapStartの有効化方法</h4>
<p>SnapStartの有効化は驚くほど簡単です。</p>
<h5 id="aws-マネジメントコンソール">AWS マネジメントコンソール</h5>
<ol>
<li>Lambda関数の設定画面に移動します。</li>
<li>「設定」タブ -&gt; 「一般設定」 -&gt; 「編集」をクリックします。</li>
<li>「SnapStart」の項目で、「PublishedVersions」を選択します。</li>
<li>変更を保存します。</li>
</ol>
<p>これだけです。あとは、新しいバージョンを発行すれば、そのバージョンに対して自動的にSnapStartが有効になります。</p>
<h5 id="aws-sam-serverless-application-model">AWS SAM (Serverless Application Model)</h5>
<p><code>template.yaml</code> にプロパティを1行追加するだけです。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">AWSTemplateFormatVersion</span>: <span style="color:#e6db74">&#39;2010-09-09&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">Transform</span>: <span style="color:#ae81ff">AWS::Serverless-2016-10-31</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">Description</span>: <span style="color:#ae81ff">Sample SAM Template with SnapStart for Python</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">Resources</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">MySnapStartFunction</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">Type</span>: <span style="color:#ae81ff">AWS::Serverless::Function</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">Properties</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">FunctionName</span>: <span style="color:#ae81ff">my-snapstart-python-function</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">CodeUri</span>: <span style="color:#ae81ff">src/</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">Handler</span>: <span style="color:#ae81ff">app.lambda_handler</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">Runtime</span>: <span style="color:#ae81ff">python3.12</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">Architectures</span>: [ <span style="color:#ae81ff">x86_64 ]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">AutoPublishAlias</span>: <span style="color:#ae81ff">live</span> <span style="color:#75715e"># バージョニングを有効化</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">SnapStart</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">ApplyOn</span>: <span style="color:#ae81ff">PublishedVersions</span> <span style="color:#75715e"># この行を追加</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h5 id="aws-cdk-cloud-development-kit">AWS CDK (Cloud Development Kit)</h5>
<p>CDK (Python) の場合も、<code>snap_start</code> プロパティを設定します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> aws_cdk <span style="color:#f92672">import</span> (
</span></span><span style="display:flex;"><span>    aws_lambda <span style="color:#66d9ef">as</span> _lambda,
</span></span><span style="display:flex;"><span>    Stack,
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> constructs <span style="color:#f92672">import</span> Construct
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MyCdkStack</span>(Stack):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> __init__(self, scope: Construct, construct_id: str, <span style="color:#f92672">**</span>kwargs) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        super()<span style="color:#f92672">.</span>__init__(scope, construct_id, <span style="color:#f92672">**</span>kwargs)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        _lambda<span style="color:#f92672">.</span>Function(
</span></span><span style="display:flex;"><span>            self, <span style="color:#e6db74">&#34;MySnapStartFunction&#34;</span>,
</span></span><span style="display:flex;"><span>            runtime<span style="color:#f92672">=</span>_lambda<span style="color:#f92672">.</span>Runtime<span style="color:#f92672">.</span>PYTHON_3_12,
</span></span><span style="display:flex;"><span>            handler<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;app.lambda_handler&#34;</span>,
</span></span><span style="display:flex;"><span>            code<span style="color:#f92672">=</span>_lambda<span style="color:#f92672">.</span>Code<span style="color:#f92672">.</span>from_asset(<span style="color:#e6db74">&#34;src&#34;</span>),
</span></span><span style="display:flex;"><span>            snap_start<span style="color:#f92672">=</span>_lambda<span style="color:#f92672">.</span>SnapStartConf<span style="color:#f92672">.</span>ON_PUBLISHED_VERSIONS, <span style="color:#75715e"># この行を追加</span>
</span></span><span style="display:flex;"><span>            current_version_options<span style="color:#f92672">=</span>_lambda<span style="color:#f92672">.</span>VersionOptions(
</span></span><span style="display:flex;"><span>                removal_policy<span style="color:#f92672">=</span>RemovalPolicy<span style="color:#f92672">.</span>RETAIN <span style="color:#75715e"># 本番ではバージョンを保持</span>
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>        )
</span></span></code></pre></td></tr></table>
</div>
</div><p>どの方法でも、設定は非常にシンプルです。アプリケーションコードの変更は、基本的には必要ありません。</p>
<h3 id="メリットとデメリット注意点">メリットとデメリット（注意点）</h3>
<p>SnapStartは魔法のような機能ですが、その特性を理解し、正しく利用することが重要です。</p>
<h4 id="メリット">メリット</h4>
<ol>
<li><strong>劇的なコールドスタート改善</strong>: これが最大のメリットです。最大で10倍の起動高速化が期待でき、ユーザー体験を大きく向上させます。</li>
<li><strong>追加コストなし</strong>: SnapStartを有効にすること自体に追加料金はかかりません。通常のLambdaの料金モデル（リクエスト数と実行時間）のまま、パフォーマンスの恩恵を受けられます。</li>
<li><strong>簡単な導入</strong>: 前述の通り、設定を有効にするだけで利用を開始できます。既存のコードを大幅に書き換える必要はありません。</li>
</ol>
<h4 id="デメリットと利用上の注意点">デメリットと利用上の注意点</h4>
<p>SnapStartはスナップショットという技術に依存するため、いくつかの制約や注意すべき点が存在します。これらを理解しないまま使うと、予期せぬ挙動につながる可能性があります。</p>
<ol>
<li>
<p><strong>$LATESTでは利用不可</strong>: SnapStartは発行済みのバージョンに対してのみ機能します。開発中は<code>$LATEST</code>を使いがちですが、SnapStartのテストや本番運用では必ずバージョンを発行する必要があります。エイリアスを使ってバージョンを管理するプラクティスが推奨されます。</p>
</li>
<li>
<p><strong>初期化時のユニークネス（一意性）の欠如</strong>: Initフェーズで生成されたデータは、スナップショットに固定されます。そのため、そのスナップショットから復元された全ての実行環境は、<strong>全く同じ初期状態</strong>を持つことになります。</p>
<ul>
<li><strong>問題となる例</strong>: InitフェーズでUUIDや乱数を生成し、それを後続の処理でユニークなIDとして利用しようとすると、全ての実行環境で同じIDが使われてしまい、問題を引き起こします。</li>
<li><strong>対策</strong>: ユニークな値が必要な場合は、必ずInvokeフェーズ（ハンドラ関数内）で生成するようにしてください。</li>
</ul>
</li>
<li>
<p><strong>初期化時のネットワーク接続</strong>: スナップショット作成時（Initフェーズ）に確立されたネットワーク接続は、スナップショットに含まれません。復元後（Invokeフェーズ）にその接続を使おうとすると、既に切断されているためエラーになります。</p>
<ul>
<li><strong>問題となる例</strong>: グローバルスコープでデータベースへのTCPコネクションを確立し、それをハンドラ関数で使い回そうとするケース。</li>
<li><strong>対策</strong>: ネットワーク接続の確立は、ハンドラ関数内で行うか、後述するランタイムフックを利用して復元後に再確立する必要があります。</li>
</ul>
</li>
<li>
<p><strong>スナップショットの鮮度 (Freshness)</strong>: Initフェーズで外部から設定値やデータを取得した場合、そのデータはスナップショット作成時点のものに固定されます。</p>
<ul>
<li><strong>問題となる例</strong>: InitフェーズでAWS Systems Manager Parameter Storeから設定値を読み込む場合、バージョンを発行した後にParameter Storeの値を更新しても、古い設定値を使い続けてしまいます。</li>
<li><strong>対策</strong>: 呼び出しごとに最新である必要があるデータは、必ずInvokeフェーズで取得するようにしましょう。</li>
</ul>
</li>
<li>
<p><strong>スナップショット作成時間の増加</strong>: Init処理が重ければ重いほど、バージョン発行からスナップショットが利用可能になるまでの時間が長くなります。デプロイパイプラインに組み込む際は、この遅延を考慮する必要があります。</p>
</li>
</ol>
<h4 id="snapstart-vs-provisioned-concurrency">SnapStart vs. Provisioned Concurrency</h4>
<p>ここで、既存のコールドスタート対策であるProvisioned Concurrencyとの比較を整理しておきましょう。</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">項目</th>
          <th style="text-align: left">Lambda SnapStart</th>
          <th style="text-align: left">Provisioned Concurrency</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><strong>コールドスタート</strong></td>
          <td style="text-align: left">ほぼ解消（ミリ秒単位の復元時間）</td>
          <td style="text-align: left">完全に解消（即時実行）</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>コスト</strong></td>
          <td style="text-align: left"><strong>無料</strong>（通常のLambda料金のみ）</td>
          <td style="text-align: left"><strong>有料</strong>（アイドル時も課金）</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>設定の容易さ</strong></td>
          <td style="text-align: left">非常に簡単（有効化のみ）</td>
          <td style="text-align: left">事前のキャパシティ計画が必要</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>スケーラビリティ</strong></td>
          <td style="text-align: left">通常のLambdaと同様に自動スケール</td>
          <td style="text-align: left">設定した同時実行数が上限</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>最適なユースケース</strong></td>
          <td style="text-align: left">断続的・予測不能なトラフィック</td>
          <td style="text-align: left">継続的・予測可能なトラフィック</td>
      </tr>
      <tr>
          <td style="text-align: left"><strong>デプロイ速度</strong></td>
          <td style="text-align: left">スナップショット作成の遅延あり</td>
          <td style="text-align: left">高速</td>
      </tr>
  </tbody>
</table>
<p><strong>結論として、多くのユースケースにおいて、SnapStartがコストパフォーマンスに優れた第一選択肢となります。</strong> 1ミリ秒の遅延も許されない非常に厳しい要件がある場合に限り、Provisioned Concurrencyを検討するという使い分けになるでしょう。</p>
<h3 id="現場で使える実践的なtips">現場で使える実践的なTips</h3>
<p>SnapStartの仕組みと注意点を理解した上で、さらに一歩進んで、現場で効果的に活用するためのTipsを紹介します。</p>
<h4 id="1-ランタイムフックを使いこなす-before_checkpoint--after_restore">1. ランタイムフックを使いこなす (<code>before_checkpoint</code> / <code>after_restore</code>)</h4>
<p>SnapStart for Pythonは、スナップショット作成前と復元後に特定の処理を差し込める「ランタイムフック」を提供しています。これは、SnapStartの注意点を克服し、より高度な制御を行うための非常に強力な機能です。</p>
<p>フックを登録するには、<code>sys</code>モジュールなどを汚染しない形で、特定のライブラリ（例えば<code>checkpoint_hooks</code>という名前）を作成し、その中にフック関数を定義します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#75715e"># src/checkpoint_hooks.py</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> logging
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> time
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> os
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> psycopg2 <span style="color:#75715e"># 例としてPostgreSQLのドライバ</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>logger <span style="color:#f92672">=</span> logging<span style="color:#f92672">.</span>getLogger()
</span></span><span style="display:flex;"><span>logger<span style="color:#f92672">.</span>setLevel(logging<span style="color:#f92672">.</span>INFO)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># グローバル変数としてコネクションを保持</span>
</span></span><span style="display:flex;"><span>db_connection <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">get_db_connection</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># 実際にはSecrets Managerなどから認証情報を取得する</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> psycopg2<span style="color:#f92672">.</span>connect(
</span></span><span style="display:flex;"><span>        host<span style="color:#f92672">=</span>os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;DB_HOST&#34;</span>),
</span></span><span style="display:flex;"><span>        dbname<span style="color:#f92672">=</span>os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;DB_NAME&#34;</span>),
</span></span><span style="display:flex;"><span>        user<span style="color:#f92672">=</span>os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;DB_USER&#34;</span>),
</span></span><span style="display:flex;"><span>        password<span style="color:#f92672">=</span>os<span style="color:#f92672">.</span>environ<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;DB_PASSWORD&#34;</span>)
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># --- Runtime Hooks ---</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">before_checkpoint</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;スナップショット作成直前に呼ばれるフック&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Hook: before_checkpoint is called.&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">global</span> db_connection
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># もしInitフェーズでテスト用にコネクションを確立していた場合、</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># スナップショットに含めないようにここで閉じておく</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> db_connection <span style="color:#f92672">and</span> <span style="color:#f92672">not</span> db_connection<span style="color:#f92672">.</span>closed:
</span></span><span style="display:flex;"><span>        logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Closing DB connection before checkpointing.&#34;</span>)
</span></span><span style="display:flex;"><span>        db_connection<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">after_restore</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;スナップショットからの復元直後に呼ばれるフック&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Hook: after_restore is called.&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">global</span> db_connection
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># 復元後に新しいDBコネクションを確立する</span>
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Re-establishing DB connection after restoring.&#34;</span>)
</span></span><span style="display:flex;"><span>    db_connection <span style="color:#f92672">=</span> get_db_connection()
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;DB connection established successfully.&#34;</span>)
</span></span></code></pre></td></tr></table>
</div>
</div><p>そして、このフックをLambda関数に登録するために、環境変数 <code>AWS_LAMBDA_RUNTIME_HOOKS_PREPEND</code> を設定します。値は、フックを含むPythonモジュールのドット表記パスです。</p>
<p><code>template.yaml</code> の例:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>      <span style="color:#f92672">Environment</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">Variables</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">AWS_LAMBDA_RUNTIME_HOOKS_PREPEND</span>: <span style="color:#ae81ff">checkpoint_hooks</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>この例では、<code>before_checkpoint</code>で安全のためにコネクションを閉じ、<code>after_restore</code>で新しいコネクションを確立しています。これにより、「初期化時のネットワーク接続」の問題をエレガントに解決できます。他にも、一時ファイルのクリーンアップや、一時的な認証情報の更新など、様々な用途が考えられます。</p>
<h4 id="2-初期化処理を積極的にinitフェーズに寄せる">2. 初期化処理を積極的にInitフェーズに寄せる</h4>
<p>SnapStartの恩恵を最大化するための基本戦略は、**「重たい初期化処理を可能な限りInitフェーズ（ハンドラ外のグローバルスコープ）に移動させる」**ことです。</p>
<p><strong>Bad Example (ハンドラ内で毎回初期化)</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> json
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> logging
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>logger <span style="color:#f92672">=</span> logging<span style="color:#f92672">.</span>getLogger()
</span></span><span style="display:flex;"><span>logger<span style="color:#f92672">.</span>setLevel(logging<span style="color:#f92672">.</span>INFO)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ハンドラ内で重いライブラリをインポートしたり、設定を読み込んでいる</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">lambda_handler</span>(event, context):
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd <span style="color:#75715e"># 重いライブラリ</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># 毎回設定を読み込む</span>
</span></span><span style="display:flex;"><span>    config <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;value&#34;</span>} 
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Handler is invoked.&#34;</span>)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;statusCode&#34;</span>: <span style="color:#ae81ff">200</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;body&#34;</span>: json<span style="color:#f92672">.</span>dumps(<span style="color:#e6db74">&#39;Hello from Lambda!&#39;</span>),
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></td></tr></table>
</div>
</div><p><strong>Good Example (Initフェーズで初期化)</strong></p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> json
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> logging
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd <span style="color:#75715e"># グローバルスコープでインポート</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>logger <span style="color:#f92672">=</span> logging<span style="color:#f92672">.</span>getLogger()
</span></span><span style="display:flex;"><span>logger<span style="color:#f92672">.</span>setLevel(logging<span style="color:#f92672">.</span>INFO)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># グローバルスコープで設定を読み込む</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># この処理はスナップショット作成時に1回だけ実行される</span>
</span></span><span style="display:flex;"><span>logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Initializing function... Loading config and libraries.&#34;</span>)
</span></span><span style="display:flex;"><span>config <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;key&#34;</span>: <span style="color:#e6db74">&#34;value&#34;</span>} 
</span></span><span style="display:flex;"><span>logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Initialization complete.&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">lambda_handler</span>(event, context):
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Initフェーズで準備したものを利用するだけ</span>
</span></span><span style="display:flex;"><span>    logger<span style="color:#f92672">.</span>info(<span style="color:#e6db74">&#34;Handler is invoked.&#34;</span>)
</span></span><span style="display:flex;"><span>    df <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>DataFrame([<span style="color:#ae81ff">1</span>,<span style="color:#ae81ff">2</span>,<span style="color:#ae81ff">3</span>]) <span style="color:#75715e"># ライブラリをすぐに使える</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;statusCode&#34;</span>: <span style="color:#ae81ff">200</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;body&#34;</span>: json<span style="color:#f92672">.</span>dumps(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;Hello from Lambda! Config is </span><span style="color:#e6db74">{</span>config<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>),
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>Good Example</code> の場合、<code>pandas</code>のインポートや<code>config</code>の読み込みにかかる時間は、スナップショット作成時に吸収されます。ユーザーリクエスト時には、これらの処理は既に完了しているため、ハンドラは即座に実行を開始できます。</p>
<h4 id="3-cloudwatch-logsでパフォーマンスを確認する">3. CloudWatch Logsでパフォーマンスを確認する</h4>
<p>SnapStartが正しく機能しているか、どの程度の効果が出ているかを確認するには、CloudWatch Logsが役立ちます。SnapStartを有効にしたLambda関数では、ログに <code>Restore Duration</code> という新しいメトリクスが出力されるようになります。</p>
<pre tabindex="0"><code>REPORT RequestId: xxxxx-xxxx-xxxx-xxxx-xxxxxxxx
Duration: 15.32 ms   Billed Duration: 16 ms   Memory Size: 128 MB   Max Memory Used: 78 MB
Restore Duration: 35.81 ms  
</code></pre><ul>
<li><strong>Duration</strong>: ハンドラ関数の実行時間。</li>
<li><strong>Restore Duration</strong>: スナップショットから実行環境を復元するのにかかった時間。</li>
</ul>
<p><strong>コールドスタート時の合計起動時間（ユーザーが体感する遅延）は、およそ <code>Restore Duration + Duration</code></strong> となります。
一方、SnapStartが無効な場合のコールドスタートでは、ログに <code>Init Duration</code> が表示されます。</p>
<pre tabindex="0"><code>REPORT RequestId: yyyyy-yyyy-yyyy-yyyy-yyyyyyyy
Duration: 12.45 ms   Billed Duration: 13 ms   Memory Size: 128 MB   Max Memory Used: 79 MB
Init Duration: 452.18 ms 
</code></pre><p>この例では、SnapStartによって初期化時間が <code>452.18 ms</code> から <code>35.81 ms</code> に短縮されたことが分かります。このように、ログを比較することでSnapStartの効果を定量的に測定できます。</p>
<h4 id="4-aws-x-rayで詳細なトレースを行う">4. AWS X-Rayで詳細なトレースを行う</h4>
<p>より複雑なアプリケーションでは、AWS X-Rayを使ってパフォーマンスのボトルネックを可視化することが有効です。X-RayはSnapStartにも対応しており、トレース情報に復元フェーズ（<code>restore</code>）が含まれるようになります。これにより、アプリケーション全体のどこで時間がかかっているのかを、より詳細に分析できます。</p>
<h3 id="まとめ">まとめ</h3>
<p>本記事では、AWS Lambdaのコールドスタート問題に対する画期的な解決策である「SnapStart for Python」について、その仕組みから実践的な活用方法までを詳細に解説しました。</p>
<p><strong>重要なポイントを振り返りましょう:</strong></p>
<ul>
<li><strong>SnapStartは、Initフェーズ完了後の実行環境をスナップショットとして保存し、呼び出し時に高速に復元することで、コールドスタートを劇的に改善します。</strong></li>
<li><strong>追加コストは不要で、設定も非常に簡単です。</strong></li>
<li><strong>バージョン管理が必須であり、$LATESTでは利用できません。</strong></li>
<li><strong>初期化時の一意性、ネットワーク接続、データの鮮度には注意が必要ですが、ランタイムフック (<code>before_checkpoint</code>/<code>after_restore</code>) を活用することで、これらの課題に対応できます。</strong></li>
<li><strong>重たい初期化処理をInitフェーズに集約することが、SnapStartの効果を最大化する鍵です。</strong></li>
</ul>
<p>Lambda SnapStart for Pythonは、これまでパフォーマンスの観点からLambdaの採用をためらっていたような、よりレイテンシに敏感なアプリケーションへの道を開くゲームチェンジャーです。特に、API Gatewayと連携する同期的なAPI、インタラクティブなWebアプリケーション、データ処理パイプラインの初段など、多岐にわたるユースケースでその真価を発揮するでしょう。</p>
<p>コールドスタートという長年の課題に対する、これほど強力かつ低コストなソリューションは他にありません。あなたのPythonサーバーレスアプリケーションのパフォーマンスを、今日から次のレベルへと引き上げてみませんか？ぜひ、ご自身のプロジェクトでLambda SnapStartを試し、その驚異的な効果を体感してみてください。</p>
]]></content:encoded>
      <category>Cloud</category>
      <category>AWS</category>
      <category>Lambda</category>
      <category>Python</category>
      <category>Serverless</category>
    </item>
  </channel>
</rss>
