<?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>Jenkins on Tarragon</title><link>https://tarrragon.github.io/blog/tags/jenkins/</link><description>Recent content in Jenkins on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 19 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/jenkins/index.xml" rel="self" type="application/rss+xml"/><item><title>Jenkins → GitHub Actions：Pipeline 5 段 lifecycle 的對位 + 翻譯</title><link>https://tarrragon.github.io/blog/backend/06-reliability/vendors/github-actions/migrate-from-jenkins/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/06-reliability/vendors/github-actions/migrate-from-jenkins/</guid><description>&lt;blockquote>
&lt;p>本文是跨 vendor migration playbook、cross-link &lt;a href="https://www.jenkins.io/">Jenkins&lt;/a> 跟 &lt;a href="https://tarrragon.github.io/blog/backend/06-reliability/vendors/github-actions/" data-link-title="GitHub Actions" data-link-desc="GitHub 原生 CI/CD、PR check、deploy gate">GitHub Actions&lt;/a>。跑 &lt;a href="https://tarrragon.github.io/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration-playbook-methodology 6 維 audit&lt;/a> 後對映 &lt;em>Schema = High（Groovy DSL ↔ YAML workflow）→ Type A phased translation&lt;/em>。&lt;/p>&lt;/blockquote>
&lt;h2 id="pipeline-5-段-lifecycle-的對位--翻譯">Pipeline 5 段 lifecycle 的對位 + 翻譯&lt;/h2>
&lt;p>本文按 &lt;em>pipeline lifecycle 5 段&lt;/em> 組織內容（variant E）— 不是「為什麼遷」driver 開頭，是 &lt;em>Jenkins vs GHA 對 5 段各自的處理&lt;/em>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Lifecycle 段&lt;/th>
 &lt;th>Jenkins 機制&lt;/th>
 &lt;th>GHA 機制&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>1. Source / SCM&lt;/td>
 &lt;td>SCM polling / webhook trigger&lt;/td>
 &lt;td>&lt;code>on: [push, pull_request]&lt;/code> event&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>2. Build / Package&lt;/td>
 &lt;td>&lt;code>stage('Build') { sh 'mvn package' }&lt;/code>&lt;/td>
 &lt;td>&lt;code>jobs.build.steps[].run: mvn package&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>3. Test / 並行 matrix&lt;/td>
 &lt;td>&lt;code>parallel { ... }&lt;/code> + agents&lt;/td>
 &lt;td>&lt;code>jobs.test.strategy.matrix: ...&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>4. Security scan&lt;/td>
 &lt;td>Plugin（Snyk / SonarQube / Aqua）&lt;/td>
 &lt;td>Action（snyk/actions / sonarsource-actions）&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>5. Deploy / promote&lt;/td>
 &lt;td>Deploy plugin + approval gate&lt;/td>
 &lt;td>&lt;code>environment: production&lt;/code> + reviewer approval&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>跑 &lt;a href="https://tarrragon.github.io/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6 維 diff dimension audit&lt;/a>：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>維度&lt;/th>
 &lt;th>評估&lt;/th>
 &lt;th>等級&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Schema / API&lt;/td>
 &lt;td>Groovy DSL ↔ YAML、syntax 完全不同&lt;/td>
 &lt;td>&lt;strong>High&lt;/strong>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Operational model&lt;/td>
 &lt;td>Self-hosted Jenkins → GHA SaaS / self-hosted runners&lt;/td>
 &lt;td>Medium&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Paradigm&lt;/td>
 &lt;td>Imperative pipeline → declarative workflow + events&lt;/td>
 &lt;td>Medium&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Components&lt;/td>
 &lt;td>Jenkins + plugins → GHA + actions marketplace&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Application change&lt;/td>
 &lt;td>Build script 多數不改、CI integration 端要改&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Data topology&lt;/td>
 &lt;td>同單一 build state&lt;/td>
 &lt;td>Low&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Schema = High（其他 Medium-Low）→ &lt;strong>Type A phased translation&lt;/strong> 為主、加 paradigm + operational 獨立段。&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是跨 vendor migration playbook、cross-link <a href="https://www.jenkins.io/">Jenkins</a> 跟 <a href="/blog/backend/06-reliability/vendors/github-actions/" data-link-title="GitHub Actions" data-link-desc="GitHub 原生 CI/CD、PR check、deploy gate">GitHub Actions</a>。跑 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">migration-playbook-methodology 6 維 audit</a> 後對映 <em>Schema = High（Groovy DSL ↔ YAML workflow）→ Type A phased translation</em>。</p></blockquote>
