<?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>Operator-Sdk on avni.sh</title>
    <link>http://www.avni.sh/tags/operator-sdk/</link>
    <description>Recent content in Operator-Sdk on avni.sh</description>
    <image>
      <title>avni.sh</title>
      <url>http://www.avni.sh/cover.webp</url>
      <link>http://www.avni.sh/cover.webp</link>
    </image>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Wed, 10 May 2023 00:00:00 +0000</lastBuildDate>
    <atom:link href="http://www.avni.sh/tags/operator-sdk/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Operator SDK and Bundle Images</title>
      <link>http://www.avni.sh/posts/cloud-native/operator-sdk/</link>
      <pubDate>Wed, 10 May 2023 00:00:00 +0000</pubDate>
      <guid>http://www.avni.sh/posts/cloud-native/operator-sdk/</guid>
      <description>Operators are packaged and distributed as bundle images</description>
      <content:encoded><![CDATA[<p>An Operator Bundle Image (OBI) is created to package custom resources and metadata associated with an <a href="/posts/computer-science/technologies/cloud-native/kubernetes-operators/" target="_blank">operator</a>. It&rsquo;s like any other <a href="/posts/computer-science/technologies/cloud-native/container-images/" target="_blank">container image</a> only difference is that it couldn&rsquo;t be executed but it could be distributed through an OCI-compliant image registry.</p>
<p>Contents of a bundle image are:</p>
<ul>
<li>Kubernetes Custom Resource Definitions (CRDs)</li>
<li><a href="/posts/computer-science/technologies/cloud-native/operators-on-openshift/#clusterserviceversion" target="_blank">ClusterServiceVersion (CSV)</a></li>
<li>Specification of operator&rsquo;s dependencies</li>
<li>Operator metadata like its name, version, channels, etc.</li>
</ul>
<p>The <a href="/posts/computer-science/technologies/cloud-native/kubernetes-operators/#control-loop" target="_blank">control loops</a> associated with the operator are defined in its <strong>Controller Manager</strong>. It is an executable that contains one or more custom controllers.</p>
<p>The <a href="/posts/computer-science/technologies/cloud-native/operators-on-openshift/#operator-lifecycle-manager-olm" target="_blank">Operator Lifecycle Manager (OLM)</a> pulls the bundle image from a registry and installs it on the cluster.</p>
<h1 id="operator-sdk">Operator SDK</h1>
<p><strong>Operator SDK</strong> is a project under Operator Framework that provides tools for building, testing, and packaging operators using the <code>operator-sdk</code> utility.</p>
<p>Using Operator SDK we can create operators based on Ansible Roles, Go Programming Language, or Helm Charts.</p>
<h1 id="creating-a-go-based-operator">Creating a Go-based Operator</h1>
<p>In this article, I will create a <strong>Memcached</strong> operator using <code>operator-sdk</code> CLI.</p>
<p>Memcached is a memory caching system, often used by developers to increase the performance of API calls or databases. It stores data as key-value pairs in RAM. The Memcached operator will make the following changes to the cluster</p>
<ul>
<li>Create a <code>Memcached</code> custom resource.</li>
<li>Add a controller manager for <code>Memcached</code> resources.</li>
<li>Implement APIs to interact with custom resources.</li>
</ul>
<p>Prerequisites:</p>
<ul>
<li>GNU Make (<code>make</code>)</li>
<li>Docker
<ul>
<li>DockerHub/Quay.io or any other public container image registry account</li>
</ul>
</li>
<li>Minikube or any Kubernetes cluster
<ul>
<li><code>kubectl</code> CLI utility</li>
</ul>
</li>
<li>Go</li>
<li>Operator SDK</li>
</ul>
<p>My environment details</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cat /etc/os-release <span class="p">|</span> head -n <span class="m">5</span>
</span></span><span class="line"><span class="cl"><span class="nv">PRETTY_NAME</span><span class="o">=</span><span class="s2">&#34;Ubuntu 22.04.2 LTS&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NAME</span><span class="o">=</span><span class="s2">&#34;Ubuntu&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">VERSION_ID</span><span class="o">=</span><span class="s2">&#34;22.04&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">VERSION</span><span class="o">=</span><span class="s2">&#34;22.04.2 LTS (Jammy Jellyfish)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">VERSION_CODENAME</span><span class="o">=</span>jammy
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ docker version
</span></span><span class="line"><span class="cl">Client: Docker Engine - Community
</span></span><span class="line"><span class="cl"> Version:           23.0.3
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ minikube version
</span></span><span class="line"><span class="cl">minikube version: v1.30.1
</span></span><span class="line"><span class="cl">commit: 08896fd1dc362c097c925146c4a0d0dac715ace0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ go version
</span></span><span class="line"><span class="cl">go version go1.20.4 linux/amd64
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">$ operator-sdk version
</span></span><span class="line"><span class="cl">operator-sdk version: <span class="s2">&#34;v1.28.0&#34;</span>, commit: <span class="s2">&#34;484013d1865c35df2bc5dfea0ab6ea6b434adefa&#34;</span>, kubernetes version: <span class="s2">&#34;1.26.0&#34;</span>, go version: <span class="s2">&#34;go1.19.6&#34;</span>, GOOS: <span class="s2">&#34;linux&#34;</span>, GOARCH: <span class="s2">&#34;amd64&#34;</span>
</span></span></code></pre></div><h2 id="initializing-operator-project">Initializing Operator Project</h2>
<p><strong>Kubebuilder</strong> provides a standardized way of creating Kubernetes API using Go. It generates the CRDs associated with the API in an organized file structure.</p>
<p><code>init</code> subcommand from <code>operator-sdk</code> will generate custom resources, API, and controller manager for an operator based on the basic kubebuilder project layout.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir memcached-operator
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> memcached-operator
</span></span><span class="line"><span class="cl">operator-sdk init --domain example.com --repo github.com/example/memcached-operator
</span></span></code></pre></div><p>Artifacts generated by the command</p>
<ul>
<li>YAML manifests for custom resources, controllers, Prometheus integration, and Role Based Access Control (RBAC) resources</li>
<li>Scorecard tests</li>
<li><code>Dockerfile</code> for an image containing the binary of the controller manager</li>
<li><code>go.mod</code> containing the definition of the Go module with basic dependencies</li>
<li><code>Makefile</code> for building, distributing, and deploying the operator</li>
<li><code>main.go</code> contains logic for the controller manager</li>
<li><code>PROJECT</code> file containing the operator&rsquo;s metadata (domain, project layout, name, repo, and version)</li>
<li><code>README.md</code> for documentation</li>
</ul>
<p><code>--domain</code> flag is used to specify a prefix of labels assigned to custom resources created by the operator.</p>
<p><code>--repo</code> refers to the Go module to be used for the operator, it needs to be specified if the project directory is outside <code>$GOPATH/src</code>.</p>
<p>Other flags for the <code>init</code> subcommand:</p>
<ul>
<li><code>--project-name</code>: To specify the name of the operator</li>
<li><code>--project-version</code>: To specify the operator version</li>
<li><code>--owner</code>: To specify the owner&rsquo;s name</li>
<li><code>--fetch-deps</code>: To toggle dependency installation by OLM during deployment</li>
</ul>
<h2 id="implementing-api">Implementing API</h2>
<p>Implementing an API for interacting with the custom resources created by the operator</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">operator-sdk create api --group cache --version v1alpha1 --kind Memcached --resource --controller
</span></span></code></pre></div><p>After executing this command a new API resource will be added to the <code>PROJECT</code> file</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">crdVersion</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">namespaced</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">controller</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">domain</span><span class="p">:</span><span class="w"> </span><span class="l">example.com</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">cache</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">Memcached</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">github.com/example/memcached-operator/api/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="l">v1alpha1</span><span class="w">
</span></span></span></code></pre></div><p>The API and controller&rsquo;s implementations will be stored in <code>api/v1alpha1/memcached_types.go</code> and <code>controllers/memcached_controller.go</code> respectively.</p>
<p>The creation of the controller could be toggled using the <code>--controller</code> flag. It is <code>true</code> by default.
<code>--group</code> flag is used to specify the group of the API resources created.
The value of the <code>--version</code> flag will specify the API version and <code>--kind</code> specifies the type of API to be implemented.
<code>--resource</code> toggles the creation of API resource&rsquo;s YAML manifests.</p>
<h2 id="building-an-operator-image">Building an Operator Image</h2>
<p>The difference between an <strong>Operator Image</strong> and an Operator Bundle Image is that an operator image could be used to deploy an operator directly on the cluster as a Deployment (it contains the binary of the controller manager) whereas the bundle image stores the necessary metadata, custom resources, and APIs associated with the operator (also used for deployment, but through OLM).</p>
<p><code>make</code> is an automation utility commonly used for processes like compiling/building applications. The <code>Makefile</code> in the operator project defines multiple <strong>targets</strong> like <code>docker-build</code> and <code>docker-push</code> for building and pushing the operator image to the registry respectively.</p>
<p>Keep in mind that you have to be logged in to the registry from your container engine before executing the following command</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make docker-build docker-push <span class="nv">IMG</span><span class="o">=</span><span class="s2">&#34;docker.io/bovem/memcached-operator:v0.0.1&#34;</span>
</span></span></code></pre></div><p>For development purposes, if you want to test the bundle image outside the cluster you can use the following command</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make install run
</span></span></code></pre></div><h2 id="building-the-operator-bundle-image">Building the Operator Bundle Image</h2>
<p><code>Makefile</code> target <code>bundle</code> will create a <code>bundle/</code> directory in the project&rsquo;s root containing manifests (CRDs) and metadata associated with the operator. A <a href="/posts/computer-science/technologies/cloud-native/building-container-images/" target="_blank">Containerfile</a> named <code>bundle.Dockerfile</code> will be created as well.</p>
<p>Targets <code>bundle-build</code> and <code>bundle-push</code> will build and push the bundle image respectively.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make bundle bundle-build bundle-push <span class="nv">BUNDLE_IMG</span><span class="o">=</span><span class="s2">&#34;docker.io/bovem/memcached-operator-bundle:v0.0.1&#34;</span>
</span></span></code></pre></div><h3 id="files-inside-an-operator-bundle-image">Files inside an Operator Bundle Image</h3>
<h3 id="manifests-directory"><code>manifests</code> directory</h3>
<p>Contains CRDs including the <code>ClusterServiceVersion</code>.</p>
<h3 id="metadata-directory"><code>metadata</code> directory</h3>
<p><code>annotations.yaml</code> in the <code>metadata</code> directory stores the operator&rsquo;s metadata. This includes the path of the manifests and metadata directory, channels, etc.
<code>dependencies.yaml</code> specifies the dependencies to be satisfied before installation of the operator is initiated. These could be dependencies on other operators or specific API/Custom Resources.</p>
<h3 id="bundledockerfile"><code>bundle.Dockerfile</code></h3>
<p>The base image of the bundle is <code>scratch</code>. Inside the container image, the path to the manifest and metadata directory is <code>test/</code> and <code>test/metadata</code> respectively.</p>
<h1 id="deploying-an-operator">Deploying an Operator</h1>
<h2 id="direct-deployment-using-operator-image">Direct Deployment using Operator Image</h2>
<p><code>Makefile</code> provides a target <code>deploy</code> for deploying the operator</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make deploy <span class="nv">IMG</span><span class="o">=</span><span class="s2">&#34;docker.io/bovem/memcached-operator:v0.0.1&#34;</span>
</span></span></code></pre></div><p>After the deployment is completed successfully, we can create <code>Memcached</code> custom resource</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml
</span></span></code></pre></div><p>Executing the <code>make</code> command with the <code>undeploy</code> target will uninstall the operator from the cluster</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">make undeploy
</span></span></code></pre></div><h2 id="olm-deployment-using-operator-bundle-image">OLM Deployment using Operator Bundle Image</h2>
<p>Before deploying the operator through the bundle image we have to make sure that OLM is installed on the cluster. To do that we can use the command</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">operator-sdk olm status
</span></span></code></pre></div><p>If OLM is missing it could be installed easily from the Operator SDK itself</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">operator-sdk olm install
</span></span></code></pre></div><p>Once OLM is installed, operator deployment is as easy as</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">operator-sdk run bundle docker.io/bovem/memcached-operator-bundle:v0.0.1
</span></span></code></pre></div><p>and uninstalling it</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">operator-sdk cleanup docker.io/bovem/memcached-operator-bundle:v0.0.1
</span></span></code></pre></div><h1 id="validating-the-operator-bundle-image">Validating the Operator Bundle Image</h1>
<p>Validating an OBI or the <code>bundle</code> directory ensures</p>
<ul>
<li>The <code>manifests</code> directory contains all the required CRDs including CSV.</li>
<li>Data present in the files inside the <code>manifests</code> directory matches the provided data.</li>
<li>The bundle format is valid.</li>
<li>Permissions and Configurations of the operator are valid for an OLM-enabled cluster.</li>
<li>Any additional validations defined with the operator are satisfied.</li>
</ul>
<p>To validate a bundle the image or <code>bundle</code> directory path could be passed as an argument</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">operator-sdk bundle validate docker.io/bovem/memcached-operator-bundle:v0.0.1
</span></span></code></pre></div><p>or</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">operator-sdk bundle validate ./bundle
</span></span></code></pre></div><h1 id="testing-operator-bundle-image-using-scorecard">Testing Operator Bundle Image using Scorecard</h1>
<p>Developers can define tests for their operator projects using the <strong>scorecard</strong>.</p>
<p>By default scorecard contains the following tests:</p>
<ul>
<li>Basic Test Suite (<code>basic-check-spec-test</code>): Tests for <code>spec</code> block in all CRDs.</li>
<li>OLM Test Suite:
<ul>
<li><code>olm-bundle-validation-test</code>: Validates bundle manifests</li>
<li><code>olm-crds-have-validation-test</code>: All CRDs contain a validation section containing validation for each spec and status field.</li>
<li><code>olm-crds-have-resources-test</code>: All CRDs have a <code>resources</code> section</li>
<li><code>olm-spec-descriptors-test</code>: Every field in the CRD&rsquo;s <code>spec</code> section has a descriptor listed in CSV</li>
<li><code>olm-status-descriptors-test</code>: Every field in the CRD&rsquo;s <code>status</code> section has a descriptor listed in CSV</li>
</ul>
</li>
</ul>
<p>Tests are defined in <code>config/scorecard/bases</code> as <strong>stages</strong> and executed on pods created on the cluster. At each stage, the tests are executed parallelly or sequentially. The result file is generated in JSON/XML/Text format.</p>
<p><code>scorecard</code> subcommand is used to trigger test execution</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">operator-sdk scorecard docker.io/bovem/memcached-operator-bundle:v0.0.1
</span></span></code></pre></div><h1 id="resources">Resources</h1>
<p><a href="https://github.com/operator-framework/operator-registry/blob/v1.16.1/docs/design/operator-bundle.md" target="_blank">Operator Bundle</a><br>
<a href="https://book-v1.book.kubebuilder.io/basics/what_is_the_controller_manager.html" target="_blank">What is the Manager</a><br>
<a href="https://medium.com/swlh/what-is-memcached-d1498623db3b" target="_blank">What is Memcached</a><br>
<a href="https://book-v1.book.kubebuilder.io/getting_started/what_is_kubebuilder.html" target="_blank">Kubebuilder</a><br>
<a href="https://book.kubebuilder.io/cronjob-tutorial/basic-project.html" target="_blank">What’s in a basic project?</a><br>
<a href="https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/" target="_blank">Go Operator Tutorial</a><br>
<a href="https://sdk.operatorframework.io/docs/cli/operator-sdk_init/" target="_blank">operator-sdk init</a><br>
<a href="https://sdk.operatorframework.io/docs/cli/operator-sdk_create_api/" target="_blank">operator-sdk create api</a><br>
<a href="https://sdk.operatorframework.io/docs/cli/operator-sdk_olm/" target="_blank">operator-sdk olm</a><br>
<a href="https://sdk.operatorframework.io/docs/cli/operator-sdk_run/" target="_blank">operator-sdk run</a><br>
<a href="https://sdk.operatorframework.io/docs/cli/operator-sdk_bundle_validate/" target="_blank">operator-sdk bundle validate</a><br>
<a href="https://sdk.operatorframework.io/docs/cli/operator-sdk_scorecard/" target="_blank">operator-sdk scorecard</a></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
