<?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>劉奇聖</title>
    <link>https://chishengliu.com/zh-tw/</link>
    <description>Recent content on 劉奇聖</description>
    <image>
      <title>劉奇聖</title>
      <url>https://static.chishengliu.com/ogimage.jpeg</url>
      <link>https://chishengliu.com/zh-tw/</link>
    </image>
    <generator>Hugo</generator>
    <language>zh-TW</language>
    <copyright>2024 劉奇聖</copyright>
    <lastBuildDate>Thu, 04 Jun 2026 03:42:28 -0700</lastBuildDate>
    <atom:link href="https://chishengliu.com/zh-tw/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>SystemExit 使 asyncio.run_coroutine_threadsafe 永遠卡住</title>
      <link>https://chishengliu.com/zh-tw/posts/run-coroutine-threadsafe-systemexit/</link>
      <pubDate>Wed, 05 Mar 2025 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/run-coroutine-threadsafe-systemexit/</guid>
      <description>本文探討與 asyncio.run_coroutine_threadsafe 和 SystemExit 相關的 debug 問題。</description>
      <content:encoded><![CDATA[<p>今天在 debug Ray 的某個 issue 的時候，因為沒注意到 Python 的 <code>asyncio.run_coroutine_threadsafe</code> 的特殊行為，官方 doc 沒有特別說明，要去看 CPython source code 才知道，導致 debug 了有點久的時間，特此紀錄。</p>
<p>根據
<a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.run_coroutine_threadsafe" target="_blank" rel="noopener">官方文檔</a>
，這個 function 是用來把一個 coroutine 跑在另一個 thread 上面的 event loop 裡面，然後他會 return 一個 <code>Future</code> object。</p>
<p>根據文檔我們可以快速的寫出一個 toy example：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">f</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Inside coroutine f()&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">raise</span> <span class="ne">ValueError</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start_loop</span><span class="p">(</span><span class="n">loop</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">set_event_loop</span><span class="p">(</span><span class="n">loop</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">run_forever</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">new_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">thread</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">start_loop</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">loop</span><span class="p">,))</span>
</span></span><span class="line"><span class="cl">    <span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">future</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run_coroutine_threadsafe</span><span class="p">(</span><span class="n">f</span><span class="p">(),</span> <span class="n">loop</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Caught exception:&#34;</span><span class="p">,</span> <span class="nb">repr</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Stopping loop&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">call_soon_threadsafe</span><span class="p">(</span><span class="n">loop</span><span class="o">.</span><span class="n">stop</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">thread</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">main</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>main</code> function 創了一個 thread 和一個 event loop，然後把 coroutine <code>f</code> 用 <code>asyncio.run_coroutine_threadsafe</code> 提交到該 event loop 上面，之後然後用 <code>future.result()</code> 拿結果，並 handle exceptions，最後把 event loop 停掉，看起來沒問題對吧。</p>
<p>執行結果如下：</p>
<pre tabindex="0"><code>Inside coroutine f()
Caught exception: ValueError()
Stopping loop
</code></pre><p>現在問題來了，如果我們在 coroutine <code>f</code> 裡面 raise 的不是 <code>ValueError</code> 而是 <code>SystemExit</code> 的話會怎麼樣？</p>
<p>根據
<a href="https://docs.python.org/3/library/exceptions.html#SystemExit" target="_blank" rel="noopener">官方文檔</a>
，<code>SystemExit</code> 是繼承 <code>BaseException</code> 而不是 <code>Exception</code>，所以我們把第 21 行的 <code>Exception</code> 換成 <code>BaseException</code> 就好了？你會發現你改完這兩個地方之後再執行一次程式它會在 print 完 <code>Inside coroutine f()</code> 之後 hang 住，永遠不會結束。</p>
<p>我們必須去看 
<a href="https://github.com/python/cpython/blob/e53d105872fafa77507ea33b7ecf0faddd4c3b60/Lib/asyncio/tasks.py#L991-L1011" target="_blank" rel="noopener"><code>asyncio.run_coroutine_threadsafe</code> 的 source code</a>
 才知道到底發生了什麼事情，我把該程式碼貼在這邊：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">run_coroutine_threadsafe</span><span class="p">(</span><span class="n">coro</span><span class="p">,</span> <span class="n">loop</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Submit a coroutine object to a given event loop.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    Return a concurrent.futures.Future to access the result.
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">coroutines</span><span class="o">.</span><span class="n">iscoroutine</span><span class="p">(</span><span class="n">coro</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="s1">&#39;A coroutine object is required&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">future</span> <span class="o">=</span> <span class="n">concurrent</span><span class="o">.</span><span class="n">futures</span><span class="o">.</span><span class="n">Future</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">callback</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">futures</span><span class="o">.</span><span class="n">_chain_future</span><span class="p">(</span><span class="n">ensure_future</span><span class="p">(</span><span class="n">coro</span><span class="p">,</span> <span class="n">loop</span><span class="o">=</span><span class="n">loop</span><span class="p">),</span> <span class="n">future</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="p">(</span><span class="ne">SystemExit</span><span class="p">,</span> <span class="ne">KeyboardInterrupt</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">BaseException</span> <span class="k">as</span> <span class="n">exc</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">future</span><span class="o">.</span><span class="n">set_running_or_notify_cancel</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">                <span class="n">future</span><span class="o">.</span><span class="n">set_exception</span><span class="p">(</span><span class="n">exc</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">call_soon_threadsafe</span><span class="p">(</span><span class="n">callback</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">future</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>我們可以發現原來是這個 function 對於 <code>SystemExit</code> 和 <code>KeyboardInterrupt</code> 這兩個 exception 有特殊處理，是直接 raise，對於其他的 exception 則是會 call <code>future.set_exception</code>，因為他沒有 call <code>set_exception</code>，所以 <code>future.result()</code> 永遠拿不到結果。</p>
<p>我們稍微改寫一下原程式：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">f</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Inside coroutine f()&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">raise</span> <span class="ne">SystemExit</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start_loop</span><span class="p">(</span><span class="n">loop</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">asyncio</span><span class="o">.</span><span class="n">set_event_loop</span><span class="p">(</span><span class="n">loop</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">run_forever</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">new_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">thread</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">start_loop</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">(</span><span class="n">loop</span><span class="p">,))</span>
</span></span><span class="line"><span class="cl">    <span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">future</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">run_coroutine_threadsafe</span><span class="p">(</span><span class="n">f</span><span class="p">(),</span> <span class="n">loop</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">TimeoutError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Timeout&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Future done?&#34;</span><span class="p">,</span> <span class="n">future</span><span class="o">.</span><span class="n">done</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Future cancelled?&#34;</span><span class="p">,</span> <span class="n">future</span><span class="o">.</span><span class="n">cancelled</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Loop alive?&#34;</span><span class="p">,</span> <span class="n">loop</span><span class="o">.</span><span class="n">is_running</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Thread alive?&#34;</span><span class="p">,</span> <span class="n">thread</span><span class="o">.</span><span class="n">is_alive</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">BaseException</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Caught exception:&#34;</span><span class="p">,</span> <span class="nb">repr</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Stopping loop&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">call_soon_threadsafe</span><span class="p">(</span><span class="n">loop</span><span class="o">.</span><span class="n">stop</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">thread</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">main</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>執行結果：</p>
<pre tabindex="0"><code>Inside coroutine f()
Timeout
Future done? False
Future cancelled? False
Loop alive? False
Thread alive? False
Stopping loop
Task exception was never retrieved
future: &lt;Task finished name=&#39;Task-1&#39; coro=&lt;f() done, defined at /home/mortalhappiness/test/test.py:5&gt; exception=SystemExit()&gt;
Traceback (most recent call last):
  File &#34;/home/mortalhappiness/miniforge3/envs/test/lib/python3.12/threading.py&#34;, line 1075, in _bootstrap_inner
    self.run()
  File &#34;/home/mortalhappiness/miniforge3/envs/test/lib/python3.12/threading.py&#34;, line 1012, in run
    self._target(*self._args, **self._kwargs)
  File &#34;/home/mortalhappiness/test/test.py&#34;, line 11, in start_loop
    loop.run_forever()
  File &#34;/home/mortalhappiness/miniforge3/envs/test/lib/python3.12/asyncio/base_events.py&#34;, line 641, in run_forever
    self._run_once()
  File &#34;/home/mortalhappiness/miniforge3/envs/test/lib/python3.12/asyncio/base_events.py&#34;, line 1986, in _run_once
    handle._run()
  File &#34;/home/mortalhappiness/miniforge3/envs/test/lib/python3.12/asyncio/events.py&#34;, line 88, in _run
    self._context.run(self._callback, *self._args)
  File &#34;/home/mortalhappiness/test/test.py&#34;, line 7, in f
    raise SystemExit
SystemExit
</code></pre><p>可以看到這個行為其實有點搞，因為 <code>future.done()</code> 和 <code>future.cancelled()</code> 都是 <code>False</code>，而 call <code>future.result()</code> 和 <code>future.exception()</code> 都會 hang 住不會有結果，但是你不 call 的話還會像上面一樣跳一個 warning 給你說 task exception was never retrieved，然後 event loop 和 thread 都還掛了。</p>
<h2 id="結論">結論</h2>
<p>如果用 <code>asyncio.run_coroutine_threadsafe</code> 去執行 coroutine，而該 coroutine raise 了 <code>SystemExit</code> 或是 <code>KeyboardInterrupt</code> 的話，call <code>future.result()</code> 和 <code>future.exception()</code> 是沒用的，會永遠 hang 住。另外 thread 和 event loop 都會死掉，但是 main thread 不會死。</p>
]]></content:encoded>
    </item>
    <item>
      <title>不尋常的找工作方式：我透過貢獻 KubeRay 開源專案找到美國跨國遠端軟體工程師職缺！</title>
      <link>https://chishengliu.com/zh-tw/posts/join-anyscale-via-kuberay/</link>
      <pubDate>Sun, 06 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/join-anyscale-via-kuberay/</guid>
      <description>這次我來分享我如何透過貢獻 KubeRay 開源專案，成功獲得 Anyscale 遠端軟體工程師職位。我會介紹貢獻開源專案是怎麼帶給我這次機遇的，但同時也會提醒大家這不是尋常路，有很多限制，不要想著要複製我的過程，因為除了需要靠個人努力之外，不免也需要機運。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>這次很高興透過貢獻 
<a href="https://github.com/ray-project/kuberay" target="_blank" rel="noopener">KubeRay</a>
 這個開源專案，得到了加入 









  


<a href="https://www.anyscale.com/" target="_blank" rel="noopener noreferrer nofollow">Anyscale</a>
 遠端工作的機會。</p>
<p>在你繼續看下去之前，先給一些免責聲明，澆一下冷水，避免你看完期望太高。很多人都只寫好的一面，我不喜歡這樣。如果看完你還是想透過做開源這條路找到工作的話再看，否則其實已經可以離開了：</p>
<ul>
<li>我是透過一位已經在 Anyscale 上班的朋友內推進去的。至於我是怎麼認識他，以及為何可以內推，會在內文說明。</li>
<li>KubeRay 是 Anyscale 這間公司的產品之一。這代表你要去的公司跟你做的開源專案必須要有很強的相關性才有機會，不要想說隨便做一個開源就對找所有工作都有加分效果。</li>
<li>我還是需要面試，請不要覺得做開源就可以不用刷 LeetCode。你可能想問有人不用面試的嗎？有，我就有認識，但是不是每個人都可以這樣的。</li>
<li>我不是開源新手。如果你是開源新手，請不要預期你可以用跟我一樣短的時間快速上手專案，以免感到挫折。走上開源這條路，必須付出很多業餘的額外時間才有辦法。</li>
<li>這條路絕對不是尋常的路，也不是最輕鬆的路。如果要輕鬆的話，請刷 LeetCode 就好，CP 值絕對最高。如果想找外商工作，乖乖去美國讀書再去面試是最有效的。</li>
</ul>
<h2 id="kuberay-專案介紹">KubeRay 專案介紹</h2>
<p>
<a href="https://github.com/ray-project/kuberay" target="_blank" rel="noopener">KubeRay</a>
 是把 
<a href="https://github.com/ray-project/ray" target="_blank" rel="noopener">Ray</a>
 這個專案跑在 Kubernetes 上面的一個專案。而 Ray 是可以把 Python 的 function 自動跑在分散式的集群，是最近很紅的一個用來跑分散式 AI 和 Python apps 的框架。Ray 的背後商業公司就是 Anyscale。</p>
<p>下圖是今年 Ray Summit 介紹 KubeRay 的投影片的其中一頁，展示了現在社群的一些數字：</p>
<p><img alt="KubeRay Key Numbers" loading="lazy" src="https://static.chishengliu.com/posts/join-anyscale-via-kuberay/kuberay-key-numbers-2024.webp"></p>
<p>下圖是現在 Ray 的一些比較大的公司使用者，除了下圖的使用者之外，還有一些 Vendor 也有採用，例如 Google 的 GCP 和 Amazon 的 AWS。這些 Ray 的使用者裡面，大部分都是用 KubeRay 去部屬的！</p>
<p><img alt="Ray Users" loading="lazy" src="https://static.chishengliu.com/posts/join-anyscale-via-kuberay/ray-users-2024.webp"></p>
<h2 id="我的開源貢獻背景">我的開源貢獻背景</h2>
<p>正如我前面所說，我不是開源新手。在貢獻 KubeRay 之前我已經貢獻過 Apache Submarine 和 Flyte 這兩個專案，而且都已經是 Committer 了。如果不知道 Committer 是什麼，簡單的說就是類似「核心貢獻者」，具有該專案寫入的權限，可以幫忙 merge pull requests。在 Apache Submarine 時期，我的 mentor 就包含了
<a href="https://www.linkedin.com/in/pingsutw/" target="_blank" rel="noopener">蘇桓平</a>
和
<a href="https://www.linkedin.com/in/kaihsun1996/" target="_blank" rel="noopener">陳楷訓</a>
等。當然還有其他 mentor 的幫助，不過本篇特別提這兩位是有原因的。蘇桓平同時也是我在貢獻 Flyte 專案時期的 mentor，而陳楷訓則是我在貢獻 KubeRay 時期的 mentor。</p>
<p>我大約是今年 4 月開始貢獻 KubeRay 的，KubeRay 基本上就是 Kubernetes Operator，這剛好跟我先前貢獻 Apache Submarine 的時候在做一樣的事。所以即使以前做 Apache Submarine 沒有得到工作，就學習技術的角度而言，我確實學到了一個帶著走的技能，而且還可以在其他專案上面用到。4 月到 9 月這期間我貢獻了大約 50 個 commits，雖然有一段是靠刷 linter issue 衝的，但其實這還是算滿多的，如果是開源新手的話不要想說可以有這個速度。</p>
<figure>
    <img loading="lazy" src="https://static.chishengliu.com/posts/join-anyscale-via-kuberay/kuberay-contributor-ranking.webp"
         alt="KubeRay 專案貢獻者排名，我現在第 4 名"/> <figcaption>
            <p>KubeRay 專案貢獻者排名，我現在第 4 名</p>
        </figcaption>
</figure>

<h2 id="內推過程">內推過程</h2>
<p>我在前言裡面提到的已經在 Anyscale 上班的朋友就是陳楷訓，其實我們原本並不認識，最開始也是因為一起做 Apache Submarine 才認識的，所以這是做開源的其中一個好處：「擴展人脈」。其實台灣做開源的人很少，意思就是圈子很小，所以很容易可以認識圈內的人，因為基本上也就那些了。這些人脈就可以幫助到你。即使當初我並沒有因為做了 Apache Submarine 就拿到工作，但試想如果我當初沒有做的話，我也不會認識楷訓，也就不會有現在的工作了。</p>
<p>但為什麼他不能直接幫我內推，而是還需要我貢獻 KubeRay 之後，才能幫我內推呢？這是因為即使我跟他都有貢獻過 Apache Submarine 的經驗，有共事過，他也知道我的能力，但因為 Apache Submarine 這個專案跟 Anyscale 並沒有什麼關係，美國公司聘外國人遠端工作又很麻煩，所以如果做的專案本身沒有和公司有非常強的關聯，基本上沒有什麼加分效果，他去跟主管內推我，主管可能也不會答應給我面試機會。而 KubeRay 則不一樣，這個專案算是他們的產品之一，如果他們聘我的話，等於是獲得一位不用培訓就已經上手的工程師，對他們也有好處，而且他們也可以看到我之前的程式碼，畢竟如果程式碼寫太爛公司也不會想聘你對吧。</p>
<h2 id="你說得這麼困難為何你還要毅然前行">你說得這麼困難，為何你還要毅然前行？</h2>
<ul>
<li>做開源即使沒有拿到工作機會，也可以累積技術。</li>
<li>可以認識其他做開源的人，拓展人脈，或許有一天他們就會幫助到你。</li>
<li>我自己覺得做開源專案比做閉源專案有意義，應該說你的貢獻比較透明，容易被別人看見。</li>
</ul>
<h2 id="我看完了即使我已經知道上面的限制但我還是想鐵頭嘗試要怎麼開始">我看完了，即使我已經知道上面的限制，但我還是想鐵頭嘗試，要怎麼開始？</h2>
<p>
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">源來適你</a>
 是台灣致力於推廣開源的非營利社團，社群內有數位導師分別帶領大家參與貢獻不同的專案，我也是受到了很多社群成員的幫助，非常歡迎大家加入來看看！這邊也有
<a href="https://github.com/opensource4you/readme" target="_blank" rel="noopener">關於源來適你的資料彙整</a>
，可以參考看看！</p>
<h2 id="致謝">致謝</h2>
<p>本次毫無疑問，必須特別感謝
<a href="https://www.linkedin.com/in/kaihsun1996/" target="_blank" rel="noopener">陳楷訓</a>
的幫忙！之後我會繼續貢獻 Ray 和 KubeRay，希望之後對 Ray 熟一點之後，可以來寫一些關於 Ray 的技術文章！</p>
]]></content:encoded>
    </item>
    <item>
      <title>如何使用 client-go 寫一個 Kubernetes Operator</title>
      <link>https://chishengliu.com/zh-tw/posts/kubernetes-operator-sample-controller/</link>
      <pubDate>Sat, 14 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/kubernetes-operator-sample-controller/</guid>
      <description>了解如何使用 client-go 實作 Kubernetes Custom Controller，並透過詳盡的 sample-controller 程式碼解析，深入探討 client-go 與 Controller 的互動。</description>
      <content:encoded><![CDATA[<h2 id="什麼是-client-go">什麼是 client-go？</h2>
<p>
<a href="https://github.com/kubernetes/client-go" target="_blank" rel="noopener">client-go</a>
 是 Kubernetes 官方的 Golang client，負責跟 Kubernetes API server 用 REST API 的方式互動。其實 <code>client-go</code> 基本上可以做任何事情，不只是拿來寫 operator，連 <code>kubectl</code> 內部的實作也是用 <code>client-go</code>。至於更專門拿來寫 operator 的框架，包含 
<a href="https://github.com/kubernetes-sigs/controller-runtime" target="_blank" rel="noopener">controller-runtime</a>
、
<a href="https://github.com/kubernetes-sigs/kubebuilder" target="_blank" rel="noopener">kubebuilder</a>
、
<a href="https://github.com/operator-framework/operator-sdk" target="_blank" rel="noopener">operator-sdk</a>
 會在本系列文的後續介紹。</p>
<h2 id="sample-controller-機制介紹">Sample Controller 機制介紹</h2>
<p>
<a href="https://github.com/kubernetes/sample-controller" target="_blank" rel="noopener">sample-controller</a>
 是 Kubernetes 官方使用 client-go 實作的範例 operator。</p>
<p>為了看懂程式碼，我們必須先了解我們自己寫的 operator 和 <code>client-go</code> 是怎麼互動的。這邊講的東西是
<a href="https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md" target="_blank" rel="noopener">官方文件</a>
的簡化版。</p>
<p><img alt="client-go-controller-interaction" loading="lazy" src="https://static.chishengliu.com/posts/kubernetes-operator-sample-controller/client-go-controller-interaction.webp"></p>
<p>上面這張圖出自官方文件，網路上也可以查到許多教學都會提到這張圖。</p>
<p>上半部是 <code>client-go</code> 裡面的東西，看起來很複雜，有什麼 Reflctor、Informer、Indexer 之類的，但其實你只要知道 Informer 就好了。Informer 最主要的功能就是當 Resource 的狀態發生改變的時候通知我們。為什麼不是我們自己打 API 到 Kubernetes API server 就好了呢？這是因為打 API 到 Kubernetes API server 是一個很貴的操作，因此 Informer 內部有維護一個 Informer Cache，減少打 Requests 到 API server 的次數。</p>
<p>下半部是我們要自己寫的部份：</p>
<ul>
<li><strong>Resource Event Handlers</strong>：當 Informer 通知我們某個 resource 的狀態改變的時候，我們應該做什麼事，基本上就是把它的 key（namespace + name）放進 workqueue 裡面。</li>
<li><strong>Workqueue</strong>：這個會存放所有待處理的 object 的 key，我們的 operator 會不斷的從裡面拿東西出來處理，然後試圖把叢集調整成我們想要的狀態，如果失敗的話可能需要再把這個 object key 加回去 workqueue 待會處理。</li>
</ul>
<h2 id="sample-controller-程式碼解析">Sample Controller 程式碼解析</h2>
<h3 id="定義-crd">定義 CRD</h3>
<p>在 
<a href="https://github.com/kubernetes/sample-controller/blob/master/pkg/apis/samplecontroller/register.go" target="_blank" rel="noopener">register.go</a>
 定義 GroupName，然後 
<a href="https://github.com/kubernetes/sample-controller/blob/master/pkg/apis/samplecontroller/v1alpha1/types.go" target="_blank" rel="noopener">v1alpha1/types.go</a>
 定義 CRD 的 type。可以看到裡面定義了一個 <code>Foo</code> 的資源如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Foo is a specification for a Foo resource</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Foo</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">metav1</span><span class="p">.</span><span class="nx">TypeMeta</span><span class="w">   </span><span class="s">`json:&#34;,inline&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">metav1</span><span class="p">.</span><span class="nx">ObjectMeta</span><span class="w"> </span><span class="s">`json:&#34;metadata,omitempty&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Spec</span><span class="w">   </span><span class="nx">FooSpec</span><span class="w">   </span><span class="s">`json:&#34;spec&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Status</span><span class="w"> </span><span class="nx">FooStatus</span><span class="w"> </span><span class="s">`json:&#34;status&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// FooSpec is the spec for a Foo resource</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">FooSpec</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">DeploymentName</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="s">`json:&#34;deploymentName&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">Replicas</span><span class="w">       </span><span class="o">*</span><span class="kt">int32</span><span class="w"> </span><span class="s">`json:&#34;replicas&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// FooStatus is the status for a Foo resource</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">FooStatus</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">AvailableReplicas</span><span class="w"> </span><span class="kt">int32</span><span class="w"> </span><span class="s">`json:&#34;availableReplicas&#34;`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>除了 <code>TypeMeta</code> 和 <code>ObjectMeta</code> 這些基本的東西之外，多定義了 <code>Spec</code> 和 <code>Status</code>。<code>Spec</code> 是可以給使用者輸入的資料，定義的是「使用者期望這個資源最後該有的狀態」。<code>Status</code> 則是由我們的 Operator 去寫入值，代表的是「目前該資源的狀態」。</p>
<p>Sample Controller 使用了 
<a href="https://github.com/kubernetes/code-generator" target="_blank" rel="noopener">Kubernetes 的 code-generator</a>
 來生成 CRD 的 typed client、informers、listers 和 deep-copy 函數。所以每次改動 <code>types.go</code> 的時候都要執行 <code>./hack/update-codegen.sh</code> 來重新生成。</p>
<h3 id="程式入口點">程式入口點</h3>
<p>接著來看 
<a href="https://github.com/kubernetes/sample-controller/blob/master/main.go" target="_blank" rel="noopener">main.go</a>
，也就是程式的入口點，其實非常簡單，注意這幾行就好：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">kubeClient</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">kubernetes</span><span class="p">.</span><span class="nf">NewForConfig</span><span class="p">(</span><span class="nx">cfg</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">exampleClient</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">clientset</span><span class="p">.</span><span class="nf">NewForConfig</span><span class="p">(</span><span class="nx">cfg</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">kubeInformerFactory</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">kubeinformers</span><span class="p">.</span><span class="nf">NewSharedInformerFactory</span><span class="p">(</span><span class="nx">kubeClient</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="o">*</span><span class="mi">30</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">exampleInformerFactory</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">informers</span><span class="p">.</span><span class="nf">NewSharedInformerFactory</span><span class="p">(</span><span class="nx">exampleClient</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="o">*</span><span class="mi">30</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">controller</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nf">NewController</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">kubeClient</span><span class="p">,</span><span class="w"> </span><span class="nx">exampleClient</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">kubeInformerFactory</span><span class="p">.</span><span class="nf">Apps</span><span class="p">().</span><span class="nf">V1</span><span class="p">().</span><span class="nf">Deployments</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">exampleInformerFactory</span><span class="p">.</span><span class="nf">Samplecontroller</span><span class="p">().</span><span class="nf">V1alpha1</span><span class="p">().</span><span class="nf">Foos</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">controller</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>基本上就是創出 Kubernetes 內建 resource 以及我們自己建的這個 <code>Foo</code> Resource 的 client 和 informers，然後傳給 <code>NewController</code>。之後呼叫 <code>controller.Run</code>。</p>
<h3 id="主要邏輯">主要邏輯</h3>
<p>最後來看最主要的部份，
<a href="https://github.com/kubernetes/sample-controller/blob/master/controller.go" target="_blank" rel="noopener">controller.go</a>
。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">fooInformer</span><span class="p">.</span><span class="nf">Informer</span><span class="p">().</span><span class="nf">AddEventHandler</span><span class="p">(</span><span class="nx">cache</span><span class="p">.</span><span class="nx">ResourceEventHandlerFuncs</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">AddFunc</span><span class="p">:</span><span class="w"> </span><span class="nx">controller</span><span class="p">.</span><span class="nx">enqueueFoo</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">UpdateFunc</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">old</span><span class="p">,</span><span class="w"> </span><span class="nx">new</span><span class="w"> </span><span class="kd">interface</span><span class="p">{})</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">controller</span><span class="p">.</span><span class="nf">enqueueFoo</span><span class="p">(</span><span class="nx">new</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">deploymentInformer</span><span class="p">.</span><span class="nf">Informer</span><span class="p">().</span><span class="nf">AddEventHandler</span><span class="p">(</span><span class="nx">cache</span><span class="p">.</span><span class="nx">ResourceEventHandlerFuncs</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">AddFunc</span><span class="p">:</span><span class="w"> </span><span class="nx">controller</span><span class="p">.</span><span class="nx">handleObject</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">UpdateFunc</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">old</span><span class="p">,</span><span class="w"> </span><span class="nx">new</span><span class="w"> </span><span class="kd">interface</span><span class="p">{})</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">newDepl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">new</span><span class="p">.(</span><span class="o">*</span><span class="nx">appsv1</span><span class="p">.</span><span class="nx">Deployment</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">oldDepl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">old</span><span class="p">.(</span><span class="o">*</span><span class="nx">appsv1</span><span class="p">.</span><span class="nx">Deployment</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">newDepl</span><span class="p">.</span><span class="nx">ResourceVersion</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">oldDepl</span><span class="p">.</span><span class="nx">ResourceVersion</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Periodic resync will send update events for all known Deployments.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Two different versions of the same Deployment will always have different RVs.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">controller</span><span class="p">.</span><span class="nf">handleObject</span><span class="p">(</span><span class="nx">new</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">DeleteFunc</span><span class="p">:</span><span class="w"> </span><span class="nx">controller</span><span class="p">.</span><span class="nx">handleObject</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>這個就是上面講的 event handler，這邊可以註冊 <code>AddFunc</code>、<code>UpdateFunc</code>、<code>DeleteFunc</code>，informer 在偵測到 resource 狀態改變的時候會呼叫相對應的函數。可以看到這邊 <code>fooInformer</code> 都只是簡單的呼叫 <code>enqueueFoo</code>，而 <code>deploymentInformer</code> 都是呼叫 <code>handleObject</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Controller</span><span class="p">)</span><span class="w"> </span><span class="nf">enqueueFoo</span><span class="p">(</span><span class="nx">obj</span><span class="w"> </span><span class="kd">interface</span><span class="p">{})</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">objectRef</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">cache</span><span class="p">.</span><span class="nf">ObjectToName</span><span class="p">(</span><span class="nx">obj</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">utilruntime</span><span class="p">.</span><span class="nf">HandleError</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">c</span><span class="p">.</span><span class="nx">workqueue</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="nx">objectRef</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>enqueueFoo</code> 其實就是把 <code>Foo</code> 這個 object 的 key 存進 workqueue 而已，可以看這裡，非常明顯：</p>
<ul>
<li>
<a href="https://pkg.go.dev/k8s.io/client-go/tools/cache#ObjectToName" target="_blank" rel="noopener">cache.ObjectToName</a>
：輸入一個 object，轉成 <code>ObjectName</code>。</li>
<li>
<a href="https://pkg.go.dev/k8s.io/client-go/tools/cache#ObjectName" target="_blank" rel="noopener">ObjectName</a>
：就是 namespace + name 而已。</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Controller</span><span class="p">)</span><span class="w"> </span><span class="nf">handleObject</span><span class="p">(</span><span class="nx">obj</span><span class="w"> </span><span class="kd">interface</span><span class="p">{})</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">ownerRef</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">metav1</span><span class="p">.</span><span class="nf">GetControllerOf</span><span class="p">(</span><span class="nx">object</span><span class="p">);</span><span class="w"> </span><span class="nx">ownerRef</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// If this object is not owned by a Foo, we should not do anything more</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="c1">// with it.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">ownerRef</span><span class="p">.</span><span class="nx">Kind</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="s">&#34;Foo&#34;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">foo</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">foosLister</span><span class="p">.</span><span class="nf">Foos</span><span class="p">(</span><span class="nx">object</span><span class="p">.</span><span class="nf">GetNamespace</span><span class="p">()).</span><span class="nf">Get</span><span class="p">(</span><span class="nx">ownerRef</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="nx">logger</span><span class="p">.</span><span class="nf">V</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">Info</span><span class="p">(</span><span class="s">&#34;Ignore orphaned object&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;object&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">klog</span><span class="p">.</span><span class="nf">KObj</span><span class="p">(</span><span class="nx">object</span><span class="p">),</span><span class="w"> </span><span class="s">&#34;foo&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">ownerRef</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">			</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">c</span><span class="p">.</span><span class="nf">enqueueFoo</span><span class="p">(</span><span class="nx">foo</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>這個是 <code>handleObject</code> 的節錄，這邊做的事情是檢查這個 deployment 的 owner 是不是 <code>Foo</code>，如果不是的話我們就不管他，是的話就把對應的 <code>Foo</code> 的 key 加進 workqueue。這邊要帶到一個觀念叫做 
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/" target="_blank" rel="noopener">OwnerRefernce</a>
，在 Kubernetes 裡面，某些 object 是其他 object 的 owner，預設行為是當 owner 被刪除的時候，他 own 的其他 object 也會被刪除，例如 ReplicaSet 就是 Pod 的 owner，所以 ReplicaSet 被刪除的時候它管的 pods 也會被刪除。這也是為什麼上面的 <code>fooInformer</code> 沒有加上 <code>DeleteFunc</code> handler 的原因，因為當 <code>Foo</code> 被刪除的時候我們在這裡想做的事就是把他 own 的對應的所有 deployment 刪掉，但因為我們已經把 deployment 的 owner 設成 <code>Foo</code>，當 <code>Foo</code> 被刪掉的時候對應的 deployment 本來就也會被刪掉，所以我們不用特別處理。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Controller</span><span class="p">)</span><span class="w"> </span><span class="nf">Run</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">workers</span><span class="w"> </span><span class="kt">int</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p">&lt;</span><span class="w"> </span><span class="nx">workers</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">go</span><span class="w"> </span><span class="nx">wait</span><span class="p">.</span><span class="nf">UntilWithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">runWorker</span><span class="p">,</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Controller</span><span class="p">)</span><span class="w"> </span><span class="nf">runWorker</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">for</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nf">processNextWorkItem</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>Run</code> 就是 <code>main.go</code> 呼叫 controller 的入口點，會起多個 goruntine 跑 <code>runWorker</code>。<code>runWorker</code> 其實就只是跑一個無窮迴圈不斷執行 <code>processNextWorkItem</code> 而已。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Controller</span><span class="p">)</span><span class="w"> </span><span class="nf">processNextWorkItem</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">objRef</span><span class="p">,</span><span class="w"> </span><span class="nx">shutdown</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">workqueue</span><span class="p">.</span><span class="nf">Get</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Run the syncHandler, passing it the structured reference to the object to be synced.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nf">syncHandler</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">objRef</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">c</span><span class="p">.</span><span class="nx">workqueue</span><span class="p">.</span><span class="nf">Forget</span><span class="p">(</span><span class="nx">objRef</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">logger</span><span class="p">.</span><span class="nf">Info</span><span class="p">(</span><span class="s">&#34;Successfully synced&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;objectName&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">objRef</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">utilruntime</span><span class="p">.</span><span class="nf">HandleErrorWithContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;Error syncing; requeuing for later retry&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;objectReference&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">objRef</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">workqueue</span><span class="p">.</span><span class="nf">AddRateLimited</span><span class="p">(</span><span class="nx">objRef</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>上面是 <code>processNextWorkItem</code> 的節錄，首先就是先從 workqueue 拿一個 object key 出來，然後呼叫 <code>syncHandler</code> 處理，如果成功的話就把它從 workqueue 裡面忘掉，不然就要做 error handling 然後放回去 workqueue 等等處理。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">c</span><span class="w"> </span><span class="o">*</span><span class="nx">Controller</span><span class="p">)</span><span class="w"> </span><span class="nf">syncHandler</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">objectRef</span><span class="w"> </span><span class="nx">cache</span><span class="p">.</span><span class="nx">ObjectName</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Get the Foo resource with this namespace/name</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">foo</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">foosLister</span><span class="p">.</span><span class="nf">Foos</span><span class="p">(</span><span class="nx">objectRef</span><span class="p">.</span><span class="nx">Namespace</span><span class="p">).</span><span class="nf">Get</span><span class="p">(</span><span class="nx">objectRef</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">deploymentName</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">foo</span><span class="p">.</span><span class="nx">Spec</span><span class="p">.</span><span class="nx">DeploymentName</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Get the deployment with the name specified in Foo.spec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">deployment</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">deploymentsLister</span><span class="p">.</span><span class="nf">Deployments</span><span class="p">(</span><span class="nx">foo</span><span class="p">.</span><span class="nx">Namespace</span><span class="p">).</span><span class="nf">Get</span><span class="p">(</span><span class="nx">deploymentName</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// If the resource doesn&#39;t exist, we&#39;ll create it</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">errors</span><span class="p">.</span><span class="nf">IsNotFound</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">deployment</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">kubeclientset</span><span class="p">.</span><span class="nf">AppsV1</span><span class="p">().</span><span class="nf">Deployments</span><span class="p">(</span><span class="nx">foo</span><span class="p">.</span><span class="nx">Namespace</span><span class="p">).</span><span class="nf">Create</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="nf">newDeployment</span><span class="p">(</span><span class="nx">foo</span><span class="p">),</span><span class="w"> </span><span class="nx">metav1</span><span class="p">.</span><span class="nx">CreateOptions</span><span class="p">{</span><span class="nx">FieldManager</span><span class="p">:</span><span class="w"> </span><span class="nx">FieldManager</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// If the Deployment is not controlled by this Foo resource, we should log</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// a warning to the event recorder and return error msg.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">metav1</span><span class="p">.</span><span class="nf">IsControlledBy</span><span class="p">(</span><span class="nx">deployment</span><span class="p">,</span><span class="w"> </span><span class="nx">foo</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">msg</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprintf</span><span class="p">(</span><span class="nx">MessageResourceExists</span><span class="p">,</span><span class="w"> </span><span class="nx">deployment</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">c</span><span class="p">.</span><span class="nx">recorder</span><span class="p">.</span><span class="nf">Event</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span><span class="w"> </span><span class="nx">corev1</span><span class="p">.</span><span class="nx">EventTypeWarning</span><span class="p">,</span><span class="w"> </span><span class="nx">ErrResourceExists</span><span class="p">,</span><span class="w"> </span><span class="nx">msg</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;%s&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">msg</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// If this number of the replicas on the Foo resource is specified, and the</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// number does not equal the current desired replicas on the Deployment, we</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// should update the Deployment resource.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">foo</span><span class="p">.</span><span class="nx">Spec</span><span class="p">.</span><span class="nx">Replicas</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="o">*</span><span class="nx">foo</span><span class="p">.</span><span class="nx">Spec</span><span class="p">.</span><span class="nx">Replicas</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="o">*</span><span class="nx">deployment</span><span class="p">.</span><span class="nx">Spec</span><span class="p">.</span><span class="nx">Replicas</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">logger</span><span class="p">.</span><span class="nf">V</span><span class="p">(</span><span class="mi">4</span><span class="p">).</span><span class="nf">Info</span><span class="p">(</span><span class="s">&#34;Update deployment resource&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;currentReplicas&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="nx">foo</span><span class="p">.</span><span class="nx">Spec</span><span class="p">.</span><span class="nx">Replicas</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;desiredReplicas&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">*</span><span class="nx">deployment</span><span class="p">.</span><span class="nx">Spec</span><span class="p">.</span><span class="nx">Replicas</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="nx">deployment</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nx">kubeclientset</span><span class="p">.</span><span class="nf">AppsV1</span><span class="p">().</span><span class="nf">Deployments</span><span class="p">(</span><span class="nx">foo</span><span class="p">.</span><span class="nx">Namespace</span><span class="p">).</span><span class="nf">Update</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="nf">newDeployment</span><span class="p">(</span><span class="nx">foo</span><span class="p">),</span><span class="w"> </span><span class="nx">metav1</span><span class="p">.</span><span class="nx">UpdateOptions</span><span class="p">{</span><span class="nx">FieldManager</span><span class="p">:</span><span class="w"> </span><span class="nx">FieldManager</span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// Finally, we update the status block of the Foo resource to reflect the</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c1">// current state of the world</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">err</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">c</span><span class="p">.</span><span class="nf">updateFooStatus</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span><span class="w"> </span><span class="nx">deployment</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">		</span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nx">c</span><span class="p">.</span><span class="nx">recorder</span><span class="p">.</span><span class="nf">Event</span><span class="p">(</span><span class="nx">foo</span><span class="p">,</span><span class="w"> </span><span class="nx">corev1</span><span class="p">.</span><span class="nx">EventTypeNormal</span><span class="p">,</span><span class="w"> </span><span class="nx">SuccessSynced</span><span class="p">,</span><span class="w"> </span><span class="nx">MessageResourceSynced</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>最後是 <code>syncHandler</code> 的節錄，這邊就是我們真正要寫的邏輯，把叢集調整成使用者在 <code>Spec</code> 裡面宣告的狀態。這邊想要的狀態就是在 <code>Spec</code> 裡面宣告的 deployment 有被創出來，且 replica 的數量和 <code>Spec</code> 裡面宣告的一致。</p>
<h2 id="結語">結語</h2>
<p>看完之後，是不是覺得我好像只講了 Sample Controller 的程式碼一小部份？其實這是因為 <code>client-go</code> 算是很底層的 library，用來寫 operator 的話有一些缺點：</p>
<ul>
<li>我們真正需要寫自己的邏輯地方沒有那麼多，但還是必須寫上某些固定要寫的東西，導致有些地方有點冗餘。</li>
<li>要監聽不同資源的時候，必須對每個資源都宣告 informers、listers 之類重複的東西，例如 Sample Controller 裡面就宣告了 <code>fooInformer</code> 和 <code>deploymentInformer</code>，要管的資源一多的話非常麻煩。</li>
</ul>
<p>這些缺點也催生了其他更專門拿來寫 operator 的框架，包含 
<a href="https://github.com/kubernetes-sigs/controller-runtime" target="_blank" rel="noopener">controller-runtime</a>
、
<a href="https://github.com/kubernetes-sigs/kubebuilder" target="_blank" rel="noopener">kubebuilder</a>
、
<a href="https://github.com/operator-framework/operator-sdk" target="_blank" rel="noopener">operator-sdk</a>
 等，歡迎關注本系列文的後續介紹來了解這些框架。</p>
<h2 id="參考資料">參考資料</h2>
<ul>
<li>
<a href="https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md" target="_blank" rel="noopener">https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md</a>
</li>
<li>
<a href="https://www.linkedin.com/pulse/kubernetes-custom-controllers-part-1-kritik-sachdeva/" target="_blank" rel="noopener">https://www.linkedin.com/pulse/kubernetes-custom-controllers-part-1-kritik-sachdeva/</a>
</li>
<li>
<a href="https://www.linkedin.com/pulse/kubernetes-custom-controller-part-2-kritik-sachdeva/" target="_blank" rel="noopener">https://www.linkedin.com/pulse/kubernetes-custom-controller-part-2-kritik-sachdeva/</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>本地架設 GraphRAG &#43; Ollama 完整避坑指南</title>
      <link>https://chishengliu.com/zh-tw/posts/graphrag-local-ollama/</link>
      <pubDate>Wed, 11 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/graphrag-local-ollama/</guid>
      <description>藉由 monkey-patch GraphRAG 的套件讓 GraphRAG 支援本地 Ollama 架設的 Llama 3.1 LLM 模型。坑都幫你踩過一遍了！</description>
      <content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p>若只想看結論，請直接跳到
<a href="#%e7%b8%bd%e7%b5%90">總結</a>
。請注意本文所述內容只確保版本 0.3.2 可以用，沒有測試過其他版本。</p>
<h2 id="簡介">簡介</h2>
<p>
<a href="https://microsoft.github.io/graphrag/" target="_blank" rel="noopener">GraphRAG</a>
 是微軟釋出的一種新的 RAG (Retrieval-Augmented Generation) 技術，對比於一般的 RAG 作法是把文本切塊然後向量化存在 vector store，查詢的時候把使用者的輸入也向量化然後再去比較相似度，GraphRAG 則是從文本之中提取出資訊並建成一個 Knowledge Graph，優點是可以更好的理解不同文本之間複雜的關聯性。</p>
<p>直至撰文當下，官方最新的版本為 0.3.2，這是 
<a href="https://github.com/microsoft/graphrag/tree/v0.3.2" target="_blank" rel="noopener">GitHub Repository 的對應版本 0.3.2 的連結</a>
，本文所述內容或許其他版本也可以用，但不保證。</p>
<p>如果你看了官方文件的教學就會發現，0.3.2 的官方設定只支援 OpenAI 和 Azure OpenAI，你需要在 config file 裡面放進你的 API Key，這樣雖然可以用，也最簡單，但是 GraphRAG 在 Indexing 的時候需要頻繁呼叫 LLM，所以這樣會很貴，而且沒辦法用自己 fine tune 的模型。</p>
<p>那有沒有辦法用跑在本地的開源模型例如 
<a href="https://llama.meta.com/" target="_blank" rel="noopener">Llama 3.1</a>
 呢？結論是有，但是你需要先踩一堆坑，因為官方的實作現在並不支援本地的模型，需要自己魔改一些東西。</p>
<p>你可能會問說為什麼不用 LangChain 裡面的 <code>LLMGraphTransformer</code> 呢？我只能說畢竟現在它還是 experimental 的功能，跟微軟官方的實作還是有點差距。有興趣的話可以看下面這幾個連結：</p>
<ul>
<li>
<a href="https://python.langchain.com/v0.2/docs/how_to/graph_constructing/" target="_blank" rel="noopener">https://python.langchain.com/v0.2/docs/how_to/graph_constructing/</a>
</li>
<li>
<a href="https://python.langchain.com/v0.2/docs/tutorials/graph/" target="_blank" rel="noopener">https://python.langchain.com/v0.2/docs/tutorials/graph/</a>
</li>
</ul>
<h2 id="先備條件">先備條件</h2>
<ul>
<li>安裝好 
<a href="https://ollama.com/" target="_blank" rel="noopener">Ollama</a>
 然後把它跑起來。</li>
<li>一個 LLM 模型，例如 <code>ollama pull llama3.1:8b</code>。</li>
<li>一個 Text Embedding 模型，例如 <code>ollama pull nomic-embed-text</code>。</li>
</ul>
<h2 id="踩坑過程">踩坑過程</h2>
<p>當我一開始打開 
<a href="https://github.com/microsoft/graphrag/blob/v0.3.2/docsite/posts/get_started.md" target="_blank" rel="noopener">Get Started</a>
 頁面，滿心歡喜的發現看起來非常的短，應該很快就可以弄好。現在來看那時候的我真是太天真了&hellip;</p>
<p>首先這幾步都沒有問題：</p>
<pre tabindex="0"><code>pip install graphrag
mkdir -p ./ragtest/input
curl https://www.gutenberg.org/cache/epub/24022/pg24022.txt &gt; ./ragtest/input/book.txt
python -m graphrag.index --init --root ./ragtest
</code></pre><p>接下來要編輯 <code>settings.yaml</code>，第一個問題來了，model 要怎麼填呢？</p>
<p>首先把 <code>.env</code> 刪了，因為我們並不需要 API key。</p>
<p>然後打開 <code>settings.yaml</code>，寫入以下內容：</p>

<p><a href="https://gist.github.com/MortalHappiness/7030bbe96c4bece8a07ea9057ba18b86" target="_blank" rel="noopener">View settings.yaml on GitHub Gist</a></p>

<p>幾個跟原本的 <code>settings.yaml</code> 不同的地方：</p>
<ul>
<li><code>llm</code>
<ul>
<li><code>api_key</code>: 隨便亂設，不重要，因為我們不會呼叫 API，而是查詢本地的模型，這裡我設成 <code>NONE</code>。</li>
<li><code>model</code>: 設成你的本地 LLM 模型，例如 <code>llama3.1:8b</code>。</li>
<li><code>max_tokens</code>: 這個數字要設成小於你的 LLM 模型可接受的最大 Token 數量。</li>
<li><code>api_base</code>: 設成 <code>http://localhost:11434/v1</code>。</li>
</ul>
</li>
<li><code>embeddings</code>
<ul>
<li><code>llm</code>
<ul>
<li><code>api_key</code>: 隨便亂設，不重要，因為我們不會呼叫 API，而是查詢本地的模型，這裡我設成 <code>NONE</code>。</li>
</ul>
</li>
<li><code>model</code>: 設成你的本地 Text Embedding 模型，例如 <code>nomic-embed-text</code>。</li>
<li><code>api_base</code>: 設成 <code>http://localhost:11434/api</code>。</li>
<li><code>batch_max_tokens</code>: 這個數字要設成小於你的 Text Embedding 模型可接受的最大 Token 數量。</li>
</ul>
</li>
<li><code>chunks</code>
<ul>
<li>
<p><code>size</code>：下調成 300，沒有調的話會噴以下 error，相關 issue： 
<a href="https://github.com/microsoft/graphrag/issues/362" target="_blank" rel="noopener">https://github.com/microsoft/graphrag/issues/362</a>

<img alt="Error when indexing" loading="lazy" src="https://static.chishengliu.com/posts/graphrag-local-ollama/index-error.webp"></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">09:44:07,997 datashaper.workflow.workflow ERROR Error executing verb &#34;cluster_graph&#34; in create_base_entity_graph: Columns must be same length as key
</span></span><span class="line"><span class="cl">Traceback (most recent call last):
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/datashaper/workflow/workflow.py&#34;, line 410, in _execute_verb
</span></span><span class="line"><span class="cl">    result = node.verb.func(**verb_args)
</span></span><span class="line"><span class="cl">             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/graph/clustering/cluster_graph.py&#34;, line 102, in cluster_graph
</span></span><span class="line"><span class="cl">    output_df[[level_to, to]] = pd.DataFrame(
</span></span><span class="line"><span class="cl">    ~~~~~~~~~^^^^^^^^^^^^^^^^
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/pandas/core/frame.py&#34;, line 4299, in __setitem__
</span></span><span class="line"><span class="cl">    self._setitem_array(key, value)
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/pandas/core/frame.py&#34;, line 4341, in _setitem_array
</span></span><span class="line"><span class="cl">    check_key_length(self.columns, key, value)
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/pandas/core/indexers/utils.py&#34;, line 390, in check_key_length
</span></span><span class="line"><span class="cl">    raise ValueError(&#34;Columns must be same length as key&#34;)
</span></span><span class="line"><span class="cl">ValueError: Columns must be same length as key
</span></span><span class="line"><span class="cl">16:25:46,215 graphrag.index.reporting.file_workflow_callbacks INFO Error executing verb &#34;cluster_graph&#34; in create_base_entity_graph: Columns must be same length as key details=None
</span></span><span class="line"><span class="cl">16:25:46,216 graphrag.index.run ERROR error running workflow create_base_entity_graph
</span></span><span class="line"><span class="cl">Traceback (most recent call last):
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/run.py&#34;, line 325, in run_pipeline
</span></span><span class="line"><span class="cl">    result = await workflow.run(context, callbacks)
</span></span><span class="line"><span class="cl">             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/datashaper/workflow/workflow.py&#34;, line 369, in run
</span></span><span class="line"><span class="cl">    timing = await self._execute_verb(node, context, callbacks)
</span></span><span class="line"><span class="cl">             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/datashaper/workflow/workflow.py&#34;, line 410, in _execute_verb
</span></span><span class="line"><span class="cl">    result = node.verb.func(**verb_args)
</span></span><span class="line"><span class="cl">             ^^^^^^^^^^^^^^^^^^^^^^^^^^^
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/graph/clustering/cluster_graph.py&#34;, line 102, in cluster_graph
</span></span><span class="line"><span class="cl">    output_df[[level_to, to]] = pd.DataFrame(
</span></span><span class="line"><span class="cl">    ~~~~~~~~~^^^^^^^^^^^^^^^^
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/pandas/core/frame.py&#34;, line 4299, in __setitem__
</span></span><span class="line"><span class="cl">    self._setitem_array(key, value)
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/pandas/core/frame.py&#34;, line 4341, in _setitem_array
</span></span><span class="line"><span class="cl">    check_key_length(self.columns, key, value)
</span></span><span class="line"><span class="cl">  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/pandas/core/indexers/utils.py&#34;, line 390, in check_key_length
</span></span><span class="line"><span class="cl">    raise ValueError(&#34;Columns must be same length as key&#34;)
</span></span><span class="line"><span class="cl">ValueError: Columns must be same length as key
</span></span></code></pre></div></li>
</ul>
</li>
</ul>
<p>改完設定後，開心的執行 <code>python -m graphrag.index --init --root ./ragtest</code>，就會發現噴了以下的 error：</p>
<pre tabindex="0"><code>09:19:54,508 datashaper.workflow.workflow ERROR Error executing verb &#34;text_embed&#34; in create_final_entities: &#39;NoneType&#39; object is not iterable
Traceback (most recent call last):
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/datashaper/workflow/workflow.py&#34;, line 415, in _execute_verb
    result = await result
             ^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/text_embed.py&#34;, line 105, in text_embed
    return await _text_embed_in_memory(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/text_embed.py&#34;, line 130, in _text_embed_in_memory
    result = await strategy_exec(texts, callbacks, cache, strategy_args)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/strategies/openai.py&#34;, line 62, in run
    embeddings = await _execute(llm, text_batches, ticker, semaphore)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/strategies/openai.py&#34;, line 106, in _execute
    results = await asyncio.gather(*futures)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/strategies/openai.py&#34;, line 100, in embed
    chunk_embeddings = await llm(chunk)
                       ^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/caching_llm.py&#34;, line 96, in __call__
    result = await self._delegate(input, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/rate_limiting_llm.py&#34;, line 177, in __call__
    result, start = await execute_with_retry()
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/rate_limiting_llm.py&#34;, line 159, in execute_with_retry
    async for attempt in retryer:
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/tenacity/asyncio/__init__.py&#34;, line 166, in __anext__
    do = await self.iter(retry_state=self._retry_state)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/tenacity/asyncio/__init__.py&#34;, line 153, in iter
    result = await action(retry_state)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/tenacity/_utils.py&#34;, line 99, in inner
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/tenacity/__init__.py&#34;, line 398, in &lt;lambda&gt;
    self._add_action_func(lambda rs: rs.outcome.result())
                                     ^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/concurrent/futures/_base.py&#34;, line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/concurrent/futures/_base.py&#34;, line 401, in __get_result
    raise self._exception
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/rate_limiting_llm.py&#34;, line 165, in execute_with_retry
    return await do_attempt(), start
           ^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/rate_limiting_llm.py&#34;, line 147, in do_attempt
    return await self._delegate(input, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/base_llm.py&#34;, line 49, in __call__
    return await self._invoke(input, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/base_llm.py&#34;, line 53, in _invoke
    output = await self._execute_llm(input, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/openai/openai_embeddings_llm.py&#34;, line 36, in _execute_llm
    embedding = await self.client.embeddings.create(
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/resources/embeddings.py&#34;, line 237, in create
    return await self._post(
           ^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_base_client.py&#34;, line 1816, in post
    return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_base_client.py&#34;, line 1510, in request
    return await self._request(
           ^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_base_client.py&#34;, line 1613, in _request
    return await self._process_response(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_base_client.py&#34;, line 1710, in _process_response
    return await api_response.parse()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_response.py&#34;, line 420, in parse
    parsed = self._options.post_parser(parsed)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/resources/embeddings.py&#34;, line 225, in parser
    for embedding in obj.data:
TypeError: &#39;NoneType&#39; object is not iterable
09:19:54,533 graphrag.index.reporting.file_workflow_callbacks INFO Error executing verb &#34;text_embed&#34; in create_final_entities: &#39;NoneType&#39; object is not iterable details=None
09:19:54,541 graphrag.index.run ERROR error running workflow create_final_entities
Traceback (most recent call last):
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/run.py&#34;, line 325, in run_pipeline
    result = await workflow.run(context, callbacks)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/datashaper/workflow/workflow.py&#34;, line 369, in run
    timing = await self._execute_verb(node, context, callbacks)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/datashaper/workflow/workflow.py&#34;, line 415, in _execute_verb
    result = await result
             ^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/text_embed.py&#34;, line 105, in text_embed
    return await _text_embed_in_memory(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/text_embed.py&#34;, line 130, in _text_embed_in_memory
    result = await strategy_exec(texts, callbacks, cache, strategy_args)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/strategies/openai.py&#34;, line 62, in run
    embeddings = await _execute(llm, text_batches, ticker, semaphore)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/strategies/openai.py&#34;, line 106, in _execute
    results = await asyncio.gather(*futures)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/index/verbs/text/embed/strategies/openai.py&#34;, line 100, in embed
    chunk_embeddings = await llm(chunk)
                       ^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/caching_llm.py&#34;, line 96, in __call__
    result = await self._delegate(input, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/rate_limiting_llm.py&#34;, line 177, in __call__
    result, start = await execute_with_retry()
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/rate_limiting_llm.py&#34;, line 159, in execute_with_retry
    async for attempt in retryer:
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/tenacity/asyncio/__init__.py&#34;, line 166, in __anext__
    do = await self.iter(retry_state=self._retry_state)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/tenacity/asyncio/__init__.py&#34;, line 153, in iter
    result = await action(retry_state)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/tenacity/_utils.py&#34;, line 99, in inner
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/tenacity/__init__.py&#34;, line 398, in &lt;lambda&gt;
    self._add_action_func(lambda rs: rs.outcome.result())
                                     ^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/concurrent/futures/_base.py&#34;, line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/concurrent/futures/_base.py&#34;, line 401, in __get_result
    raise self._exception
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/rate_limiting_llm.py&#34;, line 165, in execute_with_retry
    return await do_attempt(), start
           ^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/rate_limiting_llm.py&#34;, line 147, in do_attempt
    return await self._delegate(input, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/base_llm.py&#34;, line 49, in __call__
    return await self._invoke(input, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/base/base_llm.py&#34;, line 53, in _invoke
    output = await self._execute_llm(input, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/openai/openai_embeddings_llm.py&#34;, line 36, in _execute_llm
    embedding = await self.client.embeddings.create(
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/resources/embeddings.py&#34;, line 237, in create
    return await self._post(
           ^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_base_client.py&#34;, line 1816, in post
    return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_base_client.py&#34;, line 1510, in request
    return await self._request(
           ^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_base_client.py&#34;, line 1613, in _request
    return await self._process_response(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_base_client.py&#34;, line 1710, in _process_response
    return await api_response.parse()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/_response.py&#34;, line 420, in parse
    parsed = self._options.post_parser(parsed)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/openai/resources/embeddings.py&#34;, line 225, in parser
    for embedding in obj.data:
TypeError: &#39;NoneType&#39; object is not iterable
</code></pre><p>解法參考自 
<a href="https://github.com/microsoft/graphrag/issues/370#issuecomment-2211821370" target="_blank" rel="noopener">https://github.com/microsoft/graphrag/issues/370#issuecomment-2211821370</a>
</p>
<p>留言說要把某個檔案的內容換掉，但我覺得這樣不太好，因為這樣有一天你想用套件原本的功能反而可能會出問題，所以我用外部 monkey patch 的方式，首先創一個檔案叫做 <code>monkey_patch.py</code>，寫入以下內容：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">patch_openai_embeddings_llm</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="kn">from</span> <span class="nn">graphrag.llm.openai.openai_embeddings_llm</span> <span class="kn">import</span> <span class="n">OpenAIEmbeddingsLLM</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">ollama</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">_execute_llm</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">input</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">embedding_list</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">inp</span> <span class="ow">in</span> <span class="nb">input</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">embedding</span> <span class="o">=</span> <span class="n">ollama</span><span class="o">.</span><span class="n">embeddings</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">configuration</span><span class="o">.</span><span class="n">model</span><span class="p">,</span> <span class="n">prompt</span><span class="o">=</span><span class="n">inp</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">embedding_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">embedding</span><span class="p">[</span><span class="s2">&#34;embedding&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">embedding_list</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">OpenAIEmbeddingsLLM</span><span class="o">.</span><span class="n">_execute_llm</span> <span class="o">=</span> <span class="n">_execute_llm</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>記得執行 <code>pip install ollama</code>。</p>
<p>我們下載 <code>graphrag.index</code> 這個 CLI 的內容，然後 monkey patch 它。</p>
<p>先執行</p>
<pre tabindex="0"><code>wget https://raw.githubusercontent.com/microsoft/graphrag/v0.3.2/graphrag/index/__main__.py -O index.py
</code></pre><p>然後把第 8 行的 <code>from .cli import index_cli</code> 換成下面的內容：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">graphrag.index.cli</span> <span class="kn">import</span> <span class="n">index_cli</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">monkey_patch</span> <span class="kn">import</span> <span class="n">patch_openai_embeddings_llm</span>
</span></span><span class="line"><span class="cl"><span class="n">patch_openai_embeddings_llm</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>接著執行 <code>python index.py --root ./ragtest</code>，終於沒有 error 了！</p>
<p><img alt="Successfully Indexed" loading="lazy" src="https://static.chishengliu.com/posts/graphrag-local-ollama/index-success.webp"></p>
<p>試著執行 Global Query</p>
<pre tabindex="0"><code>python -m graphrag.query \
--root ./ragtest \
--method global \
&#34;What are the top themes in this story?&#34;
</code></pre><p>發生這個 error</p>
<pre tabindex="0"><code>Traceback (most recent call last):
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/openai/utils.py&#34;, line 130, in try_parse_json_object
    result = json.loads(input)
             ^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/__init__.py&#34;, line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 355, in raw_decode
    raise JSONDecodeError(&#34;Expecting value&#34;, s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
not expected dict type. type=&lt;class &#39;str&#39;&gt;:
Traceback (most recent call last):
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/openai/utils.py&#34;, line 130, in try_parse_json_object
    result = json.loads(input)
             ^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/__init__.py&#34;, line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 355, in raw_decode
    raise JSONDecodeError(&#34;Expecting value&#34;, s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
not expected dict type. type=&lt;class &#39;str&#39;&gt;:
Traceback (most recent call last):
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/openai/utils.py&#34;, line 130, in try_parse_json_object
    result = json.loads(input)
             ^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/__init__.py&#34;, line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 355, in raw_decode
    raise JSONDecodeError(&#34;Expecting value&#34;, s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
not expected dict type. type=&lt;class &#39;str&#39;&gt;:
Traceback (most recent call last):
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/openai/utils.py&#34;, line 130, in try_parse_json_object
    result = json.loads(input)
             ^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/__init__.py&#34;, line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 355, in raw_decode
    raise JSONDecodeError(&#34;Expecting value&#34;, s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
not expected dict type. type=&lt;class &#39;str&#39;&gt;:
Traceback (most recent call last):
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/openai/utils.py&#34;, line 130, in try_parse_json_object
    result = json.loads(input)
             ^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/__init__.py&#34;, line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 355, in raw_decode
    raise JSONDecodeError(&#34;Expecting value&#34;, s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
not expected dict type. type=&lt;class &#39;str&#39;&gt;:
Traceback (most recent call last):
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/llm/openai/utils.py&#34;, line 130, in try_parse_json_object
    result = json.loads(input)
             ^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/__init__.py&#34;, line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/json/decoder.py&#34;, line 355, in raw_decode
    raise JSONDecodeError(&#34;Expecting value&#34;, s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
</code></pre><p>試著執行 Local Query</p>
<pre tabindex="0"><code>python -m graphrag.query \
--root ./ragtest \
--method local \
&#34;Who is Scrooge, and what are his main relationships?&#34;
</code></pre><p>發生這個 error</p>
<pre tabindex="0"><code>Error embedding chunk {&#39;OpenAIEmbedding&#39;: &#34;&#39;NoneType&#39; object is not iterable&#34;}
Traceback (most recent call last):
  File &#34;&lt;frozen runpy&gt;&#34;, line 198, in _run_module_as_main
  File &#34;&lt;frozen runpy&gt;&#34;, line 88, in _run_code
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/__main__.py&#34;, line 92, in &lt;module&gt;
    run_local_search(
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/cli.py&#34;, line 164, in run_local_search
    response, context_data = asyncio.run(
                             ^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/asyncio/runners.py&#34;, line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/asyncio/runners.py&#34;, line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/asyncio/base_events.py&#34;, line 654, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/api.py&#34;, line 222, in local_search
    result: SearchResult = await search_engine.asearch(query=query)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/structured_search/local_search/search.py&#34;, line 67, in asearch
    context_text, context_records = self.context_builder.build_context(
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/structured_search/local_search/mixed_context.py&#34;, line 139, in build_context
    selected_entities = map_query_to_entities(
                        ^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/context_builder/entity_extraction.py&#34;, line 55, in map_query_to_entities
    search_results = text_embedding_vectorstore.similarity_search_by_text(
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/vector_stores/lancedb.py&#34;, line 118, in similarity_search_by_text
    query_embedding = text_embedder(text)
                      ^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/context_builder/entity_extraction.py&#34;, line 57, in &lt;lambda&gt;
    text_embedder=lambda t: text_embedder.embed(t),
                            ^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/llm/oai/embedding.py&#34;, line 96, in embed
    chunk_embeddings = np.average(chunk_embeddings, axis=0, weights=chunk_lens)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &#34;/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/numpy/lib/function_base.py&#34;, line 550, in average
    raise ZeroDivisionError(
ZeroDivisionError: Weights sum to zero, can&#39;t be normalized
</code></pre><p>所以只好再來 monkey patch query CLI 了，相關的 issue 留言：</p>
<ul>
<li>
<a href="https://github.com/microsoft/graphrag/issues/345#issuecomment-2230317752" target="_blank" rel="noopener">https://github.com/microsoft/graphrag/issues/345#issuecomment-2230317752</a>
</li>
<li>
<a href="https://github.com/microsoft/graphrag/issues/575#issuecomment-2252045859" target="_blank" rel="noopener">https://github.com/microsoft/graphrag/issues/575#issuecomment-2252045859</a>
</li>
</ul>
<p>在 <code>monkey_patch.py</code> 裡面新增兩個 Function 如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">patch_query_embedding</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="kn">from</span> <span class="nn">graphrag.query.llm.oai.embedding</span> <span class="kn">import</span> <span class="n">OpenAIEmbedding</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">ollama</span>
</span></span><span class="line"><span class="cl">    <span class="kn">from</span> <span class="nn">tenacity</span> <span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">AsyncRetrying</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">RetryError</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">Retrying</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">retry_if_exception_type</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">stop_after_attempt</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">wait_exponential_jitter</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_embed_with_retry</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">retryer</span> <span class="o">=</span> <span class="n">Retrying</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">max_retries</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">wait</span><span class="o">=</span><span class="n">wait_exponential_jitter</span><span class="p">(</span><span class="nb">max</span><span class="o">=</span><span class="mi">10</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">reraise</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">retry</span><span class="o">=</span><span class="n">retry_if_exception_type</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">retry_error_types</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="n">retryer</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">with</span> <span class="n">attempt</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">embedding</span> <span class="o">=</span> <span class="p">(</span><span class="n">ollama</span><span class="o">.</span><span class="n">embeddings</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="p">,</span> <span class="n">prompt</span><span class="o">=</span><span class="n">text</span><span class="p">)[</span><span class="s2">&#34;embedding&#34;</span><span class="p">]</span> <span class="ow">or</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">                    <span class="k">return</span> <span class="p">(</span><span class="n">embedding</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="n">RetryError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_reporter</span><span class="o">.</span><span class="n">error</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Error at embed_with_retry()&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">details</span><span class="o">=</span><span class="p">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)},</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">([],</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># TODO: why not just throw in this case?</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">([],</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">_aembed_with_retry</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">retryer</span> <span class="o">=</span> <span class="n">AsyncRetrying</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">stop</span><span class="o">=</span><span class="n">stop_after_attempt</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">max_retries</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">wait</span><span class="o">=</span><span class="n">wait_exponential_jitter</span><span class="p">(</span><span class="nb">max</span><span class="o">=</span><span class="mi">10</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">reraise</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">retry</span><span class="o">=</span><span class="n">retry_if_exception_type</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">retry_error_types</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">async</span> <span class="k">for</span> <span class="n">attempt</span> <span class="ow">in</span> <span class="n">retryer</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">with</span> <span class="n">attempt</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">embedding</span> <span class="o">=</span> <span class="p">(</span><span class="n">ollama</span><span class="o">.</span><span class="n">embeddings</span><span class="p">(</span><span class="n">model</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">model</span><span class="p">,</span> <span class="n">prompt</span><span class="o">=</span><span class="n">text</span><span class="p">)[</span><span class="s2">&#34;embedding&#34;</span><span class="p">]</span> <span class="ow">or</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">                    <span class="k">return</span> <span class="p">(</span><span class="n">embedding</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="n">RetryError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_reporter</span><span class="o">.</span><span class="n">error</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">message</span><span class="o">=</span><span class="s2">&#34;Error at embed_with_retry()&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">details</span><span class="o">=</span><span class="p">{</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)},</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">([],</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># TODO: why not just throw in this case?</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">([],</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">OpenAIEmbedding</span><span class="o">.</span><span class="n">_embed_with_retry</span> <span class="o">=</span> <span class="n">_embed_with_retry</span>
</span></span><span class="line"><span class="cl">    <span class="n">OpenAIEmbedding</span><span class="o">.</span><span class="n">_aembed_with_retry</span> <span class="o">=</span> <span class="n">_aembed_with_retry</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">patch_global_search</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="kn">from</span> <span class="nn">graphrag.query.structured_search.global_search.search</span> <span class="kn">import</span> <span class="n">GlobalSearch</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">logging</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">time</span>
</span></span><span class="line"><span class="cl">    <span class="kn">from</span> <span class="nn">graphrag.query.llm.text_utils</span> <span class="kn">import</span> <span class="n">num_tokens</span>
</span></span><span class="line"><span class="cl">    <span class="kn">from</span> <span class="nn">graphrag.query.structured_search.base</span> <span class="kn">import</span> <span class="n">SearchResult</span>
</span></span><span class="line"><span class="cl">    <span class="n">log</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">_map_response_single_batch</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">context_data</span><span class="p">,</span> <span class="n">query</span><span class="p">,</span> <span class="o">**</span><span class="n">llm_kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Generate answer for a single chunk of community reports.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">search_prompt</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">search_prompt</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">map_system_prompt</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">context_data</span><span class="o">=</span><span class="n">context_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">search_messages</span> <span class="o">=</span> <span class="p">[</span> <span class="p">{</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="n">search_prompt</span> <span class="o">+</span> <span class="s2">&#34;</span><span class="se">\n\n</span><span class="s2">### USER QUESTION ### </span><span class="se">\n\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="n">query</span><span class="p">}</span> <span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="k">async</span> <span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">semaphore</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">search_response</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">llm</span><span class="o">.</span><span class="n">agenerate</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">messages</span><span class="o">=</span><span class="n">search_messages</span><span class="p">,</span> <span class="n">streaming</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="o">**</span><span class="n">llm_kwargs</span>
</span></span><span class="line"><span class="cl">                <span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">log</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&#34;Map response: </span><span class="si">%s</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">search_response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># parse search response json</span>
</span></span><span class="line"><span class="cl">                <span class="n">processed_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse_search_response</span><span class="p">(</span><span class="n">search_response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># Clean up and retry parse</span>
</span></span><span class="line"><span class="cl">                <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="c1"># parse search response json</span>
</span></span><span class="line"><span class="cl">                    <span class="n">processed_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse_search_response</span><span class="p">(</span><span class="n">search_response</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">log</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;Warning: Error parsing search response json - skipping this batch&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="n">processed_response</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">SearchResult</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">response</span><span class="o">=</span><span class="n">processed_response</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">context_data</span><span class="o">=</span><span class="n">context_data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">context_text</span><span class="o">=</span><span class="n">context_data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">completion_time</span><span class="o">=</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start_time</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">llm_calls</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">prompt_tokens</span><span class="o">=</span><span class="n">num_tokens</span><span class="p">(</span><span class="n">search_prompt</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">token_encoder</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">log</span><span class="o">.</span><span class="n">exception</span><span class="p">(</span><span class="s2">&#34;Exception in _map_response_single_batch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">SearchResult</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">response</span><span class="o">=</span><span class="p">[{</span><span class="s2">&#34;answer&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="s2">&#34;score&#34;</span><span class="p">:</span> <span class="mi">0</span><span class="p">}],</span>
</span></span><span class="line"><span class="cl">                <span class="n">context_data</span><span class="o">=</span><span class="n">context_data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">context_text</span><span class="o">=</span><span class="n">context_data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">completion_time</span><span class="o">=</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start_time</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">llm_calls</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">prompt_tokens</span><span class="o">=</span><span class="n">num_tokens</span><span class="p">(</span><span class="n">search_prompt</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">token_encoder</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">GlobalSearch</span><span class="o">.</span><span class="n">_map_response_single_batch</span> <span class="o">=</span> <span class="n">_map_response_single_batch</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>執行</p>
<pre tabindex="0"><code>wget https://raw.githubusercontent.com/microsoft/graphrag/v0.3.2/graphrag/query/__main__.py -O query.py
</code></pre><p>然後把第 9 行的 <code>from .cli import run_global_search, run_local_search</code> 換成以下內容</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">graphrag.query.cli</span> <span class="kn">import</span> <span class="n">run_global_search</span><span class="p">,</span> <span class="n">run_local_search</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">monkey_patch</span> <span class="kn">import</span> <span class="n">patch_query_embedding</span><span class="p">,</span> <span class="n">patch_global_search</span>
</span></span><span class="line"><span class="cl"><span class="n">patch_query_embedding</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">patch_global_search</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>然後執行 Global 和 Local Search，Local Search 看起來還不錯，Global Search 看起來不是很好，可能參數還需要再調整一下，不過至少沒有 error 了！</p>
<pre tabindex="0"><code>$ python query.py --root ./ragtest --method global &#34;What are the top themes in this story?&#34;

INFO: Reading settings from ragtest/settings.yaml
/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/indexer_adapters.py:71: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  entity_df[&#34;community&#34;] = entity_df[&#34;community&#34;].fillna(-1)
/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/indexer_adapters.py:72: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  entity_df[&#34;community&#34;] = entity_df[&#34;community&#34;].astype(int)
creating llm client with {&#39;api_key&#39;: &#39;REDACTED,len=4&#39;, &#39;type&#39;: &#34;openai_chat&#34;, &#39;model&#39;: &#39;llama3.1:8b&#39;, &#39;max_tokens&#39;: 8191, &#39;temperature&#39;: 0.0, &#39;top_p&#39;: 1.0, &#39;n&#39;: 1, &#39;request_timeout&#39;: 180.0, &#39;api_base&#39;: &#39;http://localhost:11434/v1&#39;, &#39;api_version&#39;: None, &#39;organization&#39;: None, &#39;proxy&#39;: None, &#39;cognitive_services_endpoint&#39;: None, &#39;deployment_name&#39;: None, &#39;model_supports_json&#39;: True, &#39;tokens_per_minute&#39;: 0, &#39;requests_per_minute&#39;: 0, &#39;max_retries&#39;: 10, &#39;max_retry_wait&#39;: 10.0, &#39;sleep_on_rate_limit_recommendation&#39;: True, &#39;concurrent_requests&#39;: 25}

SUCCESS: Global Search Response:
You didn&#39;t provide a story. Please share the text, and I&#39;ll be happy to help you identify the top themes.
</code></pre><pre tabindex="0"><code>$ python query.py --root ./ragtest --method local &#34;Who is Scrooge, and what are his main relationships?&#34;

INFO: Reading settings from ragtest/settings.yaml

INFO: Vector Store Args: {}
/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/indexer_adapters.py:71: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  entity_df[&#34;community&#34;] = entity_df[&#34;community&#34;].fillna(-1)
/home/chishengliu/miniforge3/envs/graphrag/lib/python3.11/site-packages/graphrag/query/indexer_adapters.py:72: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  entity_df[&#34;community&#34;] = entity_df[&#34;community&#34;].astype(int)
creating llm client with {&#39;api_key&#39;: &#39;REDACTED,len=4&#39;, &#39;type&#39;: &#34;openai_chat&#34;, &#39;model&#39;: &#39;llama3.1:8b&#39;, &#39;max_tokens&#39;: 8191, &#39;temperature&#39;: 0.0, &#39;top_p&#39;: 1.0, &#39;n&#39;: 1, &#39;request_timeout&#39;: 180.0, &#39;api_base&#39;: &#39;http://localhost:11434/v1&#39;, &#39;api_version&#39;: None, &#39;organization&#39;: None, &#39;proxy&#39;: None, &#39;cognitive_services_endpoint&#39;: None, &#39;deployment_name&#39;: None, &#39;model_supports_json&#39;: True, &#39;tokens_per_minute&#39;: 0, &#39;requests_per_minute&#39;: 0, &#39;max_retries&#39;: 10, &#39;max_retry_wait&#39;: 10.0, &#39;sleep_on_rate_limit_recommendation&#39;: True, &#39;concurrent_requests&#39;: 25}
creating embedding llm client with {&#39;api_key&#39;: &#39;REDACTED,len=4&#39;, &#39;type&#39;: &#34;openai_embedding&#34;, &#39;model&#39;: &#39;nomic-embed-text&#39;, &#39;max_tokens&#39;: 4000, &#39;temperature&#39;: 0, &#39;top_p&#39;: 1, &#39;n&#39;: 1, &#39;request_timeout&#39;: 180.0, &#39;api_base&#39;: &#39;http://localhost:11434/api&#39;, &#39;api_version&#39;: None, &#39;organization&#39;: None, &#39;proxy&#39;: None, &#39;cognitive_services_endpoint&#39;: None, &#39;deployment_name&#39;: None, &#39;model_supports_json&#39;: None, &#39;tokens_per_minute&#39;: 0, &#39;requests_per_minute&#39;: 0, &#39;max_retries&#39;: 10, &#39;max_retry_wait&#39;: 10.0, &#39;sleep_on_rate_limit_recommendation&#39;: True, &#39;concurrent_requests&#39;: 25}

SUCCESS: Local Search Response:
A classic character!

Ebenezer Scrooge is the main protagonist of Charles Dickens&#39; novella &#34;A Christmas Carol&#34;. He is a miserly and bitter old man who lives in London during the Victorian era. Scrooge is known for his extreme love of money, his disdain for charity and kindness, and his general grumpiness.

Scrooge&#39;s main relationships are:

1. **Jacob Marley**: The ghost of his deceased business partner, who appears to Scrooge on Christmas Eve to warn him about the consequences of his selfish ways.
2. **Bob Cratchit**: His underpaid and overworked clerk, who is struggling to provide for his large family during the holiday season. Scrooge&#39;s treatment of Bob is particularly cruel, as he pays him a meager salary and expects him to work long hours without complaint.
3. **Tiny Tim**: The youngest son of Bob Cratchit, who suffers from illness and poverty. Scrooge&#39;s heartlessness towards Tiny Tim serves as a catalyst for his transformation in the story.
4. **His nephew, Fred**: A kind and generous young man who invites Scrooge to join him for Christmas dinner, but is rebuffed by his miserly uncle.
5. **The Ghosts of Christmas Past, Present, and Yet to Come**: Three supernatural visitations that haunt Scrooge on Christmas Eve, forcing him to confront the consequences of his actions and the possibility of a better future.

Through these relationships, Dickens explores themes of redemption, kindness, and the importance of human connection in &#34;A Christmas Carol&#34;.
</code></pre><h2 id="總結">總結</h2>
<p>本文章的程式碼都放在
<a href="https://gist.github.com/MortalHappiness/7030bbe96c4bece8a07ea9057ba18b86" target="_blank" rel="noopener">這個 Gist</a>
 裡面，總結一下完整的步驟為：</p>
<ul>
<li>照著官方的 Get Started 跑完 <code>python -m graphrag.index --init --root ./ragtest</code></li>
<li>把 <code>settings.yaml</code> 換成 Gist 裡面的 <code>settings.yaml</code></li>
<li>執行
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">wget https://raw.githubusercontent.com/microsoft/graphrag/v0.3.2/graphrag/index/__main__.py -O index.py
</span></span></code></pre></div></li>
<li>執行
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">wget https://raw.githubusercontent.com/microsoft/graphrag/v0.3.2/graphrag/query/__main__.py -O query.py
</span></span></code></pre></div></li>
<li>下載 Gist 裡面的 <code>graphrag_monkey_patch.py</code></li>
<li>把 <code>index.py</code> 的第 8 行換成
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">graphrag.index.cli</span> <span class="kn">import</span> <span class="n">index_cli</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">graphrag_monkey_patch</span> <span class="kn">import</span> <span class="n">patch_graphrag</span>
</span></span><span class="line"><span class="cl"><span class="n">patch_graphrag</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>把 <code>query.py</code> 的第 9 行換成
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">graphrag.query.cli</span> <span class="kn">import</span> <span class="n">run_global_search</span><span class="p">,</span> <span class="n">run_local_search</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">graphrag_monkey_patch</span> <span class="kn">import</span> <span class="n">patch_graphrag</span>
</span></span><span class="line"><span class="cl"><span class="n">patch_graphrag</span><span class="p">()</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>之後遇到要使用 <code>python -m graphrag.index</code> 的時候都換成用 <code>python index.py</code>，遇到要使用 <code>python -m graphrag.query</code> 的時候都換成用 <code>python query.py</code> 即可。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>什麼是 Kubernetes Operator 以及 Custom Resources?</title>
      <link>https://chishengliu.com/zh-tw/posts/kubernetes-operator-introduction/</link>
      <pubDate>Thu, 29 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/kubernetes-operator-introduction/</guid>
      <description>這篇文章中，我將介紹什麼是 Custom Resources 和 Kubernetes Operator，並通過一個範例來創建一個名為 ChiShengLiu 的 Custom Resource。</description>
      <content:encoded><![CDATA[<h2 id="kubernetes-api-resources">Kubernetes API Resources</h2>
<p>這邊假設你已經對 Kubernetes 有基本的認識，知道怎麼用 <code>kubectl</code>。Pod、ReplicaSet、Service，甚至是 Namespace 其實都是一種 API Resource。</p>
<p>可以用 <code>kubectl api-resources</code> 來查看現在 Kubernetes 裡面存在的所有 API Resources。</p>
<pre tabindex="0"><code>$ kubectl api-resources
NAME                     SHORTNAMES   APIVERSION   NAMESPACED   KIND
bindings                              v1           true         Binding
componentstatuses        cs           v1           false        ComponentStatus
configmaps               cm           v1           true         ConfigMap
endpoints                ep           v1           true         Endpoints
events                   ev           v1           true         Event
limitranges              limits       v1           true         LimitRange
namespaces               ns           v1           false        Namespace
nodes                    no           v1           false        Node
persistentvolumeclaims   pvc          v1           true         PersistentVolumeClaim
persistentvolumes        pv           v1           false        PersistentVolume
pods                     po           v1           true         Pod
podtemplates                          v1           true         PodTemplate
replicationcontrollers   rc           v1           true         ReplicationController
resourcequotas           quota        v1           true         ResourceQuota
secrets                               v1           true         Secret
serviceaccounts          sa           v1           true         ServiceAccount
services                 svc          v1           true         Service
...
</code></pre><h2 id="kubernetes-custom-resources">Kubernetes Custom Resources</h2>
<p>Custom Resources，顧名思義是指我們可以創造自定義的 API Resources 安裝到 Kubernetes 裡面來擴充它。</p>
<p>根據官方文件，創造 Custom Resources 有兩種方式，但最常用的是使用 Custom Resource Definition (CRD)，因此這邊只討論 CRD。</p>
<p>其實 CRD 自己本身也是一種 API Resource，藉由以下指令便可以很清楚看到：</p>
<pre tabindex="0"><code>$ kubectl api-resources | grep -i custom
customresourcedefinitions   crd,crds   apiextensions.k8s.io/v1   false   CustomResourceDefinition
</code></pre><p>想想我們平常是怎麼創建 Resources 的，通常是直接寫一個 YAML 檔，然後直接用 <code>kubectl apply</code> 去創建，所以其實創建 CRD 的步驟也一樣。</p>
<p>首先創建一個檔案叫做 <code>chishengliu-crd.yaml</code>，內容如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">apiextensions.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">CustomResourceDefinition</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">chishenglius.chishengliu.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l">chishengliu.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">scope</span><span class="p">:</span><span class="w"> </span><span class="l">Namespaced</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">names</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">plural</span><span class="p">:</span><span class="w"> </span><span class="l">chishenglius</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">singular</span><span class="p">:</span><span class="w"> </span><span class="l">chishengliu</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ChiShengLiu</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">versions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">served</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">storage</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">openAPIV3Schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">object</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nt">mood</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">string</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>之後執行以下指令</p>
<pre tabindex="0"><code>$ kubectl apply -f chishengliu-crd.yaml
customresourcedefinition.apiextensions.k8s.io/chishenglius.chishengliu.com created

$ kubectl api-resources | grep -i chishengliu
chishenglius   chishengliu.com/v1   true   ChiShengLiu
</code></pre><p>可以發現現在叢集裡面就多一個新的 Resouce 叫做 <code>ChiShengLiu</code> 了。</p>
<p>然後要創建 <code>ChiShengLiu</code> 這種 Resource 的話，一樣是寫一個 YAML 檔然後 <code>kubectl apply</code>。創一個檔案叫做 <code>happy-chishengliu.yaml</code>，內容如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">chishengliu.com/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ChiShengLiu</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">happy-chishengliu</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mood</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;happy&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>然後執行以下指令：</p>
<pre tabindex="0"><code>$ kubectl apply -f happy-chishengliu.yaml
chishengliu.chishengliu.com/happy-chishengliu created

$ kubectl describe chishengliu happy-chishengliu
Name:         happy-chishengliu
Namespace:    default
Labels:       &lt;none&gt;
Annotations:  &lt;none&gt;
API Version:  chishengliu.com/v1
Kind:         ChiShengLiu
Metadata:
  Creation Timestamp:  2024-08-28T17:37:57Z
  Generation:          1
  Resource Version:    1609
  UID:                 3cbc6964-2c7b-4e60-b113-4fa5f4dd0b3c
Spec:
  Mood:  happy
Events:  &lt;none&gt;
</code></pre><p>可以看到我們創了一個 <code>ChiShengLiu</code> 的 Resource，名字是 <code>happy-chishengliu</code>。</p>
<h2 id="kubernetes-operator">Kubernetes Operator</h2>
<p>空有 Custom Resource 並沒有任何用處，可以發現我們創了這個 <code>ChiShengLiu</code> 的 Resource 之後，Kubernetes 叢集並沒有發生任何變化，與內建的 Resources 非常不同。</p>
<p>內建的 Resources 之所以會有功能，是因為叢集裡面有對應的 Controller 在運作，Controller 的工作是負責監聽叢集內的某些 Resources，然後把叢集調整成該 Resource 在 <code>spec</code> 裡面宣告的狀態。例如 Replication Controller 就是會監聽所有叢集內的 ReplicaSet，當有任何 ReplicaSet 被創建、更新、刪除時，把叢集調整成對應的狀態，也就是創出正確數量的 Pods。</p>
<p>因此，如果要讓我們先前創的這個 <code>ChiShengLiu</code> 的 Resource 有作用，我們必須安裝一個 ChiShengLiu 的 Controller 到叢集裡面，這個 Controller 當然沒有內建，必須自己寫，所以叫做 Custom Controller，也稱作 Kubernetes Operator，官方文件稱這個模式為 Operator Pattern。</p>
<p>Kubernetes Operator 最常見的用途是封裝底層邏輯，尤其是框架或工具的開發者，讓使用者可以用 <code>kubectl apply</code> 一個 YAML 檔便可以安裝完畢，其餘的事情 Operator 會幫你處理。</p>
<p>那要怎麼寫 Kubernetes Operator 呢？歡迎關注本系列文的後續更新。</p>
<h2 id="參考資料">參考資料</h2>
<ul>
<li>
<a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/" target="_blank" rel="noopener">Custom Resources | Kubernetes</a>
</li>
<li>
<a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/" target="_blank" rel="noopener">Operator pattern | Kubernetes</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>在貢獻開源專案之前你需要知道的 5 件事</title>
      <link>https://chishengliu.com/zh-tw/posts/opensource-pre-contribution-tips/</link>
      <pubDate>Fri, 23 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/opensource-pre-contribution-tips/</guid>
      <description>新手在貢獻開源專案前，你必須先了解 5 件事：閱讀文件、Issue Tracker、Commits 的細節、Pull Request、以及一些特殊用語。</description>
      <content:encoded><![CDATA[<h2 id="閱讀文件">閱讀文件</h2>
<p>有幾個文件是必讀的，這些文件有可能是 <code>.md</code> 檔、<code>.rst</code> 檔、或是網站裡面的某一頁：</p>
<ul>
<li><code>README.md</code>：介紹專案的檔案。</li>
<li>Contribution Guide：要怎麼貢獻這個專案、提交貢獻時需要遵守哪些格式或規定等。</li>
<li>Code of Conduct：社群的行為守則，基本上你不要太誇張應該都不會踩到線，但還是要看一下。</li>
</ul>
<h2 id="issue-tracker">Issue Tracker</h2>
<p>了解專案使用的是什麼 Issue tracker，常見的有直接用 GitHub issues 或是用 JIRA。新手通常是去找現有的 issue 來解，如果要自己開 issue 的話，務必先搜尋一下有沒有重複的 issue  已經被開過了。</p>
<h2 id="commits">Commits</h2>
<p>注意該專案有沒有要求 commit message 的格式，建議養成好習慣，不要亂寫 commit message。如果沒有特別要求，可以參考 
<a href="https://www.conventionalcommits.org/en/v1.0.0/" target="_blank" rel="noopener">Conventional Commits</a>
。基本上格式會像下面這樣：</p>
<pre tabindex="0"><code>&lt;type&gt;[optional scope]: &lt;description&gt;

[optional body]

[optional footer(s)]
</code></pre><p>範例：</p>
<pre tabindex="0"><code>feat: Add XXX feature.
</code></pre><pre tabindex="0"><code>fix(frontend): Fix XXX bugs.
</code></pre><p>而最常見的 type 有以下幾種：</p>
<ul>
<li><code>feat</code>：增加新功能。</li>
<li><code>fix</code>：修 Bug。</li>
<li><code>docs</code>：改文件。</li>
<li><code>refactor</code>：Refactoring，也就是程式碼邏輯沒有變。</li>
<li><code>test</code>：新增或修改測試。</li>
<li><code>chore</code>：雜事，不知道怎麼分類就用 chore。</li>
</ul>
<p>有的專案的符號或是大小寫的格式會不太一樣，例如：</p>
<pre tabindex="0"><code>[Feat] Add XXX feature.
</code></pre><pre tabindex="0"><code>[Fix][Frontend] Fix XXX bugs.
</code></pre><p>另外有的專案會提供 <code>pre-commit</code> hooks，有的話請照著指示安裝。</p>
<p>有的專案會要求每個 commits 都必須被 sign-off，詳細請參考我的另一篇教學







  <a href="https://chishengliu.com/zh-tw/posts/opensource-git-operations/">開發開源專案常用的 Git 操作指南</a>

。</p>
<h2 id="pull-request">Pull Request</h2>
<p>如果沒有特殊情形，一個 Pull Request 就是對應一個 issue。基本上要發 Pull Request 前，如果沒有對應的 issue，要先去開一個 issue。但是如果專案沒有強制規定，且你的 Pull Requst 改動的內容也很少，則也可以直接發 Pull Request。</p>
<h2 id="一些特殊用語">一些特殊用語</h2>
<p>有時候 reviewer 會留下一些不常見的縮寫詞或用語，除了自己要看得懂之外，你也可以學著用這些用語，感覺都變專業了😂</p>
<ul>
<li>LGTM：Looks good to me。表示 reviewer 覺得你的 code 沒有問題。</li>
<li>PTAL：Please take a look。請求其他人幫忙 review。</li>
<li>WIP：Work in progress。表示這個 PR 還沒準備好。</li>
<li>ditto：「同上」的意思。</li>
<li>nit：表示 reviewer 建議你修改，但他覺得這不是很重要的修改，所以你可修可不修。例如：<code>nit: Rename this variable to XXX</code>。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>如何使用 Lazygit 大幅提升使用 Git 的效率</title>
      <link>https://chishengliu.com/zh-tw/posts/lazygit-tutorial/</link>
      <pubDate>Mon, 19 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/lazygit-tutorial/</guid>
      <description>別再慢慢敲 Git 指令了，使用 Lazygit 大量提昇你使用 Git 的效率。本文介紹了介面、我個人常用的快捷鍵、以及一個開發開源專案發 Pull Request 的實戰演練。</description>
      <content:encoded><![CDATA[<h2 id="簡介">簡介</h2>
<p>身為工程師，Git 可以說是吃飯用的工具，
<a href="https://github.com/jesseduffield/lazygit" target="_blank" rel="noopener">Lazygit</a>
 是一個用 Golang 寫的 Git 命令列工具，可以大量節省敲 Git 指令的時間。每次省個幾秒鐘，一年省了好幾個小時。</p>
<p>不要跟我說你敲指令很快（你只是懶得多學新工具），或是用了什麼 <code>alias gs='git status'</code> 之類的化名來幫忙你敲得比較快（你只是覺得學很多快捷鍵很恐怖），相信我，絕對不可能比用這個工具還快。</p>
<p>安裝步驟就不詳述了，請看
<a href="https://github.com/jesseduffield/lazygit?tab=readme-ov-file#installation" target="_blank" rel="noopener">官方文件</a>
。</p>
<h2 id="介面">介面</h2>
<p>安裝完之後在 Repository 底下執行 <code>lazygit</code> 就可以開啟介面。我設了一個化名 <code>alias lg='lazygit'</code> 幫助我敲得更快。</p>
<p>介面主要分為左半邊和右半邊，左半邊有 5 塊，分別是 Status、Files、Local branches、Commits、Stash，其中某些區塊還有分頁可以切換，例如 Files 那個區塊就還有 Worktrees 和 Submodules 這 2 個分頁。</p>
<p>右半邊有兩塊區域，分別是詳細資訊的預覽，以及每個快捷鍵底下呼叫了什麼對應的 Git 指令。</p>
<h2 id="常用快捷鍵">常用快捷鍵</h2>
<p>完整的快捷鍵可以參考
<a href="https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Keybindings_zh-TW.md" target="_blank" rel="noopener">官方文件</a>
。</p>
<p>下面是我最常用的幾個快捷鍵：</p>
<ul>
<li>全域
<ul>
<li><code>h</code> 和 <code>l</code>：上下切換左半邊的區塊。</li>
<li><code>k</code> 和 <code>j</code>：上下移動光標。</li>
<li><code>/</code>：搜尋字串。</li>
<li><code>[</code> 和 <code>]</code>：左右切換左半邊區塊的分頁。</li>
<li><code>Ctrl + o</code>：複製檔名（Files 區塊）、Branch 名稱（Local branches 區塊）、或是 commit hash（commits 區塊）到剪貼簿。</li>
<li><code>q</code>：退出 Lazygit。</li>
</ul>
</li>
<li>左半邊 Files 區塊
<ul>
<li>空白鍵：把該檔案加入或移除自 staging area。</li>
<li><code>a</code>：把全部檔案加入或移除自 staging area。</li>
<li><code>Enter</code>：用在資料夾上為折疊或展開資料夾，用在檔案上則是進入右半邊的檔案預覽區塊。</li>
<li><code>C</code>：打開外部的文字編輯器，讓我們可以編輯 commit message，之後 commit。</li>
<li><code>A</code>：Amend commit。</li>
<li><code>d</code>：取消該檔案還沒有被 commit 的所有變更，注意如果該檔案是新建立的，會被刪除。</li>
<li><code>D</code>：清空所有檔案還沒有被 commit 的所有變更，當你在做功能的時候不小心搞砸了想重來的時候用的。</li>
<li><code>S</code>：打開 stash 選項。</li>
</ul>
</li>
<li>左半邊 Local branches 區塊
<ul>
<li>空白鍵：切換到該 branch。</li>
<li><code>n</code>：從該 branch checkout 出一個新的 branch。</li>
<li><code>f</code>：Fetch 該 branch 在 remote 的新 commits。</li>
<li><code>r</code>：Rebase 到該 branch 上面。</li>
<li><code>R</code>：重新命名 branch。</li>
<li><code>p</code>：Pull 該 branch。</li>
<li><code>P</code>：Push 該 branch。</li>
<li><code>o</code>：開啟 GitHub open pull request 的頁面。</li>
<li><code>Enter</code>：查看該 branch 的 commit。使用 <code>Esc</code> 返回。</li>
</ul>
</li>
<li>左半邊 Commits 區塊
<ul>
<li><code>Enter</code>：查看該 commit 改了哪些檔案。使用 <code>Esc</code> 返回。</li>
<li><code>R</code>：打開外部編輯器更改 commit message。</li>
<li><code>d</code>：刪掉該 commit。</li>
<li><code>g</code>：打開 reset 選項。</li>
<li><code>F</code>：創一個對該 commit 的 <code>fixup!</code> commit。</li>
<li><code>S</code>：Squash 所有在該 commit 之上的 <code>fixup!</code> commits。</li>
</ul>
</li>
<li>左半邊 Stash 區塊
<ul>
<li><code>g</code>：套用並刪除該 stash。</li>
<li>空白鍵：套用但不刪除該 stash。</li>
<li><code>d</code>：刪除該 stash。</li>
</ul>
</li>
<li>右半邊預覽區塊
<ul>
<li><code>v</code>：進入多行選取模式。</li>
<li>空白鍵：把該行，或是多行（用多行選取模式選取的話）加入或移除自 staging area。</li>
<li><code>Esc</code>：回到左半邊的區塊。</li>
</ul>
</li>
</ul>
<h2 id="實戰">實戰</h2>
<p>結合一下之前的







  <a href="https://chishengliu.com/zh-tw/posts/opensource-git-operations/">開發開源專案常用的 Git 操作指南</a>

這篇文章，講解一下我怎麼用 Lazygit 來開發開源專案。</p>
<h3 id="開新的-branch">開新的 Branch</h3>
<p>首先當然是要確保 <code>upstream-master</code> 這個 branch 是和 remote 同步的，先用 <code>h</code> 和 <code>l</code> 切換到 Local branches 區塊，然後用 <code>k</code> 和 <code>j</code> 移動到 <code>upstream-master</code> 這個 branch 上面，之後按 <code>f</code> 去抓取最新變更。接著按 <code>n</code> 開一個新的 branch 即可。</p>
<h3 id="同步-upstream">同步 Upstream</h3>
<p>一樣先移動到 Local branches 區塊的 <code>upstream-master</code> 這個 branch 上面，然後按 <code>f</code> 抓取最新變更，之後按 <code>r</code> rebase 到上面。</p>
<h3 id="開發及開-pull-request">開發及開 Pull Request</h3>
<p>移動到 Files 區塊，使用空白鍵一個一個把檔案加到 staging area，或使用 <code>a</code> 一次加入所有檔案，然後按 <code>C</code> commit，之後移動到 Local branches 區塊想要 push 的 branch 上面按 <code>P</code> push，最後按 <code>o</code> 開啟 GitHub pull request 的頁面。</p>
<h2 id="結語">結語</h2>
<p>一開始你可能會覺得快捷鍵很多很難學，但用久了就會變成肌肉記憶，省下的時間絕對值得。</p>
<p>如果想了解更多的功能，請到官方的 repo 查看。</p>
]]></content:encoded>
    </item>
    <item>
      <title>貢獻開源並不難：我透過「源來適你」社群成為 Flyte 核心貢獻者！</title>
      <link>https://chishengliu.com/zh-tw/posts/become-flyte-committer/</link>
      <pubDate>Sun, 18 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/become-flyte-committer/</guid>
      <description>這篇文章講述了我這幾個月貢獻 Flyte 這個開源專案，到最後成為核心貢獻者的經歷分享。想要貢獻開源但是卻怕自己實力不夠？加入台灣開源社群「源來適你」，藉由社群內導師的幫助可以讓你克服恐懼、循序漸進的成為開源專案核心貢獻者。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>大家聽到開源專案，第一反應很多應該都是覺得那是技術大佬才能做的。說到要貢獻開源專案，身為學生或是菜鳥工程師，可能會因為沒有接觸過大型專案而感到恐懼；身為資深工程師，也可能會因為怕開發公司內部閉源專案與世界級開源專案所需的實力不同而感到卻步。但實際真的是這樣嗎？或許只是因為沒有人引導你而已。</p>
<h2 id="我的開源經歷">我的開源經歷</h2>
<p>大學的時候，因為學長的介紹而接觸了 
<a href="https://github.com/apache/submarine" target="_blank" rel="noopener">Apache Submarine</a>
 這個專案，那是我第一次接觸開源，之後也有成功拿到 Committer 的頭銜。至於 Committer 是什麼，等等我會介紹。然而很遺憾我之前沒有寫文章的習慣，所以當時並沒有留下任何心得文。這次很高興被提名為 
<a href="https://github.com/flyteorg" target="_blank" rel="noopener">Flyte</a>
 的 Committer，因此來留下一篇文章當作心得紀錄。最近我也有在貢獻 
<a href="https://github.com/ray-project/kuberay" target="_blank" rel="noopener">KubeRay</a>
 這個專案，或許有機會之後我會另外寫一篇文章，完整介紹我的開源之旅，也把以前沒有紀錄下成為 Apache Submarine Committer 的心得補上。</p>
<h2 id="flyte-專案介紹">Flyte 專案介紹</h2>
<p>
<a href="https://github.com/flyteorg" target="_blank" rel="noopener">Flyte</a>
 是一個管理 workflow 的工具，基本上就是另一個很有名的專案 
<a href="https://github.com/apache/airflow" target="_blank" rel="noopener">Apache Airflow</a>
 的競品。強調比 Airflow 更高的 scalability。背後的商業公司是 









  


<a href="https://www.union.ai/" target="_blank" rel="noopener noreferrer nofollow">Union.ai</a>
。大家不要看到開源專案背後有商業公司就覺得很奇怪，事實上<strong>幾乎所有</strong>開源專案背後都有企業在支持。</p>
<h2 id="committer-是什麼">Committer 是什麼</h2>
<p>當你想要貢獻一個開源專案，你一定不可能對 Repository 有寫入的權限，所有貢獻都是以 Pull Requests 的形式提交，必須由專案的維護者合併你的 Pull Requests，這時你會被稱為 Contributor（貢獻者）。</p>
<p>當你的貢獻達到一個程度，受到專案維護者的青睞時，這時就有機會被邀請加入組織，成為 Committer（不同專案的術語可能不同），這個我不知道中文翻譯叫做什麼，不過大概就等同於「核心貢獻者」。此時你對 Repository 就會有直接寫入的權限，也可以幫忙合併其他 Contributor 的 Pull Requests 了。</p>
<figure>
    <img loading="lazy" src="https://static.chishengliu.com/posts/become-flyte-committer/flyteorg-invitation.webp"
         alt="我被邀請進 flyteorg 組織"/> <figcaption>
            <p>我被邀請進 flyteorg 組織</p>
        </figcaption>
</figure>

<h2 id="參與貢獻-flyte-的過程">參與貢獻 Flyte 的過程</h2>
<p>今年 1 月的時候，因為
<a href="https://www.linkedin.com/in/byronhsu1230/" target="_blank" rel="noopener">許秉倫</a>
學長從美國回來的一個飯局的一次機遇，加上後續與許多前輩包括
<a href="https://www.linkedin.com/in/chia7712/" target="_blank" rel="noopener">蔡嘉平</a>
、
<a href="https://www.linkedin.com/in/weichiuchuang/" target="_blank" rel="noopener">莊偉赳</a>
職涯諮詢的關係，我決定辭掉現有的工作，一邊做開源專案，一邊申請美國研究所。做開源專案除了你的程式碼可以被很多人使用，獲得成就感之外，還有比較現實的一點就是可以幫履歷添光。為了之後去美國找工作順利以及一些其他因素，我決定重新回來做開源（正如我之前所說，我並不是第一次貢獻開源專案）。</p>
<p>從 2 月開始我加入了
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">源來適你</a>
這個開源社群，這個社群非常特別，不同於其他台灣的開源社群，源來適你裡面有許多導師帶你實際貢獻開源專案，而 Flyte 這個專案的導師主要是
<a href="https://www.linkedin.com/in/pingsutw/" target="_blank" rel="noopener">蘇桓平 (Kevin)</a>
，其實這不是我第一次認識他，因為他是 Apache Submarine 的 PMC Member，也就是說我以前貢獻 Apache Submarine 的時候他也是我的導師之一。</p>
<p>稍微扣回標題，為什麼說我是「透過源來適你」成為了核心貢獻者呢？主要就是因為源來適你的導師群的原因。正如我前面所提到的，大多數人對貢獻開源專案感到恐懼，最主要的原因其實就是不了解開源生態系。如果有個好的導師帶領你的話，貢獻的難度就會遽降。</p>
<p>無論你是學生、菜鳥工程師、資深工程師，卻步於貢獻開源專案，不外乎是因為覺得專案很複雜、技術門檻很高。而導師的存在，可以找出許多非常簡單好上手的題目給你做，甚至還可以讓你問問題、手把手教學等。相較於自己在茫茫大海的程式碼、issue 之間摸索，有個導師可謂是事半功倍。任何專案一定都會有比較簡單的任務需要人去做，所以完全不用怕自己的實力不夠導致完全不知道怎麼下手。另外導師還有一個最重要的任務：幫你合併程式碼。通常你自己提交了一個 Pull Request 到一個稍微大一點的陌生的專案，你會有 99% 以上的機率等了一個月都沒有人回覆你，而導師通常就是專案的核心貢獻者，具有合併程式碼的權限，因此你不會提交了程式碼卻石沉大海。這對於新手是很重要的，想像你一直被不讀不回，你會不會感到焦慮？會不會信心受到打擊？這也是許多人無法持之以恆貢獻一個開源專案的原因。</p>
<p>從 2 月開始我便著手貢獻 Flyte 這個專案，很高興秉倫學長分給我一些 FlyteInteractive 的東西做，讓我的名字可以出現在 
<a href="https://www.linkedin.com/blog/engineering/open-source/open-sourcing-flyteinteractive" target="_blank" rel="noopener">Linkedin 的 Blog</a>
 上面。</p>
<p>在 2~7 月期間我提交了 38 個大大小小的 Pull Requests，8 月時很高興被已經在 Union.ai 工作的
<a href="https://www.linkedin.com/in/901201-eric-chen/" target="_blank" rel="noopener">陳翰儒</a>

<a href="https://github.com/flyteorg/community/issues/30" target="_blank" rel="noopener">提名為 Committer</a>
。</p>
<p>其實比起其他更大型的專案，這個數量算很少，我在這期間是一邊準備申請研究所的東西，主要是托福，這也代表其實 Flyte 這個專案成為 Committer 的門檻不算很高，因此非常適合開源新手前來貢獻。</p>
<h2 id="源來適你詳細介紹">源來適你詳細介紹</h2>
<p>
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">源來適你（OpenSource4You）</a>
是由
<a href="https://www.linkedin.com/in/chia7712/" target="_blank" rel="noopener">蔡嘉平</a>
創立的一個台灣的民間開源社群，蔡嘉平本身是 Apache Member，也是 Apache HBase、Apache Kafka、Apache YuniKorn 專案的 PMC Member。除了蔡嘉平本人之外，社群內目前還有其他數位導師分別帶領大家參與貢獻不同的專案，例如
<a href="https://www.linkedin.com/in/kaihsun1996/" target="_blank" rel="noopener">陳楷訓</a>
、
<a href="https://www.linkedin.com/in/pingsutw/" target="_blank" rel="noopener">蘇桓平</a>
、
<a href="https://www.linkedin.com/in/byronhsu1230/" target="_blank" rel="noopener">許秉倫</a>
、
<a href="https://www.linkedin.com/in/chenyulin0719/" target="_blank" rel="noopener">陳昱霖</a>
、
<a href="https://www.linkedin.com/in/xnge/" target="_blank" rel="noopener">劉勛</a>
、
<a href="https://www.linkedin.com/in/clleew/" target="_blank" rel="noopener">李唯</a>
等。相較於其他台灣的開源社群，真正動手實作的比例更高。在導師們的指導之下已有數位社群成員成功斬獲各個專案的 Committer 頭銜。</p>
<p>截至目前源來適你的活動包含：</p>
<ol>
<li>每個開源專案小組的每週例行會議，在撰文的當下有 6 個專案小組在同時進行中。</li>
<li>每星期三晚上的讀書會，大家一起學習新知。</li>
<li>每星期六的演講，邀請台灣和美國矽谷的業界人士來分享，主題橫跨技術、職涯、八卦、管理、教育等各種和開源軟體有關的話題。</li>
<li>不定期的線下活動，讓大家面對面認識一下。</li>
<li>補助表現優異的成員開發設備或是活動經費。</li>
<li>出席各種軟體大會當講者，例如之前分享的 



  





  <a href="https://chishengliu.com/zh-tw/posts/apache-communityovercode-asia-2024-coauthored/">Apache CommunityOverCode</a>

，以及在台灣舉辦的 COSCUP，都有社群成員去演講。</li>
</ol>
<h2 id="致謝">致謝</h2>
<ul>
<li>感謝
<a href="https://www.linkedin.com/in/chia7712/" target="_blank" rel="noopener">嘉平</a>
，創辦了源來適你這麼棒的組織，這幾個月以來參加社群大大小小的活動、在 Slack 裡面和大家講幹話，真的非常充實且開心。除此之外你還提供了我生涯諮詢以及經濟援助，你真的是我非常尊敬的人，非常感謝！</li>
<li>感謝
<a href="https://www.linkedin.com/in/pingsutw/" target="_blank" rel="noopener">桓平 (Kevin)</a>
，不管是以前的 Submarine，還是現在的 Flyte，你真的是個很好的導師，對於這兩個專案感覺我問什麼你都知道，完全沒有你不會的問題。希望你之後職涯順利，之後去美國再去找你碰面！</li>
<li>感謝
<a href="https://www.linkedin.com/in/901201-eric-chen/" target="_blank" rel="noopener">翰儒</a>
，從來沒有看過這麼熱血的做開源的大學生，比我以前第一次做 Submarine 還認真，也感謝你一直幫我跑 CI 和提名我為 Committer。祝你早日被 relocate 到美國，薪資 up up up。</li>
<li>感謝
<a href="https://www.linkedin.com/in/byronhsu1230/" target="_blank" rel="noopener">秉倫 (Byron)</a>
，以前大學是你找我進資訊部的，Submarine 是追隨你才開始做的，這次做 Flyte 也是你從美國回來之後找你吃飯的飯局聊天聊出來的機遇，你甚至還丟了一些很簡單的 FlyteInteractive 的事情給我做，讓我的名字可以被放在 Linkedin 的 Blog 上面。你絕對是我從大一以來就認識的最罩的學長，沒有之一。</li>
<li>感謝 
<a href="https://www.linkedin.com/in/jason~lai/" target="_blank" rel="noopener">Jason</a>
，在你那邊學到了一些 Flyte 在 Line 公司內部的應用，和你聊天也很愉快，希望之後還有機會交流！</li>
<li>感謝 
<a href="https://www.linkedin.com/in/yi-chiu/" target="_blank" rel="noopener">Troy</a>
，雖然我們比較沒有在程式碼上面合作到，但是感謝你在 Slack 裡面指點了我一些準備申請研究所的問題。希望你之後在 Union.ai 過得順利！</li>
<li>感謝
<a href="https://www.linkedin.com/in/austin362667/" target="_blank" rel="noopener">立行 (Austin)</a>
，成為一起申請今年研究所和一起做 Flyte 的戰友，你跟我這幾個月做的事實在非常像，有種好像是同學的感覺，畢竟我同學幾乎都已經先去美國了，有戰友肯定是比孤軍奮戰好，希望我們都能順利申請到好學校！</li>
</ul>
<p>最後感謝
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">源來適你</a>
的各位導師和成員，也歡迎大家加入這個大家庭！</p>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>
<p>源來適你</p>
<ul>
<li>
<p>
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">Facebook 粉絲專頁</a>
</p>
</li>
<li>
<p>
<a href="https://calendar.google.com/calendar/u/0?cid=MzlmMjUyNzVkZDQxMWMyMDU0NGZhMzAxNzY3Yzg5YjE3YmQ1NTFlNGIzYWZkNWI1YzVmODY3OGU4MmZhNDg0OUBncm91cC5jYWxlbmRhci5nb29nbGUuY29t" target="_blank" rel="noopener">訂閱行事曆</a>
</p>
</li>
<li>
<p>
<a href="https://join.slack.com/t/opensource4you/shared_invite/zt-2j496cnar-k2iEwdQ2ulgNmo8Hyk60Bg" target="_blank" rel="noopener">加入 Slack 頻道</a>
</p>
</li>
</ul>
</li>
<li>
<p>其他社群成員的 Flyte 相關文章</p>
<ul>
<li>
<a href="https://future-outlier.medium.com/%E5%A6%82%E4%BD%95%E6%88%90%E7%82%BAopen-source-committer-flyte-%E5%A4%A7%E5%AD%B8%E7%94%9F21%E5%91%A8%E5%BF%83%E5%BE%97%E5%88%86%E4%BA%AB-c1c486af6a9c" target="_blank" rel="noopener">如何成為 Open Source Committer (Flyte) | 大學生 21 周心得分享 - Han-Ju Chen</a>
</li>
<li>
<a href="https://future-outlier.medium.com/%E6%99%AE%E9%80%9A%E4%BA%BA%E4%B9%9F%E8%83%BD%E5%81%9A%E5%88%B0%E7%9A%84-open-source-%E9%96%8B%E6%BA%90%E4%B8%80%E5%B9%B4-100-prs-%E5%AE%8C%E6%95%B4%E6%8C%87%E5%8D%97-feat-%E6%BA%90%E4%BE%86%E9%81%A9%E4%BD%A0-flyte-87b1cc29f093" target="_blank" rel="noopener">普通人也能做到的 Open Source 開源一年 100+ PRs 完整指南 feat. 源來適你, Flyte - Han-Ju Chen</a>
</li>
<li>
<a href="https://medium.com/@troychiu/from-flyte-to-union-ai-%E9%96%8B%E6%BA%90%E5%85%A5%E9%96%80%E5%BF%83%E5%BE%97-6ca2dc4cd6e4" target="_blank" rel="noopener">開源入門到加入夢幻新創！- Troy Chiu</a>
</li>
<li>
<a href="https://medium.com/@jasonlai1218/flyte%E9%96%8B%E6%BA%90%E4%B9%8B%E6%97%85-%E5%BE%9E%E9%96%8B%E5%A7%8B%E5%88%B0%E7%8F%BE%E5%9C%A8-efcf8afeb612" target="_blank" rel="noopener">Flyte 開源之旅 — 從開始到現在 - Jason Lai</a>
</li>
<li>
<a href="https://www.linkedin.com/blog/engineering/open-source/open-sourcing-flyteinteractive" target="_blank" rel="noopener">Open Sourcing FlyteInteractive: Saving thousands of AI engineering hours in developing ML interactively | Linkedin Engineering Blog - Byron (Pin-Lun) Hsu</a>
</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>開發開源專案常用的 Git 操作指南</title>
      <link>https://chishengliu.com/zh-tw/posts/opensource-git-operations/</link>
      <pubDate>Tue, 13 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/opensource-git-operations/</guid>
      <description>第一次嘗試貢獻開源專案不知所措？這篇文章教你開發開源專案時常見的 Git 操作。</description>
      <content:encoded><![CDATA[<h2 id="初始流程">初始流程</h2>
<p>要開發開源專案首先必須先 
<a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo" target="_blank" rel="noopener">Fork Repository</a>
，因為不是自己的專案，我們沒有寫入權限。</p>
<p>Fork 完之後會有一個一模一樣的 Repository 出現在自己的帳號底下，我們通常把原本的專案叫做 upstream repo，自己 fork 過來的叫做 downstream repo。</p>
<p>自己 fork 過來的 Repository 我們就有寫入權限了，首先先把他 
<a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository" target="_blank" rel="noopener">clone 下來</a>
，注意是 clone 自己 fork 過來的 repo，不是 upstream repo 。</p>
<p>假設我們要貢獻 
<a href="https://github.com/ray-project/kuberay" target="_blank" rel="noopener">kuberay</a>
 這個專案，而自己的 GitHub 名稱叫做 <code>MortalHappiness</code>，那麼 upstream repo 的 URL 會是 <code>https://github.com/ray-project/kuberay.git</code>，而自己   fork 出來的 repo URL 應該會是 <code>git@github.com:MortalHappiness/kuberay.git</code>（SSH）或是 <code>https://github.com/MortalHappiness/kuberay.git</code>（HTTPS）。假設我們使用 SSH。</p>
<p>Clone 下來之後可以執行 <code>git remote -v</code>，應該會長得像下面這樣：</p>
<pre tabindex="0"><code>origin  git@github.com:MortalHappiness/kuberay.git (fetch)
origin  git@github.com:MortalHappiness/kuberay.git (push)
</code></pre><p>接著需要添加 upstream remote，執行以下指令：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git remote add upstream https://github.com/ray-project/kuberay.git
</span></span><span class="line"><span class="cl">git remote set-url --push upstream no_push
</span></span></code></pre></td></tr></table>
</div>
</div><p>上面的指令第 1 行的 URL 要換成想貢獻的專案的 upstream URL。第 2 行則是避免我們不小心 push 到 upstream repo 上面。（其實我們也沒有寫入權限，本來就不能 push，但是說不定有一天我們做得很好，變成了 Committer 之後就有寫入權限了，加上這行可以避免我們不小心 push 到 upstream。</p>
<p>再次執行 <code>git remote -v</code> 應該會像下面這樣：</p>
<pre tabindex="0"><code>origin  git@github.com:MortalHappiness/kuberay.git (fetch)
origin  git@github.com:MortalHappiness/kuberay.git (push)
upstream        https://github.com/ray-project/kuberay.git (fetch)
upstream        no_push (push)
</code></pre><p>接下來下面這個是我個人的習慣，不一定需要這樣，為了避免不小心在 <code>master</code> branch（或 <code>main</code> branch）上面開發功能，我會創一個 local branch 去 track <code>upstream/master</code> branch，然後把 local 的 <code>master</code> branch 砍掉。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git fetch upstream
</span></span><span class="line"><span class="cl">git checkout -b upstream-master upstream/master
</span></span><span class="line"><span class="cl">git branch -d master
</span></span></code></pre></td></tr></table>
</div>
</div><p>如果有在用 
<a href="https://cli.github.com/" target="_blank" rel="noopener">GitHub CLI</a>
，可以順便把 default repo 設成 upstream。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gh repo set-default https://github.com/ray-project/kuberay.git
</span></span></code></pre></td></tr></table>
</div>
</div><p>上面指令的 URL 記得換成自己的 upstream URL。</p>
<h2 id="開發新功能流程">開發新功能流程</h2>
<ol>
<li>
<p>確保 local 的 <code>upstream-master</code> branch 和 remote 是同步的：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git checkout upstream-master
</span></span><span class="line"><span class="cl">git pull upstream master
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>Checkout 出新的 branch，假設叫做 <code>feature/example</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git checkout -b feature/example upstream-master
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>正常開發（add、commit、push）。</p>
</li>
<li>
<p>去 upstream repo 
<a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request" target="_blank" rel="noopener">開 pull request</a>
。</p>
</li>
</ol>
<h2 id="同步-upstream">同步 Upstream</h2>
<p>假設開發到一半，upstream repo 有新的 commits，這時就需要同步 upstream，執行 <code>git pull upstream master</code>。</p>
<h2 id="sign-off-commits-dco">Sign-off Commits (DCO)</h2>
<p>有些開源專案會要求每個 commit 都必須被 sign-off，沒有做的話 CI 有個叫做 DCO (Developer Certificate of Origin) 的會 fail。sign-off 的步驟很簡單，就是在執行 <code>git commit</code> 的時候加上 <code>-s</code> 的 flag，變成 <code>git commit -s</code>。效果是會在 commit message 的最後加上一行 <code>Sign-off-by</code>。所以 commit message 會變得像下面這樣：</p>
<pre tabindex="0"><code>Some commit message.

Signed-off-by: Chi-Sheng Liu &lt;chishengliu@chishengliu.com&gt;
</code></pre><p>注意這邊的 sign-off 和 
<a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification" target="_blank" rel="noopener">GPG-signed commits</a>
 是不一樣的。</p>
<p>如果 commit 的時候忘記加上 <code>-s</code> 的 flag，可以參考
<a href="https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md" target="_blank" rel="noopener">這個連結</a>
去補救。</p>
<p>如果覺得每次都要加 <code>-s</code> 這個 flag 很麻煩，可以使用 <code>commit-msg</code> hook：</p>

<p><a href="https://gist.github.com/MortalHappiness/841817e4dcb0ac20aad76ac8d2ecece8" target="_blank" rel="noopener">View this GitHub Gist</a></p>

<p>把上面的內容寫到 <code>.git/hooks/commit-msg</code> 裡面，然後執行 <code>chmod +x .git/hooks/commit-msg</code> 即可。</p>
]]></content:encoded>
    </item>
    <item>
      <title>2024 Apache CommunityOverCode 亞洲場遊記</title>
      <link>https://chishengliu.com/zh-tw/posts/apache-communityovercode-asia-2024-coauthored/</link>
      <pubDate>Sat, 03 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/apache-communityovercode-asia-2024-coauthored/</guid>
      <description>這是劉奇聖與劉立行參與 2024 Apache CommunityOverCode 亞洲場的遊記，介紹了這場國際開源軟體大會的細節與我們的收穫，以及「源來適你」這個台灣開源社群。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>今年很高興能以志工的身份參與在杭州舉辦的年度開源軟體大會 Apache CommunityOverCode 亞洲場。此外本次台灣開源社群「
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">源來適你</a>
」也有社群成員以講者和一般參與者的身份一同前往此次盛會。所以這次我（劉奇聖）和源來適你的另一位成員
<a href="https://github.com/austin362667" target="_blank" rel="noopener">劉立行</a>
來一起寫一篇遊記，介紹這場國際開源軟體大會的細節與我們的收穫，以及「源來適你」這個台灣開源社群。</p>
<h2 id="什麼是-apache-軟體基金會">什麼是 Apache 軟體基金會？</h2>
<p>Apache 軟體基金會（Apache Software Foundation, ASF）是目前全世界最大的開源軟體組織，由世界各地的開發者所組成，其組織下的軟體遵循 
<a href="https://en.wikipedia.org/wiki/Apache_License" target="_blank" rel="noopener">Apache License</a>
，孕育了 Hadoop、Spark、Kafka、Flink 等許多知名專案。</p>
<h2 id="什麼是-apache-communityovercode">什麼是 Apache CommunityOverCode？</h2>
<p>Apache CommunityOverCode，舊稱 ApacheCon，是每年都會舉辦的屬於 Apache 軟體基金會的開源軟體大會，分為亞洲場、歐洲場、北美場。經過一次重新命名，強調「
<a href="https://www.apache.org/theapacheway/#important" target="_blank" rel="noopener">健康的社群比好的程式碼更重要</a>
」。今年我們參加的是在杭州舉辦的亞洲場。</p>
<h2 id="為什麼要參加-apache-communityovercode而且是參加實體場而不是線上直播">為什麼要參加 Apache CommunityOverCode，而且是參加實體場而不是線上直播？</h2>
<p>參加現場活動有幾個顯而易見的理由：</p>
<ul>
<li><strong>人脈</strong>：到現場參加有機會認識更多的朋友或是講者，建立人脈，幫助之後職涯發展。</li>
<li><strong>臨場感</strong>：體驗國際級軟體大會現場的氛圍。</li>
<li><strong>提問的機會</strong>：在現場才有機會向講者提問與討論，線上參加的話無法提問。</li>
<li><strong>觀光</strong>：到現場可以順便到當地的旅遊景點觀光。</li>
<li><strong>網友見面會</strong>：跟一同開發過開源專案，卻因地理原因沒有實體碰面過的夥伴們面對面交流。</li>
</ul>
<h2 id="如何實體參加-apache-communityovercode">如何實體參加 Apache CommunityOverCode?</h2>
<p>基本上有 3 種管道：一般買票報名、報名當講者、申請當志工。下圖是註冊頁面。</p>
<p><img alt="Buy Ticket" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/buy-ticket.webp"></p>
<ul>
<li>一般買票報名：一般買票報名是需要報名費的，若為 Committer 身份（擁有 Apache 信箱，有興趣的話文末會介紹 Apache 專案的角色結構說明如何取得這個身份）可以打折，另外學生和目前無業的人可以申請免費門票，但需要審核。</li>
<li>報名當講者：報名當講者如果被接受可以獲得免費的講者票，此外講者還可以發公關票給親朋好友，讓他們獲得免費的票來一起參加。</li>
<li>申請當志工（劉奇聖）：
<a href="https://tac.apache.org/" target="_blank" rel="noopener">Apache Travel Assistance Committee (TAC)</a>
 是 Apache 用來補助想參加 Apache 的活動但經濟不是特別富裕的人的一個委員會，我們可以在他們的官網申請當志工，<strong>注意不用是 Committer 也可以申請</strong>！Committer 可以直接用 Apache 的信箱登入，非 Committer 則可以註冊帳號，但經過源來適你社群成員實測，Gmail 好像有時候會收不到信，Outlook 則比較容易收到信，如果收不到信可以換一個 Email 試試看。申請通過之後 TAC 會補助你機票、住宿、大會入場的票券等費用。可以省下不少支出，非常推薦大家申請志工（當講者只有免費的票，機票和住宿還是要自己買，相比之下志工真的省超多）。
<ul>
<li>Q: 志工可以省多少費用？</li>
<li>A:
<ul>
<li>機票：原本直飛是 £313 GBP，這次剛好遇到颱風改成轉機的班機，變成 £1188 GBP。另外我是從桃園國際機場（TPE）飛到杭州蕭山國際機場（HGH）而已，這次有位志工小夥伴是從英國來的，機票可以省更多錢。</li>
<li>入場費：Committer ￥799 CNY、非 Committer ￥1,099 CNY</li>
<li>住宿費：4 晚約 $8000~$10000 TWD</li>
<li>總和：依我的情形來說，省了 £1188 GBP + ￥799 CNY + $8000~$10000 TWD = 約 $62000 TWD</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="什麼都不懂的小白適合參加嗎劉立行">什麼都不懂的小白適合參加嗎？（劉立行）</h2>
<p>先講結論：「肯定適合！」</p>
<p>Apache 軟體基金會囊括形形色色的項目，每位與會的講者都有各自擅長的領域，他們也會儘量以白話的方式把講題說清楚，所以不太需要擔心對特定項目技術細節不熟悉而聽不懂。</p>
<p>建議小白參加者可以在會前先做點功課，先針對時程表挑出較不會涉及特定項目技術細節的議程：例如一些議題在討論如何提升效能或減少資源用量，這類跨項目都通用的優化手段、技巧，就很適合小白利用有限的領域知識加上本身的技術直覺參與聆聽。</p>
<p>舉例來說，「Exploration and Practice of Flink SQL Performance Optimization at ByteDance」這場由字節跳動工程師主講其公司內部對 Flink SQL 的種種優化，你可以稍微了解 Flink 在做什麼、為了解決什麼問題而生等。但不需要真的很熟悉 Flink 的 codebase 與大部分的技術細節，也能夠聽懂他們的優化的切入點，有一些通用的優化技巧，可以套用到其他專案，例如 Caching、SIMD Vectorization、Space/Time Trade-off 等；而另一場由 Cloudera 產品經理主講的「Navigating the Lakehouse with Confidence: Best Practices for Implementation with Apache Iceberg」，雖然主軸在分享他們客戶使用 Iceberg 的最佳實踐，但資源成本、效能提升等議題也總離不開 Partition、Data Compaction 等發力點。</p>
<p>總而言之相當推薦各位讀者參加，若有參與這種國際級的技術大會的機會，一定要把握！真的太值回票價了。</p>
<h2 id="部份議程聆聽心得">部份議程聆聽心得</h2>
<p><img alt="Listening Tracks" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/listening-tracks.webp"></p>
<p>議程主要分為兩部份：Keynotes 和 Guest Speakers。Keynotes 的主題通常會比較廣泛，切合大會主題並激起大家興趣等。Guest Speakers 則是由社群的人投稿，對於某個專案或領域做深入的介紹和探討。上午的議程都是 Keynotes，地點在主會議室，下午則是 Guest Speakers，分散在各個會議室，大家可以自己挑有興趣的主題聆聽。</p>
<p>Keynotes 通常是 1~2 個講者在講一個主題，但裡面也有比較特殊的部份，例如 4～5 個人坐在台上聊天談論某個主題的圓桌會議，以及每個人限時 5 分鐘講一個主題的 Lightning Talk。</p>
<p>下圖是大會第一天的行程，大家可以感受一下。為避免佔用太多版面，第二天和第三天的行程就不詳細放上來了。</p>
<p><img alt="First Day Schedule" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/first-day-schedule.webp"></p>
<h3 id="keynotes">Keynotes</h3>
<h4 id="opportunities-and-challenges-of-globalization---xu-wang-junping-du-william-guo-zili-chen">Opportunities and Challenges of Globalization - Xu Wang, Junping Du, William Guo, Zili Chen</h4>
<p><img alt="Keynote speakers" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/keynote-speakers.webp"></p>
<p><strong>劉奇聖</strong>：</p>
<p>這個圓桌會議討論了許多開源和國際化的問題，我覺得非常有趣。</p>
<h5 id="開源社群本地化和國際化語言上的矛盾">開源社群本地化和國際化語言上的矛盾</h5>
<p>有一點提到開源社群的語言問題，想要國際化勢必需要英文的文檔，否則會把貢獻者的來源限縮在本地。但是如果最後的市場是本地的市場，則可能也需要使用本地的語言，不然若有競品而且競品的文檔是使用本地語言，則很可能完全無法與之競爭。但是同時維護多語言的文檔是一件非常累的事，一來不是所有開發者都精通各種語言，二來同時具有多語言文檔的時候，若某個段落需要更改，則需要更改多個地方，容易導致不同語言文檔之間資訊不同步的問題。因此如果需要專案需要國際化，通常還是以只維護一份英文文檔為主。</p>
<h5 id="地區偏好差異形成專案推廣的絆腳石">地區偏好差異形成專案推廣的絆腳石</h5>
<p>另一個問題是 Product Market Fit（PMF）的問題，一個在某地區非常盛行的專案，想要推廣到不同地區時，有可能因為該地區的使用習慣與其他地區不同，導致無人問津。一位講者提到他曾參與過一個專案，使用 Drag-and-drop 的 No-code 的方式建立 workflow，在亞洲非常盛行，但是想推廣到歐美市場時發現該地區的人習慣使用寫程式的方式建立 workflow，所以推廣得非常不順利。</p>
<h5 id="不同地區開發氛圍與價值觀的磨合">不同地區開發氛圍與價值觀的磨合</h5>
<p>還有一個有趣的議題是不同地區的開發氛圍問題，一位講者提到對於歐美開發者來說，使用郵件溝通，回覆時長可能超過一天以上，大家可能覺得沒什麼問題，但是對於華人來說，開發氛圍屬於比較「卷」，所以很多人都期望訊息能在幾十分鐘之內被回覆，這會導致除了面對國際的溝通軟體之外，如果最後產品想推往華人市場，可能還需要使用不同的溝通軟體創建一個面對華人開發者及用戶的頻道，增加維護的困難性。一位講者提到一個觀點，我認為非常值得我們反思，也就是「一時慢不代表長久會慢」，回覆慢可以讓開發者們仔細審視程式碼的品質，降低之後發生問題的機率，若每次都追求快速合併程式碼而不經仔細思考，可能在未來付出需要一直修補漏洞的代價，反而拖慢整體速度。此外做一件事情慢也不代表整體會慢，因為國際化的開源專案會有很多公司參與其中開發不同功能，可能一個功能會做得很慢，但同時間會有很多個功能在開發中，因此把時間拉長來看，整體速度其實是會非常快的。</p>
<h4 id="open-source-how-we-got-here-where-were-going---bruce-perens">Open Source: how we got here, where we&rsquo;re going - Bruce Perens</h4>
<p><strong>劉奇聖</strong>：</p>
<p>這位講者非常厲害，是「開源定義」的主要作者，現在該定義變成美國法院裁決一個軟體是否為開源的基準。他也是 
<a href="https://en.wikipedia.org/wiki/BusyBox" target="_blank" rel="noopener">BusyBox</a>
 的作者以及第二任 Debian Linux 項目的負責人。</p>
<h5 id="開源的歷史及現在的挑戰">開源的歷史及現在的挑戰</h5>
<p>講者首先講述了開源的歷史，接著提到了目前開源的困境，包括現在世界上多數大公司都在用著各種開源專案，但這些開源專案維護者很多都是用愛發電做功德，大公司並沒有付錢給這些人，當開源專案維護者一個人用愛發電無法做完全部的事情的時候，難免會開權限給其他貢獻者讓他們有提交程式碼的機會，如果這時這些貢獻者裡面有惡意的人，就會發生大災難，例如前陣子鬧得沸沸揚揚的 
<a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor" target="_blank" rel="noopener">xz 後門事件</a>
。另一個困境是現在的開源 License 實在太多，大公司每年需要花許多錢去確保程式碼符合這些 License。</p>
<h5 id="後開源拒絕讓大公司再當免費仔該付錢啦">「後開源」：拒絕讓大公司再當免費仔，該付錢啦！</h5>
<p>講者針對上述困境提出了一個倡議，叫做「
<a href="https://postopen.org/" target="_blank" rel="noopener">後開源（Post Open）</a>
」，概念是對於個人和小公司，開源軟體依舊保持免費，但是對於年收入超過五百萬美元的大公司，若要使用開源軟體則需付一部分的錢給開源社群，這些錢會依照開發者的貢獻程度以及每個開發者的貢獻分別有多少公司正在使用去做分配。</p>
<h5 id="個人對後開源的看法">個人對「後開源」的看法</h5>
<p>我認為講者提出的這個概念是個非常美好的願景，能夠給開源專案維護者不小的動力，畢竟人也不能一直用愛發電和做功德，熱情總會有耗盡的一天，大家都會有 burn out 的時候，對於先前默默貢獻的偉大開源貢獻者們，我一直都是對他們有著最崇高的敬意。這個倡議要推行一定也會遇到許多困難，但講者在幾十年前都能成功將開源變成美國法院裁決基準，我相信講者想要推動的這個願景肯定也不是完全不可能的事，希望他會成功！</p>
<p><strong>劉立行</strong></p>
<p>奇聖上述心得大致描述了 Bruce Perens（Busybox 的作者）的分享：Bruce 對 Post Open Source 時代的期許與願景，值得一提的是他表示透過開源，企業得以共享基礎設施的開發維護成本，專注在自家商業產品的差異化，然而全世界最重要的幾個關鍵基礎軟體設施，往往都由一兩位自願貢獻者無償維護，對整個生態系是很不健康的，Post Open Source 希望在未來建立健康的開源盈利模式，解決複雜的授權問題，他也表示這很有野心極巨挑戰，但仍認為值得一試。</p>
<p>這位滿頭白髮的 66 歲講者雖然也因颱風班機取消，只能預錄影片，依然仍能感受到他充滿活力貢獻社群。</p>
<p>我個人認為這已經是開源生態系長年的文化了，要突然發生質變的難度頗高，但也很樂見這種倡議把開源世界推向更不投機、更健康的路上，我也非常期待「
<a href="https://postopen.org/" target="_blank" rel="noopener">後開源（Post Open）</a>
」之後的發展。</p>
<h4 id="toward-a-sustainable-asf-community-the-power-of-cross-cultural-understanding-and-consensus---willem-jiang-richard-sikang-bian">Toward a Sustainable ASF Community: The Power of Cross-Cultural Understanding and Consensus - Willem Jiang, Richard Sikang Bian</h4>
<p><strong>劉立行</strong>：</p>
<h5 id="開源如戲透過一場小品瞭解開源社群的衝突與因應之道">開源如戲，透過一場小品瞭解開源社群的衝突與因應之道</h5>
<p>來自螞蟻集團和字節跳動的工程師透過話劇的方式演示開源組織裡的工程師們工作的日常，溝通之間的摩擦、文化衝突對立以及如何達成共識，這場分享以活潑生動的方式呈現，吸引了台下觀眾的們眼光，娛樂效果十足。</p>
<p>雖說是演戲，不過這些衝突、誤會的場景對於投入開源世界的工程師們個該不算陌生，甚至是每天上演的。兩位講者巧妙地透果旁觀者清的話劇模式拋出這些議題讓大家思考，發人省思：code review 時應該給足 context、不要替對方預設立場、還要考量到彼此的 unknown unknowns，希冀我們都能成為更優秀的工程師，為世界增添一份美好。</p>
<h3 id="guest-speakers">Guest Speakers</h3>
<h4 id="state-of-apache-yunikorn---yu-lin-chen-chia-ping-tsai-kuan-po-tseng-tingyao-huang-poan-yang">State of Apache YuniKorn - Yu-Lin Chen, Chia-Ping Tsai, Kuan Po Tseng, TingYao Huang, PoAn Yang</h4>
<figure>
    <img loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/yunikorn-speakers.webp"
         alt="YuniKorn 講者群，由左至右分別為 Yu-Lin 、 PoAn 、 Chia-Ping 、 TingYao 、 Kuan Po 和一位社群成員"/> <figcaption>
            <p>YuniKorn 講者群，由左至右分別為 
<a href="https://www.linkedin.com/in/chenyulin0719/" target="_blank" rel="noopener">Yu-Lin</a>
、
<a href="https://www.linkedin.com/in/poan-yang/" target="_blank" rel="noopener">PoAn</a>
、
<a href="https://www.linkedin.com/in/chia7712/" target="_blank" rel="noopener">Chia-Ping</a>
、
<a href="https://www.linkedin.com/in/tingyao-huang-106b8220b/" target="_blank" rel="noopener">TingYao</a>
、
<a href="https://www.linkedin.com/in/brandboat/" target="_blank" rel="noopener">Kuan Po</a>
 和一位社群成員</p>
        </figcaption>
</figure>

<p><strong>劉奇聖</strong>：</p>
<h5 id="從小白到成為國際級軟體大會講者">從小白到成為國際級軟體大會講者</h5>
<p>這個演講比較特別，是由我們「
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">源來適你</a>
」的社群成員組了一隊 YuniKorn 小隊投稿當講者。
<a href="https://yunikorn.apache.org/" target="_blank" rel="noopener">Apache YuniKorn</a>
 是 Kubernetes 原生 Scheduler 的一個替代品，實現了更多進階的功能，目前在 Apple 內部被大量使用。這個演講簡單介紹了 YuniKorn 的基本功能、最新版本已經實現的新功能、現在社群的狀態以及正在實做的功能等。聽完這個演講我最大的感觸是源來適你真的是一個很棒的組織，雖然我沒有參加 YuniKorn 的開發，但知道這些講者有些人一開始也是完全沒有接觸過開源專案的小白，而在社群裡面導師的指導之下，成功獲得 Committer 頭銜，甚至還一起組隊前往國際級的軟體大會給演講。部份成員在取得頭銜之後也不會消聲匿跡，例如昱霖（Yu-Lin）在成為 committer 後仍繼續提攜後輩回饋社群，而廷堯（TingYao）則在忙碌的工作下依然會抽空以 PMC Member 的視角陪大家討論技術。</p>
<p><strong>劉立行</strong>：</p>
<p>沒錯，這場分享十分特別，由各自來自不同公司的一群熱情工程師們帶來他們對 YuniKorn 社群近況的更新。</p>
<p>YuniKorn 是一款 Kubernetes scheduler，提供了原生 scheduler 沒有的 gang scheduling 的能力，也就是在進行資源排程時，能做到 all or nothing，這對於現代 AI workload 是很重要的，舉個例子：假設 distributed model training data parallelism 需要用到 10 個 K8s Pods，每代算完要把參數全部 sync 後才更新傳回去給 10 個 Pods，這正是需要用到 gang scheduler 的時機，此外 YuniKorn 也支援 Multi-tenancy，可以設定各 Queue 用到各種資源的 Quota， Multi-tenancy 對現代雲端服務可說是不可或缺的需求，YuniKorn 更提供 Preemption fence，讓同叢集內不同組織的資源不會在排程時被彼此影響或搶用。</p>
<p>YuniKorn 台灣戰隊的成員們，各自不辭平日白天工作辛勞，晚上假日無薪投身開源貢獻程式碼、解 bug，鍛鍊自我工程能力，為履歷鍍金再鍍金，這種情操相當偉大也令人佩服，非常歡迎大家一同加入 YuniKorn 台灣戰隊的行列，成為一位 10 倍工程師。</p>
<h4 id="the-evolution-of-data-infrastructure-in-the-age-of-llm---xun-liu">The Evolution of Data Infrastructure in the Age of LLM - Xun Liu</h4>
<p><img alt="Xun Liu" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/xun-liu.webp"></p>
<p><strong>劉奇聖</strong>：</p>
<h5 id="大型網友見面會透過本次大會與過往開源前輩見面交流">大型網友見面會？透過本次大會與過往開源前輩見面交流</h5>
<p>這個演講是 
<a href="https://www.linkedin.com/in/xnge/" target="_blank" rel="noopener">Xun Liu</a>
（劉勛，我們都稱呼他為勛哥）主講，他現在是新創公司 Datastrato 的 COO，其主要產品是 
<a href="https://gravitino.apache.org/" target="_blank" rel="noopener">Gravitino</a>
，目前已經捐獻給 Apache 基金會，這次演講介紹了 Gravitino 在 LLM 時代的應用。勛哥之前是 Apache Submarine 的 PMC Member，在我以前還是個開源小白，開發 Apache Submarine 時給了我許多幫助，也是他提名我成為 Apache Submarine Committer 的。從前我們都只在線上開會的時候見面，這次非常高興有機會能有實體碰面的機會。我想這也是這種開源大會的一個目的：大型網友見面會。開源專案通常都是一群志願者遠端開發，甚至有可能在地理上跨越了非常遠的距離，很難有實體見面交流的機會。而開源軟體大會充當了一個媒介，讓開發者們可以面對面交流。這次勛哥還當了杭州在地嚮導，帶我們到處吃喝玩樂，非常感謝他的幫忙！也歡迎大家加入源來適你 Slack 裡的 <code>#apache-gravitino</code> 頻道，讓勛哥帶領你一起開發 Gravitino，有機會成為 Committer，獲得酷炫 Apache 信箱，為履歷添光！</p>
<p><strong>劉立行</strong>：</p>
<p>Xun Liu 是 
<a href="https://datastrato.ai/" target="_blank" rel="noopener">Datastrato</a>
 的共同創辦人，他們公司最近將旗下項目 
<a href="https://gravitino.apache.org/" target="_blank" rel="noopener">Gravitino</a>
 捐給 Apache 基金會，Gravitino 提供統一 metadata 的管理方案，適逢 Tabular 併購案掀起的波瀾未平，Gravitino 在 metadata 領域可說是完美鎖定了下一個技術熱點，在兵家必爭之地洞燭機先。</p>
<p>在這場 Gravitino 與 AI 分享，其中特別提到一個大 feature 
<a href="https://github.com/apache/gravitino/issues/4104" target="_blank" rel="noopener">Issue #4104</a>
，示範了統一 metadata 在大型 dataset 管理中扮演的角色，確實很有趣。</p>
<h4 id="gravitino-rest-catalog-service-for-apache-iceberg-why-and-beyond---xiaojing-fang">Gravitino REST catalog service for Apache Iceberg, why and beyond - Xiaojing Fang</h4>
<p><strong>劉立行</strong>：</p>
<h5 id="優化再優化給我最好用的-iceberg-catalog">優化再優化，給我最好用的 Iceberg Catalog</h5>
<p>Xiaojing 也是來自 Datastrato 的軟體工程師，這場演講分享了 Gravitino 如何引入整合了 Apache Iceberg REST catalog 客戶端，提到了當前 Iceberg catalog 的侷限：</p>
<ol>
<li>不完善的語言與環境相容性</li>
<li>像 Hive lock 一樣的效能極限</li>
<li>為了保證不同 catalogs 一致性所造成的複雜性</li>
<li>除錯、監控、審核的難度較高。</li>
</ol>
<p>Gravitino 嘗試透過建立可插拔的 Iceberg REST catalog 系統進而降低維護雜度、提升效能與與提供更高的安全性。</p>
<p>其中特別有趣的一點是，小客戶端大伺服端的設計決策，把整個系統中不少元件都移到伺服端，所以整個架構的 design space 又更大了，能操作的事更多，能加入的優化機制也更多元，例如在伺服端實作 metadata caching，顯著的提升了效能。</p>
<p>我在 2024 年初有貢獻了一點點 PR 到 Gravitino，Datastrato 的工程師們都非常有效率且有能量，大家的回饋都非常即時且有建設性，開會的強度也都很高，身為 Apache 基金會的董事會成員和 VP Incubator 的 Justin Mclean 也是 Datastrato 的成員之一，大會期間也時常能見到他到處和大家聊天的身影，Justin 甚至有來 review 過我的 PR! 總之有興趣的夥伴千萬不要錯過下個焦點技術 
<a href="https://github.com/apache/gravitino" target="_blank" rel="noopener">Gravitino</a>
，開源的好處就是程式碼都是公開的，各位不妨來看看程式碼一探究竟，說不定能順手貢獻幾隻 PR。</p>
<h4 id="其他">其他</h4>
<p><strong>劉奇聖</strong>：</p>
<h5 id="演講太多眼花撩亂要怎麼挑選適合自己聆聽的主題">演講太多眼花撩亂？要怎麼挑選適合自己聆聽的主題？</h5>
<p>由於 Guest Speakers 的演講實在太多，因此剩下的就在這邊挑幾個簡單講一下心得。沒有人是什麼領域都精通的，因此聽演講時聽不懂非常正常，這時就可以挑和自己會的東西比較相近的主題去聆聽。「The best pratice of integration technology driven by event in cloud - 晓慧 武, Alan Liu」是由 RedHat 的工程師來講 Kubernetes 和 microservices 相關的東西，由於我之前在開源專案裡很常寫 Kubernetes Operator，因此這個演講就比較聽得懂。另外也可以挑一些非技術的演講去聽，例如「Developing Soft Skills for a successful Open Source career - Sumaiya Nalukwago」。當然也可以聽當前非常熱門的技術主題，例如由 Cloudera 的人講的「Navigating the Lakehouse with Confidence: Best Practices for Implementation with Apache - Bill Zhang」，探討了 
<a href="https://iceberg.apache.org/" target="_blank" rel="noopener">Apache Iceberg</a>
 的一些議題，我覺得非常的有深度。</p>
<h2 id="志工心得劉奇聖">志工心得（劉奇聖）</h2>
<p><img alt="Volunteers Group Photo" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/volunteers-group-photo.webp"></p>
<h3 id="歡迎會晚餐聚餐">歡迎會晚餐聚餐</h3>
<p><img alt="Volunteers Restaurant Photo" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/volunteers-restaurant-photo.webp"></p>
<p>Day 0 活動都還沒開始的時候，旅遊補助委員會的成員就邀請志工們一起到一家叫做「乾塘大院」的餐廳聚餐，很意外的志工裡面的亞洲人只有包含我在內的 3 位華人和 4 位印度人，沒有亞洲的其他地區的人，我原本以為亞洲場可能會有像是日本、韓國之類的亞洲人。其他志工幾乎都是母語為英文的人，加上印度人的母語也是英文，另外兩位華人的英文也非常溜，然後就發現尷尬了，我的破英文好像沒辦法跟上大家的尬聊內容，只能偶爾回個幾句加上陪笑🥲</p>
<p>不過大家人都非常 nice，氣氛非常融洽，經過這次教訓我深刻認知到英文的重要性，之後我會好好學習英文的。</p>
<h3 id="工作內容">工作內容</h3>
<p><img alt="Categorizing Stickers" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/categorizing-stickers.webp"></p>
<p>其實志工的工作並沒有很多，只需要在 3 天內選一個 2 小時的時段坐在 Apache 的貼紙桌前面（上圖），有人經過的時候跟他們說貼紙隨便拿就可以了，因為太無聊了，所以我就順便把亂七八糟的貼紙堆分類了一下，就像上圖那樣。</p>
<p>此外還要選一天到註冊桌那邊幫忙處理註冊的東西，但是後來發現註冊桌已經有本地的工作人員了，因此我根本就不用做事，坐在旁邊當花瓶就好了😂</p>
<p>最後是統計有去聽的 Guest Speaker 的場次的聽眾有多少，回報給大會，方便大會了解哪些 Guest Speaker 的主題是比較多人聽的，這項工作也是非常輕鬆，數人頭而已。</p>
<h3 id="證書">證書</h3>
<p><img alt="Volunteer Certificate" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/volunteer-certificate.webp"></p>
<p>活動結束後志工還會收到一張精美的裱框證書，感覺放在 LinkedIn 或是申請研究所的履歷上面都非常不錯！志工好處多多，非常推薦大家報名當志工！</p>
<h2 id="杭州浙大森林會場周邊環境與交通">杭州浙大森林會場周邊環境與交通</h2>
<p>這次會場座落於杭州浙江大學紫金港校區，距離機場車程約 1 小時，不算市中心，但腹地廣大，下圖為校園一隅。</p>
<p><img alt="Venue" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/venue.webp">
<img alt="Aisle" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/aisle.webp">
<img alt="Courtyard" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/courtyard.webp"></p>
<p>離開校區外交通移動上使用當地的網約車（例如滴滴出行）還算方便，街道上以及我們搭乘的幾乎全是電動車。</p>
<p>手機應用程式方面，計程車主要就是滴滴，食物外送主要就是美團，行動支付則有支付寶（alipay）以及微信支付，而阿里巴巴集團總部正位於杭州，整個城市數位化相當普及，建議如果未來有讀者參加在中國舉辦的 Apache CommunityOverCode，可以先安裝當地的 App 熟悉一下。</p>
<h2 id="會外行程劉立行">會外行程（劉立行）</h2>
<p><img alt="Skewer Restaurant" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/skewer-restaurant.webp"></p>
<p>原訂的出發日我們不幸遇到颱風攪局，發生全員班機被取消的插曲，但大風大雨仍澆不息我們的熱情，甚至大會志工的奇聖在停班日天未亮一大早就先到香港等待轉機。</p>
<p>我們一行人從台灣抵達杭州當晚，身為杭州在地人的勛哥立馬在中國知名連鎖串燒店「木屋燒烤」設接風宴招待我們嚐嚐鮮。</p>
<p>大夥們痛飲著啤酒、大口啃著串燒，一群技術 nerd 在酒酣耳熱之際也不忘討論技術、開源專案的社群文化治理、開源與商業間的策略權衡等議題，勛哥舉了 Docker 背後的公司為例：即使做出了技術很強、用戶非常多的產品，但太開放到用戶都能輕易自行 fork，在商業上也不一定是成功賺錢的，所以開源專案在生命週期各階段該採用的策略與方向都是需要經過縝密的規劃。</p>
<p>很幸運透過這場活動外的飯局，讓我近距離一窺行業技術領先者的思維與視角，可說是最珍貴的收穫。</p>
<p>第二天團員們在下午的議程結束後，迫不及待相約坐車到西湖遊覽名勝美景，當個觀光客，路上有說有笑，晚上勛哥則帶我們到湖邊的餐廳大快朵頤一頓。</p>
<p>最後一晚則發生了許多神奇的事情，在此就不透露了，有興趣的讀者歡迎加入源來適你社群瞭解詳情。</p>
<h2 id="apache-基金會專案角色詳細介紹劉奇聖">Apache 基金會專案角色詳細介紹（劉奇聖）</h2>
<p>講了這麼多，對 Apache 基金會這個世界最大的開源軟體組織有興趣了嗎？也想取得酷炫的頭銜放在履歷上？這裡來介紹一下 Apache 專案的角色結構。</p>
<p>
<a href="https://www.apache.org/foundation/how-it-works/#roles" target="_blank" rel="noopener">根據官方網頁</a>
，每個 Apache 專案通常都會有以下幾種角色：</p>
<ul>
<li><strong>User</strong>：使用該專案的軟體，不參與開發。</li>
<li><strong>Developer (Contributor)</strong>：參與開發專案的人，但對專案的 repository 無寫入權限。</li>
<li><strong>Committer</strong>：當參與開發專案的貢獻達到一個程度之後，PMC 們可能會提名你為 Committer，此時會有對該專案的 repository 寫入的權限，同時如果你還沒有一個 Apache 的信箱的話會獲得一個。例如我的信箱是 
<a href="mailto:chishengliu@apache.org">chishengliu@apache.org</a>
</li>
<li><strong>PMC Member</strong>：當參與專案更久之後可能會被提名為 PMC Member，此時會獲得決定該專案長期走向、提名活躍 Contributor 成為 Committer 等權力，另外需要在專案有正式的 release 的時候進行投票。</li>
<li><strong>PMC Chair</strong>：PMC Chair 是 PMC Member 之中的老大，負責跟董事會溝通，還有一些額外的職責。</li>
<li><strong>ASF Member</strong>：當成為了多個專案的 PMC Member 之後，有機會被提名為 ASF Member，擁有成為董事會的選舉人、被選舉人的權力，以及孵化新的專案等。</li>
</ul>
<p>擁有 Apache 信箱除了看起來非常酷炫之外，也有些其他附帶的好處喔！例如 
<a href="https://blog.jetbrains.com/blog/2019/05/30/jetbrains-supports-the-apache-software-foundation/" target="_blank" rel="noopener">JetBrains 全套 IDE 的免費使用</a>
！</p>
<h2 id="源來適你是怎樣的組織">源來適你是怎樣的組織？</h2>
<p><img alt="OpenSource4You Group Photo" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/opensource4you-group-photo.webp"></p>
<p>
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">源來適你（OpenSource4You）</a>
是由
<a href="https://www.linkedin.com/in/chia7712/" target="_blank" rel="noopener">蔡嘉平</a>
（左邊數來第三個，穿花襯衫的那位）創立的一個台灣的民間開源社群，蔡嘉平本身是 Apache Member，也是 Apache HBase、Apache Kafka、Apache YuniKorn 專案的 PMC Member。除了蔡嘉平本人之外，社群內目前還有其他數位導師分別帶領大家參與貢獻不同的專案，例如
<a href="https://www.linkedin.com/in/kaihsun1996/" target="_blank" rel="noopener">陳楷訓</a>
、
<a href="https://www.linkedin.com/in/pingsutw/" target="_blank" rel="noopener">蘇桓平</a>
、
<a href="https://www.linkedin.com/in/byronhsu1230/" target="_blank" rel="noopener">許秉倫</a>
、
<a href="https://www.linkedin.com/in/chenyulin0719/" target="_blank" rel="noopener">陳昱霖</a>
、
<a href="https://www.linkedin.com/in/xnge/" target="_blank" rel="noopener">劉勛</a>
、
<a href="https://www.linkedin.com/in/clleew/" target="_blank" rel="noopener">李唯</a>
等。相較於其他台灣的開源社群，真正動手實作的比例更高。在導師們的指導之下已有數位社群成員成功斬獲各個專案的 Committer 頭銜。</p>
<p>截至目前源來適你的活動包含：</p>
<ol>
<li>每個開源專案小組的每週例行會議，在撰文的當下有 6 個專案小組在同時進行中。</li>
<li>每星期三晚上的讀書會，大家一起學習新知。</li>
<li>每星期六的演講，邀請台灣和美國矽谷的業界人士來分享，主題橫跨技術、職涯、八卦、管理、教育等各種和開源軟體有關的話題。</li>
<li>不定期的線下活動，讓大家面對面認識一下。</li>
<li>補助表現優異的成員，例如本次的 Apache CommunityOverCode 亞洲場就有部份社群成員獲得補助，得以參加此次國際級的大會增廣見聞。</li>
<li>出席各種軟體大會當講者，例如這次 Apache CommunityOverCode 的 YuniKorn 講者群，以及近期在台灣舉辦的 COSCUP 都有社群成員去演講。</li>
</ol>
<p>非常歡迎大家看完這篇文章之後可以到 
<a href="https://www.facebook.com/opensource4you" target="_blank" rel="noopener">Facebook 粉絲專頁按讚</a>
、
<a href="https://calendar.google.com/calendar/u/0?cid=MzlmMjUyNzVkZDQxMWMyMDU0NGZhMzAxNzY3Yzg5YjE3YmQ1NTFlNGIzYWZkNWI1YzVmODY3OGU4MmZhNDg0OUBncm91cC5jYWxlbmRhci5nb29nbGUuY29t" target="_blank" rel="noopener">訂閱我們的行事曆</a>
、並
<a href="https://join.slack.com/t/opensource4you/shared_invite/zt-2j496cnar-k2iEwdQ2ulgNmo8Hyk60Bg" target="_blank" rel="noopener">加入我們的 Slack 頻道</a>
一起聊天！</p>
<h2 id="結語">結語</h2>
<p>本次參與 2024 Apache CommunityOverCode 亞洲場是一次非常難得的經驗，真的非常推薦大家不管是學生還是資深軟體工程師都至少參加一次這種國際級的軟體大會！</p>
<h2 id="相關連結">相關連結</h2>
<ul>
<li>
<a href="https://mp.weixin.qq.com/s/kG8dqDtI4U7sYKrR1hKRUQ" target="_blank" rel="noopener">官方回顧文章</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>如何用 Hugo 免費架設個人網站</title>
      <link>https://chishengliu.com/zh-tw/posts/build-personal-website/</link>
      <pubDate>Sat, 06 Jul 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/build-personal-website/</guid>
      <description>你相信嗎？除了域名之外，我這個個人網站完全免費！這篇文章分享了如何搭建這個免費網站，包括框架選擇、字體設定、留言板整合、部署流程及 SEO 優化。希望這篇文章能幫助你注意到部署網站時的關鍵細節！</description>
      <content:encoded><![CDATA[<h2 id="前言">前言</h2>
<p>你敢信？我這個個人網站的架設除了域名之外都是免費的！畢竟這只是我打算拿來發廢文的網站，能當免費仔就當免費仔。下面來講我怎麼搭建這個網站的。</p>
<h2 id="框架選擇">框架選擇</h2>
<p>既然要免費，那就不能選擇像是 WordPress 那種 SSR（Server-Side Rendering）的框架，畢竟要部屬到主機上面就要錢了，雖然有些雲端平台有提供免費額度，但隨著瀏覽人數變多勢必要花錢。因此選擇 SSG（Static Side Generation）的框架然後部屬在有提供免費 Static Site Hosting 的平台更為合理。</p>
<p>我之前的網站是使用 
<a href="https://www.gatsbyjs.com/" target="_blank" rel="noopener">Gatsby</a>
 這個框架，原因也很簡單，因為我非常熟悉 
<a href="https://react.dev/" target="_blank" rel="noopener">React</a>
，想說之後要客製化比較簡單，但後來發現我實在沒時間維護一堆 Typescript 和 Javascript，導致後來我都很懶得更新我的個人網站。因此這次我選擇使用 
<a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a>
，維護少量的 Go Template 至少比較輕鬆，而且我剛好也會 Golang。</p>
<h2 id="版型選擇">版型選擇</h2>
<p>
<a href="https://themes.gohugo.io/" target="_blank" rel="noopener">Hugo 的官網提供了許多版型可以選擇</a>
，我這次選擇的是 
<a href="https://github.com/adityatelange/hugo-PaperMod" target="_blank" rel="noopener">hugo-PaperMod</a>
 這個版型。版型通常會提供下載步驟，照著做就行了。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">hugo new site personal-website --format yaml
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> personal-website
</span></span><span class="line"><span class="cl">git submodule add --depth<span class="o">=</span><span class="m">1</span> https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
</span></span><span class="line"><span class="cl">git submodule update --init --recursive
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="設定">設定</h2>
<p>這個就只能照著不同的版型提供的文件去慢慢設定成自己想要的樣子了，下面只講一些比較難設定的東西。</p>
<h3 id="多語言">多語言</h3>
<p>我選用的這個版型有支援多語言，但是地方沒有翻譯好，這個就沒辦法了，只能去看原始碼自己找辦法解決，或是可以去搜尋 issues 看看有沒有人提出解法，例如我就找到了一些解法：</p>
<ul>
<li>
<a href="https://github.com/adityatelange/hugo-PaperMod/issues/46" target="_blank" rel="noopener">https://github.com/adityatelange/hugo-PaperMod/issues/46</a>
</li>
</ul>
<h3 id="最後修改時間">最後修改時間</h3>
<p>同樣是去 issues 裡面搜尋解法</p>
<ul>
<li>
<a href="https://github.com/adityatelange/hugo-PaperMod/issues/1037" target="_blank" rel="noopener">https://github.com/adityatelange/hugo-PaperMod/issues/1037</a>
</li>
</ul>
<h3 id="中文字體">中文字體</h3>
<p>通常版型都不會特別對中文字體做設定，所以中文字會很醜。我比較常用 
<a href="https://fonts.google.com/knowledge/glossary/web_font" target="_blank" rel="noopener">Web font</a>
 的方式解決，比較方便，這裡使用 Google 的 
<a href="https://fonts.google.com/noto/specimen/Noto&#43;Sans&#43;TC" target="_blank" rel="noopener">Noto Sans Traditional Chinese</a>
 這個字體。</p>
<p>首先必須先去複製字體的 embed code，點選 Get fonts</p>
<p><img alt="Get Font from Noto Sans" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/noto-sans-get-font.webp"></p>
<p>之後點 Get embed code</p>
<p><img alt="Get embed code from Noto Sans" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/noto-sans-copy-code.webp"></p>
<p>然後點 copy code</p>
<p><img alt="Noto Sans copy code" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/noto-sans-get-embed-code.webp"></p>
<p>接著需要去版型的原始碼裡面找一下要插入 HTML tags 到 head 底下要插入在哪裡，經過搜尋之後發現是
<a href="https://github.com/adityatelange/hugo-PaperMod/blob/9ea3bb0e1f3aa06ed7715e73b5fabb36323f7267/layouts/partials/extend_head.html" target="_blank" rel="noopener">這個檔案</a>
，於是我們必須創一個檔案叫做 <code>layouts/partials/extend_head.html</code> 然後把剛才複製的內容寫進去。</p>
<p>再來需要去版型的原始碼裡面找一下控制字體的 css 在哪，經過搜尋之後
<a href="https://github.com/adityatelange/hugo-PaperMod/blob/9ea3bb0e1f3aa06ed7715e73b5fabb36323f7267/assets/css/core/reset.css#L27" target="_blank" rel="noopener">我發現是這一行</a>
</p>
<p>創一個檔案叫做 <code>assets/css/extended/custom-font.css</code> 然後寫入以下內容：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">body</span><span class="p">:</span><span class="nd">lang</span><span class="o">(</span><span class="nt">zh-tw</span><span class="o">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">font-family</span><span class="p">:</span> <span class="o">-</span><span class="n">apple-system</span><span class="p">,</span> <span class="n">BlinkMacSystemFont</span><span class="p">,</span> <span class="s2">&#34;Segoe UI&#34;</span><span class="p">,</span> <span class="n">Roboto</span><span class="p">,</span> <span class="n">Oxygen</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Ubuntu</span><span class="p">,</span> <span class="n">Cantarell</span><span class="p">,</span> <span class="s2">&#34;Open Sans&#34;</span><span class="p">,</span> <span class="s2">&#34;Helvetica Neue&#34;</span><span class="p">,</span> <span class="s2">&#34;Noto Sans TC&#34;</span><span class="p">,</span> <span class="kc">sans-serif</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>注意這一段就是直接從原本的程式碼抄過來，加上 <code>body:lang(zh-tw)</code> 的 selector 去覆蓋掉繁體中文的字體風格，但因為我的文章是中英文混雜的，為了不改變到英文字體，所以 <code>&quot;Noto Sans TC&quot;</code> 要放在 fallback font 的前面，也就是 <code>sans-serif</code> 的前面。</p>
<h3 id="留言板">留言板</h3>
<p>因為我們的網站是 Static Site，因此必須依靠外部服務來提供留言板功能，最有名的應該是 
<a href="https://disqus.com/" target="_blank" rel="noopener">Disqus</a>
。但這個服務的免費方案會有一堆廣告顯示在網站上，我覺得不是很好，因此最後選擇使用 
<a href="https://github.com/giscus/giscus" target="_blank" rel="noopener">giscus</a>
，這是使用 GitHub Discussions 當作留言的存放地方，唯一缺點是不像 Disqus 可以讓使用者用 Google 或社群媒體帳號登入留言，但我的文章目前應該都會是技術文章，看技術文章的人應該都有 GitHub，所以問題不大。</p>
<p>比較麻煩的是我必須讓這個留言板支援多語言和 light、dark mode 切換，所以必須自己寫一點 Javascript，最後大概長這樣（一些敏感資訊已被替換掉）</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;giscus-script&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">lang</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nx">lang</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">category</span> <span class="o">=</span> <span class="s2">&#34;English Comments&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">categoryId</span> <span class="o">=</span> <span class="s2">&#34;&lt;your-category-id&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">lang</span> <span class="o">===</span> <span class="s2">&#34;zh-tw&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">lang</span> <span class="o">=</span> <span class="s2">&#34;zh-TW&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">category</span> <span class="o">=</span> <span class="s2">&#34;Traditional Chinese Comments&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">categoryId</span> <span class="o">=</span> <span class="s2">&#34;&lt;your-category-id&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s2">&#34;pref-theme&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">theme</span> <span class="o">!==</span> <span class="s2">&#34;light&#34;</span> <span class="o">&amp;&amp;</span> <span class="nx">theme</span> <span class="o">!==</span> <span class="s2">&#34;dark&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">theme</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">matchMedia</span><span class="p">(</span><span class="s1">&#39;(prefers-color-scheme: dark)&#39;</span><span class="p">).</span><span class="nx">matches</span> <span class="o">?</span> <span class="s2">&#34;dark&#34;</span> <span class="o">:</span> <span class="s2">&#34;light&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">giscusAttributes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;src&#34;</span><span class="o">:</span> <span class="s2">&#34;https://giscus.app/client.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-repo&#34;</span><span class="o">:</span> <span class="s2">&#34;&lt;your-repo&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-repo-id&#34;</span><span class="o">:</span> <span class="s2">&#34;&lt;your-repo-id&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-category&#34;</span><span class="o">:</span> <span class="nx">category</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-category-id&#34;</span><span class="o">:</span> <span class="nx">categoryId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-mapping&#34;</span><span class="o">:</span> <span class="s2">&#34;pathname&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-strict&#34;</span><span class="o">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-reactions-enabled&#34;</span><span class="o">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-emit-metadata&#34;</span><span class="o">:</span> <span class="s2">&#34;0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-input-position&#34;</span><span class="o">:</span> <span class="s2">&#34;top&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-theme&#34;</span><span class="o">:</span> <span class="nx">theme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-lang&#34;</span><span class="o">:</span> <span class="nx">lang</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;data-loading&#34;</span><span class="o">:</span> <span class="s2">&#34;lazy&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;crossorigin&#34;</span><span class="o">:</span> <span class="s2">&#34;anonymous&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;async&#34;</span><span class="o">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">giscusScript</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&#34;script&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">giscusAttributes</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(([</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">])</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">giscusScript</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;giscus-script&#34;</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">giscusScript</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">function</span> <span class="nx">setGiscusTheme</span><span class="p">(</span><span class="nx">theme</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">giscusScript</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">giscusScript</span><span class="p">.</span><span class="nx">remove</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">newGiscusScript</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&#34;script&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">giscusAttributes</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(([</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">])</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">newGiscusScript</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="nx">newGiscusScript</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="s1">&#39;data-theme&#39;</span><span class="p">,</span> <span class="nx">theme</span><span class="p">);</span> <span class="c1">// Set the new theme
</span></span></span><span class="line"><span class="cl">      <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;giscus-script&#34;</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">newGiscusScript</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">function</span> <span class="nx">handleStorageChange</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s1">&#39;pref-theme&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setGiscusTheme</span><span class="p">(</span><span class="nx">theme</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">&#34;storage&#34;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="s1">&#39;pref-theme&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">handleStorageChange</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Override the localStorage setItem method to detect changes in the same browsing context
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">originalSetItem</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">originalSetItem</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">key</span> <span class="o">===</span> <span class="s1">&#39;pref-theme&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">handleStorageChange</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="部屬">部屬</h2>
<p>Static Site Hosting 的平台很多，包含 
<a href="https://pages.github.com/" target="_blank" rel="noopener">GitHub Pages</a>
、
<a href="https://www.netlify.com/" target="_blank" rel="noopener">Netlify</a>
、
<a href="https://pages.cloudflare.com/" target="_blank" rel="noopener">Cloudflare Pages</a>
 等，我選用 Cloudflare Pages，原因很簡單，因為他的免費方案的限制最少，甚至沒有頻寬限制。</p>
<p>部屬非常簡單，照著
<a href="https://developers.cloudflare.com/pages/get-started/git-integration/" target="_blank" rel="noopener">官方的文件</a>
，連接 GitHub repo 之後，build command 選擇 <code>hugo</code> 就行了。</p>
<h2 id="cms">CMS</h2>
<p>每次都要坐到電腦前面，打開文字編輯器，才有辦法寫部落格，不是很方便，常見解法是用 CMS（Content Management System），以前我都是用 
<a href="https://decapcms.org/" target="_blank" rel="noopener">Decap CMS</a>
（之前叫做 Netlify CMS），但他對接 GitHub repo 不是很方便，而且 YAML frontmatter 和 body 之間不會產生空行，有時候會有點問題。所以這次我用 
<a href="https://tina.io/" target="_blank" rel="noopener">Tina CMS</a>
。剛好 Tina Cloud 2 人以下是免費的。</p>
<p>設定 Tina CMS 其實沒有很難，照著官網設定完之後，去 Cloudflare Pages 把 build command 改成 <code>git fetch --unshallow &amp;&amp; npx --yes tinacms build &amp;&amp; [ &quot;$CF_PAGES_BRANCH&quot; = &quot;main&quot; ] &amp;&amp; hugo --minify || hugo -b $CF_PAGES_URL --minify</code> 並且把 Environment variables 都加上去就行了。效果會像這樣：</p>
<p><img alt="Tina CMS" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/tina-cms.webp"></p>
<p><img alt="Tina CMS Edit Post" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/tina-cms-edit-post.webp"></p>
<h2 id="seo">SEO</h2>
<h3 id="favicon">Favicon</h3>
<p>
<a href="https://en.wikipedia.org/wiki/Favicon" target="_blank" rel="noopener">Favicon</a>
 是網頁上面的小圖案，沒有設定的話在 Chrome 裡面就會變成一顆地球，不是很好。</p>
<p>去 
<a href="https://realfavicongenerator.net/" target="_blank" rel="noopener">Favicon Generator</a>
，丟一個圖片上去，然後把檔案下載下來，解壓縮之後把裡面的檔案全部複製到 <code>static</code> 資料夾裡面。之後參考各個版型的文件要怎麼設定 favicon。</p>
<h3 id="opengraph">OpenGraph</h3>
<p>
<a href="https://en.wikipedia.org/wiki/Facebook_Platform#Open_Graph_protocol" target="_blank" rel="noopener">OpenGraph</a>
 是你的網站被分享到社群網站的時候的的一些圖片、描述等設定。可以使用 
<a href="https://www.opengraph.xyz/" target="_blank" rel="noopener">https://www.opengraph.xyz/</a>
 這個網站來檢查你的網站被分享到社群網站的樣子會長怎樣。</p>
<h3 id="google-search-console">Google Search Console</h3>
<p>把網站提交到 
<a href="https://search.google.com/search-console/about" target="_blank" rel="noopener">Google Search Console</a>
 可以加快被 Indexing 的速度。在裡面可以提交 sitemap，記得把 sitemap 也提交上去，XML 和 RSS 的 sitemap 都提交，可以再加快 indexing 的速度。</p>
<p><img alt="Submit Sitemap to Google Search Console" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/google-search-console-submit-sitemap.webp"></p>
<h3 id="lighthouse">LightHouse</h3>
<p>LightHouse 可以用來測量網站的效能、SEO 等。首先先用無痕視窗開啟網站，避免被 Chrome 插件影響。接著按 <code>Ctrl+Shift+J</code> 進入 Chrome Dev Tools，然後選擇 LightHouse 分頁</p>
<p><img alt="Open LightHouse" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/open-lighthouse.webp"></p>
<p>選擇 Desktop 然後點擊 Analyze page load</p>
<p><img alt="LightHouse start analyze" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/lighthouse-analyze-page-load.webp"></p>
<p>結果：</p>
<p><img alt="LightHouse Result" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/lighthouse-result.webp"></p>
<blockquote>
<p>備註：Google Web Font 效能調校</p>
<p>當使用 Lighthouse 時可能會發現有一個效能問題是來自於 Google Web Font，此時可以使用 <code>preload</code> 的方式解決：</p>
<p><img alt="Google Font preload performance problem" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/google-font-preload.webp"></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&amp;display=swap&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;preload&#34;</span> <span class="na">as</span><span class="o">=</span><span class="s">&#34;font&#34;</span> <span class="na">crossorigin</span><span class="o">=</span><span class="s">&#34;anonymous&#34;</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></blockquote>
<h2 id="統計資料">統計資料</h2>
<p>要統計網頁資料，例如瀏覽人數等，最常用的是 
<a href="https://developers.google.com/analytics" target="_blank" rel="noopener">Google Analytics</a>
，超出本篇範圍，故不詳述。</p>
<h2 id="結語">結語</h2>
<p>部屬一個網站其實要注意的細節不少，很多主題這篇文章也只是粗略帶過，畢竟不同的框架和版型要做的設定都會有點不太一樣。雖然看完這篇文章沒辦法直接復刻出一個網頁，但希望看完這篇文章可以讓你在部署自己的個人網站的時候去注意到這些細節。</p>
]]></content:encoded>
    </item>
    <item>
      <title>如何使用 K3d 在本地環境中重現 Kubernetes 的 Node-pressure Eviction</title>
      <link>https://chishengliu.com/zh-tw/posts/kubernetes-node-pressure/</link>
      <pubDate>Fri, 05 Jul 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/zh-tw/posts/kubernetes-node-pressure/</guid>
      <description>在這篇文章中，我將分享如何在本地開發環境中重現 Kubernetes 的 Node-pressure Eviction。透過設定 k3d cluster 的記憶體限制，我成功模擬了這個情境，並介紹了具體的步驟來模擬 KubeRay 專案中的一個 issue。</description>
      <content:encoded><![CDATA[<h2 id="前情提要">前情提要</h2>
<p>前幾天在開發 KubeRay 專案的時候，從 
<a href="https://github.com/ray-project/kuberay/issues/2125#issuecomment-2113971140" target="_blank" rel="noopener">issue 的留言區</a>
學到了一個 Kubernetes 的知識，原來 Eviction 還有分 
<a href="https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/" target="_blank" rel="noopener">Node-pressure Eviction</a>
 和 
<a href="https://kubernetes.io/docs/concepts/scheduling-eviction/api-eviction/" target="_blank" rel="noopener">API-initiated Eviction</a>
 兩種。API-initiated Eviction 是直接 call API，或是使用像是 kubectl drain 之類的指令，特性是不管怎樣最終以這種方式被 Evict 的 Pod 會被 delete 掉，通常就會在另一個 node 上被重新 create。但是如果是 Node-pressure Eviction 的話，kubelet 只會把 Pod 的 Phase 設成 Failed，而不會把 Pod delete 掉，所以 controller 沒有特別處理的話，Pod 就不會在另一個 node 上面被重新 create。</p>
<p>現在簡述一下這個 issue 的問題：當 KubeRay operator 創出來的某個 Pod 的所在的 node 如果遇到 disk 容量不夠，該 Pod 會被 Evict，稍後 disk 容量被清出來之後，該 Pod 仍然處於 Failed 狀態，也沒有被重新 create 在其他 node 上。</p>
<p>現在我需要 reproduce 這個 issue，但重點來了，既然這兩種 Eviction 的行為不同，這表示我不能直接用 kubectl drain 之類的指令去 reproduce 出這個 issue 的情境，必須確確實實的弄出 Node-pressure Eviction。但是我也沒有 cluster 可以使用，我都是用個人電腦開發的，這導致我很難 reproduce 出這個 issue。原因是如果是在本機開發 Kubernetes 相關的應用程式，大部分會使用 
<a href="" title="https\://minikube.sigs.k8s.io/docs/">minikube</a>
、
<a href="https://kind.sigs.k8s.io/" target="_blank" rel="noopener">kind</a>
 或是 
<a href="https://k3d.io/v5.7.0/" target="_blank" rel="noopener">k3d</a>
。我要模擬的情境必須要有多個 node，因此先排出 minikube，雖然
<a href="https://minikube.sigs.k8s.io/docs/tutorials/multi_node/" target="_blank" rel="noopener">它現在也支援 multiple nodes</a>
 了，但是它還是比較常用來開發 single node 的情境。而 kind 和 k3d 上使用 Docker container 去當作 Kubernetes nodes，而我的作業系統是 Linux Mint，Docker 是原生的 Docker，不像 macOS 的 Docker 是跑在虛擬機裡的，如果我要搞出 Node-pressure 的情境，因為資源（memory、disk 等）都是跟本機共用的，所以我如果真的把某個 node 搞到有 pressure 我的電腦大概也不能用了。</p>
<p>經過瘋狂 Google 過後，我發現 
<a href="https://docs.docker.com/config/containers/resource_constraints/" target="_blank" rel="noopener">Docker 可以設定 Runtime 的 Memory 限制</a>
，還有 
<a href="https://k3d.io/v5.6.3/usage/commands/k3d_cluster_create/" target="_blank" rel="noopener">k3d 有個 &ndash;agents-memory 的 flag 可以設定 agent node 的 memory</a>
，我才找到
<a href="https://github.com/ray-project/kuberay/issues/2125#issuecomment-2203388145" target="_blank" rel="noopener">重現這個 issue 的辦法</a>
。</p>
<h2 id="步驟">步驟</h2>
<p>首先創一個 k3d cluster，有 2 個 agent nodes，每個 agent node 有 3GB 的 memory，並且在剩餘可用 memory 少於 1GiB 的時候就會開始觸發 Pod Eviction。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">k3d cluster create <span class="se">\
</span></span></span><span class="line"><span class="cl">  --agents <span class="m">2</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --k3s - arg <span class="s2">&#34;--disable=traefik@server:0&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --agents - memory 3g <span class="se">\
</span></span></span><span class="line"><span class="cl">  --k3s - arg <span class="s2">&#34;--kubelet-arg=eviction-hard=memory.available&lt;1Gi@agent:0&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  --k3s - arg <span class="s2">&#34;--kubelet-arg=eviction-hard=memory.available&lt;1Gi@agent:1&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>檢查所有 Node 的記憶體</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl get nodes -o custom-columns<span class="o">=</span>NAME:.metadata.name,CAPACITY_MEMORY:.status.capacity.memory,ALLOCATABLE_MEMORY:.status.allocatable.memory
</span></span></code></pre></td></tr></table>
</div>
</div><p>輸出：</p>
<pre tabindex="0"><code># NAME                       CAPACITY_MEMORY   ALLOCATABLE_MEMORY
# k3d-k3s-default-agent-1    3221225Ki         2172649Ki
# k3d-k3s-default-agent-0    3221225Ki         2172649Ki
# k3d-k3s-default-server-0   32590664Ki        32590664Ki
</code></pre><p>可以看到 agent 0 和 agent 1 都有 3GB 的記憶體，但可以 allocate 的記憶體是 2GB，因為剩餘記憶體不足 1GiB 的時候就會觸發 Pod Eviction。</p>
<p>接著把 agent 0 和 agent 1 上面加上 taint，這樣之後的 Pod 都只會被 deploy 到 server-0 這個 node 上面。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl taint nodes k3d-k3s-default-agent-0 <span class="nv">k3d</span><span class="o">=</span>noschedule:NoSchedule
</span></span><span class="line"><span class="cl">kubectl taint nodes k3d-k3s-default-agent-1 <span class="nv">k3d</span><span class="o">=</span>noschedule:NoSchedule
</span></span></code></pre></td></tr></table>
</div>
</div><p>安裝 KubeRay operator，這時 operator Pod 會跑在 server-0 這個 node 上。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">helm install kuberay-operator kuberay/kuberay-operator --namespace ray-system --version 1.1.1 --create-namespace
</span></span></code></pre></td></tr></table>
</div>
</div><p>移除 agent 0 和 agent 1 的 taint，為 server 0 加上 taint，這樣之後的 Pod 就不會被 deploy 到 server 0 上面。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl taint nodes k3d-k3s-default-server-0 <span class="nv">k3d</span><span class="o">=</span>noschedule:NoSchedule
</span></span><span class="line"><span class="cl">kubectl taint nodes k3d-k3s-default-agent-0 <span class="nv">k3d</span><span class="o">=</span>noschedule:NoSchedule-
</span></span><span class="line"><span class="cl">kubectl taint nodes k3d-k3s-default-agent-1 <span class="nv">k3d</span><span class="o">=</span>noschedule:NoSchedule-
</span></span></code></pre></td></tr></table>
</div>
</div><p>安裝 <code>RayCluster</code> custom resource，安裝完後 KubeRay operator 會創出一個 head pod 和一個 worker pod，因為在 helm chart 裡面 head pod 的 memory resource request 是 2G，worker pod 是 1G，而 agent 0 和 agent 1 都只有 2G 的可分配記憶體，所以這兩個 Pod 一定不會在同一個 node 上面。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">helm install raycluster kuberay/ray-cluster --version 1.1.1
</span></span></code></pre></td></tr></table>
</div>
</div><p>接下來要進入 head pod 所在的 node 裡面執行記憶體壓力測試，一樣 Google 完發現大家常用的是 
<a href="https://github.com/ColinIanKing/stress-ng" target="_blank" rel="noopener">stress-ng</a>
，所以就用它。我們必須讓 head pod 裡面有 <code>stress-ng</code> 可以用，最簡單的方式是把靜態編譯過的 <code>stress-ng</code> 直接複製到 head pod 裡面，這樣就不用管 head pod 的 base image 是什麼、缺了什麼 dependency 之類的。至於要怎麼取得靜態編譯過的執行檔，可以自己編譯，但我比較懶，我直接去把
<a href="https://hub.docker.com/r/alexeiled/stress-ng" target="_blank" rel="noopener">一個裡面帶有靜態編譯執行檔的 docker image 裡面的執行檔複製出來</a>
。假設 head pod 的名字是 <code>raycluster-kuberay-head-ldg9f</code></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl cp ./stress-ng raycluster-kuberay-head-ldg9f:/home/ray
</span></span></code></pre></td></tr></table>
</div>
</div><p>開一個在 head pod 上面的 shell</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl <span class="nb">exec</span> -it raycluster-kuberay-head-ldg9f -- bash
</span></span></code></pre></td></tr></table>
</div>
</div><p>模擬記憶體不足情形</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">./stress-ng --vm <span class="m">4</span> --vm-bytes 2G --vm-keep
</span></span></code></pre></td></tr></table>
</div>
</div><p>如此一來就可以看到 head pod 被 Evict 掉，而且是 Node-pressure Eviction。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