<h2 id="pipeline-5-段-lifecycle-的對位--翻譯">Pipeline 5 段 lifecycle 的對位 + 翻譯</h2>
<p>本文按 <em>pipeline lifecycle 5 段</em> 組織內容（variant E）— 不是「為什麼遷」driver 開頭，是 <em>Jenkins vs GHA 對 5 段各自的處理</em>：</p>
<table>
  <thead>
      <tr>
          <th>Lifecycle 段</th>
          <th>Jenkins 機制</th>
          <th>GHA 機制</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1. Source / SCM</td>
          <td>SCM polling / webhook trigger</td>
          <td><code>on: [push, pull_request]</code> event</td>
      </tr>
      <tr>
          <td>2. Build / Package</td>
          <td><code>stage('Build') { sh 'mvn package' }</code></td>
          <td><code>jobs.build.steps[].run: mvn package</code></td>
      </tr>
      <tr>
          <td>3. Test / 並行 matrix</td>
          <td><code>parallel { ... }</code> + agents</td>
          <td><code>jobs.test.strategy.matrix: ...</code></td>
      </tr>
      <tr>
          <td>4. Security scan</td>
          <td>Plugin（Snyk / SonarQube / Aqua）</td>
          <td>Action（snyk/actions / sonarsource-actions）</td>
      </tr>
      <tr>
          <td>5. Deploy / promote</td>
          <td>Deploy plugin + approval gate</td>
          <td><code>environment: production</code> + reviewer approval</td>
      </tr>
  </tbody>
</table>
<p>跑 <a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">6 維 diff dimension audit</a>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>評估</th>
          <th>等級</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Schema / API</td>
          <td>Groovy DSL ↔ YAML、syntax 完全不同</td>
          <td><strong>High</strong></td>
      </tr>
      <tr>
          <td>Operational model</td>
          <td>Self-hosted Jenkins → GHA SaaS / self-hosted runners</td>
          <td>Medium</td>
      </tr>
      <tr>
          <td>Paradigm</td>
          <td>Imperative pipeline → declarative workflow + events</td>
          <td>Medium</td>
      </tr>
      <tr>
          <td>Components</td>
          <td>Jenkins + plugins → GHA + actions marketplace</td>
          <td>Low</td>
      </tr>
      <tr>
          <td>Application change</td>
          <td>Build script 多數不改、CI integration 端要改</td>
          <td>Low</td>
      </tr>
      <tr>
          <td>Data topology</td>
          <td>同單一 build state</td>
          <td>Low</td>
      </tr>
  </tbody>
