<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us"><generator uri="https://gohugo.io/" version="0.156.0">Hugo</generator><title type="html">Golang on Marcin Jasion - Pragmatic DevOps</title><link href="https://6f95e4af.mjasion.pages.dev/tags/golang/" rel="alternate" type="text/html" title="html"/><link href="https://6f95e4af.mjasion.pages.dev/tags/golang/index.xml" rel="alternate" type="application/rss+xml" title="rss"/><updated>2023-06-25T00:00:00+02:00</updated><id>https://6f95e4af.mjasion.pages.dev/tags/golang/</id><entry><title type="html">Implementing Leader Election in Golang using Kubernetes API</title><link href="https://6f95e4af.mjasion.pages.dev/posts/kubernetes/implementing-leader-election-in-go-using-kubernetes-api/?utm_source=atom_feed" rel="alternate" type="text/html"/><link href="https://6f95e4af.mjasion.pages.dev/posts/kubernetes/how-to-debug-istio-upstream-reset/?utm_source=atom_feed" rel="related" type="text/html" title="How to debug Istio Upstream Reset 502 UPE (old 503 UC)"/><id>https://6f95e4af.mjasion.pages.dev/posts/kubernetes/implementing-leader-election-in-go-using-kubernetes-api/</id><author><name>Marcin Jasion</name></author><published>2023-06-25T00:00:00+02:00</published><updated>2023-06-25T00:00:00+02:00</updated><content type="html"><![CDATA[<blockquote>Learn how to implement a leader election mechanism in Golang using the Kubernetes API, leveraging
lease locks and distributed coordination to ensure reliable task execution in distributed systems.</blockquote><h2 id="introduction">Introduction</h2>
<p>Leader election is a crucial pattern in distributed systems where multiple instances or nodes compete
to perform certain tasks. In a Kubernetes cluster, leader election can be used to ensure that only
one instance is responsible for executing leader-specific tasks at any given time. This blog post will
explore how to implement a leader election mechanism in Kubernetes using lease locks.</p>
<h2 id="overview">Overview</h2>
<p>The leader election mechanism implemented in Go code relies on Kubernetes coordination
features, specifically <a href="https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/lease-v1/" target="_blank" rel="noopener">Lease</a>
object in the <code>coordination.k8s.io</code> API Group. Lease locks provide a way to acquire a lease on a shared resource,
which can be used to determine the leader among a group of nodes.</p>
<h3 id="repository">Repository</h3>
<p>The example code, used for this blog is available on <a href="https://github.com/mjasion/golang-k8s-leader-example" target="_blank" rel="noopener">mjasion/golang-k8s-leader-example</a> GitHub repository.</p>
<h2 id="code-walkthrough">Code Walkthrough</h2>
<p>The main function is the entry point of the program. It reads configuration values from environment
variables and obtains the Kubernetes <code>clientset</code> by getting access to Kube-Api by ServiceAccount attached to Pod.
The application is written to work in Kubernetes Pod, that&rsquo;s why it is using <code>rest.InClusterConfig()</code> function.</p>
<p>The leader election configuration is set up using the <code>LeaderElectionConfig</code> struct from the Kubernetes
client library. It specifies the lease lock, lease duration, renewal deadline, retry period, and callback
functions for leader-specific tasks.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">leaderElectionConfig</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">leaderelection</span>.<span style="color:#a6e22e">LeaderElectionConfig</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Lock</span>: <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">resourcelock</span>.<span style="color:#a6e22e">LeaseLock</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">LeaseMeta</span>: <span style="color:#a6e22e">metav1</span>.<span style="color:#a6e22e">ObjectMeta</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Name</span>:      <span style="color:#a6e22e">lockName</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Namespace</span>: <span style="color:#a6e22e">leaseNamespace</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">Client</span>: <span style="color:#a6e22e">clientset</span>.<span style="color:#a6e22e">CoordinationV1</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">LockConfig</span>: <span style="color:#a6e22e">resourcelock</span>.<span style="color:#a6e22e">ResourceLockConfig</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Identity</span>: <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;HOSTNAME&#34;</span>),
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">LeaseDuration</span>: <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>(<span style="color:#a6e22e">leaseDuration</span>) <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">RenewDeadline</span>: <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>(<span style="color:#a6e22e">renewalDeadline</span>) <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">RetryPeriod</span>:   <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Duration</span>(<span style="color:#a6e22e">retryPeriod</span>) <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Callbacks</span>: <span style="color:#a6e22e">leaderelection</span>.<span style="color:#a6e22e">LeaderCallbacks</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">OnStartedLeading</span>: <span style="color:#a6e22e">onStartedLeading</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">OnStoppedLeading</span>: <span style="color:#a6e22e">onStoppedLeading</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ReleaseOnCancel</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The most important settings are the <strong>lease duration</strong>, <strong>renewal deadline</strong>, and <strong>retry period</strong>:</p>
<ul>
<li>The <code>LeaseDuration</code> specifies how long the lease is valid.</li>
<li>The <code>RenewDeadline</code> specifies the amount
of time that the current node has to renew the lease before it expires.</li>
<li>The <code>RetryPeriod</code> specifies the amount of time  that the current holder of a lease has last updated the lease.</li>
</ul>
<p>The leader-specific tasks are performed in the <code>onStartedLeading</code> function, which is called
when the current node becomes the leader. The <code>updateServiceSelectorToCurrentPod</code> function updates the
service selector to include the current pod&rsquo;s hostname.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">onStartedLeading</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Context</span>) {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Became leader: &#34;</span>, <span style="color:#a6e22e">os</span>.<span style="color:#a6e22e">Getenv</span>(<span style="color:#e6db74">&#34;HOSTNAME&#34;</span>))
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">clientset</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">getKubeClient</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">updateServiceSelectorToCurrentPod</span>(<span style="color:#a6e22e">clientset</span>)
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">select</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">case</span> <span style="color:#f92672">&lt;-</span><span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Done</span>():
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Stopped leader loop&#34;</span>)
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">default</span>:
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Performing leader tasks...&#34;</span>)
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Sleep</span>(<span style="color:#ae81ff">1</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">time</span>.<span style="color:#a6e22e">Second</span>)
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>onStoppedLeading</code> function is called when the current node stops being the leader. It can be used for cleanup tasks.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">onStoppedLeading</span>() {
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">log</span>.<span style="color:#a6e22e">Println</span>(<span style="color:#e6db74">&#34;Stopped being leader&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>A context and a wait group are created to manage goroutines. A goroutine is started to run the leader
election using the <code>leaderelection.RunOrDie</code> function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">cancel</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">WithCancel</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">Background</span>())
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">wg</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">sync</span>.<span style="color:#a6e22e">WaitGroup</span>{}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">go</span> <span style="color:#66d9ef">func</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Done</span>()
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">leaderelection</span>.<span style="color:#a6e22e">RunOrDie</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#a6e22e">leaderElectionConfig</span>)
</span></span><span style="display:flex;"><span>}()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">cancel</span>()
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Wait</span>()
</span></span></code></pre></div><p>The program also sets up a Gin router and defines a root endpoint that returns the hostname of the
current node, to easily check which Pod is being the leader.</p>
<h2 id="demo-1---deploying-a-single-pod">Demo 1 - Deploying a single Pod</h2>
<p>In this demo, we will deploy a single Pod to a Kubernetes cluster and observe how the leader election works.</p>
<style>
.video-shortcode {
    max-width: 100%;
    height: auto;
}
</style>

