<?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>Chi-Sheng Liu</title>
    <link>https://chishengliu.com/</link>
    <description>Recent content on Chi-Sheng Liu</description>
    <image>
      <title>Chi-Sheng Liu</title>
      <url>https://static.chishengliu.com/ogimage.jpeg</url>
      <link>https://chishengliu.com/</link>
    </image>
    <generator>Hugo</generator>
    <language>en-US</language>
    <copyright>2024 Chi-Sheng Liu</copyright>
    <lastBuildDate>Thu, 04 Jun 2026 05:10:27 -0700</lastBuildDate>
    <atom:link href="https://chishengliu.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>[External] Introducing KubeRay v1.4</title>
      <link>https://www.anyscale.com/blog/kuberay-v1-4</link>
      <pubDate>Sat, 21 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://www.anyscale.com/blog/kuberay-v1-4</guid>
      <description>Co-authored by me and published on the Anyscale Blog.</description>
    </item>
    <item>
      <title>SystemExit Hangs asyncio.run_coroutine_threadsafe</title>
      <link>https://chishengliu.com/posts/run-coroutine-threadsafe-systemexit/</link>
      <pubDate>Wed, 05 Mar 2025 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/run-coroutine-threadsafe-systemexit/</guid>
      <description>This post examines a debugging issue with asyncio.run_coroutine_threadsafe and the SystemExit exception.</description>
      <content:encoded><![CDATA[<p>Today, while debugging an issue with Ray, I encountered a problem because I did not notice the special behavior of Python&rsquo;s <code>asyncio.run_coroutine_threadsafe</code>. The official documentation does not explicitly mention it; you have to look at the CPython source code to understand what is happening, which led to a long debugging session. I am documenting this issue here.</p>
<p>According to the 
<a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.run_coroutine_threadsafe" target="_blank" rel="noopener">official documentation</a>
, this function is used to run a coroutine in an event loop on another thread, and it returns a <code>Future</code> object.</p>
<p>Based on the documentation, we can quickly write a 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>The <code>main</code> function creates a thread and an event loop, then submits the coroutine <code>f</code> to that event loop using <code>asyncio.run_coroutine_threadsafe</code>. It then retrieves the result with <code>future.result()</code>, handles exceptions, and finally stops the event loop. Everything seems to work fine.</p>
<p>The output is:</p>
<pre tabindex="0"><code>Inside coroutine f()
Caught exception: ValueError()
Stopping loop
</code></pre><p>Now, here is the question: what happens if the coroutine <code>f</code> raises <code>SystemExit</code> instead of <code>ValueError</code>?</p>
<p>According to the 
<a href="https://docs.python.org/3/library/exceptions.html#SystemExit" target="_blank" rel="noopener">official documentation</a>
, <code>SystemExit</code> inherits from <code>BaseException</code> rather than <code>Exception</code>. So would it be enough to change <code>Exception</code> to <code>BaseException</code> on line 21? You will find that after making these two changes and running the program again, it prints <code>Inside coroutine f()</code> and then hangs indefinitely.</p>
<p>We need to check the 
<a href="https://github.com/python/cpython/blob/e53d105872fafa77507ea33b7ecf0faddd4c3b60/Lib/asyncio/tasks.py#L991-L1011" target="_blank" rel="noopener">source code of <code>asyncio.run_coroutine_threadsafe</code></a>
 to understand what is happening. Here is the relevant part:</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>This function treats <code>SystemExit</code> and <code>KeyboardInterrupt</code> specially by directly raising them, while for other exceptions, it calls <code>future.set_exception</code>. Since it does not call <code>set_exception</code> for these two exceptions, <code>future.result()</code> never gets a result.</p>
<p>Here is a modified version of the original program:</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>The output is:</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>This behavior is a bit tricky. Both <code>future.done()</code> and <code>future.cancelled()</code> return <code>False</code>, and calling <code>future.result()</code> or <code>future.exception()</code> will hang indefinitely without returning anything. However, if you do not call them, you will see a warning stating that a task exception was never retrieved. And you can see that both the event loop and the thread died.</p>
<h2 id="conclusion">Conclusion</h2>
<p>If you use <code>asyncio.run_coroutine_threadsafe</code> to execute a coroutine that raises <code>SystemExit</code> or <code>KeyboardInterrupt</code>, calling <code>future.result()</code> or <code>future.exception()</code> will not work and will hang indefinitely. Additionally, the thread and event loop will terminate, but the main thread will remain alive.</p>
]]></content:encoded>
    </item>
    <item>
      <title>[External] Introducing the Ray Kubectl Plugin: A Simpler Way to Manage Ray Clusters on Kubernetes</title>
      <link>https://www.anyscale.com/blog/introducing-ray-kubectl-plugin</link>
      <pubDate>Thu, 20 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://www.anyscale.com/blog/introducing-ray-kubectl-plugin</guid>
      <description>Co-authored by me and published on the Anyscale Blog.</description>
    </item>
    <item>
      <title>[External] KubeRay v1.3.0: Enhancing Observability, Reliability, and Usability</title>
      <link>https://www.anyscale.com/blog/kuberay-v1-3-0</link>
      <pubDate>Thu, 20 Feb 2025 00:00:00 +0000</pubDate>
      <guid>https://www.anyscale.com/blog/kuberay-v1-3-0</guid>
      <description>Co-authored by me and published on the Anyscale Blog.</description>
    </item>
    <item>
      <title>How to Write a Kubernetes Operator Using client-go</title>
      <link>https://chishengliu.com/posts/kubernetes-operator-sample-controller/</link>
      <pubDate>Sat, 14 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/kubernetes-operator-sample-controller/</guid>
      <description>Learn to implement Kubernetes custom controllers with client-go through a detailed sample-controller codebase walkthrough.</description>
      <content:encoded><![CDATA[<h2 id="what-is-client-go">What is client-go?</h2>
<p>
<a href="https://github.com/kubernetes/client-go" target="_blank" rel="noopener">client-go</a>
 is the official Golang client for Kubernetes, responsible for interacting with the Kubernetes API server using REST API. In fact, <code>client-go</code> can do almost anything, not just for writing operators. Even the internal implementation of <code>kubectl</code> is based on <code>client-go</code>. As for more specialized frameworks used to write operators, including 
<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>
, and 
<a href="https://github.com/operator-framework/operator-sdk" target="_blank" rel="noopener">operator-sdk</a>
, they will be introduced later in this series.</p>
<h2 id="introduction-to-sample-controller-mechanism">Introduction to Sample Controller Mechanism</h2>
<p>
<a href="https://github.com/kubernetes/sample-controller" target="_blank" rel="noopener">sample-controller</a>
 is an official Kubernetes example operator implemented using client-go.</p>
<p>To understand the code, we need to first understand how the operator we write interacts with <code>client-go</code>. The explanation here is a simplified version of the 
<a href="https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md" target="_blank" rel="noopener">official documentation</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>The above image comes from the official documentation and can also be found in many tutorials online.</p>
<p>The upper part of the image shows the internal components of <code>client-go</code>. It looks complicated, with terms like Reflector, Informer, and Indexer, but actually, you only need to understand Informer. The main function of Informer is to notify us when the status of resources changes. Why don&rsquo;t we just hit the API directly to the Kubernetes API server? This is because calling the API is an expensive operation, and so the Informer maintains an <em>Informer Cache</em> to reduce the number of requests to the API server.</p>
<p>The lower part shows what we need to write ourselves:</p>
<ul>
<li><strong>Resource Event Handlers</strong>: When Informer notifies us of a change in a resource&rsquo;s status, we decide what to do, which usually means putting its key (namespace + name) into the workqueue.</li>
<li><strong>Workqueue</strong>: This stores the keys of all objects waiting to be processed. Our operator constantly retrieves items from the workqueue and tries to bring the cluster to the desired state. If it fails, the object key may need to be added back to the workqueue for further processing.</li>
</ul>
<h2 id="sample-controller-codebase-walkthrough">Sample Controller Codebase Walkthrough</h2>
<h3 id="defining-the-crd">Defining the CRD</h3>
<p>In 
<a href="https://github.com/kubernetes/sample-controller/blob/master/pkg/apis/samplecontroller/register.go" target="_blank" rel="noopener">register.go</a>
, the GroupName is defined, and in 
<a href="https://github.com/kubernetes/sample-controller/blob/master/pkg/apis/samplecontroller/v1alpha1/types.go" target="_blank" rel="noopener">v1alpha1/types.go</a>
, the type for the CRD is defined. You can see that it defines a <code>Foo</code> resource as follows:</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>Apart from the basic <code>TypeMeta</code> and <code>ObjectMeta</code>, it defines <code>Spec</code> and <code>Status</code>. <code>Spec</code> is where users can input data, defining the &ldquo;desired state of the resource.&rdquo; <code>Status</code> is where our Operator writes values, representing the &ldquo;current state of the resource.&rdquo;</p>
<p>The Sample Controller uses 
<a href="https://github.com/kubernetes/code-generator" target="_blank" rel="noopener">Kubernetes&rsquo; code-generator</a>
 to generate typed clients, informers, listers, and deep-copy functions for the CRD. So whenever you modify <code>types.go</code>, you need to run <code>./hack/update-codegen.sh</code> to regenerate the code.</p>
<h3 id="program-entrypoint">Program Entrypoint</h3>
<p>Next, look at 
<a href="https://github.com/kubernetes/sample-controller/blob/master/main.go" target="_blank" rel="noopener">main.go</a>
, which is the entry point of the program. It&rsquo;s actually very simple, just pay attention to these lines:</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>Basically, it creates clients and informers for both Kubernetes built-in resources and our custom <code>Foo</code> resource, then passes them to <code>NewController</code>, and finally calls <code>controller.Run</code>.</p>
<h3 id="main-logic">Main Logic</h3>
<p>Now, let&rsquo;s examine the main part: 
<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>This part shows the event handler we talked about earlier, where you can register <code>AddFunc</code>, <code>UpdateFunc</code>, and <code>DeleteFunc</code>. When the informer detects a change in the resource, it will call the corresponding function. You can see that for <code>fooInformer</code>, it simply calls <code>enqueueFoo</code>, while for <code>deploymentInformer</code>, it calls <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> is just adding the key of the <code>Foo</code> object to the workqueue. You can see here:</p>
<ul>
<li>
<a href="https://pkg.go.dev/k8s.io/client-go/tools/cache#ObjectToName" target="_blank" rel="noopener">cache.ObjectToName</a>
: Takes an object and converts it to <code>ObjectName</code>.</li>
<li>
<a href="https://pkg.go.dev/k8s.io/client-go/tools/cache#ObjectName" target="_blank" rel="noopener">ObjectName</a>
: This is just 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>This is part of the <code>handleObject</code> function. It checks whether the owner of the deployment is <code>Foo</code>. If it&rsquo;s not, we ignore it. If it is, we add the corresponding <code>Foo</code> key to the workqueue. This relates to a concept called 
<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/" target="_blank" rel="noopener">OwnerReference</a>
, where certain objects in Kubernetes are owned by others. The default behavior is that when the owner is deleted, the owned objects are also deleted. For example, a ReplicaSet is the owner of Pods, so when the ReplicaSet is deleted, the Pods it manages are also deleted. This is also why there is no <code>DeleteFunc</code> handler for <code>fooInformer</code> — when <code>Foo</code> is deleted, we want to delete all corresponding deployments, but since the owner of the deployment is already set to <code>Foo</code>, they will be deleted automatically without further handling.</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> is the entry point called by the controller in <code>main.go</code>. It starts multiple goroutines to run <code>runWorker</code>. <code>runWorker</code> is simply an infinite loop calling <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><span class="lnt">17
</span><span class="lnt">18
</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;
</span></span></span><span class="line"><span class="cl"><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s"> 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>This is a portion of <code>processNextWorkItem</code>. First, it retrieves an object key from the workqueue, then calls <code>syncHandler</code> to handle it. If successful, it removes it from the workqueue. Otherwise, it performs error handling and puts the key back into the workqueue for later processing.</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>Finally, this is a portion of <code>syncHandler</code>. Here is where we write the actual logic, adjusting the cluster to match the desired state declared by the user in the <code>Spec</code>. The desired state in this case is that the deployment specified in <code>Spec</code> has been created and that the replica count matches what is declared in <code>Spec</code>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>After going through this, you may feel that I&rsquo;ve only covered a small part of the Sample Controller code. That&rsquo;s because <code>client-go</code> is a rather low-level library, and there are some downsides to using it for writing operators:</p>
<ul>
<li>We don&rsquo;t need to write much custom logic, but still have to write some boilerplate, which can feel redundant.</li>
<li>When watching to different resources, we need to declare informers, listers, and other repetitive things for each resource. For example, in the Sample Controller, both <code>fooInformer</code> and <code>deploymentInformer</code> are declared, and managing multiple resources becomes cumbersome.</li>
</ul>
<p>These drawbacks have led to the development of other frameworks that are more specialized for writing operators, such as 
<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>
, and 
<a href="https://github.com/operator-framework/operator-sdk" target="_blank" rel="noopener">operator-sdk</a>
. Stay tuned for future articles in this series to learn about these frameworks.</p>
<h2 id="references">References</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 Local Setup via Ollama: Pitfalls Prevention Guide</title>
      <link>https://chishengliu.com/posts/graphrag-local-ollama/</link>
      <pubDate>Wed, 11 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/graphrag-local-ollama/</guid>
      <description>By monkey-patching GraphRAG&amp;#39;s package, make it support the locally hosted Llama 3.1 LLM model on Ollama. All the pitfalls have been handled for you!</description>
      <content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<p>If you just want to see the conclusion, skip to the 
<a href="#summary">Summary</a>
. Please note that the content discussed here is only guaranteed to work for version 0.3.2. Other versions have not been tested.</p>
<h2 id="introduction">Introduction</h2>
<p>
<a href="https://microsoft.github.io/graphrag/" target="_blank" rel="noopener">GraphRAG</a>
 is a new Retrieval-Augmented Generation (RAG) technology released by Microsoft. Unlike the usual RAG approach, which breaks text into chunks, vectorizes them, and stores them in a vector store, and then compares the similarity by vectorizing the user&rsquo;s query, GraphRAG extracts information from text and builds a Knowledge Graph. This approach has the advantage of better understanding the complex relationships between different documents.</p>
<p>As of the time of writing, the latest official version is 0.3.2, and here is the 
<a href="https://github.com/microsoft/graphrag/tree/v0.3.2" target="_blank" rel="noopener">GitHub repository link for version 0.3.2</a>
. While the content discussed here might work for other versions, it is not guaranteed.</p>
<p>If you&rsquo;ve read the official tutorial, you&rsquo;ll notice that the config for version 0.3.2 only supports OpenAI and Azure OpenAI. You need to put your API Key in the config file to use it, which works and is the simplest approach. However, GraphRAG requires frequent LLM calls during indexing, which makes it costly, and we cannnot use our own custom fine-tuned model.</p>
<p>So, is it possible to use an open-source model running locally, such as 
<a href="https://llama.meta.com/" target="_blank" rel="noopener">Llama 3.1</a>
? The answer is yes, but you&rsquo;ll need to overcome several challenges, as the official implementation doesn&rsquo;t support local models, and you&rsquo;ll need to modify some things manually.</p>
<p>You might wonder why not use <code>LLMGraphTransformer</code> from LangChain instead? I can only say that, as of now, it is still an experimental feature and differs slightly from Microsoft&rsquo;s official implementation. If you&rsquo;re interested, you can check out the following links:</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="prerequisites">Prerequisites</h2>
<ul>
<li>Install 
<a href="https://ollama.com/" target="_blank" rel="noopener">Ollama</a>
 and have it running.</li>
<li>An LLM model, such as <code>ollama pull llama3.1:8b</code>.</li>
<li>A Text Embedding model, such as <code>ollama pull nomic-embed-text</code>.</li>
</ul>
<h2 id="troubleshooting-process">Troubleshooting Process</h2>
<p>When I first opened the 
<a href="https://github.com/microsoft/graphrag/blob/v0.3.2/docsite/posts/get_started.md" target="_blank" rel="noopener">Get Started</a>
 page, I was excited to see how short it was and thought I could get it working quickly. Looking back, I was overly optimistic&hellip;</p>
<p>The first few steps were straightforward and did not cause issues:</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>Next, I needed to edit <code>settings.yaml</code>, and that&rsquo;s where the problems began. How should I configure the model?</p>
<p>First, delete <code>.env</code> because we don&rsquo;t need the API key.</p>
<p>Then, open <code>settings.yaml</code> and input the following:</p>

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

<p>The differences compared to the original <code>settings.yaml</code> are:</p>
<ul>
<li><code>llm</code>
<ul>
<li><code>api_key</code>: Set it to anything. It&rsquo;s not important since we won&rsquo;t be using an API. I set it to <code>NONE</code>.</li>
<li><code>model</code>: Set this to your local LLM model, such as <code>llama3.1:8b</code>.</li>
<li><code>max_tokens</code>: Set this to a number smaller than the maximum token limit for your LLM model.</li>
<li><code>api_base</code>: Set this to <code>http://localhost:11434/v1</code>.</li>
</ul>
</li>
<li><code>embeddings</code>
<ul>
<li><code>llm</code>
<ul>
<li><code>api_key</code>: Again, set it to anything since we won&rsquo;t use the API. I set it to <code>NONE</code>.</li>
</ul>
</li>
<li><code>model</code>: Set this to your local Text Embedding model, such as <code>nomic-embed-text</code>.</li>
<li><code>api_base</code>: Set this to <code>http://localhost:11434/api</code>.</li>
<li><code>batch_max_tokens</code>: Set this to a number smaller than the maximum token limit for your Text Embedding model.</li>
</ul>
</li>
<li><code>chunks</code>
<ul>
<li>
<p><code>size</code>: Lower this to 300. If not adjusted, you&rsquo;ll encounter the following error. More details in the related 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>After changing the settings, excitedly run <code>python -m graphrag.index --init --root ./ragtest</code>, and you&rsquo;ll get the following 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>The solution comes from this 
<a href="https://github.com/microsoft/graphrag/issues/370#issuecomment-2211821370" target="_blank" rel="noopener">GitHub comment</a>
.</p>
<p>The comment suggests modifying a file&rsquo;s content, but I don&rsquo;t think that&rsquo;s a good idea, as it might cause issues when using the package&rsquo;s original functionality later. So, I opted for an external monkey patch approach. First, create a file called <code>monkey_patch.py</code> and input the following content:</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>Make sure to install <code>ollama</code> by running <code>pip install ollama</code>.</p>
<p>We download the <code>graphrag.index</code> CLI content and monkey-patch it.</p>
<p>First, run</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>Then, replace line 8 of <code>index.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></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">.cli</span> <span class="kn">import</span> <span class="n">index_cli</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>with the following:</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>Now, run <code>python index.py --root ./ragtest</code>. Finally, no more errors!</p>
<p><img alt="Successfully Indexed" loading="lazy" src="https://static.chishengliu.com/posts/graphrag-local-ollama/index-success.webp"></p>
<p>Try executing a 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>This error occurs:</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>Try executing a 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>This error occurs:</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>So, I had to monkey-patch the query CLI as well. Related issues and comments:</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>Add the following two functions to <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><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>Run</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>Replace line 9 of <code>query.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></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">.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></code></pre></td></tr></table>
</div>
</div><p>with the following:</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>Then, run the Global and Local Search queries. Local Search looks good, while Global Search still needs parameter adjustments. But at least there are no more errors! Hooray!</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="summary">Summary</h2>
<p>All the code from this article is in 
<a href="https://gist.github.com/MortalHappiness/7030bbe96c4bece8a07ea9057ba18b86" target="_blank" rel="noopener">this Gist</a>
. Here&rsquo;s a quick summary of the steps:</p>
<ul>
<li>Follow the official Get Started guide and run <code>python -m graphrag.index --init --root ./ragtest</code>.</li>
<li>Replace <code>settings.yaml</code> with the one in the Gist.</li>
<li>Run
<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>Run
<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>Download the <code>graphrag_monkey_patch.py</code> from the Gist.</li>
<li>Replace line 8 of <code>index.py</code> with:
<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>Replace line 9 of <code>query.py</code> with:
<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>Whenever you need to use <code>python -m graphrag.index</code>, switch to using <code>python index.py</code> instead. Similarly, for <code>python -m graphrag.query</code>, switch to <code>python query.py</code>.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>What are Kubernetes Operators and Custom Resources?</title>
      <link>https://chishengliu.com/posts/kubernetes-operator-introduction/</link>
      <pubDate>Thu, 29 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/kubernetes-operator-introduction/</guid>
      <description>In this post, I will introduce what Custom Resources and Kubernetes Operators are, with a hands-on example to create a custom resource named ChiShengLiu.</description>
      <content:encoded><![CDATA[<h2 id="kubernetes-api-resources">Kubernetes API Resources</h2>
<p>I assume that you have a basic understanding of Kubernetes and know how to use <code>kubectl</code>.</p>
<p>Pod, ReplicaSet, Service, and even Namespace are actually types of API Resources.</p>
<p>You can use <code>kubectl api-resources</code> to view all the API Resources currently available in your Kubernetes cluster.</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, as the name suggests, refer to custom API Resources that we can create and install in Kubernetes to extend its functionality.</p>
<p>According to the official documentation, there are two ways to create Custom Resources, but the most common method is using Custom Resource Definitions (CRD), so we will focus on CRDs here.</p>
<p>In fact, CRD itself is a type of API Resource, and this can be clearly seen with the following command:</p>
<pre tabindex="0"><code>$ kubectl api-resources | grep -i custom
customresourcedefinitions   crd,crds   apiextensions.k8s.io/v1   false   CustomResourceDefinition
</code></pre><p>Think about how we usually create Resources; typically, we write a YAML file and then create it with <code>kubectl apply</code>. The steps to create a CRD are the same.</p>
<p>First, create a file called <code>chishengliu-crd.yaml</code> with the following content:</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>Then run the following command:</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>You will notice that there is now a new Resource in the cluster called <code>ChiShengLiu</code>.</p>
<p>To create this kind of <code>ChiShengLiu</code> Resource, you also write a YAML file and then use <code>kubectl apply</code>. Create a file called <code>happy-chishengliu.yaml</code> with the following content:</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>Then run the following command:</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>You can see that we have created a <code>ChiShengLiu</code> Resource named <code>happy-chishengliu</code>.</p>
<h2 id="kubernetes-operator">Kubernetes Operator</h2>
<p>Having a Custom Resource alone is not very useful. You might notice that after creating this <code>ChiShengLiu</code> Resource, nothing changes in the Kubernetes cluster, unlike with built-in Resources.</p>
<p>Built-in Resources have functionality because there are corresponding Controllers running in the cluster. The Controller&rsquo;s job is to monitor certain Resources in the cluster and adjust the cluster to the state declared in the Resource&rsquo;s <code>spec</code>. For example, the Replication Controller monitors all ReplicaSets in the cluster, and when any ReplicaSet is created, updated, or deleted, it adjusts the cluster to the corresponding state, which means creating the correct number of Pods.</p>
<p>Therefore, if we want the <code>ChiShengLiu</code> Resource we created earlier to have an effect, we need to install a Controller for <code>ChiShengLiu</code> in the cluster. This Controller is not built-in and needs to be written by ourselves, so it&rsquo;s called a Custom Controller, also known as a Kubernetes Operator. The official documentation refers to this pattern as the Operator Pattern.</p>
<p>The most common use case for Kubernetes Operators is to encapsulate underlying logic, especially for framework or tool developers, allowing users to install everything by simply applying a YAML file with <code>kubectl apply</code>, with the Operator handling the rest.</p>
<p>So, how do we write a Kubernetes Operator? Stay tuned for future updates in this series.</p>
<h2 id="references">References</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 Things to Know Before Making Open Source Contributions</title>
      <link>https://chishengliu.com/posts/opensource-pre-contribution-tips/</link>
      <pubDate>Fri, 23 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/opensource-pre-contribution-tips/</guid>
      <description>Before contributing to open-source projects, know these 5 things: documentation, issue tracker, commits, Pull Requests, and some special terms.</description>
      <content:encoded><![CDATA[<h2 id="read-the-documentation">Read the Documentation</h2>
<p>There are a few essential documents that you must read. These might be <code>.md</code> files, <code>.rst</code> files, or specific pages on the project&rsquo;s website:</p>
<ul>
<li><code>README.md</code>: The file that introduces the project.</li>
<li>Contribution Guide: This outlines how to contribute to the project, including any formatting or styling rules you need to follow when submitting contributions.</li>
<li>Code of Conduct: The community&rsquo;s behavior guidelines. Generally, as long as you are not too rude or disrespectful, you should be fine, but it&rsquo;s still important to read through it.</li>
</ul>
<h2 id="issue-tracker">Issue Tracker</h2>
<p>Understand what issue tracker the project uses. Common ones include GitHub issues or JIRA. As a beginner, it&rsquo;s typical to start by looking for existing issues to work on. If you need to open an issue, be sure to search first to see if a similar issue has already been opened.</p>
<h2 id="commits">Commits</h2>
<p>Check whether the project has specific requirements for commit message formatting. It&rsquo;s a good habit not to write commit messages in a sloppy manner. If there are no special requirements, you can refer to 
<a href="https://www.conventionalcommits.org/en/v1.0.0/" target="_blank" rel="noopener">Conventional Commits</a>
. The basic format looks like this:</p>
<pre tabindex="0"><code>&lt;type&gt;[optional scope]: &lt;description&gt;

[optional body]

[optional footer(s)]
</code></pre><p>Example:</p>
<pre tabindex="0"><code>feat: Add XXX feature.
</code></pre><pre tabindex="0"><code>fix(frontend): Fix XXX bugs.
</code></pre><p>The most common types are:</p>
<ul>
<li><code>feat</code>: Add new features.</li>
<li><code>fix</code>: Bug fixes.</li>
<li><code>docs</code>: Documentation only changes.</li>
<li><code>refactor</code>: Refactoring. That means it does not change any existing functionalities.</li>
<li><code>test</code>: Add or correcting existing tests.</li>
<li><code>chore</code>: Miscellaneous tasks; use <code>chore</code> if you don&rsquo;t know how to classify it.</li>
</ul>
<p>Some projects might have slight variations in their symbols or casing, such as:</p>
<pre tabindex="0"><code>[Feat] Add XXX feature.
</code></pre><pre tabindex="0"><code>[Fix][Frontend] Fix XXX bugs.
</code></pre><p>Additionally, some projects provide <code>pre-commit</code> hooks. If so, follow the instructions to install them.</p>
<p>Some projects require every commit to be sign-offed. For details, please refer to the other tutorial written by me: 







  <a href="https://chishengliu.com/posts/opensource-git-operations/">The Ultimate Guide to Git for Open-Source Development</a>

.</p>
<h2 id="pull-request">Pull Request</h2>
<p>Unless there are special circumstances, one Pull Request usually corresponds to one issue. Generally, before you submit a Pull Request, if there is no corresponding issue, you should open an issue first. However, if the project does not strictly enforce this rule, and your Pull Request contains only minor changes, you can also directly submit a Pull Request.</p>
<h2 id="some-special-terms">Some Special Terms</h2>
<p>Sometimes reviewers leave comments using uncommon abbreviations or terms. Besides understanding them, you can also learn to use these terms, which will make you feel more professional 😂.</p>
<ul>
<li>LGTM: Looks good to me. Indicates that the reviewer thinks your code is fine.</li>
<li>PTAL: Please take a look. A request for someone else to review.</li>
<li>WIP: Work in progress. Indicates that this PR is not ready yet.</li>
<li>ditto: Means &ldquo;same as above&rdquo;.</li>
<li>nit: Indicates a suggestion for a change that the reviewer considers minor, so you can choose whether to make it or not. For example: <code>nit: Rename this variable to XXX</code>.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>How to Use Lazygit to Skyrocket Your Git Efficiency</title>
      <link>https://chishengliu.com/posts/lazygit-tutorial/</link>
      <pubDate>Mon, 19 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/lazygit-tutorial/</guid>
      <description>Boost your Git efficiency with Lazygit CLI tool. This post covers frequently used hotkeys and an example of how I use it for open-source development.</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>As an engineer, Git is an essential tool, and 
<a href="https://github.com/jesseduffield/lazygit" target="_blank" rel="noopener">Lazygit</a>
 is a command-line Git tool written in Golang that can save you a significant amount of time by reducing the need to manually type Git commands. Saving a few seconds each time can add up to hours saved over the course of a year.</p>
<p>Don&rsquo;t tell me you type commands quickly (you&rsquo;re just too lazy to learn new tools), or that you use something like <code>alias gs='git status'</code> to speed things up (you&rsquo;re just afraid of learning a lot of shortcuts). Trust me, nothing can be faster than using this tool.</p>
<p>I won&rsquo;t go into the installation steps here; please refer to the 
<a href="https://github.com/jesseduffield/lazygit?tab=readme-ov-file#installation" target="_blank" rel="noopener">official documentation</a>
 for that.</p>
<h2 id="interface">Interface</h2>
<p>After installation, you can start Lazygit in a repository by executing <code>lazygit</code>. I set up an alias <code>alias lg='lazygit'</code> to help me launch it even faster.</p>
<p>The interface is divided into the left and right sides. The left side has five sections: Status, Files, Local branches, Commits, and Stash. Some sections also have tabs, such as the Files section, which has tabs for Worktrees and Submodules.</p>
<p>The right side has two areas: a detailed information preview and a list of the corresponding Git commands triggered by each shortcut.</p>
<h2 id="frequently-used-shortcuts">Frequently Used Shortcuts</h2>
<p>You can find the complete list of shortcuts in the 
<a href="https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Keybindings_en.md" target="_blank" rel="noopener">official documentation</a>
.</p>
<p>Here are a few shortcuts I use the most:</p>
<ul>
<li>Global
<ul>
<li><code>h</code> and <code>l</code>: Switch between the sections on the left side.</li>
<li><code>k</code> and <code>j</code>: Move the cursor up and down.</li>
<li><code>/</code>: Search for a string.</li>
<li><code>[</code> and <code>]</code>: Switch between the tabs on the left side.</li>
<li><code>Ctrl + o</code>: Copy the file name (in the Files section), branch name (in the Local branches section), or commit hash (in the commits section) to the clipboard.</li>
<li><code>q</code>: Exit Lazygit.</li>
</ul>
</li>
<li>Left Side - Files Section
<ul>
<li><code>Space</code>: Add or remove the selected file from the staging area.</li>
<li><code>a</code>: Add or remove all files from the staging area.</li>
<li><code>Enter</code>: Collapse or expand folders when used on a folder, or enter the file preview area on the right side when used on a file.</li>
<li><code>C</code>: Open an external text editor to edit the commit message, then commit.</li>
<li><code>A</code>: Amend the last commit.</li>
<li><code>d</code>: Discard all changes to the selected file that have not been committed. Note that if the file is newly created, it will be deleted.</li>
<li><code>D</code>: Discard all changes to all files that have not been committed. Use this when you&rsquo;ve messed up and want to start over.</li>
<li><code>S</code>: Open the stash options.</li>
</ul>
</li>
<li>Left Side - Local Branches Section
<ul>
<li><code>Space</code>: Switch to the selected branch.</li>
<li><code>n</code>: Checkout a new branch from the selected branch.</li>
<li><code>f</code>: Fetch new commits for the selected branch from the remote.</li>
<li><code>r</code>: Rebase onto the selected branch.</li>
<li><code>R</code>: Rename the selected branch.</li>
<li><code>p</code>: Pull the selected branch.</li>
<li><code>P</code>: Push the selected branch.</li>
<li><code>o</code>: Open the GitHub pull request page for the selected branch.</li>
<li><code>Enter</code>: View the commits of the selected branch. Use <code>Esc</code> to go back.</li>
</ul>
</li>
<li>Left Side - Commits Section
<ul>
<li><code>Enter</code>: View the files changed in the selected commit. Use <code>Esc</code> to go back.</li>
<li><code>R</code>: Open an external editor to modify the commit message.</li>
<li><code>d</code>: Delete the selected commit.</li>
<li><code>g</code>: Open the reset options.</li>
<li><code>F</code>: Create a <code>fixup!</code> commit for the selected commit.</li>
<li><code>S</code>: Squash all <code>fixup!</code> commits on top of the selected commit.</li>
</ul>
</li>
<li>Left Side - Stash Section
<ul>
<li><code>g</code>: Apply and delete the selected stash.</li>
<li><code>Space</code>: Apply the selected stash without deleting it.</li>
<li><code>d</code>: Delete the selected stash.</li>
</ul>
</li>
<li>Right Side - Preview Area
<ul>
<li><code>v</code>: Enter multi-line selection mode.</li>
<li><code>Space</code>: Add or remove the selected line or lines (when in multi-line selection mode) from the staging area.</li>
<li><code>Esc</code>: Return to the left side sections.</li>
</ul>
</li>
</ul>
<h2 id="practical-example">Practical Example</h2>
<p>Let&rsquo;s combine this with the previous article on 







  <a href="https://chishengliu.com/posts/opensource-git-operations/">The Ultimate Guide to Git for Open-Source Development</a>

 and show how I use Lazygit to develop open-source projects.</p>
<h3 id="creating-a-new-branch">Creating a New Branch</h3>
<p>First, make sure that the <code>upstream-master</code> branch is in sync with the remote. Use <code>h</code> and <code>l</code> to switch to the Local branches section, then use <code>k</code> and <code>j</code> to navigate to the <code>upstream-master</code> branch. Next, press <code>f</code> to fetch the latest changes. Finally, press <code>n</code> to create a new branch.</p>
<h3 id="syncing-with-upstream">Syncing with Upstream</h3>
<p>Again, navigate to the <code>upstream-master</code> branch in the Local branches section, then press <code>f</code> to fetch the latest changes and <code>r</code> to rebase onto it.</p>
<h3 id="development-and-pull-request">Development and Pull Request</h3>
<p>Navigate to the Files section and press <code>Space</code> to add files to the staging area one by one, or use <code>a</code> to add all files at once. Then, press <code>C</code> to commit. Next, navigate to the branch you want to push in the Local branches section and press <code>P</code> to push. Finally, press <code>o</code> to open the GitHub pull request page.</p>
<h2 id="conclusion">Conclusion</h2>
<p>At first, you might find it difficult to learn all the shortcuts, but with time, they will become muscle memory. The time saved is definitely worth it.</p>
<p>If you want to learn more about the features, please visit the official repository.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Ultimate Guide to Git for Open-Source Development</title>
      <link>https://chishengliu.com/posts/opensource-git-operations/</link>
      <pubDate>Tue, 13 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/opensource-git-operations/</guid>
      <description>Feeling lost on your first attempt to contribute to an open-source project? This post covers the essential Git operations used in open-source development.</description>
      <content:encoded><![CDATA[<h2 id="initial-process">Initial Process</h2>
<p>To develop an open-source project, we must first 
<a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo" target="_blank" rel="noopener">Fork the Repository</a>
, as it is not our own project, and we don&rsquo;t have write access.</p>
<p>Once we fork the repository, an identical copy will appear under our account. We usually refer to the original project as the upstream repo and the forked copy as the downstream repo.</p>
<p>Since we have write access to the forked repository, the first step is to 
<a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository" target="_blank" rel="noopener">clone it</a>
. Note that we should clone our forked repo, not the upstream repo.</p>
<p>For example, if you want to contribute to the 
<a href="https://github.com/ray-project/kuberay" target="_blank" rel="noopener">kuberay</a>
 project, and our GitHub username is <code>MortalHappiness</code>, the URL of the upstream repo would be <code>https://github.com/ray-project/kuberay.git</code>, and the URL of our forked repo should be <code>git@github.com:MortalHappiness/kuberay.git</code> (SSH) or <code>https://github.com/MortalHappiness/kuberay.git</code> (HTTPS). Let&rsquo;s assume we are using SSH.</p>
<p>After cloning, we can run <code>git remote -v</code>, and it should look something like this:</p>
<pre tabindex="0"><code>origin  git@github.com:MortalHappiness/kuberay.git (fetch)
origin  git@github.com:MortalHappiness/kuberay.git (push)
</code></pre><p>Next, we need to add the upstream remote by executing the following commands:</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>In the above commands, replace the URL in the first line with the upstream URL of the project you want to contribute to. The second line is to prevent us from accidentally pushing to the upstream repo. (Although we don&rsquo;t have write access and can&rsquo;t push, if one day we become a committer with write access, this line helps avoid accidentally pushing to the upstream.)</p>
<p>Run <code>git remote -v</code> again, and it should look like this:</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>The following is my personal habit and is not necessarily required. To avoid accidentally developing features on the <code>master</code> branch (or <code>main</code> branch), I create a local branch to track the <code>upstream/master</code> branch and then delete the 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>If you&rsquo;re using the 
<a href="https://cli.github.com/" target="_blank" rel="noopener">GitHub CLI</a>
, you can also set the default repo to 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>Remember to replace the URL in the above command with your upstream URL.</p>
<h2 id="developing-new-features">Developing New Features</h2>
<ol>
<li>
<p>Ensure that our local <code>upstream-master</code> branch is in sync with the 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>Check out a new branch, let&rsquo;s say <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>Proceed with normal development (add, commit, push).</p>
</li>
<li>
<p>Go to the upstream repo to 
<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">open a pull request</a>
.</p>
</li>
</ol>
<h2 id="syncing-upstream">Syncing Upstream</h2>
<p>If we are in the middle of development and the upstream repo has new commits, we need to sync with the upstream by running <code>git pull upstream master</code>.</p>
<h2 id="sign-off-commits-dco">Sign-off Commits (DCO)</h2>
<p>Some open-source projects require every commit to be signed off. If it&rsquo;s not done, one of the CI check called DCO (Developer Certificate of Origin), will fail. The sign-off process is straightforward: when you run <code>git commit</code>, you add the <code>-s</code> flag, turning it into <code>git commit -s</code>. This flag automatically appends a <code>Sign-off-by</code> line at the end of your commit message. So, the commit message will look like this:</p>
<pre tabindex="0"><code>Some commit message.

Signed-off-by: Chi-Sheng Liu &lt;chishengliu@chishengliu.com&gt;
</code></pre><p>Note that this sign-off is different from 
<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>If you forget to add the <code>-s</code> flag when committing, you can follow 
<a href="https://github.com/src-d/guide/blob/master/developer-community/fix-DCO.md" target="_blank" rel="noopener">this guide</a>
 to remedy it.</p>
<p>If you find it cumbersome to add the <code>-s</code> flag every time, you can use a <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>Write the content above into <code>.git/hooks/commit-msg</code> and then execute <code>chmod +x .git/hooks/commit-msg</code> to make it executable.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Apache CommunityOverCode Asia 2024 Volunteering Experience</title>
      <link>https://chishengliu.com/posts/apache-communityovercode-asia-2024/</link>
      <pubDate>Thu, 08 Aug 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/apache-communityovercode-asia-2024/</guid>
      <description>Sharing my volunteer experience at Apache CommunityOverCode Asia 2024 in Hangzhou, including event highlights, networking, and volunteer tasks.</description>
      <content:encoded><![CDATA[<h2 id="what-is-the-apache-software-foundation">What is the Apache Software Foundation?</h2>
<p>The Apache Software Foundation (ASF) is currently one of the world&rsquo;s largest open-source software organizations, composed of developers from around the globe. The software under its umbrella follows the 
<a href="https://en.wikipedia.org/wiki/Apache_License" target="_blank" rel="noopener">Apache License</a>
, and it has fostered many well-known projects such as Hadoop, Spark, Kafka, and Flink.</p>
<h3 id="roles-in-asf-projects">Roles in ASF Projects</h3>
<p>
<a href="https://www.apache.org/foundation/how-it-works/#roles" target="_blank" rel="noopener">According to the official website</a>
, each Apache project typically has the following roles:</p>
<ul>
<li><strong>User</strong>: Uses the project&rsquo;s software but does not participate in development.</li>
<li><strong>Developer (Contributor)</strong>: Participates in project development but does not have write access to the repository.</li>
<li><strong>Committer</strong>: After making significant contributions, the PMCs may nominate you as a Committer, granting write access to the repository. If you don&rsquo;t already have an Apache email, you&rsquo;ll get one. For example, my email is 
<a href="mailto:chishengliu@apache.org">chishengliu@apache.org</a>
.</li>
<li><strong>PMC Member</strong>: After more prolonged involvement, you may be nominated as a PMC Member, having the power to decide the project&rsquo;s long-term direction, nominate active contributors as Committers, and vote on formal releases.</li>
<li><strong>PMC Chair</strong>: The PMC Chair is the leader among PMC Members, responsible for communicating with the board and additional duties.</li>
<li><strong>ASF Member</strong>: After becoming a PMC Member for multiple projects, you may be nominated as an ASF Member, with the right to elect and be elected to the board and incubate new projects.</li>
</ul>
<h2 id="what-is-apache-communityovercode">What is Apache CommunityOverCode?</h2>
<p>Apache CommunityOverCode, formerly known as ApacheCon, is an annual open-source software conference organized by the Apache Software Foundation, held in Asia, Europe, and North America. It was renamed to emphasize that &ldquo;
<a href="https://www.apache.org/theapacheway/#important" target="_blank" rel="noopener">a healthy community is a higher priority than good code</a>
.&rdquo;</p>
<h2 id="why-attend-apache-communityovercode-in-person-instead-of-online">Why Attend Apache CommunityOverCode In-Person Instead of Online?</h2>
<p>There are several obvious reasons for attending in person:</p>
<ul>
<li><strong>Networking</strong>: Attending in person allows you to make more friends, meet speakers, build connections, and aid in career development.</li>
<li><strong>Atmosphere</strong>: Experience the vibe of an international software conference firsthand.</li>
<li><strong>Opportunity to Ask Questions</strong>: You have the chance to ask questions and discuss with speakers in person, which is not possible with online participation.</li>
<li><strong>Sightseeing</strong>: Attend the conference while also visiting local tourist attractions.</li>
<li><strong>Meet Online Friends</strong>: Meet in person those you have collaborated with on open-source projects but have never met due to geographical reasons.</li>
</ul>
<h2 id="how-to-attend-apache-communityovercode-in-person">How to Attend Apache CommunityOverCode In-Person?</h2>
<p>Basically, there are three ways: buying a ticket, registering as a speaker, or applying to be a volunteer. Below is the registration page.</p>
<p><img alt="Buy Ticket page" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/buy-ticket.webp"></p>
<ul>
<li><strong>Buying a ticket</strong>: Regular registration requires a fee. If you are a Committer (with an Apache email), you can get a discount. Additionally, students and unemployed individuals can apply for free tickets.</li>
<li><strong>Registering as a speaker</strong>: If accepted, you can get a free speaker ticket. Speakers can also give complimentary tickets to friends and family, allowing them to attend for free.</li>
<li><strong>Applying to be a volunteer</strong>: The 
<a href="https://tac.apache.org/" target="_blank" rel="noopener">Apache Travel Assistance Committee (TAC)</a>
 is a committee that helps those who want to attend Apache events but are financially constrained. You can apply to be a volunteer on their website. <strong>Note that you don&rsquo;t need to be a Committer to apply</strong>! Committers can log in directly with their Apache email, while non-Committers can register for an account. Based on the experience from some friends of mine, Gmail sometimes fails to receive the email, while Outlook is more reliable. If you don&rsquo;t receive an email, try another email service. Once approved, TAC will cover your airfare, accommodation, and conference tickets, saving a lot of expenses.</li>
</ul>
<h2 id="my-thoughts-on-some-keynotes">My Thoughts on Some Keynotes</h2>
<p>The sessions were divided into two parts: Keynotes and Guest Speakers. Keynotes usually cover broad topics aligning with the conference theme and arouse general interest. Guest Speakers are community members who submit topics for in-depth introduction and discussion of specific projects or fields. Keynotes are held in the main conference room in the morning, while Guest Speakers are spread across various rooms in the afternoon, allowing attendees to choose topics of interest.</p>
<p>Keynotes usually feature one or two speakers discussing a topic, but there are also unique formats like roundtable discussions with 4-5 people chatting on stage about a topic, and Lightning Talks where each person speaks on a topic for 5 minutes.</p>
<p>Below is the schedule for the first day of the conference, to give you a sense of the event. To avoid taking up too much space, the schedules for the second and third days are not included in detail.</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="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</h3>
<p><img alt="Keynote speakers" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/keynote-speakers.webp"></p>
<p>This roundtable discussion addressed many issues related to open-source and globalization, which I found particularly interesting.</p>
<h4 id="language-contradictions-in-localizing-and-globalizing-open-source-communities">Language Contradictions in Localizing and Globalizing Open-Source Communities</h4>
<p>One point discussed was the language issue in open-source communities. To internationalize, English documentation is necessary; otherwise, contributors would be limited to local sources. However, if the target market is local, local language documentation is needed to compete with other products that have local language documentation. Maintaining multi-language documentation is challenging because not all developers are proficient in multiple languages. When a passage needs modification, multiple documents need updates, leading to inconsistent information. Therefore, if internationalization is required, it&rsquo;s usually best to maintain a single English document.</p>
<h4 id="regional-preferences-hindering-project-promotion">Regional Preferences Hindering Project Promotion</h4>
<p>Another issue is Product Market Fit (PMF). A project popular in one region may struggle to gain traction in another due to different user habits. One speaker mentioned a project using drag-and-drop no-code workflows that was popular in Asia but struggled in Western markets where users preferred coding workflows, making promotion difficult.</p>
<h4 id="cultural-differences-affecting-development-atmosphere-and-values">Cultural Differences Affecting Development Atmosphere and Values</h4>
<p>Another interesting topic was the development atmosphere in different regions. A speaker noted that Western developers are accustomed to communication via email with responses taking over a day, while Chinese developers expect replies within minutes due to a more competitive atmosphere. This difference may require using different communication tools for various markets, increasing the maintenance burden. One speaker noted that short-term slowness does not mean long-term slowness, as it allows developers to carefully review code quality, reducing future problems. Furthermore, doing things slowly does not mean overall progress will be slow, as many features can be developed simultaneously by many companies, leading to rapid overall progress.</p>
<h3 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</h3>
<p>This speaker is the primary author of the Open Source Definition, which is now case law, with a US court enforcing that software should not be called Open Source if it doesn&rsquo;t meet the definition. He is also the author of 
<a href="https://en.wikipedia.org/wiki/BusyBox" target="_blank" rel="noopener">BusyBox</a>
 and the second Debian project leader.</p>
<h4 id="history-and-current-challenges-of-open-source">History and Current Challenges of Open Source</h4>
<p>The speaker first discussed the history of open source and then addressed current challenges. Many major companies use various open-source projects without paying the maintainers, who often work voluntarily. When maintainers cannot handle all tasks alone, they may grant permissions to other contributors, some of whom might have malicious intentions, leading to disasters like the 
<a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor" target="_blank" rel="noopener">xz backdoor incident</a>
. Another challenge is the abundance of open-source licenses, requiring companies to spend money ensuring compliance.</p>
<h4 id="post-open-ending-free-use-by-major-companies">&ldquo;Post-Open&rdquo;: Ending Free Use by Major Companies</h4>
<p>The speaker proposed a concept called &ldquo;
<a href="https://postopen.org/" target="_blank" rel="noopener">Post-Open</a>
,&rdquo; where open-source software remains free for individuals and small companies, but companies with annual revenues exceeding five million dollars must pay the open-source community. The funds would be distributed based on developers&rsquo; contributions and the number of companies using the software.</p>
<h4 id="my-thoughts-on-post-open">My Thoughts on &ldquo;Post-Open&rdquo;</h4>
<p>I believe the concept is a wonderful vision, motivating open-source project maintainers who cannot rely solely on voluntary work. Passion will eventually be exhausted, and almost everyone has experienced burnout before. I have immense respect for contributors to open-source projects. Although fulfilling this initiative will face many challenges, I believe the speaker can achieve this vision, just as he made open source a benchmark for U.S. courts.</p>
<h2 id="too-many-talks-to-choose-from-how-to-select-the-right-topic-for-you">Too Many Talks to Choose From? How to Select the Right Topic for You?</h2>
<p>It may be hard to choose which talk to attend if this is your first time at such a conference, especially when you see so many guest speakers. I recommend you choose:</p>
<ol>
<li><strong>Topics you are familiar with</strong>. For example, &ldquo;The best practice of integration technology driven by event in cloud - Xiaohui Wu, Alan Liu&rdquo; by RedHat engineers on Kubernetes and microservices was easy for me to understand due to my experience writing Kubernetes Operators in open-source projects.</li>
<li><strong>Non-technical talks</strong> like &ldquo;Developing Soft Skills for a successful Open Source career - Sumaiya Nalukwago.&rdquo; Non-technical topics do not require base knowledge, so almost all of them can be easily understood.</li>
<li><strong>Popular technical topics</strong> like &ldquo;Navigating the Lakehouse with Confidence: Best Practices for Implementation with Apache - Bill Zhang&rdquo; by Cloudera, discussing 
<a href="https://iceberg.apache.org/" target="_blank" rel="noopener">Apache Iceberg</a>
, can also be chosen to learn about the applications and trends of these popular topics.</li>
</ol>
<h2 id="volunteer-experience">Volunteer Experience</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="welcome-dinner">Welcome Dinner</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>On the day before the event started, the Travel Assistance Committee invited volunteers to a restaurant called &ldquo;乾塘大院&rdquo; for a welcome dinner. The evening was filled with lively conversations and a friendly atmosphere as volunteers from various backgrounds came together. It was interesting to meet people from different countries and learn about each other&rsquo;s cultures, sharing stories and experiences. The dinner gave us a chance to make new friends and feel like part of a team. We talked about our work in open-source projects and shared our excitement for the upcoming conference. The evening helped us understand different cultures better and set a positive tone for the days ahead.</p>
<h3 id="volunteer-tasks">Volunteer Tasks</h3>
<p><img alt="Categorizing Stickers" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/categorizing-stickers.webp"></p>
<p>Volunteering was not very demanding. We only needed to sit at the Apache booth for a 2-hour shift over the 3 days. There were many stickers of different projects on the table, and we had to inform passersby that they could take the stickers freely. Initially, the stickers were scattered in a pile, but because I found it a bit boring to just sit there, I started organizing them.</p>
<p>Additionally, we were supposed to help at the registration desk for a day, but since there were already local staff members there, I didn&rsquo;t have much to do.</p>
<p>Lastly, we needed to count the number of attendees at Guest Speaker sessions and report it to the conference to help them understand which topics were most attended. This was also an easy task.</p>
<h3 id="certificate">Certificate</h3>
<p><img alt="Volunteer Certificate" loading="lazy" src="https://static.chishengliu.com/posts/apache-community-overcode-asia-2024/volunteer-certificate.webp"></p>
<p>After the event, volunteers received a beautifully framed certificate. It was a nice gesture!</p>
<h2 id="conclusion">Conclusion</h2>
<p>Participating in the Apache CommunityOverCode Asia 2024 was an incredible experience, and I highly recommend that everyone attend at least once!</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Build Personal Website for Free With Hugo</title>
      <link>https://chishengliu.com/posts/build-personal-website/</link>
      <pubDate>Sat, 06 Jul 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/build-personal-website/</guid>
      <description>Learn how I built my personal website for free (except the domain) with tips on framework selection, comment system integration, deployment, and SEO.</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Can you believe it? Except for the domain name, the setup of my personal website is completely free! After all, this is just a site where I plan to post casual articles, so I&rsquo;ll save money wherever I can. Let me explain how I built this website.</p>
<h2 id="framework-selection">Framework Selection</h2>
<p>Since I wanted it to be free, I couldn&rsquo;t choose an SSR (Server-Side Rendering) framework like WordPress, as deploying it to a host would cost money. Although some cloud platforms offer free tiers, increased traffic would inevitably incur costs. Therefore, choosing an SSG (Static Site Generation) framework and deploying it on a platform offering free static site hosting is more sensible.</p>
<p>My previous website used the 
<a href="https://www.gatsbyjs.com/" target="_blank" rel="noopener">Gatsby</a>
 framework, for a simple reason: I am very familiar with 
<a href="https://react.dev/" target="_blank" rel="noopener">React</a>
, so I thought customization would be easier. However, I later realized I didn&rsquo;t have time to maintain a bunch of Typescript and Javascript, which made me reluctant to update my website. This time, I choose 
<a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo</a>
. Maintaining a small amount of Go Template is easier, and I am familiar with Golang, too.</p>
<h2 id="theme-selection">Theme Selection</h2>
<p>
<a href="https://themes.gohugo.io/" target="_blank" rel="noopener">Hugo&rsquo;s official website offers many themes</a>
 to choose from. This time, I chose the 
<a href="https://github.com/adityatelange/hugo-PaperMod" target="_blank" rel="noopener">hugo-PaperMod</a>
 theme. Themes usually provide download steps, just follow them.</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="configuration">Configuration</h2>
<p>You&rsquo;ll have to follow the documentation provided by different themes to configure them to your liking. So in the following I only discuss some challenging configurations.</p>
<h3 id="multilingual">Multilingual</h3>
<p>The theme I chose supports multiple languages, but some parts are not well translated. You&rsquo;ll need to look at the source code and find a solution, or search the issues to see if anyone has provided solutions. Luckily, I found some solutions:</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="last-modified-time">Last Modified Time</h3>
<p>Similarly, search the issues for solutions:</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="chinese-fonts">Chinese Fonts</h3>
<p>Themes usually don&rsquo;t specifically set Chinese fonts, so Chinese characters look ugly. I often use 
<a href="https://fonts.google.com/knowledge/glossary/web_font" target="_blank" rel="noopener">web fonts</a>
 to solve this, which is more convenient. Here, I use 
<a href="https://fonts.google.com/noto/specimen/Noto&#43;Sans&#43;TC" target="_blank" rel="noopener">Google&rsquo;s Noto Sans Traditional Chinese font</a>
.</p>
<p>First, copy the font&rsquo;s embed code by clicking &ldquo;Get fonts&rdquo;</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>Then click &ldquo;Get embed code&rdquo;</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>Then click &ldquo;copy code&rdquo;</p>
<p><img alt="Copy code from Noto Sans" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/noto-sans-get-embed-code.webp"></p>
<p>Next, find where to insert HTML tags in the theme&rsquo;s source code. After searching, I found 
<a href="https://github.com/adityatelange/hugo-PaperMod/blob/9ea3bb0e1f3aa06ed7715e73b5fabb36323f7267/layouts/partials/extend_head.html" target="_blank" rel="noopener">this file</a>
. Create a file called <code>layouts/partials/extend_head.html</code> and write the copied content into it.</p>
<p>Then, find where the CSS controlling the fonts is located. After searching, I found this line: 
<a href="https://github.com/adityatelange/hugo-PaperMod/blob/9ea3bb0e1f3aa06ed7715e73b5fabb36323f7267/assets/css/core/reset.css#L27" target="_blank" rel="noopener">https://github.com/adityatelange/hugo-PaperMod/blob/9ea3bb0e1f3aa06ed7715e73b5fabb36323f7267/assets/css/core/reset.css#L27</a>
.</p>
<p>Create a file called <code>assets/css/extended/custom-font.css</code> and write the following content:</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>Note that this part is directly copied from the original code, adding the selector <code>body:lang(zh-tw)</code> to override the font style for Traditional Chinese. Since my articles mix Chinese and English, to avoid changing the English font, &ldquo;Noto Sans TC&rdquo; should be placed before the fallback font, which is before <code>sans-serif</code>.</p>
<h3 id="comment-system">Comment System</h3>
<p>Since our site is a static site, we need to rely on external services to provide a comment system. The most famous one is 
<a href="https://disqus.com/" target="_blank" rel="noopener">Disqus</a>
. However, its free plan forces use to show a lot of ADs to the users, which I don&rsquo;t like. So I chose 
<a href="https://github.com/giscus/giscus" target="_blank" rel="noopener">giscus</a>
, which uses GitHub Discussions as the storage place for comments. The only downside is that it doesn&rsquo;t allow users to log in and comment with Google or social media accounts like Disqus does, but since my articles are mostly technical, people reading them are likely to have a GitHub account, so it&rsquo;s not a big issue.</p>
<p>It&rsquo;s a bit tricky to support multilingual and light/dark mode switching for this comment system, so I had to write some Javascript. Here&rsquo;s what it roughly looks like (sensitive information has been replaced):</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="deployment">Deployment</h2>
<p>There are many platforms for Static Site Hosting, including 
<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>
, etc. I chose Cloudflare Pages for a simple reason: its free plan has the fewest restrictions. It even has no bandwidth limit!</p>
<p>Deployment is straightforward. Follow the official documentation, connect to the GitHub repo, and choose <code>hugo</code> as the build command.</p>
<h2 id="cms">CMS</h2>
<p>It&rsquo;s inconvenient to have to sit in front of a computer and open a text editor to write a blog post every time. A common solution is to use a CMS (Content Management System). I used to use 
<a href="https://decapcms.org/" target="_blank" rel="noopener">Decap CMS</a>
 (formally Netlify CMS), but it wasn&rsquo;t very convenient for connecting to a GitHub repo, and it didn&rsquo;t generate a blank line between YAML frontmatter and the body, which sometimes caused issues. So this time, I used 
<a href="https://tina.io/" target="_blank" rel="noopener">Tina CMS</a>
. Fortunately, Tina Cloud is free for up to two users.</p>
<p>Setting up Tina CMS is not difficult. Follow the official setup instructions, then go to Cloudflare Pages and change the build command to <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> and add the environment variables. It will look like this:</p>
<p><img alt="Tina CMS all posts view" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/tina-cms.webp"></p>
<p><img alt="Tina CMS edit single post view" 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 
<a href="https://en.wikipedia.org/wiki/Favicon" target="_blank" rel="noopener">Favicon</a>
 is a small icon on a webpage. Without setting it, it appears as a globe in Chrome, which doesn&rsquo;t look good.</p>
<p>Go to 
<a href="https://realfavicongenerator.net/" target="_blank" rel="noopener">Favicon Generator</a>
, upload an image, download the files, unzip them, and copy all the files into the <code>static</code> folder. Then refer to the theme documentation to see how to set the 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>
 is used to set images and descriptions when your website is shared on social media. You can use 
<a href="https://www.opengraph.xyz/" target="_blank" rel="noopener">https://www.opengraph.xyz/</a>
 to check how your website will look when shared on social media.</p>
<h3 id="google-search-console">Google Search Console</h3>
<p>Submitting your site to 
<a href="https://search.google.com/search-console/about" target="_blank" rel="noopener">Google Search Console</a>
 can speed up indexing. Remember to submit the sitemaps (both XML and RSS sitemaps) to speed up indexing furthermore.</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 can be used to measure website performance and SEO. First, open the website in incognito mode to avoid interference from Chrome extensions. Then press <code>Ctrl+Shift+J</code> to enter Chrome Dev Tools, and select the LightHouse tab.</p>
<p><img alt="Open LightHouse" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/open-lighthouse.webp"></p>
<p>Select Desktop and click &ldquo;Analyze page load&rdquo;.</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>Results:</p>
<p><img alt="LightHouse Result" loading="lazy" src="https://static.chishengliu.com/posts/build-personal-website/lighthouse-result.webp"></p>
<blockquote>
<p>Note: Google Web Font Performance Tuning</p>
<p>When using Lighthouse, you might notice a performance issue related to Google Web Fonts. In this case, you can use the <code>preload</code> method to solve it:</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="page-analytics">Page Analytics</h2>
<p>To analyze website data, such as the number of visitors, the most commonly used tool is 
<a href="https://developers.google.com/analytics" target="_blank" rel="noopener">Google Analytics</a>
, which is beyond the scope of this article, so it is not detailed here.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Deploying a website requires attention to many details. This article only briefly covers many topics, as different frameworks and themes require different configurations. Although you can&rsquo;t directly replicate a website by reading this article, I hope it helps you notice these details when deploying your personal website.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Reproduce Kubernetes Node-pressure Eviction via K3d</title>
      <link>https://chishengliu.com/posts/kubernetes-node-pressure/</link>
      <pubDate>Fri, 05 Jul 2024 00:00:00 +0000</pubDate>
      <guid>https://chishengliu.com/posts/kubernetes-node-pressure/</guid>
      <description>Learn how to reproduce Kubernetes Node-pressure Eviction locally with memory limits in a k3d cluster, including steps to reproduce a KubeRay issue.</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>A few days ago, while developing the KubeRay project, I learned about a Kubernetes behavior from the 
<a href="https://github.com/ray-project/kuberay/issues/2125#issuecomment-2113971140" target="_blank" rel="noopener">issue&rsquo;s comment section</a>
. There are two types of Eviction: 
<a href="https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/" target="_blank" rel="noopener">Node-pressure Eviction</a>
 and 
<a href="https://kubernetes.io/docs/concepts/scheduling-eviction/api-eviction/" target="_blank" rel="noopener">API-initiated Eviction</a>
. API-initiated Eviction is done by directly calling the API or using commands like <code>kubectl drain</code>. Pods evicted this way will ultimately be deleted and usually recreated on another node. However, for Node-pressure Eviction, kubelet will only set the Pod&rsquo;s Phase to <code>Failed</code> without deleting it. Therefore, if the controller does not handle it properly, the Pod will not be recreated on another node.</p>
<p>Here&rsquo;s a brief overview of the issue: When a Pod created by the KubeRay operator is on a node with insufficient disk space, the Pod gets evicted. After the disk space is cleared, the Pod remains in a Failed state and is not recreated on another node.</p>
<p>Now, I need to reproduce this issue. The key point is that since these two types of Evictions behave differently, I cannot use <code>kubectl drain</code> or similar commands to reproduce the scenario. I need to specifically create a Node-pressure Eviction. However, I don&rsquo;t have a cluster to use; I do all my development on my personal computer, making it difficult to reproduce the issue. When developing Kubernetes applications locally, most people use 
<a href="https://minikube.sigs.k8s.io/docs/" target="_blank" rel="noopener">minikube</a>
, 
<a href="https://kind.sigs.k8s.io/" target="_blank" rel="noopener">kind</a>
, or 
<a href="https://k3d.io/v5.7.0/" target="_blank" rel="noopener">k3d</a>
. Since I need a multi-node environment, minikube is excluded. Although 
<a href="https://minikube.sigs.k8s.io/docs/tutorials/multi_node/" target="_blank" rel="noopener">it now supports multiple nodes</a>
, it&rsquo;s still more commonly used for single-node scenarios. Both <code>kind</code> and <code>k3d</code> use Docker containers as Kubernetes nodes. My operating system is Linux Mint, and Docker runs natively, unlike macOS where Docker runs in a virtual machine. Because the resources (memory, disk, etc.) are shared between Docker and my local machine, if I do create a Node-pressure scenario, my computer might become unusable.</p>
<p>After extensive Googling, I discovered that 
<a href="https://docs.docker.com/config/containers/resource_constraints/" target="_blank" rel="noopener">Docker can set Runtime Memory Limits</a>
, and 
<a href="https://k3d.io/v5.6.3/usage/commands/k3d_cluster_create/" target="_blank" rel="noopener">k3d has a <code>--agents-memory</code> flag to set agent node memory</a>
. This is how I found 
<a href="https://github.com/ray-project/kuberay/issues/2125#issuecomment-2203388145" target="_blank" rel="noopener">a way to reproduce the issue</a>
.</p>
<h2 id="steps">Steps</h2>
<p>First, create a k3d cluster with 2 agent nodes, each with 3GB of memory, and trigger Pod Eviction when the available memory is less than 1GiB.</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>Check the memory of all nodes</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>Output:</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>You can see that both agent 0 and agent 1 have 3GB of memory, but only 2GB is allocatable because Pod Eviction is triggered when available memory is less than 1GiB.</p>
<p>Next, add taints to agent 0 and agent 1 so that subsequent Pods will only be deployed to the 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>Install the KubeRay operator, so the operator Pod will run on the 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>Remove the taints from agent 0 and agent 1 and add a taint to server 0 so that subsequent Pods will not be deployed to 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>Install the <code>RayCluster</code> custom resource. After installation, the KubeRay operator will create a head pod and a worker pod. Since the memory resource request for the head pod is 2GB and for the worker pod is 1GB in the helm chart, and both agent 0 and agent 1 have only 2GB of allocatable memory, these two Pods will definitely not be on the same 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>Next, we need to perform a memory stress test on the node where the head pod is located. After some Googling, I found that 
<a href="https://github.com/ColinIanKing/stress-ng" target="_blank" rel="noopener">stress-ng</a>
 is commonly used for this purpose, so I&rsquo;ll use it as well. We need to ensure that the head pod has <code>stress-ng</code> available. The simplest way is to copy the statically compiled <code>stress-ng</code> binary directly into the head pod, so we don&rsquo;t have to worry about the head pod&rsquo;s base image or any missing dependencies. As for obtaining the statically compiled binary, you can compile it yourself, but I took a shortcut by copying it from a 
<a href="https://hub.docker.com/r/alexeiled/stress-ng" target="_blank" rel="noopener">Docker image that includes the binary</a>
. Assuming the head pod is named <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>Open a shell on the head pod</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>Simulate memory stress</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>In this way, you can see the head pod being evicted due to Node-pressure Eviction.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