</table>
<p>Schema = High（其他 Medium-Low）→ <strong>Type A phased translation</strong> 為主、加 paradigm + operational 獨立段。</p>
<h2 id="為什麼遷cost--vendor--cloud-native-三條-driver">為什麼遷：cost / vendor / cloud-native 三條 driver</h2>
<ul>
<li><strong>Cost</strong>：Jenkins self-hosted 是「免費 software + 高 ops cost」、GHA 按 minute 計費對中小團隊更便宜</li>
<li><strong>Vendor consolidation</strong>：repository 已在 GitHub、整合進 GHA 省一個外部系統</li>
<li><strong>Cloud-native</strong>：GHA matrix build + reusable workflow 對 cloud-native deploy（K8s / serverless）有 first-class action</li>
</ul>
<h2 id="phase-0audit--classify">Phase 0：Audit + classify</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Jenkins workspace 盤點</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">find . -name <span class="s2">&#34;Jenkinsfile&#34;</span> -o -name <span class="s2">&#34;*.groovy&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># 列所有 pipeline file</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="c1"># 統計 plugin 使用</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1"># Jenkinsfile 內 import / @Library / sh &#34;tool plugin...&#34;</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">grep -rE <span class="s2">&#34;@Library|import|tools\s*\{&#34;</span> Jenkinsfile*
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># 每 pipeline 評估 complexity</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"># - Simple linear pipeline: 1-3 stage、無 shared library</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># - Medium: parallel stage + 2-5 shared library</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># - Complex: 條件分支 + 動態 stage + 10+ plugin / 5+ shared library</span></span></span></code></pre></div><p>Audit output：</p>
<ul>
<li>列「100 個 pipeline、35 simple / 50 medium / 15 complex」</li>
<li>每 complexity level 估翻譯時間（simple 0.5 day / medium 2 day / complex 5-10 day）</li>
<li>Plugin 依賴清單對應 GHA action 替代品</li>
</ul>
<h2 id="phase-1schema-對位groovy-dsl--yaml">Phase 1：Schema 對位（Groovy DSL ↔ YAML）</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// Jenkins Declarative Pipeline
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="n">pipeline</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">  <span class="n">agent</span> <span class="o">{</span> <span class="n">label</span> <span class="s1">&#39;docker-build&#39;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="n">stages</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="n">stage</span><span class="o">(</span><span class="s1">&#39;Test&#39;</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">      <span class="n">parallel</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">stage</span><span class="o">(</span><span class="s1">&#39;Unit&#39;</span><span class="o">)</span> <span class="o">{</span> <span class="n">steps</span> <span class="o">{</span> <span class="n">sh</span> <span class="s1">&#39;mvn test&#39;</span> <span class="o">}</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">stage</span><span class="o">(</span><span class="s1">&#39;Integration&#39;</span><span class="o">)</span> <span class="o">{</span> <span class="n">steps</span> <span class="o">{</span> <span class="n">sh</span> <span class="s1">&#39;mvn verify&#39;</span> <span class="o">}</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">      <span class="o">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="o">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="n">post</span> <span class="o">{</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">failure</span> <span class="o">{</span> <span class="n">mail</span> <span class="nl">to:</span> <span class="s1">&#39;devops@&#39;</span><span class="o">,</span> <span class="nl">subject:</span> <span class="s1">&#39;Build failed&#39;</span> <span class="o">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="o">}</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="o">}</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># GHA Workflow 對等</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">CI</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="nt">on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">push]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="nt">jobs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">  </span><span class="nt">test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">self-hosted, docker-build]</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">strategy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">      </span><span class="nt">matrix</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">        </span><span class="nt">suite</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">unit, integration]</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">actions/checkout@v4</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Run ${{ matrix.suite }}</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">        </span><span class="nt">run</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="sd">          case &#34;${{ matrix.suite }}&#34; in
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="sd">            unit) mvn test ;;
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="sd">            integration) mvn verify ;;
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="sd">          esac</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">  </span><span class="nt">notify-failure</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">    </span><span class="nt">needs</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">    </span><span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">failure()</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">    </span><span class="nt">runs-on</span><span class="p">:</span><span class="w"> </span><span class="l">ubuntu-latest</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">    </span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">      </span>- <span class="nt">uses</span><span class="p">:</span><span class="w"> </span><span class="l">dawidd6/action-send-mail@v3</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">        </span><span class="nt">with</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">          </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="l">devops@</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">          </span><span class="nt">subject</span><span class="p">:</span><span class="w"> </span><span class="l">Build failed</span></span></span></code></pre></div><p>對位差異：</p>
<ul>
<li><code>parallel { ... }</code> → <code>strategy.matrix</code>（粒度不同、matrix 是「同 step 不同參數」、parallel 是「不同 step」）</li>
<li><code>post.failure</code> → 獨立 job + <code>if: failure()</code></li>
<li><code>@Library</code> shared library → reusable workflow（<code>uses: ./.github/workflows/reusable.yml</code>）</li>
<li>Jenkins <code>tools { jdk 'java17' }</code> → setup-java action（手動配 toolchain）</li>
</ul>
<h2 id="phase-2translation-pipeline3-tier-hybrid">Phase 2：Translation pipeline（3-tier hybrid）</h2>
<p>對應 <a href="/blog/backend/07-security-data-protection/vendors/splunk/migrate-to-elastic-security/" data-link-title="Splunk → Elastic Security Detection Rule Migration：6 段 phased playbook 跟 5 大踩雷" data-link-desc="從 Splunk Enterprise Security 遷到 Elastic Security 的 detection rule translation playbook：SPL ↔ KQL/ES|QL schema 對位、AI-assisted translation pipeline、parallel run 比對、cutover routing、5 個 production 踩雷（macro 沒對應 / time zone 差異 / summary index 不對位 / alert dedup key 衝突 / 過早 decommission）、capacity / cost 對照">Splunk → Elastic translation</a> 同 3-tier：</p>
<ul>
<li><strong>Tier 1</strong>：community tool（jenkins-to-actions converter、cover 簡單 pipeline 30-50%）</li>
<li><strong>Tier 2</strong>：LLM-assisted（Claude / GPT 翻 medium complexity、人工 verify）</li>
<li><strong>Tier 3</strong>：manual（shared library 改 reusable workflow / conditional 動態 stage 重寫）</li>
</ul>
<h2 id="phase-3parallel-run雙-ci-跑-4-8-週">Phase 3：Parallel run（雙 CI 跑 4-8 週）</h2>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">Repository ──┬─→ Jenkins webhook ──→ Jenkinsfile pipeline
</span></span><span class="line"><span class="ln">2</span><span class="cl">             └─→ GitHub Action ────→ .github/workflows/ci.yml
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">Compare:
</span></span><span class="line"><span class="ln">5</span><span class="cl">- 同 commit 兩端結果一致
</span></span><span class="line"><span class="ln">6</span><span class="cl">- Latency / cost / artifact location 對齊</span></span></code></pre></div><p>Diff dashboard 列「test pass rate / build time / failure mode」三 metric、跑到 95%+ 一致才進 cutover。</p>
<h2 id="phase-4cutover--cleanup">Phase 4：Cutover + cleanup</h2>
<ul>
<li>Disable Jenkins webhook</li>
<li>GHA 成 primary CI</li>
<li>Jenkins 留 standby 2 週 fallback</li>
<li>Decommission Jenkins controller + agents</li>
</ul>
<h2 id="production-故障演練">Production 故障演練</h2>
<h3 id="case-1shared-library-equivalencereusable-workflow-表達不足">Case 1：Shared library equivalence、reusable workflow 表達不足</h3>
<p><strong>徵兆</strong>：複雜 Jenkins shared library（含 Groovy class / closure / 動態變數）翻成 reusable workflow 後失準、某些動態邏輯無法表達。</p>
<p><strong>根因</strong>：Jenkins Groovy 是 imperative + 完整 programming language；GHA reusable workflow 是 declarative YAML、limited expressiveness。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>複雜邏輯外包到 script</strong>：reusable workflow 只當 <em>orchestrator</em>、複雜邏輯放 <code>.github/scripts/*.sh</code> 或 <code>actions/javascript-action</code></li>
<li><strong>自定 composite action</strong>：multi-step logic 包進 composite action、reuse 程度比 reusable workflow 高</li>
<li><strong>退役過度設計的 shared library</strong>：trans 過程暴露 90% library code 其實只用 10%</li>
</ol>
<h3 id="case-2ephemeral-workspacebuild-cache-失敗">Case 2：Ephemeral workspace、build cache 失敗</h3>
<p><strong>徵兆</strong>：cutover 後 build time 從 5 分鐘漲到 20 分鐘；Maven / Gradle / node_modules / Docker layer 每次都重抓。</p>
<p><strong>根因</strong>：Jenkins agent workspace persistent、build cache 跨 build 保留；GHA ephemeral runner 每次新 VM、cache 預設沒帶。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong><code>actions/cache@v4</code></strong>：cache key 用 <code>hashFiles('**/pom.xml')</code> 等 lock file、cross-build 復用</li>
<li><strong>Self-hosted runner with cache</strong>：critical pipeline 跑 self-hosted runner、persistent volume</li>
<li><strong>Docker layer cache</strong>：用 <code>docker/build-push-action</code> 配 BuildKit cache、不 rebuild full image</li>
</ol>
<h3 id="case-3plugin-不對等ci-feature-退化">Case 3：Plugin 不對等、CI feature 退化</h3>
<p><strong>徵兆</strong>：Jenkins 用 50+ plugin、GHA action marketplace 找不到對應；team 對 SonarQube quality gate / Jira integration / custom report 等失去 first-class 支援。</p>
<p><strong>根因</strong>：Jenkins plugin ecosystem 20+ 年累積、GHA marketplace 5 年；某些 niche plugin 在 GHA 沒對等 action。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>API-based integration</strong>：用 <code>curl</code> 對 vendor API 直接 call、不依賴 plugin / action</li>
<li><strong>自寫 action</strong>：critical feature 自寫 composite / JavaScript action、publish 到 marketplace</li>
<li><strong>退役舊 plugin</strong>：trans 期間 audit plugin 真實使用、80% 可退役</li>
</ol>
<h3 id="case-4self-hosted-runner-setup--scaling">Case 4：Self-hosted runner setup + scaling</h3>
<p><strong>徵兆</strong>：production workload 需要 GPU / large memory runner；GHA hosted runner spec 不夠、想用 self-hosted runner、發現 scaling / security / monitoring 比 Jenkins agent 複雜。</p>
<p><strong>根因</strong>：GHA self-hosted runner 是 ephemeral、scaling 需要 <em>runner controller</em>（actions-runner-controller on K8s）；跟 Jenkins agent / Kubernetes plugin 對應但 setup 不同。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>actions-runner-controller (ARC)</strong>：K8s-native runner scaling、跟 Jenkins K8s plugin 對應</li>
<li><strong>Runner labels</strong>：用 label 路由 job（<code>runs-on: [self-hosted, gpu, linux]</code>）</li>
<li><strong>Security</strong>：ephemeral runner 用 short-lived token、不跨 job persist secret</li>
</ol>
<h3 id="case-5matrix-build-vs-parallel-stage-表達差">Case 5：Matrix build vs parallel stage 表達差</h3>
<p><strong>徵兆</strong>：Jenkins 有 <em>動態 parallel</em>（runtime 決定要跑哪些 stage、按 input 變動）；GHA matrix 是 <em>static at workflow load time</em>、表達不到。</p>
<p><strong>根因</strong>：GHA matrix 是 declarative、workflow parse 時 expand；runtime 動態決定 stage 需要用 <code>if:</code> condition + 多 job。</p>
<p><strong>修法</strong>：</p>
<ol>
<li><strong>動態 matrix</strong>：用 <code>jobs.set-matrix</code> 先跑一個 job 算 matrix、輸出 JSON、後續 job <code>strategy.matrix: ${{ needs.set-matrix.outputs.matrix }}</code></li>
<li><strong>conditional job</strong>：每個 dynamic stage 寫獨立 job + <code>if:</code> 控制觸發</li>
<li><strong>重設計</strong>：90% 動態邏輯其實可改 static matrix + condition、純 runtime 動態通常是 over-engineering</li>
</ol>
<h2 id="capacity--cost">Capacity / cost</h2>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>Self-managed Jenkins</th>
          <th>GitHub Actions</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Compute cost</td>
          <td>EC2 + agent licenses</td>
          <td>per-minute billing（free tier + over-cap）</td>
      </tr>
      <tr>
          <td>Operational FTE</td>
          <td>0.5-1.5 FTE</td>
          <td>0.1-0.3 FTE</td>
      </tr>
      <tr>
          <td>Plugin / action ecosystem</td>
          <td>20+ 年成熟</td>
          <td>5 年快速成長</td>
      </tr>
      <tr>
          <td>Cold start</td>
          <td>Agent ready &lt; 1 min</td>
          <td>Hosted runner 30-60s spin-up</td>
      </tr>
      <tr>
          <td>Self-hosted scaling</td>
          <td>Jenkins K8s plugin</td>
          <td>ARC（actions-runner-controller）</td>
      </tr>
      <tr>
          <td>Security</td>
          <td>Self-managed VPC + secret</td>
          <td>OIDC + repository secret + environment</td>
      </tr>
      <tr>
          <td>Migration cost</td>
          <td>-</td>
          <td>1-3 FTE × 1-3 個月</td>
      </tr>
  </tbody>