<video class="video-shortcode"
       id="video-424"
       preload='auto'
       autoplay='true'
       loop='true'
    controls>
  <source src='1_log_and_lease.webm' type='video/webm '>
</video>

<p>As you can see here, the pod is elected as a leader and performs leader-specific tasks. The <code>lease</code> object
contains the information about the current leader in the <code>HOLDER</code> column.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>NAME                 HOLDER                               AGE
</span></span><span style="display:flex;"><span>k8s-leader-example   k8s-leader-example-8dd646bb7-dsfmq   11s
</span></span></code></pre></div><h2 id="demo-2---deploying-multiple-pods-and-killing-the-leader">Demo 2 - Deploying multiple Pods and killing the leader</h2>
<p>In this demo, we will deploy multiple Pods to a Kubernetes cluster and observe how the leader election works.
The settings used for this demo are as follows:</p>
<table>
  <thead>
      <tr>
          <th>Setting</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Lease Duration</td>
          <td>10 seconds</td>
      </tr>
      <tr>
          <td>Renewal Deadline</td>
          <td>5 seconds</td>
      </tr>
      <tr>
          <td>Retry Period</td>
          <td>1 seconds</td>
      </tr>
  </tbody>
</table>
<p>The leader election mechanism will attempt to renew the lease every 5 seconds. If the lease is not renewed
within 5 seconds, the leader election mechanism will attempt to acquire the lease. If the lease is not acquired
within 1 second, the leader election mechanism will retry to acquire the lease.</p>
<style>
.video-shortcode {
    max-width: 100%;
    height: auto;
}
</style>

<video class="video-shortcode"
       id="video-342"
       preload='auto'
       autoplay='true'
       loop='true'
    controls>
  <source src='2_multi_instance.webm' type='video/webm '>
</video>

<p>Running command <code>kubectl get lease --watch</code> allows to observe the leader election process. The <code>lease</code> object
contains first the information about the previous leader, when the leader is killed, and then the information
about the new leader.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Implementing leader election in Kubernetes using lease locks is an effective way to ensure that only
one instance or node performs leader-specific tasks at a time. In this blog post, we explored the provided
Go code that demonstrates how to implement leader election in a Kubernetes cluster.</p>
<p>By incorporating leader election into your distributed system, you can enhance its reliability and prevent
conflicts that may arise from multiple instances attempting to execute the same tasks simultaneously.</p>
]]></content><category scheme="https://6f95e4af.mjasion.pages.dev/tags/golang" term="golang" label="golang"/><category scheme="https://6f95e4af.mjasion.pages.dev/tags/kubernetes" term="kubernetes" label="kubernetes"/><category scheme="https://6f95e4af.mjasion.pages.dev/tags/distributed-systems" term="distributed-systems" label="distributed-systems"/><category scheme="https://6f95e4af.mjasion.pages.dev/tags/leader-election" term="leader-election" label="leader-election"/></entry></feed>