</table>
<p><strong>判讀</strong>：100+ pipeline organization 切 GHA 通常 6-12 月 ROI 持平、之後省 ops cost；&lt; 30 pipeline 早就該切。</p>
<h2 id="整合--下一步">整合 / 下一步</h2>
<h3 id="跟-gitlab-ci-對位">跟 <a href="https://docs.gitlab.com/ee/ci/">GitLab CI</a> 對位</h3>
<p>GitLab CI YAML 語法跟 GHA 接近、shared library 對應 <code>include:</code>、self-hosted runner 對等；Jenkins → GitLab CI migration 流程跟本文鏡像對稱、3-tier translation pipeline 通用。</p>
<h3 id="跟-circle-ci-對位">跟 <a href="/blog/backend/06-reliability/vendors/circleci/" data-link-title="CircleCI" data-link-desc="CI/CD 平台、強 cache 與 parallelism">Circle CI</a> 對位</h3>
<p>CircleCI orb 對等 GHA composite action；跨 SaaS CI 切換比 Jenkins → GHA 簡單（都 YAML-based）。</p>
<h3 id="反向-migrationgha--jenkins">反向 migration（GHA → Jenkins）</h3>
<p>少數 enterprise（金融 / 政府）合規要求 self-hosted CI / on-prem；GHA → Jenkins 鏡像對稱、注意 Jenkins shared library 表達力更強、reusable workflow 內 dynamic 邏輯可不必拆。</p>
<h3 id="下一步議題">下一步議題</h3>
<ul>
<li><strong>Reusable workflow + composite action 混用</strong>：reusable workflow 適合 <em>跨 repo orchestration</em>、composite action 適合 <em>單 repo logic encapsulation</em></li>
<li><strong>OIDC + cloud deploy</strong>：用 OIDC token 取代 long-lived cloud credential、是 GHA migration 順便升級的機會</li>
<li><strong>Cost optimization</strong>：minute-based billing 對 high-volume CI 需要 monitoring + budget alert</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>Target vendor：<a href="/blog/backend/06-reliability/vendors/github-actions/" data-link-title="GitHub Actions" data-link-desc="GitHub 原生 CI/CD、PR check、deploy gate">GitHub Actions</a></li>
<li>平行 vendor：<a href="/blog/backend/06-reliability/vendors/circleci/" data-link-title="CircleCI" data-link-desc="CI/CD 平台、強 cache 與 parallelism">CircleCI</a></li>
<li>平行 migration playbook（Type A）：<a href="/blog/backend/07-security-data-protection/vendors/splunk/migrate-to-elastic-security/" data-link-title="Splunk → Elastic Security Detection Rule Migration：6 段 phased playbook 跟 5 大踩雷" data-link-desc="從 Splunk Enterprise Security 遷到 Elastic Security 的 detection rule translation playbook：SPL ↔ KQL/ES|QL schema 對位、AI-assisted translation pipeline、parallel run 比對、cutover routing、5 個 production 踩雷（macro 沒對應 / time zone 差異 / summary index 不對位 / alert dedup key 衝突 / 過早 decommission）、capacity / cost 對照">Splunk → Elastic Security</a> / <a href="/blog/backend/01-database/vendors/mysql/migrate-to-postgresql/" data-link-title="MySQL → PostgreSQL：從 SQL dialect diff 跑出來的 Type A 6-phase migration" data-link-desc="MySQL → PostgreSQL 是 Type A 高 schema 差 migration 的標準形態 — SQL dialect / collation / case sensitivity / replication 模型差異主導；用 pgloader / AWS DMS / 自管 dual-write 三條 path、5 個 production 踩雷（auto_increment vs SERIAL / charset 跟 collation / case sensitivity / index syntax / triggers）">MySQL → PostgreSQL</a></li>
<li>Methodology：<a href="/blog/posts/migration-playbook-%E6%96%B9%E6%B3%95%E8%AB%96%E7%9A%84%E6%BC%94%E5%8C%96%E7%B4%80%E9%8C%84stage-0-variant-%E8%A6%8F%E5%8A%83%E6%8A%8A-collapse-%E7%8E%87%E5%BE%9E-60-%E9%99%8D%E5%88%B0-0/" data-link-title="Migration Playbook 方法論的演化紀錄：Stage 0 variant 規劃把 collapse 率從 60% 降到 0%" data-link-desc="跨 vendor migration playbook 需要獨立寫作方法論的依據，以及這套方法論從三輪 batch dogfood 中演化出來的驗證證據。">Migration playbook methodology</a></li>
</ul>
]]></content:encoded></item></channel></rss>