<?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>GitOps on TechBlog about OpenShift/Ansible/Satellite and much more</title><link>https://blog.stderr.at/categories/gitops/</link><description>TechBlog about OpenShift/Ansible/Satellite and much more</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>Toni Schmidbauer &amp; Thomas Jungbauer</copyright><lastBuildDate>Thu, 05 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.stderr.at/categories/gitops/index.xml" rel="self" type="application/rss+xml"/><item><title>GitOps Catalog</title><link>https://blog.stderr.at/whats-new/2025-12-23-gitops-catalog/</link><pubDate>Mon, 22 Dec 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/whats-new/2025-12-23-gitops-catalog/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;a href="https://blog.stderr.at/gitops-catalog/"&gt;GitOps Catalog&lt;/a&gt; page provides an interactive visualization of all available ArgoCD applications from the openshift-clusterconfig-gitops repository. Check out the page &lt;strong&gt;&lt;a href="https://blog.stderr.at/gitops-catalog/"&gt;GitOps Catalog&lt;/a&gt;&lt;/strong&gt; for more details.&lt;/p&gt;
&lt;/div&gt;</description></item><item><title>GitOps Catalog</title><link>https://blog.stderr.at/gitops-catalog/</link><pubDate>Mon, 22 Dec 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitops-catalog/</guid><description>
&lt;div class="paragraph"&gt;
&lt;p&gt;This page provides an interactive visualization of all available ArgoCD applications from the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" target="_blank" rel="noopener"&gt;openshift-clusterconfig-gitops&lt;/a&gt; repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The repository demonstrates the usage of OpenShift GitOps with (mainly) Helm Charts that I use for my own clusters. As easy Secret Management I have Sealed Secrets.
It focuses on &lt;strong&gt;main cluster configuration&lt;/strong&gt; using a GitOps approach. Some of the charts or configurations are discussied in my blog posts. Please refer to the different GitOps blog posts for more details and to understand why it is done this way.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_how_to_use"&gt;How to Use&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Browse&lt;/strong&gt; the catalog below to discover available configurations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Filter&lt;/strong&gt; by category using the buttons (Security, Observability, Storage, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Search&lt;/strong&gt; for specific applications using the search box&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Click&lt;/strong&gt; on &amp;#34;Blog&amp;#34; where available to read detailed documentation or &amp;#34;Source&amp;#34; to view the code on GitHub&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_quick_start"&gt;Quick Start&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Clone the repository
git clone https://github.com/tjungbauer/openshift-clusterconfig-gitops.git
cd openshift-clusterconfig-gitops
# Initialize GitOps (deploys OpenShift GitOps operator and App of Apps)
./init_GitOps.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock tip"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-tip" title="Tip"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Most Helm Charts used in this repository can be found at &lt;a href="https://charts.stderr.at/" target="_blank" rel="noopener"&gt;charts.stderr.at&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;div class="paragraph"&gt;
&lt;div class="gitops-catalog"&gt;
&lt;div class="gitops-catalog-header"&gt;
&lt;div class="gitops-catalog-info"&gt;
&lt;h2&gt;OpenShift GitOps Configuration Catalog&lt;/h2&gt;
&lt;p class="gitops-catalog-subtitle"&gt;
Available ArgoCD applications for cluster configuration via
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" target="_blank" rel="noopener"&gt;openshift-clusterconfig-gitops&lt;/a&gt;
&lt;/p&gt;
&lt;p class="gitops-catalog-updated"&gt;
&lt;i class="fas fa-clock"&gt;&lt;/i&gt; Last updated: 2026-02-02
&lt;span class="gitops-app-count"&gt;&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; 40 Applications&lt;/span&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-filter-container"&gt;
&lt;div class="gitops-search-box"&gt;
&lt;i class="fas fa-search"&gt;&lt;/i&gt;
&lt;input type="text" id="gitops-search" placeholder="Search applications..." autocomplete="off"&gt;
&lt;/div&gt;
&lt;div class="gitops-category-filters"&gt;
&lt;button class="gitops-filter-btn active" data-category="all"&gt;
&lt;i class="fas fa-th"&gt;&lt;/i&gt; All
&lt;/button&gt;
&lt;button class="gitops-filter-btn" data-category="development" style="--cat-color: #fd7e14"&gt;
&lt;i class="fas fa-code"&gt;&lt;/i&gt; Development Tools
&lt;/button&gt;
&lt;button class="gitops-filter-btn" data-category="multicluster" style="--cat-color: #20c997"&gt;
&lt;i class="fas fa-project-diagram"&gt;&lt;/i&gt; Multi-Cluster Management
&lt;/button&gt;
&lt;button class="gitops-filter-btn" data-category="observability" style="--cat-color: #17a2b8"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/button&gt;
&lt;button class="gitops-filter-btn" data-category="platform" style="--cat-color: #6f42c1"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/button&gt;
&lt;button class="gitops-filter-btn" data-category="security" style="--cat-color: #dc3545"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/button&gt;
&lt;button class="gitops-filter-btn" data-category="storage" style="--cat-color: #28a745"&gt;
&lt;i class="fas fa-database"&gt;&lt;/i&gt; Storage &amp;amp; Data
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-apps-grid"&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="deploy-base-operators" data-description="deploy base operators without any additional configuration. this simply adds the subscription and leave further configuration to you or another chart."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-puzzle-piece"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;deploy-base-operators&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploy base operators WITHOUT any additional configuration. This simply adds the Subscription and leave further configuration to you or another Chart.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/all/base-operators&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/all/base-operators" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="proj-onboarding" data-description="this chart shall deploy namespaces and their depending resources, like networkpolicies or quotas etc."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-folder-plus"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.5&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;proj-onboarding&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;This Chart shall deploy namespaces and their depending resources, like NetworkPolicies or Quotas etc.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/all/project-onboarding&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/all/project-onboarding" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="gitops-deployment" data-description="deployment of a gitops for application management"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;gitops-deployment&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deployment of a GitOps for Application Management&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/applications-gitops&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#openshift-gitops" class="gitops-dep-link gitops-dep-helm" title="View openshift-gitops Helm chart"&gt;
openshift-gitops
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/applications-gitops" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="secure-supply-chain" data-name="clusterbranding" data-description="deploys cluster branding such as custom login page or logo etc."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-paint-brush"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;clusterbranding&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploys Cluster Branding such as custom Login Page or Logo etc.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/branding&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#generic-cluster-config" class="gitops-dep-link gitops-dep-helm" title="View generic-cluster-config Helm chart"&gt;
generic-cluster-config
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;secure-supply-chain&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/branding" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="secure-supply-chain" data-name="clusterbranding" data-description="deploys cluster branding such as custom login page or logo etc."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-paint-brush"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;clusterbranding&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploys Cluster Branding such as custom Login Page or Logo etc.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/branding-bottom&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#generic-cluster-config" class="gitops-dep-link gitops-dep-helm" title="View generic-cluster-config Helm chart"&gt;
generic-cluster-config
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;secure-supply-chain&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/branding-bottom" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="compliance" data-name="cert-manager" data-description="setup and configure the cert manager operator"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-certificate"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v2.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;cert-manager&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Setup and configure the cert Manager operator&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/cert-manager&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#cert-manager" class="gitops-dep-link gitops-dep-helm" title="View cert-manager Helm chart"&gt;
cert-manager
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;compliance&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/cert-manager" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="security" data-name="clusterconfig-apiserver" data-description="enables etcd encryption, customer certificate and audit-profile for apiserver"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-server"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.3&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;clusterconfig-apiserver&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Enables ETCD encryption, customer certificate and audit-profile for APIServer&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/clusterconfig-apiserver&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#generic-cluster-config" class="gitops-dep-link gitops-dep-helm" title="View generic-cluster-config Helm chart"&gt;
generic-cluster-config
&lt;/a&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#cert-manager" class="gitops-dep-link gitops-dep-helm" title="View cert-manager Helm chart"&gt;
cert-manager
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;security&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/clusterconfig-apiserver" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="enable-etcd-backup" data-description="create a cronjob that performs etcd backup and stores the backup to a pv."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-server"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;enable-etcd-backup&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Create a CronJob that performs ETCD Backup and stores the backup to a PV.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/etcd-backup&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#etcd-backup" class="gitops-dep-link gitops-dep-helm" title="View etcd-backup Helm chart"&gt;
etcd-backup
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/etcd-backup" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="generic" data-name="clusterconfig" data-description="installs cluster configuration which is usually valid for all clusters"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-sliders-h"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;clusterconfig&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Installs cluster configuration which is usually valid for ALL clusters&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/generic-clusterconfig&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#generic-cluster-config" class="gitops-dep-link gitops-dep-helm" title="View generic-cluster-config Helm chart"&gt;
generic-cluster-config
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;generic&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/generic-clusterconfig" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="compliance" data-name="cert-manager" data-description="setup and configure the cert manager operator"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;cert-manager&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Setup and configure the cert Manager operator&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/idp&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#generic-cluster-config" class="gitops-dep-link gitops-dep-helm" title="View generic-cluster-config Helm chart"&gt;
generic-cluster-config
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;compliance&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/idp" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="ingress,routing,infrastructure" data-name="ingress controller" data-description="configures the openshift ingresscontroller object with replicas, nodeselector, and tolerations for infrastructure node placement."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-door-open"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;Ingress Controller&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Configures the OpenShift IngressController object with replicas, nodeSelector, and tolerations for infrastructure node placement.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/ingresscontroller&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;ingress&lt;/span&gt;
&lt;span class="gitops-tag"&gt;routing&lt;/span&gt;
&lt;span class="gitops-tag"&gt;infrastructure&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/ingresscontroller" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="secure-supply-chain" data-name="install-cyclonedx" data-description="install cyclonedx to store sboms"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-list-check"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;install-cyclonedx&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Install Cyclonedx to store SBOMs&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/install-cyclonedx&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#cyclonedx" class="gitops-dep-link gitops-dep-helm" title="View cyclonedx Helm chart"&gt;
cyclonedx
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;secure-supply-chain&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/install-cyclonedx" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="storage" data-keywords="" data-name="internal-registry" data-description="configures the internal openshift registry"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #28a745"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-cube"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-database"&gt;&lt;/i&gt; Storage &amp;amp; Data
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;internal-registry&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Configures the internal OpenShift registry&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/internal-registry&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/internal-registry" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="node-labels" data-description="manage the labelling of the nodes using openshift-gitops 1.6&amp;#43; and server side apply"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-sliders-h"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;node-labels&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Manage the labelling of the nodes using openshift-gitops 1.6&amp;#43; and Server Side Apply&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/node-configuration&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/node-configuration" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="acs" data-name="setup-acm" data-description="deploys advanced cluster managment (acm) on target cluster."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-project-diagram"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.4&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-acm&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploys Advanced Cluster Managment (ACM) on target cluster.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-acm&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#rhacm-setup" class="gitops-dep-link gitops-dep-helm" title="View rhacm-setup Helm chart"&gt;
rhacm-setup
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;acs&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-acm" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="security" data-name="setup-acs" data-description="deploys advanced cluster security (acs) on target cluster. if enabled central will be deployed too."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-shield-virus"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-acs&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploys Advanced Cluster Security (ACS) on target cluster. If enabled Central will be deployed too.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-acs&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#rhacs-setup" class="gitops-dep-link gitops-dep-helm" title="View rhacs-setup Helm chart"&gt;
rhacs-setup
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;security&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-acs" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="security" data-name="setup-acs-backup" data-description="deploys backup for advanced cluster security (acs) on target cluster."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-shield-virus"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-acs-backup&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploys Backup for Advanced Cluster Security (ACS) on target cluster.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-acs-backup&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#rhacs-backup" class="gitops-dep-link gitops-dep-helm" title="View rhacs-backup Helm chart"&gt;
rhacs-backup
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;security&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-acs-backup" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="sandbox" data-name="setup-cert-utils-test" data-description="test deployment of cert utils operator. this is used to test new operator deployment methods."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-certificate"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-cert-utils-test&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Test Deployment of Cert Utils Operator. This is used to test new operator deployment methods.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-cert-utils-test&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;sandbox&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-cert-utils-test" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="observability" data-name="setup-cluster-observability-operator" data-description="installs the cluster observability operator. currently it simply installs the operator and its namespace."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-binoculars"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-cluster-observability-operator&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Installs the Cluster Observability Operator. Currently it simply installs the Operator and its namespace.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-cluster-observability-operator&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;observability&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-cluster-observability-operator" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="compliance" data-name="setup-compliance-operator" data-description="deploy and configure the compliance operator"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-clipboard-check"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.2&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-compliance-operator&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploy and configure the Compliance Operator&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-compliance-operator&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#compliance-operator-full-stack" class="gitops-dep-link gitops-dep-helm" title="View compliance-operator-full-stack Helm chart"&gt;
compliance-operator-full-stack
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;compliance&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-compliance-operator" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="acs" data-name="setup-container-security-operator" data-description="setup the quay container security operator."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-shield-virus"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.6&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-container-security-operator&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Setup the Quay Container Security Operator.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-container-security-operator&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;acs&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-container-security-operator" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="setup-cost-management-operator" data-description="setup cost management operator"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-puzzle-piece"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-cost-management-operator&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Setup Cost Management Operator&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-cost-management-operator&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#cost-management" class="gitops-dep-link gitops-dep-helm" title="View cost-management Helm chart"&gt;
cost-management
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-cost-management-operator" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="storage" data-keywords="crunchy" data-name="setup-crunchy-postgres" data-description="deploy and configure crunchy postgres operator and postgres clusters"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #28a745"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-database"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-database"&gt;&lt;/i&gt; Storage &amp;amp; Data
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-crunchy-postgres&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploy and configure Crunchy Postgres Operator and Postgres clusters&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-crunchy-postgres&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;crunchy&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-crunchy-postgres" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="development" data-keywords="dev" data-name="setup-dev-spaces" data-description="deploy dev spaces operator"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #fd7e14"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-laptop-code"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-code"&gt;&lt;/i&gt; Development Tools
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-dev-spaces&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploy Dev Spaces Operator&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-dev-spaces&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;dev&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-dev-spaces" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="" data-name="setup-file-integrity-operator" data-description="setup file integrity operator"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-puzzle-piece"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-file-integrity-operator&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Setup File Integrity Operator&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-file-integrity-operator&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#file-integrity-operator" class="gitops-dep-link gitops-dep-helm" title="View file-integrity-operator Helm chart"&gt;
file-integrity-operator
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-file-integrity-operator" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="grafana" data-name="setup-grafana" data-description="deploy the grafana operator"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-chart-bar"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-grafana&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploy the Grafana Operator&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-grafana&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;grafana&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-grafana" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="loki" data-name="setup-loki-operator" data-description="installs loki operator. configuration should be done by the appropriate service that wants to use loki. for example openshift-logging"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-scroll"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-loki-operator&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Installs Loki Operator. Configuration should be done by the appropriate service that wants to use Loki. For example openshift-logging&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-loki-operator&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;loki&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-loki-operator" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="opentelemetry" data-name="setup-lokistack-otel-k8s-events" data-description="create a lokistack instance to prepare a storage to opentelemetry collector for kubernetes events."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-scroll"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-lokistack-otel-k8s-events&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Create a LokiStack instance to prepare a storage to OpenTelemetry Collector for Kubernetes Events.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-lokistack-otel-k8s-events&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;opentelemetry&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-lokistack-otel-k8s-events" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="" data-name="setup-multicluster-observability" data-description="enabled mutliclusterobservability once acm has been installed"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-binoculars"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-multicluster-observability&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Enabled MutliClusterObservability once ACM has been installed&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-multicluster-observability&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-app-app-deps"&gt;
&lt;div class="gitops-deps-header" title="Other GitOps applications that should be deployed first"&gt;
&lt;i class="fas fa-project-diagram"&gt;&lt;/i&gt; Requires:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="#" class="gitops-dep-link gitops-dep-app" data-app-id="setup-acm" title="Jump to setup-acm"&gt;
setup-acm
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-multicluster-observability" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="" data-name="setup-network-observability" data-description="installs and configures openshift network observability."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-network-wired"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-network-observability&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Installs and configures OpenShift Network Observability.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-network-observability&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-network-observability" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="clusterbranding" data-description="deploys cluster branding such as custom login page or logo etc."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.2&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;clusterbranding&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploys Cluster Branding such as custom Login Page or Logo etc.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-openshift-data-foundation&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#openshift-data-foundation" class="gitops-dep-link gitops-dep-helm" title="View openshift-data-foundation Helm chart"&gt;
openshift-data-foundation
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-openshift-data-foundation" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="logging" data-name="setup-openshift-logging" data-description="installs and configures openshift logging by deploying logging and loki operator and configuring them accordingly. example configuration is creating a bucket using openshift data foundation."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-file-alt"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-openshift-logging&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Installs and configures OpenShift Logging by deploying Logging and Loki Operator and configuring them accordingly. Example configuration is creating a Bucket using OpenShift Data Foundation.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-openshift-logging&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#openshift-logging" class="gitops-dep-link gitops-dep-helm" title="View openshift-logging Helm chart"&gt;
openshift-logging
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-app-app-deps"&gt;
&lt;div class="gitops-deps-header" title="Other GitOps applications that should be deployed first"&gt;
&lt;i class="fas fa-project-diagram"&gt;&lt;/i&gt; Requires:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="#" class="gitops-dep-link gitops-dep-app" data-app-id="setup-loki-operator" title="Jump to setup-loki-operator"&gt;
setup-loki-operator
&lt;/a&gt;
&lt;a href="#" class="gitops-dep-link gitops-dep-app" data-app-id="setup-openshift-data-foundation" title="Jump to setup-openshift-data-foundation"&gt;
setup-openshift-data-foundation
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;logging&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-openshift-logging" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="tracing" data-name="setup-otel-operator" data-description="installs and configures the opentelemetry operator."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-satellite-dish"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-otel-operator&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Installs and configures the OpenTelemetry Operator.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-otel-operator&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#rh-build-of-opentelemetry" class="gitops-dep-link gitops-dep-helm" title="View rh-build-of-opentelemetry Helm chart"&gt;
rh-build-of-opentelemetry
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;tracing&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-otel-operator" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="storage" data-keywords="" data-name="setup-quay" data-description="setup quay registry"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #28a745"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-cube"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-database"&gt;&lt;/i&gt; Storage &amp;amp; Data
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v2.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-quay&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Setup Quay Registry&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-quay&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#quay-registry-setup" class="gitops-dep-link gitops-dep-helm" title="View quay-registry-setup Helm chart"&gt;
quay-registry-setup
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-app-app-deps"&gt;
&lt;div class="gitops-deps-header" title="Other GitOps applications that should be deployed first"&gt;
&lt;i class="fas fa-project-diagram"&gt;&lt;/i&gt; Requires:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="#" class="gitops-dep-link gitops-dep-app" data-app-id="setup-openshift-data-foundation" title="Jump to setup-openshift-data-foundation"&gt;
setup-openshift-data-foundation
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-quay" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="security" data-keywords="keycloak" data-name="setup-rh-build-of-keycloak" data-description="deploy and configure the operator red hat build of keycloak"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #dc3545"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-key"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-shield-alt"&gt;&lt;/i&gt; Security &amp;amp; Compliance
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.2&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-rh-build-of-keycloak&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Deploy and configure the operator Red Hat Build of Keycloak&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-rh-build-of-keycloak&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#cert-manager" class="gitops-dep-link gitops-dep-helm" title="View cert-manager Helm chart"&gt;
cert-manager
&lt;/a&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#rh-build-keycloak" class="gitops-dep-link gitops-dep-helm" title="View rh-build-keycloak Helm chart"&gt;
rh-build-keycloak
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;keycloak&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-rh-build-of-keycloak" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="observability" data-keywords="tracing" data-name="setup-tempo-operator" data-description="installs and configures the tempo operator (distributed tracing). this operator will require a s3 storage, which can be installed and provided using odf and available helm sub-charts."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #17a2b8"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-stopwatch"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-chart-line"&gt;&lt;/i&gt; Observability &amp;amp; Monitoring
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-tempo-operator&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Installs and configures the Tempo Operator (distributed tracing). This Operator will require a S3 storage, which can be installed and provided using ODF and available Helm Sub-Charts.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/setup-tempo-operator&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#tempo-tracing" class="gitops-dep-link gitops-dep-helm" title="View tempo-tracing Helm chart"&gt;
tempo-tracing
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;tracing&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-tempo-operator" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="setup-trusted-profile-analyzer" data-description="setup trusted profile analyzer"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-signature"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-trusted-profile-analyzer&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Setup Trusted Profile Analyzer&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/trusted-artifact-signer&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/trusted-artifact-signer" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="" data-name="setup-trusted-profile-analyzer" data-description="setup trusted profile analyzer"&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;setup-trusted-profile-analyzer&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Setup Trusted Profile Analyzer&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/trusted-profile-analyzer&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-dependencies-section"&gt;
&lt;div class="gitops-dependencies-header"&gt;
&lt;i class="fas fa-layer-group"&gt;&lt;/i&gt; Dependencies / Requirements
&lt;/div&gt;
&lt;div class="gitops-app-deps gitops-helm-deps"&gt;
&lt;div class="gitops-deps-header" title="Helm chart dependencies from charts.stderr.at"&gt;
&lt;i class="fas fa-cubes"&gt;&lt;/i&gt; Helm Charts:
&lt;/div&gt;
&lt;div class="gitops-deps-list"&gt;
&lt;a href="https://blog.stderr.at/helm-charts/#redhat-trusted-profile-analyzer" class="gitops-dep-link gitops-dep-helm" title="View redhat-trusted-profile-analyzer Helm chart"&gt;
redhat-trusted-profile-analyzer
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/trusted-profile-analyzer" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="platform" data-keywords="upgrade,version,clusterversion" data-name="cluster version update" data-description="updates openshift clusterversion for controlled cluster upgrades via gitops."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #6f42c1"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-sync-alt"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-cogs"&gt;&lt;/i&gt; Platform Configuration
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.1&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;Cluster Version Update&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Updates OpenShift ClusterVersion for controlled cluster upgrades via GitOps.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/update-clusterversion&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;upgrade&lt;/span&gt;
&lt;span class="gitops-tag"&gt;version&lt;/span&gt;
&lt;span class="gitops-tag"&gt;clusterversion&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://blog.stderr.at/gitopscollection/2024-06-07-update-cluster-version-with-gitops/" class="gitops-btn gitops-btn-blog" title="Read blog post"&gt;
&lt;i class="fas fa-book"&gt;&lt;/i&gt; Blog
&lt;/a&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/update-clusterversion" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;article class="gitops-app-card" data-category="multicluster" data-keywords="acm,policy,governance,waves" data-name="acm policy management waves" data-description="manages acm policy deployment waves using argocd sync waves for ordered policy application."&gt;
&lt;div class="gitops-app-card-header" style="--cat-color: #20c997"&gt;
&lt;div class="gitops-app-icon"&gt;
&lt;i class="fas fa-wave-square"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="gitops-app-meta"&gt;
&lt;span class="gitops-app-category"&gt;
&lt;i class="fas fa-project-diagram"&gt;&lt;/i&gt; Multi-Cluster Management
&lt;/span&gt;
&lt;span class="gitops-app-version"&gt;v1.0.0&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-body"&gt;
&lt;h3 class="gitops-app-name"&gt;ACM Policy Management Waves&lt;/h3&gt;
&lt;p class="gitops-app-description"&gt;Manages ACM policy deployment waves using ArgoCD sync waves for ordered policy application.&lt;/p&gt;
&lt;div class="gitops-app-details"&gt;
&lt;div class="gitops-app-detail gitops-app-path"&gt;
&lt;i class="fas fa-code-branch"&gt;&lt;/i&gt;
&lt;code&gt;clusters/management-cluster/wave-acm-policy-management&lt;/code&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-tags"&gt;
&lt;span class="gitops-tag"&gt;acm&lt;/span&gt;
&lt;span class="gitops-tag"&gt;policy&lt;/span&gt;
&lt;span class="gitops-tag"&gt;governance&lt;/span&gt;
&lt;span class="gitops-tag"&gt;waves&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="gitops-app-card-footer"&gt;
&lt;div class="gitops-app-actions"&gt;
&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/wave-acm-policy-management" target="_blank" rel="noopener" class="gitops-btn gitops-btn-github" title="View on GitHub"&gt;
&lt;i class="fab fa-github"&gt;&lt;/i&gt; Source
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;/div&gt;
&lt;div class="gitops-no-results" style="display: none;"&gt;
&lt;i class="fas fa-search"&gt;&lt;/i&gt;
&lt;p&gt;No applications found matching your criteria.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
(function() {
'use strict';
const catalog = document.querySelector('.gitops-catalog');
if (!catalog) return;
const searchInput = catalog.querySelector('#gitops-search');
const filterBtns = catalog.querySelectorAll('.gitops-filter-btn');
const cards = catalog.querySelectorAll('.gitops-app-card');
const noResults = catalog.querySelector('.gitops-no-results');
let activeCategory = 'all';
let searchTerm = '';
function filterCards() {
let visibleCount = 0;
cards.forEach(card =&gt; {
const cardCategory = card.dataset.category;
const cardName = card.dataset.name;
const cardDescription = card.dataset.description;
const cardKeywords = card.dataset.keywords;
const matchesCategory = activeCategory === 'all' || cardCategory === activeCategory;
const matchesSearch = searchTerm === '' ||
cardName.includes(searchTerm) ||
cardDescription.includes(searchTerm) ||
cardKeywords.includes(searchTerm);
if (matchesCategory &amp;&amp; matchesSearch) {
card.style.display = '';
card.classList.add('gitops-card-visible');
visibleCount++;
} else {
card.style.display = 'none';
card.classList.remove('gitops-card-visible');
}
});
noResults.style.display = visibleCount === 0 ? 'flex' : 'none';
}
filterBtns.forEach(btn =&gt; {
btn.addEventListener('click', () =&gt; {
filterBtns.forEach(b =&gt; b.classList.remove('active'));
btn.classList.add('active');
activeCategory = btn.dataset.category;
filterCards();
});
});
searchInput.addEventListener('input', (e) =&gt; {
searchTerm = e.target.value.toLowerCase().trim();
filterCards();
});
filterCards();
catalog.querySelectorAll('.gitops-dep-app').forEach(link =&gt; {
link.addEventListener('click', (e) =&gt; {
e.preventDefault();
const appId = link.dataset.appId;
const targetCard = catalog.querySelector(`.gitops-app-card[data-category][data-name*="${appId.toLowerCase()}"]`) ||
catalog.querySelector(`.gitops-app-card[data-keywords*="${appId}"]`);
if (targetCard) {
filterBtns.forEach(b =&gt; b.classList.remove('active'));
catalog.querySelector('[data-category="all"]').classList.add('active');
activeCategory = 'all';
searchTerm = '';
searchInput.value = '';
filterCards();
targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
targetCard.classList.add('gitops-card-highlight');
setTimeout(() =&gt; targetCard.classList.remove('gitops-card-highlight'), 2000);
}
});
});
})();
&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - Initialisation, Unsealing, and Auto-Unseal - Part 6</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-05-openbao-part-6-auto-unsealing/</link><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-03-05-openbao-part-6-auto-unsealing/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;After deploying OpenBao via GitOps (&lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-20-openbao-part-5-gitops-deployment/"&gt;Part 5&lt;/a&gt;), OpenBao must be initialised and then unsealed before it becomes functional. You usually do not want to do this unsealing manually, since this is not scalable especially in bigger, productive environments. This article explains how to handle initialisation and unsealing, and possible options to configure an &lt;strong&gt;auto-unseal&lt;/strong&gt; process so that OpenBao unseals itself on every restart without manual key entry.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_is_happening"&gt;What is happening?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenBao starts in a &lt;strong&gt;sealed&lt;/strong&gt; state. You must:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Initialise (once): run &lt;code&gt;bao operator init&lt;/code&gt; to generate unseal keys (or recovery keys when using auto-unseal).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unseal (after each restart): provide a threshold of unseal keys so each node can decrypt the root key. In the previous articles, we used a threshold of 3 (out of 5). This means that you need to run &lt;strong&gt;bao operator unseal&lt;/strong&gt; three times with different keys on every node after every start before the service becomes available.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Unseal workflow (assuming a threshold of 3 and 5 nodes and initialisation already happened):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-mermaid {align=" center"="" zoom="true" }="" hljs"="" data-lang="mermaid {align=" center"="" zoom="true" }"=""&gt;---
title: &amp;#34;Unseal Workflow&amp;#34;
config:
theme: &amp;#39;dark&amp;#39;
---
flowchart LR
A[&amp;#34;openbao-0 starts → 3× unseal using different keys&amp;#34;]
A --&amp;gt; B[&amp;#34;openbao-1 starts → 3× unseal using different keys&amp;#34;]
B --&amp;gt; C[&amp;#34;openbao-2 starts → 3× unseal using different keys&amp;#34;]
C --&amp;gt; D[OpenBao is ready]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This is a chicken-egg problem: you need to start somewhere, but you cannot start without the keys.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Manual unsealing does not scale very well and for production, &lt;strong&gt;auto-unseal&lt;/strong&gt; is recommended.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This article covers:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Practical approaches to initialisation and unsealing (manual, Jobs, init containers).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Potential auto-unseal options: with AWS KMS, static key, and transit seal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_handling_initialisation_and_unsealing"&gt;Handling Initialisation and Unsealing&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The challenge with GitOps is that initialisation and unsealing are one-off or stateful operations. Below are several approaches. The problem is always the same: where do you start? You must have the unseal keys available to start the process. Do you keep them locally, do you keep them in a Kubernetes Secret, do you use an external Key Management System (KMS)? The most robust for production in my opinion is auto-unseal using a KMS (see &lt;a href="#_approach_4_auto_unseal_with_kms_recommended_for_production"&gt;[_approach_4_auto_unseal_with_kms_recommended_for_production]&lt;/a&gt; and the following sections). It uses an external Key Management System (KMS) to store the keys. This way, the keys are not stored in the cluster and are not exposed to the risk of being compromised.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s have a look at the different options.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_approach_1_manual_initialisation_simplest"&gt;Approach 1: Manual Initialisation (simplest)&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While manual initialisation and unsealing is the simplest approach, it is not recommended for production. Still, I would like to mention it here for completeness and at least provide a simple for loop to unseal the OpenBao service (on Kubernetes/OpenShift).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Manual unsealing is still a valid option when you have a small cluster and assume that it is not required often.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After Argo CD deploys OpenBao, the first pod, openbao-0, will not become ready until it is initialised and unsealed. Once done, the next pod, openbao-1, will try to start and wait until it is unsealed, and then the third pod, and so on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following, simplified script will go through the pods and unseal them one by one. It initialises the first pod and stores the unseal keys in the file &lt;strong&gt;openbao-init.json&lt;/strong&gt; locally on your machine. From there it takes three different keys and unseals the OpenBao service.
Since it takes a while until other pods are pulling the image and starting, we will use a sleep timer of 30 seconds between each pod. This may or may not be enough depending on your network and cluster speed. As said, this is a very simplified script:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The CLI tool &lt;strong&gt;oc&lt;/strong&gt; can be replaced by &lt;strong&gt;kubectl&lt;/strong&gt; in case you are using a different cluster. In addition, you will need the jq command line tool to parse the JSON file.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If your OpenBao was initialised previously, you can remove the first three commands and start with the unseal commands.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Initialise the first pod, assuming the Pod openbao-0 is already running.
# You can skip this if OpenBao was initialised previously.
echo &amp;#34;Initialising OpenBao on openbao-0 (keys will be saved to openbao-init.json)...&amp;#34;
oc exec -it openbao-0 -n openbao -- bao operator init \
-key-shares=5 -key-threshold=3 -format=json &amp;gt; openbao-init.json &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
echo &amp;#34;Init complete. Unseal keys and root token are in openbao-init.json.&amp;#34;
# Unseal each pod (three times each with different keys)
echo &amp;#34;Unsealing pods openbao-0, openbao-1, openbao-2 (3 key steps each)...&amp;#34;
for i in 0 1 2; do
sleep_timer=30
SLEEPER_TMP=1
echo &amp;#34;Unsealing openbao-$i...&amp;#34;
for j in 1 2 3; do &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
KEY=$(cat openbao-init.json | jq -r &amp;#34;.unseal_keys_b64[$((j-1))]&amp;#34;)
oc exec -it openbao-$i -n openbao -- bao operator unseal $KEY
done
echo &amp;#34;openbao-$i unsealed. Waiting ${sleep_timer}s for next pod to be ready...&amp;#34;
while [[ $SLEEPER_TMP -le &amp;#34;$sleep_timer&amp;#34; ]]; do &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
if (( SLEEPER_TMP % 10 == 0 )); then
echo -n &amp;#34;$SLEEPER_TMP&amp;#34;
else
echo -n &amp;#34;.&amp;#34;
fi
sleep 1
SLEEPER_TMP=$((SLEEPER_TMP + 1))
done
echo &amp;#34;&amp;#34;
done
echo &amp;#34;All pods unsealed. Store init data securely (e.g. in a password manager).&amp;#34;
# Store init data securely (e.g. in a password manager)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Initialise the first pod, assuming the Pod openbao-0 is already running. This will store the keys (including the root token) in the file &lt;strong&gt;openbao-init.json&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Unseal each pod (three times each with different keys). The keys are taken from the &lt;strong&gt;openbao-init.json&lt;/strong&gt; file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Wait 30 seconds between each pod to give the other pods time to start and pull the image …​ super-fancy sleep timer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The unseal keys as well as the root token are stored in the &lt;strong&gt;openbao-init.json&lt;/strong&gt; file. This file is stored locally on your machine. Make sure to store it securely and do not share it with anyone.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_approach_2_kubernetes_job_for_initialisation"&gt;Approach 2: Kubernetes Job for initialisation&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A Job can run after OpenBao is deployed, initialise it if needed, and store the init output (unseal keys and root token) in a Kubernetes Secret. You can then unseal manually or with a second Job that reads from that Secret. This section assumes the init output is stored in a Kubernetes Secret and explains how to create that Secret from the Job and how to use it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_how_the_secret_is_created"&gt;How the Secret is created&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The init Job runs &lt;code&gt;bao operator init&lt;/code&gt; and then creates the Secret using &lt;code&gt;oc&lt;/code&gt; (or &lt;code&gt;kubectl&lt;/code&gt;). The Secret name and key used here are &lt;code&gt;openbao-init-data&lt;/code&gt; and &lt;code&gt;init.json&lt;/code&gt; (the file contains the JSON output of &lt;code&gt;bao operator init&lt;/code&gt;, including &lt;code&gt;unseal_keys_b64&lt;/code&gt; and &lt;code&gt;root_token&lt;/code&gt;). One advantage of this approach is that you can integrate it into your GitOps pipeline. However, we can argue whether it is a good idea to store the unseal keys and the root token as a Secret in the same cluster where OpenBao is running.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Example structure of &lt;code&gt;init.json&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;unseal_keys_b64&amp;#34;: [
&amp;#34;key1&amp;#34;,
&amp;#34;key2&amp;#34;,
&amp;#34;key3&amp;#34;,
&amp;#34;key4&amp;#34;,
&amp;#34;key5&amp;#34;
],
&amp;#34;unseal_keys_hex&amp;#34;: [
&amp;#34;hex1&amp;#34;,
&amp;#34;hex2&amp;#34;,
&amp;#34;hex3&amp;#34;,
&amp;#34;hex4&amp;#34;,
&amp;#34;hex5&amp;#34;
],
&amp;#34;unseal_shares&amp;#34;: 5,
&amp;#34;unseal_threshold&amp;#34;: 3,
&amp;#34;recovery_keys_b64&amp;#34;: null,
&amp;#34;recovery_keys_hex&amp;#34;: null,
&amp;#34;recovery_keys_shares&amp;#34;: 0,
&amp;#34;recovery_keys_threshold&amp;#34;: 0,
&amp;#34;root_token&amp;#34;: &amp;#34;root.token&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You need at least &lt;code&gt;unseal_threshold&lt;/code&gt; keys (e.g. 3) from &lt;code&gt;unseal_keys_b64&lt;/code&gt; to unseal each node. Store this file securely; anyone with access can unseal OpenBao and the &lt;code&gt;root_token&lt;/code&gt; has full admin access.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_rbac_configuration"&gt;RBAC Configuration&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Job’s service account (&lt;code&gt;openbao-init&lt;/code&gt;) must be allowed to create (and optionally get/update) Secrets in the &lt;code&gt;openbao&lt;/code&gt; namespace. Create a Role and RoleBinding:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: v1
kind: ServiceAccount
metadata:
name: openbao-init &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: openbao
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: openbao-init-secret-writer
namespace: openbao
rules: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- apiGroups: [&amp;#34;&amp;#34;]
resources: [&amp;#34;secrets&amp;#34;]
verbs: [&amp;#34;create&amp;#34;, &amp;#34;get&amp;#34;, &amp;#34;update&amp;#34;, &amp;#34;patch&amp;#34;]
- apiGroups: [&amp;#34;&amp;#34;] &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
resources: [&amp;#34;pods&amp;#34;]
verbs: [&amp;#34;get&amp;#34;, &amp;#34;list&amp;#34;]
- apiGroups: [&amp;#34;&amp;#34;] &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
resources: [&amp;#34;pods/exec&amp;#34;]
verbs: [&amp;#34;create&amp;#34;]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
metadata:
name: openbao-init-secret-writer
namespace: openbao
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: openbao-init-secret-writer
subjects:
- kind: ServiceAccount
name: openbao-init
namespace: openbao&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The ServiceAccount is used by the Job(s) to create the Secret and unseal the OpenBao pods.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Role is used by the Job to create the Secret. The rules might be extended to include the permission to execute into the pods to unseal the OpenBao service (see below).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Permissions to get and list the Pods, required for the unseal process later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Permissions to execute into the Pods, required for the unseal process later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The RoleBinding is used by the Job to create the Secret.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Job must also be able to reach the OpenBao API (e.g. &lt;code&gt;openbao.openbao.svc:8200&lt;/code&gt;). This is usually possible when running in the same namespace as the OpenBao service.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The example below shows a working Job manifest that will perform the initialisation and the creation of the Secret in the &lt;code&gt;openbao&lt;/code&gt; namespace. The manifest is already prepared for Argo CD with useful annotations.
It runs an init container to initialise the OpenBao service and a main container to create the Secret. The init container writes (using the OpenBao CLI) the init.json file to a shared volume and the main container (using the kubectl command) creates the Secret from that file.
In addition, the Secret containing the CA certificate is mounted as a volume and the BAO_CACERT environment variable is set to the path of the CA certificate.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_init_job_that_creates_the_secret"&gt;Init Job that creates the Secret&lt;/h4&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: batch/v1
kind: Job
metadata:
name: openbao-init
namespace: openbao
annotations: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
argocd.argoproj.io/sync-wave: &amp;#34;30&amp;#34;
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
serviceAccountName: openbao-init &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
initContainers: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
- name: openbao-init
image: ghcr.io/openbao/openbao:latest
command: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- /bin/sh
- -c
- |
if [ -f /etc/openbao-ca/ca.crt ]; then
export BAO_CACERT=/etc/openbao-ca/ca.crt
export BAO_ADDR=https://openbao:8200
echo &amp;#34;Using TLS CA from /etc/openbao-ca/ca.crt&amp;#34;
else
export BAO_ADDR=http://openbao:8200
echo &amp;#34;Using plain HTTP&amp;#34;
fi
until bao status 2&amp;gt;&amp;amp;1 | grep -q &amp;#34;Initialized&amp;#34;; do
echo &amp;#34;Waiting for OpenBao...&amp;#34;
sleep 5
done
if bao status | grep -q &amp;#34;Initialized.*false&amp;#34;; then
echo &amp;#34;Initialising OpenBao...&amp;#34;
bao operator init -key-shares=5 -key-threshold=3 \
-format=json &amp;gt; /shared/init.json
echo &amp;#34;Init complete; init.json written to shared volume.&amp;#34;
else
echo &amp;#34;OpenBao already initialised (skipping init).&amp;#34;
fi
volumeMounts:
- name: openbao-ca
mountPath: /etc/openbao-ca
readOnly: true
- name: shared
mountPath: /shared
containers: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- name: create-secret
image: bitnami/kubectl:latest
command:
- /bin/sh
- -c
- |
if [ -f /shared/init.json ]; then
echo &amp;#34;Creating Secret openbao-init-data from init.json...&amp;#34;
kubectl create secret generic openbao-init-data \
--from-file=init.json=/shared/init.json \
-n openbao --dry-run=client -o yaml | kubectl apply -f -
echo &amp;#34;Initialisation complete!&amp;#34;
else
echo &amp;#34;No init.json (OpenBao already initialised or init skipped).&amp;#34;
fi
volumeMounts:
- name: shared
mountPath: /shared
readOnly: true
volumes:
- name: openbao-ca
secret:
secretName: openbao-ca-secret
optional: true
- name: shared &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
emptyDir: {}
restartPolicy: Never
backoffLimit: 3&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The annotations are used by Argo CD to trigger the Job after the OpenBao deployment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The ServiceAccount is used by the Job to initialise the OpenBao service.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The init container runs the OpenBao CLI (wait for API, then operator init) and writes init.json to a shared volume.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The command is used to initialise the OpenBao service.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The main container runs kubectl to create the Secret from that file. This avoids needing a single image that bundles both &lt;code&gt;bao&lt;/code&gt; and &lt;code&gt;kubectl&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The shared volume is used to store the init.json file.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
As a result the secret &lt;code&gt;openbao-init-data&lt;/code&gt; is created in the &lt;code&gt;openbao&lt;/code&gt; namespace. It contains the unseal keys and the root token.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_using_the_secret_in_a_job_unseal"&gt;Using the Secret in a Job (unseal)&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A second Job can use the Secret &lt;code&gt;openbao-init-data&lt;/code&gt; that was created by the init Job to unseal each OpenBao pod. A lot is happening here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;The init container extracts the unseal keys from the Secret using &lt;code&gt;jq&lt;/code&gt; and writes them to a shared volume.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The main container is reading the unseal keys from the shared volume and unseals the first pod&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then the process is waiting for 30 seconds, to give the next pod time to pull the image and start&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The process is repeated for the next pod and so on until all pods are unsealed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;First of all, the unseal Job needs permission to &lt;code&gt;exec&lt;/code&gt; into the OpenBao pods. Add a Role that grants the Job’s service account &lt;code&gt;create&lt;/code&gt; on &lt;code&gt;pods/exec&lt;/code&gt; in the &lt;code&gt;openbao&lt;/code&gt; namespace.
To make it easier, we can extend the Role &lt;code&gt;openbao-init-secret-writer&lt;/code&gt; that was created in the init Job.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
These rules were already applied with the configuration for the init Job, but we will repeat them here for completeness.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;rules: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
- apiGroups: [&amp;#34;&amp;#34;]
resources: [&amp;#34;pods&amp;#34;]
verbs: [&amp;#34;get&amp;#34;, &amp;#34;list&amp;#34;]
- apiGroups: [&amp;#34;&amp;#34;]
resources: [&amp;#34;pods/exec&amp;#34;]
verbs: [&amp;#34;create&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The role &lt;code&gt;openbao-init-secret-writer&lt;/code&gt; must be extended to include the permission to execute into the pods to unseal the OpenBao service.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: batch/v1
kind: Job
metadata:
name: openbao-unseal
namespace: openbao
annotations:
argocd.argoproj.io/sync-wave: &amp;#34;35&amp;#34;
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
serviceAccountName: openbao-init &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
initContainers:
- name: extract-keys
image: quay.io/codefreshplugins/curl-jq
command:
- /bin/sh
- -c
- |
if [ ! -f /secrets/init.json ]; then
echo &amp;#34;Secret not found; run init Job first.&amp;#34;
exit 1
fi
for i in 0 1 2; do
jq -r &amp;#34;.unseal_keys_b64[$i]&amp;#34; /secrets/init.json | tr -d &amp;#39;\n&amp;#39; &amp;gt; &amp;#34;/shared/key$i&amp;#34; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
done
echo &amp;#34;Unseal keys extracted to shared volume.&amp;#34;
volumeMounts:
- name: init-secret
mountPath: /secrets
readOnly: true
- name: shared
mountPath: /shared
containers: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
- name: unseal
image: bitnami/kubectl:latest
command: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- /bin/sh
- -c
- |
if [ -f /etc/openbao-ca/ca.crt ]; then
export BAO_CACERT=/etc/openbao-ca/ca.crt
export BAO_ADDR=https://openbao:8200
echo &amp;#34;Using TLS CA from /etc/openbao-ca/ca.crt&amp;#34;
else
export BAO_ADDR=http://openbao:8200
echo &amp;#34;Using plain HTTP&amp;#34;
fi
sleep_timer=30
if [ ! -f /shared/key0 ]; then
echo &amp;#34;Keys not found; extract-keys init container may have failed.&amp;#34;
exit 1
fi
echo &amp;#34;Unsealing pods openbao-0, openbao-1, openbao-2 (3 key steps each), ${sleep_timer}s delay between pods...&amp;#34;
for pod in openbao-0 openbao-1 openbao-2; do
echo &amp;#34;Unsealing $pod...&amp;#34;
for i in 0 1 2; do
key=$(cat &amp;#34;/shared/key$i&amp;#34; | tr -d &amp;#39;\n&amp;#39;)
kubectl exec -n openbao &amp;#34;$pod&amp;#34; -- sh -c &amp;#39;bao operator unseal &amp;#34;$1&amp;#34;&amp;#39; _ &amp;#34;$key&amp;#34; || true
done
echo &amp;#34;$pod unsealed.&amp;#34;
if [ &amp;#34;$pod&amp;#34; != &amp;#34;openbao-2&amp;#34; ]; then
echo &amp;#34;Waiting ${sleep_timer}s for next pod to be ready...&amp;#34;
SLEEPER_TMP=1
while [ &amp;#34;$SLEEPER_TMP&amp;#34; -le &amp;#34;$sleep_timer&amp;#34; ]; do
if [ $(( SLEEPER_TMP % 10 )) -eq 0 ]; then
echo -n &amp;#34;$SLEEPER_TMP&amp;#34;
else
echo -n &amp;#34;.&amp;#34;
fi
sleep 1
SLEEPER_TMP=$(( SLEEPER_TMP + 1 ))
done
echo &amp;#34;&amp;#34;
fi
done
echo &amp;#34;Unseal complete.&amp;#34;
volumeMounts:
- name: shared
mountPath: /shared
readOnly: true
- name: openbao-ca
mountPath: /etc/openbao-ca
readOnly: true
volumes: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- name: init-secret
secret:
secretName: openbao-init-data
- name: shared
emptyDir: {}
- name: openbao-ca
secret:
secretName: openbao-ca-secret
optional: true
restartPolicy: Never
backoffLimit: 2&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The ServiceAccount is used by the Job to unseal the OpenBao pods. Here we are using the same ServiceAccount as the one used to initialise the OpenBao service.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The init container extracts unseal keys from init.json using jq and writes them to a shared volume.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The main container runs kubectl to exec into each OpenBao pod and run &lt;code&gt;bao operator unseal&lt;/code&gt; (the OpenBao image is used inside the target pods).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The command is used to unseal the OpenBao service.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The volumes that are mounted: init-secret (contains the unseal keys), shared (contains the extracted unseal keys, shared between containers), openbao-ca (contains the CA certificate).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Both Jobs are using the same ServiceAccount and therefore the same RBAC rules. It is also possible to use a different ServiceAccount for the unseal Job.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Store init data securely. Keeping unseal keys in a cluster Secret is convenient but less secure than auto-unseal or an external secrets manager. Restrict access to the &lt;code&gt;openbao-init-data&lt;/code&gt; Secret (e.g. RBAC and network policies) and consider rotating or moving keys to a more secure store after bootstrap.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_approach_3_auto_unseal_with_key_management_service_kms_recommended_for_production"&gt;Approach 3: Auto-unseal with Key Management Service KMS (recommended for production)&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Configure a KMS (e.g. AWS KMS, Azure Key Vault, GCP Cloud KMS, Transit etc.) so that OpenBao unseals itself on every restart. You initialise &lt;strong&gt;once&lt;/strong&gt; and thereafter no manual unseal step is needed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The rest of this article will mention the different options. However, I can only demonstrate a real example using the AWS KMS option.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_auto_unseal_options_overview"&gt;Auto-unseal options overview&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Without auto-unseal, you must run &lt;code&gt;bao operator unseal&lt;/code&gt; with a threshold of keys after each restart. With auto-unseal, OpenBao uses a seal backend to protect the root key and unseals itself on startup.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Supported options:&lt;/p&gt;
&lt;/div&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 25%;"/&gt;
&lt;col style="width: 25%;"/&gt;
&lt;col style="width: 50%;"/&gt;
&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Option&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Use case&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Notes&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;1. AWS KMS&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;AWS (EKS, OpenShift on AWS)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;IRSA or IAM user; no keys in cluster with IRSA.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;2. Azure Key Vault&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Azure (AKS, OpenShift on Azure)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Service principal or managed identity.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;3. Google Cloud KMS&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;GCP (GKE)&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Service account key or Workload Identity.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;5. Transit&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Existing Vault/OpenBao as root of trust; or a &lt;strong&gt;dedicated seal-only OpenBao&lt;/strong&gt; that exists only to unseal production&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;External transit engine; token in a Secret.&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_why_use_openbao_when_the_seal_is_in_kms_why_not_keep_all_secrets_in_kms_and_skip_openbao"&gt;Why use OpenBao when the seal is in KMS? Why not keep all secrets in KMS and skip OpenBao?&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When using a cloud KMS (or Key Vault) for auto-unseal, a natural question is: why not store and retrieve &lt;strong&gt;all&lt;/strong&gt; secrets from the KMS and run without OpenBao at all? OpenBao remains useful for operators and applications for several reasons:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;KMS is for keys, not a full secrets platform.&lt;/strong&gt; AWS KMS, Azure Key Vault keys, and GCP Cloud KMS are built to encrypt and decrypt small blobs (e.g. data encryption keys, or OpenBao’s seal blob). They are not a general-purpose secrets store with versioning, path-based access, dynamic credentials, and per-request audit. OpenBao provides that layer: applications request secrets by path, get short-lived tokens, and you get a single place to define who can read what.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic secrets.&lt;/strong&gt; OpenBao can generate short-lived credentials on demand (e.g. database users, cloud IAM roles, PKI certificates). The KMS does not create or rotate such credentials; it only holds keys. If you need “give this pod a DB password that expires in 1 hour,” that is OpenBao’s domain, not the KMS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fine-grained, identity-aware access.&lt;/strong&gt; OpenBao supports auth methods (Kubernetes, OIDC, AppRole, LDAP) and path-based policies. You can say “this service account may read only &lt;code&gt;secret/data/myapp/*&lt;/code&gt;.” The KMS has IAM or Key Vault RBAC, but not the same model of “one API, many identities, many paths” that applications and operators use day to day.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit and compliance.&lt;/strong&gt; OpenBao logs every secret read and write with identity and path. That gives a clear audit trail of who accessed which secret and when. KMS audit logs key usage, which is different from “which application read which secret at what time” in a unified way.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Abstraction and portability.&lt;/strong&gt; Applications talk to OpenBao’s API. You can move the seal or the storage backend (or even the cloud) without changing how applications request secrets. If you put everything directly in a cloud KMS, you tie every app to that vendor’s API and limits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encryption as a service (Transit).&lt;/strong&gt; OpenBao’s Transit secrets engine lets applications encrypt data with a key without ever seeing the key—useful for application-level encryption and key rotation. KMS can do something similar, but OpenBao integrates that with the same auth and audit as the rest of your secrets.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
In short: the KMS is the &lt;strong&gt;root of trust&lt;/strong&gt; for the seal (so OpenBao can unseal itself). OpenBao is the &lt;strong&gt;secrets management platform&lt;/strong&gt; that applications and operators use for storing, retrieving, generating, and auditing secrets. Both layers complement each other.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;General flow (same for all options):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Set up the seal backend (KMS key, Key Vault, static key, or transit server).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the appropriate &lt;code&gt;seal &amp;#34;…​&amp;#34;&lt;/code&gt; stanza to OpenBao configuration (e.g. in Helm &lt;code&gt;server.ha.raft.config&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy OpenBao; pods start and remain sealed until initialised.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run one-time initialisation: &lt;code&gt;bao operator init -recovery-shares=5 -recovery-threshold=3&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From then on, restarts are automatically unsealed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_option_aws_kms"&gt;Option: AWS KMS&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Best for: EKS, OpenShift on AWS (ROSA), or any Kubernetes on AWS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_create_a_kms_key"&gt;Create a KMS key&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a &lt;strong&gt;symmetric&lt;/strong&gt; KMS key used only for the OpenBao seal (do not use for application data).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS WebUI:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;search for: KMS → Create key → Symmetric, Encrypt/Decrypt&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optionally set an alias (e.g. &lt;code&gt;openbao-unseal&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OPTIONAL: Define key administrative permissions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OPTIONAL: Define key usage permissions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit key policy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS CLI:&lt;/strong&gt; (requires the AWS CLI to be installed and configured)&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;aws kms create-key \
--description &amp;#34;OpenBao auto-unseal key&amp;#34; \
--key-usage ENCRYPT_DECRYPT
aws kms create-alias \
--alias-name openbao-unseal \
--target-key-id &amp;lt;key-id-from-above&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Note the &lt;strong&gt;Key ID&lt;/strong&gt; or &lt;strong&gt;Alias&lt;/strong&gt; and the &lt;strong&gt;region&lt;/strong&gt; (e.g. &lt;code&gt;us-west-1&lt;/code&gt;) from the output of the command.&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_key_policy_for_openbao"&gt;Key policy for OpenBao&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The OpenBao process needs permission to use the key. Create an IAM policy. In the UI you can configure this directly when creating the key. When using the CLI, create a &lt;code&gt;policy.json&lt;/code&gt; file with the following content:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;Version&amp;#34;: &amp;#34;2012-10-17&amp;#34;,
&amp;#34;Statement&amp;#34;: [
{
&amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;,
&amp;#34;Action&amp;#34;: [
&amp;#34;kms:Encrypt&amp;#34;,
&amp;#34;kms:Decrypt&amp;#34;,
&amp;#34;kms:DescribeKey&amp;#34;
],
&amp;#34;Resource&amp;#34;: &amp;#34;arn:aws:kms:&amp;lt;REGION&amp;gt;:&amp;lt;ACCOUNT_ID&amp;gt;:key/&amp;lt;KEY_ID&amp;gt;&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
}
]
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Replace &lt;code&gt;&amp;lt;REGION&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ACCOUNT_ID&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;KEY_ID&amp;gt;&lt;/code&gt; with your values (or use the key ARN).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_create_an_iam_user_for_the_seal"&gt;Create an IAM user for the seal&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When using static credentials (e.g. on OpenShift or when IRSA is not available), create a dedicated IAM user that has permission only to use the KMS key above. Use that user’s access keys in the Kubernetes Secret.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS WebUI:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;search for: IAM → Users → Create user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;User name: e.g. &lt;code&gt;openbao-seal&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DO NOT select &amp;#34;Provide user access to the AWS Management Console&amp;#34; (programmatic access only)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the created user → Permissions → Add permissions → Create inline policy (or Attach policies → Create policy)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the policy editor, choose JSON and paste the policy from &lt;a href="#_key_policy_for_openbao"&gt;Key policy for OpenBao&lt;/a&gt;, replacing &lt;code&gt;&amp;lt;REGION&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ACCOUNT_ID&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;KEY_ID&amp;gt;&lt;/code&gt; with your KMS key ARN or alias&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Name the policy (e.g. &lt;code&gt;OpenBaoSealKMS&lt;/code&gt;) and attach it to the user&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Security credentials → Create access key → Application running outside AWS (or &amp;#34;Third-party service&amp;#34;) → Create access key&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Save the Access key ID and Secret access key; you will put them in the Kubernetes Secret &lt;code&gt;openbao-aws-credentials&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS CLI:&lt;/strong&gt; (requires the AWS CLI to be installed and configured)&lt;/p&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a policy file (e.g. &lt;code&gt;openbao-seal-policy.json&lt;/code&gt;) with the same JSON as in &lt;a href="#_key_policy_for_openbao"&gt;Key policy for OpenBao&lt;/a&gt;, then create the user, attach the policy, and create access keys:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;# Replace REGION, ACCOUNT_ID, and KEY_ID in the policy (or use alias: arn:aws:kms:REGION:ACCOUNT_ID:alias/openbao-unseal)
aws iam create-policy \
--policy-name OpenBaoSealKMS \
--policy-document file://openbao-seal-policy.json
aws iam create-user --user-name openbao-seal
aws iam attach-user-policy \
--user-name openbao-seal \
--policy-arn arn:aws:iam::ACCOUNT_ID:policy/OpenBaoSealKMS
aws iam create-access-key --user-name openbao-seal&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The last command returns &lt;code&gt;AccessKeyId&lt;/code&gt; and &lt;code&gt;SecretAccessKey&lt;/code&gt;; store them in the Secret &lt;code&gt;openbao-aws-credentials&lt;/code&gt; with key &lt;code&gt;AWS_REGION&lt;/code&gt; set to your region (e.g. &lt;code&gt;us-west-1&lt;/code&gt;).
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_create_the_secret"&gt;Create the Secret&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create the Secret &lt;code&gt;openbao-aws-credentials&lt;/code&gt; in the &lt;code&gt;openbao&lt;/code&gt; namespace. It contains the access key, secret access key, and region.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: v1
kind: Secret
metadata:
name: openbao-aws-credentials
namespace: openbao
type: Opaque
stringData: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
AWS_ACCESS_KEY_ID: &amp;lt;your-access-key&amp;gt;
AWS_SECRET_ACCESS_KEY: &amp;lt;your-secret-key&amp;gt;
AWS_REGION: &amp;lt;your-region&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Replace &lt;code&gt;&amp;lt;your-access-key&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;your-secret-key&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;your-region&amp;gt;&lt;/code&gt; with the values from the previous steps.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_helm_argo_cd_configuration_aws"&gt;Helm / Argo CD configuration (AWS)&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Add the &lt;code&gt;seal &amp;#34;awskms&amp;#34;&lt;/code&gt; block inside the same HCL config that contains &lt;code&gt;listener&lt;/code&gt;, &lt;code&gt;storage &amp;#34;raft&amp;#34;&lt;/code&gt;. In Helm values this is typically under &lt;code&gt;openbao.server.ha.raft.config&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Be sure to update the region and key ID.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The values file we used for Argo CD can be found at: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/openbao/values.yaml" target="_blank" rel="noopener"&gt;OpenBao Argo CD values file&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;openbao:
server:
extraSecretEnvironmentVars: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
- envName: AWS_ACCESS_KEY_ID
secretName: openbao-aws-credentials
secretKey: AWS_ACCESS_KEY_ID
- envName: AWS_SECRET_ACCESS_KEY
secretName: openbao-aws-credentials
secretKey: AWS_SECRET_ACCESS_KEY
- envName: AWS_REGION
secretName: openbao-aws-credentials
secretKey: AWS_REGION
ha:
raft:
config: |
ui = true
seal &amp;#34;awskms&amp;#34; { &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
region = &amp;#34;us-east-1&amp;#34;
kms_key_id = &amp;#34;alias/openbao-unseal&amp;#34;
}
listener &amp;#34;tcp&amp;#34; {
tls_disable = 0
address = &amp;#34;[::]:8200&amp;#34;
cluster_address = &amp;#34;[::]:8201&amp;#34;
tls_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.crt&amp;#34;
tls_key_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.key&amp;#34;
tls_min_version = &amp;#34;tls12&amp;#34;
}
storage &amp;#34;raft&amp;#34; {
path = &amp;#34;/openbao/data&amp;#34;
}
service_registration &amp;#34;kubernetes&amp;#34; {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;If you use static credentials (e.g. for OpenShift), inject them via a Secret.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;This must be added. Be sure to update your region and key ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock important"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-important" title="Important"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The &lt;code&gt;seal &amp;#34;awskms&amp;#34;&lt;/code&gt; block alone is not enough. OpenBao must have AWS credentials available at runtime. If you see an error such as &lt;code&gt;NoCredentialProviders: no valid providers in chain&lt;/code&gt; or &lt;code&gt;error fetching AWS KMS wrapping key information&lt;/code&gt;, the pod has no credentials. You must either inject static credentials from a Secret (see below) or use IRSA so the pod can assume an IAM role.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_approach_4_init_container_with_external_secret_store"&gt;Approach 4: Init container with external secret store&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Another option is to use an init container that retrieves secrets from an external source. Two use cases can be considered here:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shamir unseal:&lt;/strong&gt; The init container fetches the unseal keys from an external secret manager and then runs &lt;code&gt;bao operator unseal&lt;/code&gt; (or writes keys to a file used by a sidecar/unseal script). This is the classic “external secret store” idea (e.g. using AWS Secrets Manager, HashiCorp Vault, another Kubernetes cluster).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Transit seal token:&lt;/strong&gt; The init container fetches the &lt;strong&gt;token&lt;/strong&gt; that production OpenBao uses to call the dedicated unseal service (see &lt;a href="#_dedicated_unseal_service_seal_only_openbao"&gt;Dedicated unseal service (seal-only OpenBao)&lt;/a&gt;). That token is written to a file or shared volume so the main OpenBao process can use it in &lt;code&gt;seal &amp;#34;transit&amp;#34;&lt;/code&gt;. The token never lives in a Kubernetes Secret in Git or in plain YAML; it is fetched at pod start from the external store.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Below is a generic placeholder for the init container.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_option_init_container_example"&gt;Option: init Container example&lt;/h4&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The following options are generic and more theoretical. Unlike the other options, I could not test them in a real environment yet. This may be covered in a future article.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Generic placeholder (implement according to your secret store):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# Additional values for the Helm chart
server:
extraInitContainers:
- name: fetch-secrets
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
command:
- /bin/sh
- -c
- |
# Fetch unseal keys OR transit token from external secret manager
echo &amp;#34;Waiting for OpenBao to be ready...&amp;#34;
sleep 30
# Your fetch logic here (e.g. aws secretsmanager get-secret-value, vault kv get, curl to API) &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
env:
- name: EXTERNAL_SECRET_ENDPOINT &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
value: &amp;#34;https://your-external-secret-store.example.com&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Replace with your fetch logic.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Replace with your external secret store endpoint.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_option_transit_seal"&gt;Option: Transit seal&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When you already have a Vault or OpenBao cluster and want it to act as the root of trust (e.g. central Vault encrypts the seal key), you can use this option.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To prepare everything, you need to do the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Enable the &lt;strong&gt;Transit&lt;/strong&gt; secrets engine on the external Vault/OpenBao and create a key (e.g. &lt;code&gt;openbao-unseal&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Grant the token used by this OpenBao permission to encrypt/decrypt with that key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Store the token in a Kubernetes Secret and inject via &lt;code&gt;extraSecretEnvironmentVars&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Do not put the token in the config file.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following HCL configuration is used to configure the OpenBao cluster to use the transit seal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-hcl hljs" data-lang="hcl"&gt;seal &amp;#34;transit&amp;#34; {
address = &amp;#34;https://external-vault.example.com:8200&amp;#34;
token = &amp;#34;s.xxx&amp;#34;
disable_renewal = &amp;#34;false&amp;#34;
key_name = &amp;#34;openbao-unseal&amp;#34;
mount_path = &amp;#34;transit/&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;See the &lt;a href="https://openbao.org/docs/configuration/seal/transit/" target="_blank" rel="noopener"&gt;OpenBao transit seal&lt;/a&gt; documentation for further details.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect4"&gt;
&lt;h5 id="_dedicated_unseal_service_seal_only_openbao"&gt;Dedicated unseal service (seal-only OpenBao)&lt;/h5&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A common and recommended pattern is to run a &lt;strong&gt;separate, standalone OpenBao instance&lt;/strong&gt; whose &lt;strong&gt;only&lt;/strong&gt; role is to provide the seal for your production OpenBao. In other words: the external OpenBao holds the unseal keys—via its Transit secrets engine—and the production OpenBao cluster uses the transit seal to auto-unseal by calling that external instance. No application secrets or other workloads run on the dedicated instance; it exists solely to unseal the production OpenBao.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Why use a dedicated unseal service?&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separation of concerns:&lt;/strong&gt; The root of trust for unsealing lives outside the production cluster. If the production OpenBao is compromised or rebuilt, the seal keys remain in the dedicated instance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simpler operations:&lt;/strong&gt; You unseal and manage only one small, locked-down OpenBao (the seal service). The production OpenBao unseals itself automatically via transit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No cloud KMS required:&lt;/strong&gt; On-premise or air-gapped environments can use this pattern instead of AWS KMS, Azure Key Vault, or GCP Cloud KMS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clear trust boundary:&lt;/strong&gt; The dedicated instance can be hardened, network-isolated, and backed up independently.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Architecture (high level):&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dedicated unseal service:&lt;/strong&gt; A standalone OpenBao (single node is often enough) with the Transit secrets engine enabled and a dedicated transit key (e.g. &lt;code&gt;production-openbao-unseal&lt;/code&gt;). This instance is initialised and unsealed once; you keep its unseal keys and root token very secure. It does not store application secrets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Production OpenBao:&lt;/strong&gt; Your HA OpenBao cluster (e.g. in Kubernetes) is configured with &lt;code&gt;seal &amp;#34;transit&amp;#34;&lt;/code&gt; pointing at the dedicated service URL and a token that has permission only to use that transit key. On startup, each production node asks the dedicated OpenBao to decrypt its seal blob and thus unseals automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_migration_from_shamir_to_auto_unseal"&gt;Migration from Shamir to auto-unseal&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You might wonder if you can migrate from Shamir to auto-unseal. The answer is yes, you can.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If OpenBao was previously initialised with &lt;strong&gt;Shamir&lt;/strong&gt; unseal keys and you want to switch to any auto-unseal backend, you can do the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Plan a short maintenance window. Raft HA will tolerate one node at a time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the appropriate &lt;code&gt;seal &amp;#34;…​&amp;#34;&lt;/code&gt; block to the config and deploy (e.g. via Argo CD).&lt;/p&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Do NOT re-run init.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unseal the &lt;strong&gt;leader&lt;/strong&gt; with the existing Shamir unseal keys.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;strong&gt;seal migration&lt;/strong&gt; on the leader:&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;bao operator unseal -migrate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart the leader. It should auto-unseal via the new seal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update and restart standby nodes. They should auto-unseal as well.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_resources"&gt;Resources&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/concepts/seal/" target="_blank" rel="noopener"&gt;OpenBao seal concepts&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/platform/k8s/" target="_blank" rel="noopener"&gt;OpenBao on Kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/awskms/" target="_blank" rel="noopener"&gt;AWS KMS seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/azurekeyvault/" target="_blank" rel="noopener"&gt;Azure Key Vault seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/gcpckms/" target="_blank" rel="noopener"&gt;Google Cloud KMS seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/static/" target="_blank" rel="noopener"&gt;Static seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://openbao.org/docs/configuration/seal/transit/" target="_blank" rel="noopener"&gt;Transit seal&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>The Guide to OpenBao - GitOps Deployment with Argo CD - Part 5</title><link>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-20-openbao-part-5-gitops-deployment/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-20-openbao-part-5-gitops-deployment/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Following the GitOps mantra &amp;#34;If it is not in Git, it does not exist&amp;#34;, this article demonstrates how to deploy and manage OpenBao using Argo CD. This approach provides version control, audit trails, and declarative management for your secret management infrastructure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_introduction"&gt;Introduction&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Deploying OpenBao via GitOps offers significant advantages:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Version Control&lt;/strong&gt;: All configuration changes are tracked in Git&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit Trail&lt;/strong&gt;: Who changed what, when, and why&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Declarative&lt;/strong&gt;: Desired state is defined, not imperative commands&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reproducible&lt;/strong&gt;: Same deployment process across environments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Self-healing&lt;/strong&gt;: Argo CD ensures actual state matches desired state&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, there are challenges specific to secret management:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Initial unsealing requires manual intervention or automationxw&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Root tokens and unseal keys must be handled carefully&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Chicken-and-egg problem: How to store OpenBao secrets before OpenBao exists?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This article will focus on the deployment of the applicaiotns for the OpenBao deployment. The problem with the automatic unsealing will be addressed in the next article.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before you begin, ensure you have:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenShift GitOps (Argo CD) installed and configured&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A Git repository for your cluster configuration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understanding of the App-of-Apps pattern (see &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/"&gt;Configure App-of-Apps&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The official OpenBao Helm chart from &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/"&gt;Part 3&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_wrapper_helm_chart"&gt;The (Wrapper) Helm Chart&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The official OpenBao Helm Chart works well for deploying OpenBao itself. However, additional settings such as certificates are not covered there. Therefore, I created a (wrapper) Helm Chart that includes the official OpenBao Helm Chart, a &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;Cert-Manager Helm Chart&lt;/a&gt; that I created a while ago, and allows you to add additional objects in the templates folder.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I will follow the same setup as discussed in &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls"&gt;Part 4&lt;/a&gt;. This means:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create Issuer for Cert-Manager&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create CA Certificate for OpenBao&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create Certificate for OpenBao Server&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create Certificate for OpenBao Agent Injector&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install OpenBao Helm Chart&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, there is &lt;strong&gt;one significant issue&lt;/strong&gt; with this approach: we need the certificate details (especially the CA certificate) before we can install the OpenBao Helm Chart, since the certificate must be referenced in the values file. This is a chicken-and-egg problem—particularly if you use self-signed CA certificates.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since you typically create the CA certificate first and only once (or have it already), a separate Application for the CA certificate is a good approach. Once this is synchronised, the OpenBao Application can be updated and deployed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This means we will need two Helm Charts: one for the CA certificate and one for OpenBao itself.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_helm_chart_for_the_ca_certificate"&gt;Helm Chart for the CA Certificate&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a new Helm Chart for the CA certificate. This will use the &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;Cert-Manager Helm Chart&lt;/a&gt; as a dependency and will create the:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Issuer for Cert-Manager&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Request the CA certificate from the Issuer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the &lt;strong&gt;openbao&lt;/strong&gt; namespace&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The example I am using can be found at &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/openbao-ca-certificate" target="_blank" rel="noopener"&gt;GitOps openbao-ca-certificate&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Please check the &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/" target="_blank" rel="noopener"&gt;GitOps Repository Structure&lt;/a&gt; and subsequent articles for more information on how to structure your Git repository.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the &lt;strong&gt;Chart.yaml&lt;/strong&gt; file you can see two dependencies:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;dependencies:
- name: tpl
version: ~1.0.0
repository: https://charts.stderr.at/
- name: cert-manager
version: ~2.0.3 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
repository: https://charts.stderr.at/
condition: cert-manager.enabled&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Cert-Manager Helm Chart version must be at least 2.0.3 to support the namespace parameter for the Issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;tpl&lt;/strong&gt; dependency is a library that contains templates for the namespace and other shared components (I keep this library in my &lt;a href="https://blog.stderr.at/helm-charts/" target="_blank" rel="noopener"&gt;Helm Charts&lt;/a&gt; repository). The &lt;strong&gt;cert-manager&lt;/strong&gt; dependency is the Cert-Manager Helm Chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;values.yaml&lt;/strong&gt; file below contains the configuration for the Cert-Manager:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
All settings that are passed to a subchart (cert-manager) must be prefixed with the name of the subchart. In this case, &lt;code&gt;cert-manager.&lt;/code&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;namespace:
create: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
name: &amp;#34;openbao&amp;#34;
description: &amp;#34;OpenBao Namespace&amp;#34;
displayName: &amp;#34;OpenBao Namespace&amp;#34;
additionalLabels:
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
cert-manager: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
issuer:
# Name of issuer
- name: openbao-selfsigned &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
# -- Syncwave to create this issuer
syncwave: 5 &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
# -- Type can be either ClusterIssuer or Issuer
type: Issuer
# -- Enable this issuer.
# @default -- false
enabled: true
# -- Namespace for Issuer (ignored for ClusterIssuer). Defaults to the default namespace when not set.
namespace: openbao &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
# -- Create a selfSigned issuer. The SelfSigned issuer doesn&amp;#39;t represent a certificate authority as such, but instead denotes that certificates will &amp;#34;sign themselves&amp;#34; using a given private key.
selfSigned: true &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
certificates: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
enabled: true
# List of certificates
certificate:
- name: openbao-ca
enabled: true
namespace: openbao
syncwave: &amp;#34;10&amp;#34;
secretName: openbao-ca-secret
duration: 87660h
dnsNames:
- openbao-ca
privateKey:
algorithm: ECDSA
size: 256
rotationPolicy: Always
isCA: true
# Reference to the issuer that shall be used.
issuerRef:
name: openbao-selfsigned
kind: Issuer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Create the &lt;strong&gt;openbao&lt;/strong&gt; namespace with various settings.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable the Cert-Manager subchart. All settings below will be passed to the Cert-Manager subchart.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enables Cert-Manager.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Syncwave for this issuer; it must be lower than the syncwave of the certificate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace for the issuer; supported since version 2.0.3 of the Cert-Manager Helm Chart.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Creates a self-signed issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The certificate section and all the settings. Verify the &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls/#_step_1_certificate_authority_ca_for_openbao"&gt;Part 4&lt;/a&gt; for more information.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This chart creates the &lt;strong&gt;openbao&lt;/strong&gt; namespace, which is required for the OpenBao deployment.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_helm_chart_for_the_openbao_deployment"&gt;Helm Chart for the OpenBao Deployment&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Create a new Helm Chart for the OpenBao deployment. This will use the official &lt;a href="https://github.com/openbao/openbao-helm" target="_blank" rel="noopener"&gt;OpenBao Helm chart&lt;/a&gt; and the &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;Cert-Manager Helm Chart&lt;/a&gt; as dependencies and will create the:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao deployment (including Route)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Issuer for the OpenBao server using the CA certificate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Required OpenBao certificates based on the CA certificate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I have added the full values.yaml file below. Please check the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/openbao/values.yaml" target="_blank" rel="noopener"&gt;GitOps openbao/values.yaml&lt;/a&gt; to fetch the full chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Besides the two certificates added here in the values.yaml file, I made two further changes: fullnameOverride and nameOverride are both set to &lt;strong&gt;openbao&lt;/strong&gt;. This is good practice for setting the name of the deployment when using Argo CD.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This values file contains the CA certificate in plain text. Whether that is acceptable is debatable—it is a public certificate, and here it is self-signed and used only in my demo environment.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# Full values.yaml file for the OpenBao deployment
########################################################
# Cert-Manager
########################################################
cert-manager:
enabled: true
issuer:
# Name of issuer
- name: openbao-ca-issuer &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# -- Syncwave to create this issuer
syncwave: &amp;#39;-1&amp;#39;
# -- Type can be either ClusterIssuer or Issuer
type: Issuer
# -- Enable this issuer.
# @default -- false
enabled: true
# -- Namespace for Issuer (ignored for ClusterIssuer). Defaults to the default namespace when not set.
namespace: openbao
ca:
secretName: openbao-ca-secret
certificates:
enabled: true
# List of certificates
certificate:
########################################################
# OpenBao Server TLS Certificate
########################################################
- name: openbao-server-tls &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
enabled: true
namespace: openbao
syncwave: &amp;#34;0&amp;#34;
secretName: openbao-server-tls
duration: 8760h
renewBefore: 720h
dnsNames:
- openbao.openbao.svc
- openbao.apps.ocp.aws.ispworld.at # Route host (adjust to your domain)
- openbao
- openbao.openbao
- openbao.openbao.svc
- openbao.openbao.svc.cluster.local
- openbao-internal
- openbao-internal.openbao
- openbao-internal.openbao.svc
- openbao-internal.openbao.svc.cluster.local
- openbao-0.openbao-internal
- openbao-0.openbao-internal.openbao
- openbao-0.openbao-internal.openbao.svc
- openbao-0.openbao-internal.openbao.svc.cluster.local
- openbao-1.openbao-internal
- openbao-1.openbao-internal.openbao
- openbao-1.openbao-internal.openbao.svc
- openbao-2.openbao-internal
- openbao-2.openbao-internal.openbao
- openbao-2.openbao-internal.openbao.svc
ipAddresses:
- 127.0.0.1
- &amp;#34;::1&amp;#34;
# Reference to the issuer that shall be used.
issuerRef:
name: openbao-ca-issuer
kind: Issuer
########################################################
# Injector TLS Certificate
########################################################
- name: injector-certificate &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
enabled: true
namespace: openbao
syncwave: &amp;#34;0&amp;#34;
secretName: injector-tls
duration: 24h
renewBefore: 144m
dnsNames:
- openbao-agent-injector-svc
- openbao-agent-injector-svc.openbao
- openbao-agent-injector-svc.openbao.svc
- openbao-agent-injector-svc.openbao.svc.cluster.local
# Reference to the issuer that shall be used.
issuerRef:
name: openbao-ca-issuer
kind: Issuer
########################################################
# OpenBao Deployment
########################################################
openbao: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
# Override the full name of the deployment via Argo CD
fullnameOverride: openbao
nameOverride: openbao
global:
# Enable OpenShift-specific settings
openshift: true
# -- The namespace to deploy to. Defaults to the `helm` installation namespace.
namespace: openbao
# Required when TLS is enabled: tells the chart to use HTTPS for readiness/liveness
# probes and for in-pod API_ADDR (127.0.0.1:8200). Otherwise you get &amp;#34;client sent
# an HTTP request to an HTTPS server&amp;#34; from the probes.
tlsDisable: false
server:
extraEnvironmentVars:
BAO_CACERT: /openbao/tls/openbao-server-tls/ca.crt
# High Availability configuration
ha:
enabled: true
replicas: 3
# Raft storage configuration
raft:
enabled: true
setNodeId: true
config: |
ui = true
listener &amp;#34;tcp&amp;#34; {
tls_disable = 0
address = &amp;#34;[::]:8200&amp;#34;
cluster_address = &amp;#34;[::]:8201&amp;#34;
tls_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.crt&amp;#34;
tls_key_file = &amp;#34;/openbao/tls/openbao-server-tls/tls.key&amp;#34;
tls_min_version = &amp;#34;tls12&amp;#34;
telemetry {
unauthenticated_metrics_access = &amp;#34;true&amp;#34;
}
}
storage &amp;#34;raft&amp;#34; {
path = &amp;#34;/openbao/data&amp;#34;
retry_join {
leader_api_addr = &amp;#34;https://openbao-0.openbao-internal:8200&amp;#34;
leader_tls_servername = &amp;#34;openbao-0.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34;
}
retry_join {
leader_api_addr = &amp;#34;https://openbao-1.openbao-internal:8200&amp;#34;
leader_tls_servername = &amp;#34;openbao-1.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34;
}
retry_join {
leader_api_addr = &amp;#34;https://openbao-2.openbao-internal:8200&amp;#34;
leader_tls_servername = &amp;#34;openbao-2.openbao-internal&amp;#34;
leader_ca_cert_file = &amp;#34;/openbao/tls/openbao-server-tls/ca.crt&amp;#34;
}
}
service_registration &amp;#34;kubernetes&amp;#34; {}
telemetry {
prometheus_retention_time = &amp;#34;30s&amp;#34;
disable_hostname = true
}
route:
enabled: true
host: openbao.apps.ocp.aws.ispworld.at
tls:
# Route terminates client TLS; backend can use reencrypt or passthrough
termination: reencrypt
insecureEdgeTerminationPolicy: Redirect
destinationCACertificate: |
-----BEGIN CERTIFICATE-----
MIIBWzCCAQCgAwIBAgIQNdbg4KIu9oi6dDClE8drmjAKBggqhkjOPQQDAjAAMB4X
DTI2MDIxODA5NDIxNFoXDTM2MDIxODIxNDIxNFowADBZMBMGByqGSM49AgEGCCqG
SM49AwEHA0IABIeYw35/kEHvyctLtOA5xMlyQNxUtXtfBbZMUfPh6AN5MFjIGuNS
cn07a3EpSpfY6/3DaPpu+4wYNFlc+/qDNYajXDBaMA4GA1UdDwEB/wQEAwICpDAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQgyRk5Vv9K0VULcCgga5mRg4O9kTAY
BgNVHREBAf8EDjAMggpvcGVuYmFvLWNhMAoGCCqGSM49BAMCA0kAMEYCIQCwQ7lZ
Q0jzUjJFzpTGkQjU2+OB159LIQMSbSQ7dz8nVQIhAIDa7f87tjQxDxbJio+/vJx2
awFaWnueGOOQpvwCcV/+
-----END CERTIFICATE-----
extraVolumes:
- type: secret
name: openbao-server-tls
path: /openbao/tls
readOnly: true
extraVolumeMounts:
- name: openbao-server-tls
mountPath: /openbao/tls
readOnly: true
# Resource requests and limits
resources:
requests:
memory: 256Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 1000m
# Persistent volume for data
dataStorage:
enabled: true
size: 10Gi
# storageClass: &amp;#34;gp3-csi&amp;#34;
# Injector configuration
injector:
enabled: true
replicas: 2 # HA for the injector too
certs:
secretName: injector-tls
# For a private CA: set caBundle to the CA cert (PEM) so the Kubernetes API server trusts the injector webhook. E.g. oc get secret openbao-ca-secret -n openbao -o jsonpath=&amp;#39;{.data.ca\.crt}&amp;#39; | base64 -d
caBundle: &amp;#34;LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJXekNDQVFDZ0F3SUJBZ0lRTmRiZzRLSXU5b2k2ZERDbEU4ZHJtakFLQmdncWhrak9QUVFEQWpBQU1CNFgKRFRJMk1ESXhPREE1TkRJeE5Gb1hEVE0yTURJeE9ESXhOREl4TkZvd0FEQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxRwpTTTQ5QXdFSEEwSUFCSWVZdzM1L2tFSHZ5Y3RMdE9BNXhNbHlRTnhVdFh0ZkJiWk1VZlBoNkFONU1GaklHdU5TCmNuMDdhM0VwU3BmWTYvM0RhUHB1KzR3WU5GbGMrL3FETllhalhEQmFNQTRHQTFVZER3RUIvd1FFQXdJQ3BEQVAKQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlFneVJrNVZ2OUswVlVMY0NnZ2E1bVJnNE85a1RBWQpCZ05WSFJFQkFmOEVEakFNZ2dwdmNHVnVZbUZ2TFdOaE1Bb0dDQ3FHU000OUJBTUNBMGtBTUVZQ0lRQ3dRN2xaClEwanpVakpGenBUR2tRalUyK09CMTU5TElRTVNiU1E3ZHo4blZRSWhBSURhN2Y4N3RqUXhEeGJKaW8rL3ZKeDIKYXdGYVdudWVHT09RcHZ3Q2NWLysKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=&amp;#34;
certName: tls.crt
keyName: tls.key
# UI configuration
ui:
enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Issuer for OpenBao. The syncwave is set to &amp;#39;-1&amp;#39; to create the issuer before the certificate request and the OpenBao deployment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Certificate for the OpenBao server, with all DNS names and IP addresses used to reach it. The syncwave is set to &amp;#39;0&amp;#39; so the certificate is created after the issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Certificate for the OpenBao Agent Injector, with all DNS names used to reach it. The syncwave is set to &amp;#39;0&amp;#39; so the certificate is created after the issuer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;OpenBao deployment configuration; the two overrides fullnameOverride and nameOverride are both set to &lt;strong&gt;openbao&lt;/strong&gt;. See &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls/#_step_5_helm_values_for_server_tls"&gt;Part 4&lt;/a&gt; for more information.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_creating_argo_cd_applications"&gt;Creating Argo CD Applications&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Whilst the Helm Charts are ready, we need to create the Argo CD Applications for the CA certificate and the OpenBao deployment.
If you read our blog carefully, you will notice that these Application resources are created automatically when I add something to the folder clusters/management-cluster :)
This is done by leveraging ApplicationSet resources and is fully described in the &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/" target="_blank" rel="noopener"&gt;GitOps Repository Structure&lt;/a&gt; and subsequent articles.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For the sake of simplicity, I will show the created Application resources below. The setup is the same for both; they simply target a different path in the Git repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# Full Application resource for the OpenBao CA Certificate
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: in-cluster-openbao-ca-certificate
namespace: openshift-gitops
spec:
destination:
name: in-cluster &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: default &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
info:
- name: Description
value: ApplicationSet that Deploys on Management Cluster Configuration (using Git Generator)
project: in-cluster &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
source:
path: clusters/management-cluster/openbao-ca-certificate &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
targetRevision: main
syncPolicy:
retry:
backoff:
duration: 5s
factor: 2
maxDuration: 3m
limit: 5
---
# Full Application resource for the OpenBao deployment
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: in-cluster-openbao
namespace: openshift-gitops
spec:
destination:
name: in-cluster &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: openbao &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
info:
- name: Description
value: ApplicationSet that Deploys on Management Cluster Configuration (using Git Generator)
project: in-cluster &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
source:
path: clusters/management-cluster/openbao &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
targetRevision: main
syncPolicy:
retry:
backoff:
duration: 5s
factor: 2
maxDuration: 3m
limit: 5&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Target cluster; here, the local cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace of the target cluster; here, the OpenBao deployment will be installed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Argo CD Project (must exist)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Path to the Git repository for the CA certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;URL of the Git repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Path to the OpenBao deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create the following Applications:
- in-cluster-openbao-ca-certificate
- in-cluster-openbao&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The ApplicationSet adds the prefix &lt;strong&gt;in-cluster-&lt;/strong&gt; to each Application name so that they remain unique in Argo CD.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/images/part5_openbao_argocd.png" alt="OpenBao Argo CD Applications"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Argo CD: OpenBao Argo CD Applications&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first Application to synchronise is &lt;strong&gt;in-cluster-openbao-ca-certificate&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It creates the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Namespace&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CA Issuer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CA Certificate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Then synchronise &lt;strong&gt;in-cluster-openbao&lt;/strong&gt;. It creates:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;OpenBao deployment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao Agent Injector&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao Server TLS Certificate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenBao Agent Injector TLS Certificate&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_openbao_is_runningwhat_next"&gt;OpenBao is running—what next?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Deploying OpenBao via GitOps gives you version control and declarative management for your secret management infrastructure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Whilst OpenBao is running and managed by Argo CD, the next step is to configure it: you will need to handle initialisation (for a new cluster) and unsealing (see &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-13-openbao-part-3-openshift-deployment/"&gt;Part 3&lt;/a&gt; and &lt;a href="https://blog.stderr.at/openshift-platform/security/secrets-management/openbao/2026-02-17-openbao-part-4-enabling-tls/"&gt;Part 4&lt;/a&gt;). That manual approach does not scale. In the next article, I will discuss ways to automate initialisation and unsealing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Key takeaways:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use sync waves to control deployment order&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consider auto-unseal for production (Part 6)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Store initialisation data securely outside Git&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_resources"&gt;Resources&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/" target="_blank" rel="noopener"&gt;Configure App-of-Apps&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/helm/" target="_blank" rel="noopener"&gt;Argo CD Helm Support&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/" target="_blank" rel="noopener"&gt;Argo CD Sync Waves&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Helm Charts Repository Updates</title><link>https://blog.stderr.at/whats-new/2025-12-12-helm-charts-changelog/</link><pubDate>Fri, 12 Dec 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/whats-new/2025-12-12-helm-charts-changelog/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;This page shows the &lt;strong&gt;latest updates&lt;/strong&gt; to the &lt;a href="https://blog.stderr.at/helm-charts"&gt;stderr.at Helm Charts Repository&lt;/a&gt;.
The charts are designed for OpenShift and Kubernetes deployments, with a focus on GitOps workflows using Argo CD.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The content below is dynamically loaded from the Helm repository and always shows the most recent changes.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;div id="helm-changelog-widget" class="helm-changelog"&gt;
&lt;div class="helm-changelog-loading"&gt;
&lt;i class="fa fa-spinner fa-spin"&gt;&lt;/i&gt; Loading latest Helm chart updates...
&lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
&lt;/style&gt;
&lt;script&gt;
(function() {
const container = document.getElementById('helm-changelog-widget');
const maxItems = "10";
const cacheBuster = Math.floor(Date.now() / 60000);
fetch(`https://charts.stderr.at/changelog.json?v=${cacheBuster}`)
.then(response =&gt; {
if (!response.ok) throw new Error('Failed to load changelog');
return response.json();
})
.then(data =&gt; {
if (!data.charts || data.charts.length === 0) {
container.innerHTML = '&lt;p class="helm-changelog-error"&gt;No charts found&lt;/p&gt;';
return;
}
const genDate = new Date(data.generated);
const parseDate = (dateStr) =&gt; {
if (!dateStr) return new Date(0);
const d = new Date(dateStr);
return isNaN(d.getTime()) ? new Date(0) : d;
};
const charts = data.charts
.filter(c =&gt; c.lastModified)
.sort((a, b) =&gt; parseDate(b.lastModified).getTime() - parseDate(a.lastModified).getTime())
.slice(0, maxItems);
let html = `
&lt;div class="helm-changelog-header"&gt;
&lt;h3&gt;&lt;i class="fa fa-cubes"&gt;&lt;/i&gt; Latest Helm Chart Updates&lt;/h3&gt;
&lt;span class="helm-changelog-generated"&gt;Updated: ${genDate.toLocaleDateString()}&lt;/span&gt;
&lt;/div&gt;
`;
charts.forEach(chart =&gt; {
const date = new Date(chart.lastModified);
const dateStr = date.toLocaleDateString('en-US', {
month: 'short', day: 'numeric', year: 'numeric'
});
const iconHtml = chart.icon
? `&lt;img src="${chart.icon}" alt="" class="helm-chart-icon" width="60" height="60" data-webp-upgraded="true" onerror="this.outerHTML='&lt;i class=\\'fa fa-cube helm-chart-icon-fallback\\'&gt;&lt;/i&gt;'"&gt;`
: '&lt;i class="fa fa-cube helm-chart-icon-fallback"&gt;&lt;/i&gt;';
let changesHtml = '';
if (chart.changes &amp;&amp; chart.changes.length &gt; 0) {
const sortedChanges = [...chart.changes].reverse();
changesHtml = '&lt;ul class="helm-changes-list"&gt;';
sortedChanges.slice(0, 4).forEach(change =&gt; {
const kind = (change.kind || 'changed').toLowerCase();
changesHtml += `&lt;li class="${kind}"&gt;&lt;span class="helm-change-badge ${kind}"&gt;${kind}&lt;/span&gt;${change.description}&lt;/li&gt;`;
});
if (sortedChanges.length &gt; 4) {
changesHtml += `&lt;li style="color:#888;border-left-color:#888;"&gt;... and ${sortedChanges.length - 4} more changes&lt;/li&gt;`;
}
changesHtml += '&lt;/ul&gt;';
}
const chartUrl = chart.home || 'https://github.com/tjungbauer/helm-charts';
html += `
&lt;div class="helm-chart-item" data-href="${chartUrl}" onclick="window.open('${chartUrl}', '_blank')" role="link" tabindex="0"&gt;
&lt;div class="helm-chart-header"&gt;
${iconHtml}
&lt;span class="helm-chart-name"&gt;&lt;a href="${chartUrl}" target="_blank" rel="noopener noreferrer" class="highlight"&gt;${chart.name}&lt;/a&gt;&lt;/span&gt;
&lt;span class="helm-chart-version"&gt;v${chart.version}&lt;/span&gt;
&lt;span class="helm-chart-date"&gt;📅 ${dateStr}&lt;/span&gt;
&lt;/div&gt;
&lt;div class="helm-chart-description"&gt;${chart.description}&lt;/div&gt;
${changesHtml}
&lt;/div&gt;
`;
});
html += `
&lt;div class="helm-changelog-footer"&gt;
&lt;a href="https://github.com/tjungbauer/helm-charts" target="_blank" rel="noopener noreferrer"&gt;
&lt;i class="fa fa-github"&gt;&lt;/i&gt; View all ${data.charts.length} charts on GitHub
&lt;/a&gt;
&amp;nbsp;|&amp;nbsp;
&lt;a href="https://charts.stderr.at/" target="_blank" rel="noopener noreferrer"&gt;
&lt;i class="fa fa-external-link"&gt;&lt;/i&gt; Helm Repository
&lt;/a&gt;
&lt;/div&gt;
`;
container.innerHTML = html;
})
.catch(error =&gt; {
console.error('Helm changelog error:', error);
container.innerHTML = `
&lt;p class="helm-changelog-error"&gt;
&lt;i class="fa fa-exclamation-triangle"&gt;&lt;/i&gt;
Could not load Helm chart updates.
&lt;a href="https://github.com/tjungbauer/helm-charts" target="_blank" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;
&lt;/p&gt;
`;
});
})();
&lt;/script&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_quick_links"&gt;Quick Links&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;table class="tableblock frame-all grid-all stretch"&gt;
&lt;colgroup&gt;
&lt;col style="width: 33.3333%;"/&gt;
&lt;col style="width: 66.6667%;"/&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Resource&lt;/th&gt;
&lt;th class="tableblock halign-left valign-top"&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Helm Repository&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;&lt;a href="https://charts.stderr.at/" class="bare"&gt;https://charts.stderr.at/&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;GitHub Source&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;&lt;a href="https://github.com/tjungbauer/helm-charts" class="bare"&gt;https://github.com/tjungbauer/helm-charts&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;ArtifactHub&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;&lt;a href="https://artifacthub.io/packages/search?repo=tjungbauer" class="bare"&gt;https://artifacthub.io/packages/search?repo=tjungbauer&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;Example GitOps Repo&lt;/p&gt;&lt;/td&gt;
&lt;td class="tableblock halign-left valign-top"&gt;&lt;p class="tableblock"&gt;&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" class="bare"&gt;https://github.com/tjungbauer/openshift-clusterconfig-gitops&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.15] OpenShift GitOps - Argo CD Agent</title><link>https://blog.stderr.at/gitopscollection/2026-01-14-argocd-agent/</link><pubDate>Wed, 14 Jan 2026 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2026-01-14-argocd-agent/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;OpenShift GitOps based on Argo CD is a powerful tool to manage the infrastructure and applications on an OpenShift cluster. Initially, there were two ways of deployment: centralized and decentralized (or distributed). Both methods had their own advantages and disadvantages. The choice was mainly between scalability and centralization.
With OpenShift GitOps v1.19, the Argo CD Agent was finally generally available. This agent tries to solve this problem by bringing the best of both worlds together. In this quite long article, I will show you how to install and configure the Argo CD Agent with OpenShift GitOps using hub and spoke architecture.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_classic_deployment_models"&gt;Classic Deployment Models:&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Prior to the Argo CD Agent, there were two classic and often used deployment models: &lt;strong&gt;centralized&lt;/strong&gt; and &lt;strong&gt;decentralized&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_centralized_model"&gt;Centralized Model&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In a centralized deployment, all changes are applied to a single, central Argo CD instance, often installed on a management cluster. This is the traditional way of deploying Argo CD, at least in my opinion. With this model, you have a single pane of glass to manage all your clusters. You have a single UI and will see all your clusters in one place, which makes this model very convenient when you have multiple clusters. However, the scalability of this model is limited. Organizations with a huge number of clusters or Argo CD applications would hit some boundaries at some point. A sharding configuration would help, but only to a certain extent. The performance would degrade significantly. In addition, this model creates a Single Point of Failure. If this instance is down, the company loses the ability to manage their clusters through Argo CD.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
I often saw or used this model for the cluster configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_decentralized_model"&gt;Decentralized Model&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In a decentralized deployment, multiple instances of Argo CD, often one for each cluster, are installed. With this approach, the issue with scalability is solved. Moreover, the Single Point of Failure is eliminated as well, since a broken instance will not affect the other instances. However, the disadvantages of this model are that the complexity for the operational teams will increase, since they need to manage multiple instances now. Also, the single pane of glass for management is lost, as there are multiple UIs to manage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
I often saw or used this model for the application deployment.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_not_so_secret_argo_cd_agent"&gt;The - not so secret - Argo CD Agent&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;Argo CD Agent&lt;/strong&gt;, released as generally available in OpenShift GitOps v1.19, is a new way to use Argo CD. It tries to solve the challenges of the classic deployment models by combining the best of both worlds. The Agent allows you to have a single UI in a central control plane, while the application controller is distributed across the fleet of clusters. Agents on the different clusters will communicate with the central Argo CD instance.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Agent model introduces a hub and spoke architecture:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Control plane cluster (hub) - The control plane cluster is the central cluster that manages the configuration for multiple spokes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Workload cluster (spoke) - The workload cluster is the cluster that runs the application workloads deployed by Argo CD.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Each Argo CD Agent on a cluster manages the local Argo CD instance and ensures that applications, AppProjects, and secrets remain synchronized with their source of truth.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The official documentation describes a comparison between the classic deployment models and the Argo CD Agent: &lt;a href="https://docs.redhat.com/en/documentation/red_hat_openshift_gitops/1.19/html/argo_cd_agent_architecture/argocd-agent-architecture#gitops-architecture-argocd-agent-comparison_argocd-agent-architecture-overview" target="_blank" rel="noopener"&gt;GitOps Architecture - Argo CD Agent Comparison&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_argo_cd_agent_modes"&gt;Argo CD Agent Modes&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Argo CD Agent supports two modes of operation: Managed and Autonomous.
The mode determines where the authoritative source of truth for the Application .spec field resides.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Managed mode — the control plane/hub defines Argo CD applications and their specifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Autonomous mode — each workload cluster/spoke defines its own Argo CD applications and their specifications.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
A mixed mode is also possible.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_managed_mode"&gt;Managed Mode&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Using the managed mode means that the control plane is the source of truth and is responsible for the Argo CD application resources and their distribution across the different workload clusters.
Any change on the hub cluster will be propagated to the spoke clusters. Any changes made on the spoke/workload cluster will be reverted to match the control plane configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_autonomous_mode"&gt;Autonomous Mode&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Using this mode, the Argo CD applications are defined on the workload clusters, which serve as their own source of truth. The applications are synchronized back to the control plane for observability.
Changes made on the workload cluster are not reverted, but will appear on the control plane. On the other hand, you cannot modify applications directly from the control plane.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_security"&gt;Security&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Argo CD Agent uses mTLS certificates to communicate between the hub and the spoke clusters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The certificate must be created and managed by the user.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_argo_cd_agent_installation"&gt;Argo CD Agent Installation&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The agent consists of two components that are responsible to synchronize the Argo CD applications between the hub and the spoke clusters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Principal - Deployed on the control plane cluster together with Argo CD. Here the central UI (and API) can be found.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Agent - Deployed on the workload clusters to synchronize the Argo CD applications.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The installation of both is done differently. But before we dive into the installation, let’s have a look at the terminology to understand the different components and their roles.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is a quote from the official documentation (&lt;a href="https://docs.redhat.com/en/documentation/red_hat_openshift_gitops/1.19/html/argo_cd_agent_installation/argocd-agent-installation#gitops-argocd-agent-terminologiest_argocd-agent-installation" target="_blank" rel="noopener"&gt;Argo CD Agent Terminologies&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Principal namespace&lt;/strong&gt; - Specifies the namespace where you install the Principal component. This namespace is not created by default, you must create it before adding the resources in this namespace. In Argo CD Agent CLI commands, this value is provided using the --principal-namespace flag.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agent namespace&lt;/strong&gt; - Specifies the namespace hosting the Agent component. This namespace is not created by default, you must create it before adding the resources in this namespace. In Argo CD Agent CLI commands, this value is provided using the --agent-namespace flag.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt; - A context refers to a named configuration in the oc CLI that allows you to switch between different clusters. You must be logged in to all clusters and assign distinct context names for the hub and spoke clusters. Examples for cluster names include principal-cluster, hub-cluster, managed-agent-cluster, or autonomous-agent-cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Principal context&lt;/strong&gt; - The context name you provide for the hub (control plane) cluster. For example, if you log in to the hub cluster and rename its context to principal-cluster, you specify it in Argo CD Agent CLI commands as --principal-context principal-cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agent context&lt;/strong&gt; - The context name you provide for the spoke (workload) cluster. For example, if you log in to a spoke cluster and rename its context to autonomous-agent-cluster, you specify it in Argo CD Agent CLI commands as --agent-context autonomous-agent-cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_a_word_about_the_setup"&gt;A Word about the Setup&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To create some kind of real customer scenario, I have created two clusters:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;The cluster where we installed the Principal component. This will be the Hub/Management/Principal cluster (We have too many words for this…​)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A separate cluster that will be the Agent cluster. This will be the Spoke/Workload cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first one I have installed on AWS, and the second one is a Bare Metal Single node cluster. Both can reach each other via the Internet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
We are using different contexts for the different clusters for the command line. The &lt;strong&gt;argocd-agentctl&lt;/strong&gt; tool knows the flags &lt;strong&gt;--principal-context&lt;/strong&gt; and &lt;strong&gt;--agent-context&lt;/strong&gt; to switch between the different clusters. Be sure to create the resources on the correct cluster.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before we start with the installation of the Principal or Agent component, we need to ensure that the following prerequisites are met:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We have two OpenShift test clusters.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Both clusters can reach each other&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenShift GitOps Operator is already installed (possible configuration modifications are described in the following sections)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Because of my main focus on OpenShift GitOps, we will try to deploy cluster configurations and not just workload. Therefore, the example Argo CD applications will configure cluster settings. (A banner on the top and bottom of the UI)
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configure_openshift_gitops_subscription_on_the_hub_cluster"&gt;Configure OpenShift GitOps Subscription on the Hub Cluster&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The OpenShift GitOps Operator installs by default an Argo CD instance. In this test we will disable this, as we do not need that instance. Moreover and even more important, we need to tell the Operator for which namespaces it should feel responsible for. In this case, we will tell the Operator to be responsible for all namespaces on the cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We need to modify the Subscription &lt;strong&gt;openshift-gitops-operator&lt;/strong&gt; and add the following environment variables:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;spec:
config:
env:
- name: DISABLE_DEFAULT_ARGOCD_INSTANCE &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
value: &amp;#39;true&amp;#39;
- name: ARGOCD_CLUSTER_CONFIG_NAMESPACES &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
value: &amp;#39;*&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Optional: Disable the default Argo CD instance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Tell the Operator for which namespaces it should feel responsible for. In this case, all Namespaces. This is important for a namespace-scoped Argo CD instance, which we will install in the next step.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_activate_the_principal_component"&gt;Activate the Principal Component&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To activate the Principal components we first need a cluster (the Hub) where OpenShift GitOps is installed. On this cluster, there might be a running instance of Argo CD already.
However, at this time an existing Argo CD &lt;strong&gt;cannot&lt;/strong&gt; be used. Instead, a new Argo CD instance must be created. (This is because the controller must not be activated)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In my case, I will install the Principal component in the namespace &lt;strong&gt;argocd-principal&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To create an Argo CD instance we need to create the following configuration:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
At this very stage, the principal component must be installed in a separate Argo CD instance, since the controller must not be activated. Therefore we create a new Argo CD instance in a new namespace.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1beta1
kind: ArgoCD
metadata:
name: hub-argocd
namespace: argocd-principal
spec:
controller:
enabled: false &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
argoCDAgent:
principal:
enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
auth: &amp;#34;mtls:CN=([^,]+)&amp;#34; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
logLevel: &amp;#34;info&amp;#34;
namespace:
allowedNamespaces: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- &amp;#34;*&amp;#34;
tls:
insecureGenerate: false &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
jwt:
insecureGenerate: false
sourceNamespaces: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
- &amp;#34;argocd-agent-bm01&amp;#34;
server:
route:
enabled: true &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Disable the controller component.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable the Principal component.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Authentication method for the Principal component.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Allowed namespaces for the Principal component.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Insecure generation of the TLS certificate.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Specifies the sourceNamespaces configuration. (Such a list might already exist)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable the Route for the Principal component.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will start a Pod called &lt;strong&gt;hub-gitops-agent-principal&lt;/strong&gt; in the namespace &lt;strong&gt;argocd-principal&lt;/strong&gt;. However, this pod will &lt;strong&gt;fail&lt;/strong&gt; at this moment and that is fine.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;Pod hub-gitops-agent-principal is failing with:
{&amp;#34;level&amp;#34;:&amp;#34;info&amp;#34;,&amp;#34;msg&amp;#34;:&amp;#34;Setting loglevel to info&amp;#34;,&amp;#34;time&amp;#34;:&amp;#34;2026-01-19T13:45:57Z&amp;#34;}
time=&amp;#34;2026-01-19T13:45:57Z&amp;#34; level=info msg=&amp;#34;Loading gRPC TLS certificate from secret argocd-principal/argocd-agent-principal-tls&amp;#34;
time=&amp;#34;2026-01-19T13:45:57Z&amp;#34; level=info msg=&amp;#34;Loading root CA certificate from secret argocd-principal/argocd-agent-ca&amp;#34;
time=&amp;#34;2026-01-19T13:45:57Z&amp;#34; level=info msg=&amp;#34;Loading resource proxy TLS certificate from secrets argocd-principal/argocd-agent-resource-proxy-tls and argocd-principal/argocd-agent-ca&amp;#34;
[FATAL]: Error reading TLS config for resource proxy: error getting proxy certificate: could not read TLS secret argocd-principal/argocd-agent-resource-proxy-tls: secrets &amp;#34;argocd-agent-resource-proxy-tls&amp;#34; not found &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The secret is not yet available.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The Pod is failing at the moment because the different secrets for authentication, are not yet available. The Secrets are created in a later step, because some settings, such as the principal hostname and resource proxy service names, are available only after the Red Hat OpenShift GitOps Operator enables the principal component.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At this point the Operator created the Route object already:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;Route: hub-gitops-agent-principal
Hostname: https://hub-gitops-agent-principal-argocd-principal.apps.ocp.aws.ispworld.at
Service: hub-gitops-agent-principal&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configure_the_appproject"&gt;Configure the AppProject&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you configured the AppProject with sourceNamespaces, you need to add the following to the AppProject (for example to the &lt;strong&gt;default&lt;/strong&gt; AppProject). This must match exactly the namespaces you have created for the Agent.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;spec:
sourceNamespaces:
- &amp;#34;argocd-agent-bm01&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can also use this patch command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc patch appproject default -n argocd-principal --type=&amp;#39;merge&amp;#39; \
-p &amp;#39;{&amp;#34;spec&amp;#34;: {&amp;#34;sourceNamespaces&amp;#34;: [&amp;#34;argocd-agent-bm01&amp;#34;]}}&amp;#39; --context aws&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Restart the Argo CD Pods to apply the changes.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_download_argocd_agentctl"&gt;Download argocd-agentctl&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To create the required secrets, we need to download the &lt;strong&gt;argocd-agentctl&lt;/strong&gt; tool.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This can be found at: &lt;a href="https://developers.redhat.com/content-gateway/rest/browse/pub/cgw/openshift-gitops/" class="bare"&gt;https://developers.redhat.com/content-gateway/rest/browse/pub/cgw/openshift-gitops/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Download and install it for your platform.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_create_required_secrets"&gt;Create Required Secrets&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following steps will create the required secrets for the Principal component. In this example, we will create our own CA and certificates. This is suitable for development and testing purposes. For production environments, you should use certificates issued by your organization’s PKI or a trusted certificate authority.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Use your company’s CA and certificates for production environments.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_initialize_the_certificate_authority_ca"&gt;Initialize the Certificate Authority (CA)&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To create a certificate authority (CA) that signs other certificates, we need to run the following command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;argocd-agentctl pki init \
--principal-namespace argocd-principal \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
--principal-context aws&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The namespace where the Principal component is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will initialize the CA and store it in the secret &lt;strong&gt;argocd-principal/argocd-agent-ca&lt;/strong&gt;. The certificate looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;&amp;#34;Certificate Information:
Common Name: argocd-agent-ca
Subject Alternative Names:
Organization: DO NOT USE IN PRODUCTION
Organization Unit:
Locality:
State:
Country:
Valid From: January 15, 2026
Valid To: January 15, 2036
Issuer: argocd-agent-ca, DO NOT USE IN PRODUCTION
Serial Number: 1 (0x1)&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_generate_service_certificate_for_the_principal"&gt;Generate Service Certificate for the Principal&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To generate the server certificate for the Principal’s gRPC service, run the following command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;argocd-agentctl pki issue principal \
--principal-namespace argocd-principal \
--principal-context aws \
--dns &amp;#34;&amp;lt;YOUR PRINCIPAL HOSTNAME&amp;gt;&amp;#34; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The hostname of the Principal service. This must match with the hostname of the Principal’s route (spec.host) or, in case a LoadBalancer Service is used, with .status.loadBalancer.ingress.hostname.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_generate_the_resource_proxy_certificate"&gt;Generate the Resource Proxy Certificate&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The resource proxy service requires a certificate as well. Since the proxy will run on the same cluster as the Principal, we can use the service name directly.
This is generated by the following command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;argocd-agentctl pki issue resource-proxy \
--principal-namespace argocd-principal \
--principal-context aws \
--dns hub-argocd-agent-principal-resource-proxy &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The service name for the resource-proxy. This must match with the service name of the Resource Proxy service.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_generate_the_jwt_signing_key"&gt;Generate the JWT Signing Key&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Generate the RSA private key for the JWT signing key by running the following command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;argocd-agentctl jwt create-key \
--principal-namespace argocd-principal \
--principal-context aws&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will generate the RSA private key and store it in the secret &lt;strong&gt;argocd-principal/argocd-agent-jwt&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verify_the_principal_component"&gt;Verify the Principal Component&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now the principal pod should be running successfully.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If the Pod still shows an error, wait a few moments or restart the Pod.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the logs you should see the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;{&amp;#34;level&amp;#34;:&amp;#34;info&amp;#34;,&amp;#34;msg&amp;#34;:&amp;#34;Setting loglevel to info&amp;#34;,&amp;#34;time&amp;#34;:&amp;#34;2026-01-19T14:09:31Z&amp;#34;}
time=&amp;#34;2026-01-19T14:09:31Z&amp;#34; level=info msg=&amp;#34;Loading gRPC TLS certificate from secret argocd-principal/argocd-agent-principal-tls&amp;#34;
time=&amp;#34;2026-01-19T14:09:31Z&amp;#34; level=info msg=&amp;#34;Loading root CA certificate from secret argocd-principal/argocd-agent-ca&amp;#34;
time=&amp;#34;2026-01-19T14:09:31Z&amp;#34; level=info msg=&amp;#34;Loading resource proxy TLS certificate from secrets argocd-principal/argocd-agent-resource-proxy-tls and argocd-principal/argocd-agent-ca&amp;#34;
time=&amp;#34;2026-01-19T14:09:31Z&amp;#34; level=info msg=&amp;#34;Loading JWT signing key from secret argocd-principal/argocd-agent-jwt&amp;#34;
time=&amp;#34;2026-01-19T14:09:31Z&amp;#34; level=info msg=&amp;#34;Starting argocd-agent (server) v99.9.9-unreleased (ns=argocd-principal, allowed_namespaces=[*])&amp;#34; module=server&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This concludes the configuration of the Principal component. There are a lot of steps to create the required secrets, but this is only done once. A GitOps-friendly way to achieve this might be done using a Kubernetes Job (if you consider this as GitOps-friendly…​ which I do).&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_activate_the_agent_component"&gt;Activate the Agent Component&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After the Principal component is configured, you can activate one or more Agents (spoke or workload clusters) and connect them with the Hub.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The prerequisites are:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Principal component is configured and running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You have access to both the Principal and Agent clusters.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The argocd-agentctl CLI tool is installed and accessible from your environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The helm CLI is installed and configured. Ensure that the helm CLI version is later than v3.8.0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenShift GitOps Operator is installed and configured on the Agent cluster.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Yes, a separate Helm Chart will be used to install the Agent component on the target cluster. This time it is not a Chart that I created, but one provided by Red Hat. :)
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_create_agent_secret_on_principal_cluster"&gt;Create Agent Secret on Principal Cluster&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We first need to create an agent on the Principal cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;argocd-agentctl agent create &amp;#34;argocd-agent-bm01&amp;#34; \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
--principal-context &amp;#34;aws&amp;#34; \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
--principal-namespace &amp;#34;argocd-principal&amp;#34; \ &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
--resource-proxy-server &amp;#34;hub-argocd-agent-principal-resource-proxy:9090&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A (unique) name for the Agent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The context name for the Principal cluster. In my case it is &amp;#34;aws&amp;#34;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The namespace where the Principal component is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The resource proxy server URL. This is the URL of the Principal’s resource proxy service including the port (9090).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create the secret &lt;strong&gt;cluster-argocd-agent-bm01&lt;/strong&gt; with the label &lt;strong&gt;argocd.argoproj.io/secret-type: cluster&lt;/strong&gt; in the Argo CD namespace.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_create_the_agent_namespace_on_the_agent_cluster"&gt;Create the Agent Namespace on the Agent Cluster&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Be sure that the target namespace on the agent or workload cluster exists. If not, create it first.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc create namespace argocd-agent-bm01 --context bm &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the namespace.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_propagate_the_principal_ca_to_the_agent_cluster"&gt;Propagate the Principal CA to the Agent Cluster&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To copy the CA certificate from the principal to the agent cluster the following command is used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;argocd-agentctl pki propagate \
--agent-context bm \ &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
--principal-context aws \ &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
--principal-namespace argocd-principal \ &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
--agent-namespace argocd-agent-bm01 &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The context name for the Agent cluster.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The context name for the Principal cluster.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The namespace where the Principal component is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The namespace where the Agent component is running.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will copy the CA certificate from the Principal cluster to the Agent cluster into the namespace and secret &lt;strong&gt;argocd-agent-bm01/argocd-agent-ca&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Only the certificate is copied. The private key is not copied.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_generate_a_client_certificate_for_the_agent"&gt;Generate a client certificate for the Agent&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we need to create, based on the imported CA, a client certificate for the Agent. This is done by the following command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;argocd-agentctl pki issue agent &amp;#34;argocd-agent-bm01&amp;#34; \
--principal-context &amp;#34;aws&amp;#34; \
--agent-context &amp;#34;bm&amp;#34; \
--agent-namespace &amp;#34;argocd-agent-bm01&amp;#34; \
--principal-namespace &amp;#34;argocd-principal&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create the secret &lt;strong&gt;argocd-agent-client-tls&lt;/strong&gt; on the workload cluster, containing a certificate and a key, signed by the CA certificate imported from the Principal cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configure_openshift_gitops_subscription_on_the_spoke_cluster"&gt;Configure OpenShift GitOps Subscription on the Spoke Cluster&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The OpenShift GitOps Operator installs by default an Argo CD instance. In this test we will disable this, as we do not need that instance. Moreover and even more important, we need to tell the Operator for which namespaces it should feel responsible for. In this case, we will tell the Operator to be responsible for all namespaces on the cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We need to modify the Subscription &lt;strong&gt;openshift-gitops-operator&lt;/strong&gt; and add the following environment variables:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;spec:
config:
env:
- name: DISABLE_DEFAULT_ARGOCD_INSTANCE &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
value: &amp;#39;true&amp;#39;
- name: ARGOCD_CLUSTER_CONFIG_NAMESPACES &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
value: &amp;#39;*&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Disable the default Argo CD instance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Tell the Operator for which namespaces it should feel responsible for. In this case, all Namespaces.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_create_argo_cd_instance_on_the_agent_cluster"&gt;Create Argo CD Instance on the Agent Cluster&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To create a minimalistic Argo CD instance on the Agent cluster, we can use the following Argo CD configuration:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1beta1
kind: ArgoCD
metadata:
name: agent-argocd
namespace: argocd-agent-bm01 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
spec:
server:
enabled: false&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The namespace where the Argo CD instance is running. This is also the name of the Agent we have created earlier on the principal cluster.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create a lightweight Argo CD instance in the namespace &lt;strong&gt;argocd-agent-bm01&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get pod -n argocd-agent-bm01 --context bm
NAME READY STATUS RESTARTS AGE
agent-argocd-application-controller-0 1/1 Running 0 2m49s
agent-argocd-redis-5f6759f6fb-2fdnt 1/1 Running 0 2m49s
agent-argocd-repo-server-7949d97dfd-dsk6b 1/1 Running 0 2m49s&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installing_the_agent"&gt;Installing the Agent&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To install the agent we will use a Helm Chart provided by Red Hat. This will install the Agent component on the target cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As a reminder, we have two modes of operation for the Agent:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Managed mode&lt;/strong&gt; — the control plane/hub defines Argo CD applications and their specifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Autonomous mode&lt;/strong&gt; — each workload cluster/spoke defines its own Argo CD applications and their specifications.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_create_required_network_policy"&gt;Create Required Network Policy&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before we start with the actual installation of the Agent, we need to ensure that the Redis instance on the spoke cluster is accessible for the Agent. We need to create a NetworkPolicy accordingly:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: argocd-agent-bm01-redis-network-policy
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: agent-argocd-redis &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
ingress:
- ports:
- protocol: TCP
port: 6379
from:
- podSelector:
matchLabels:
app.kubernetes.io/name: argocd-agent-agent
policyTypes:
- Ingress&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the Redis instance. The label is based on &amp;lt;instance name&amp;gt;-redis.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Apply the NetworkPolicy to the spoke cluster:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc apply -f network-policy.yaml -n argocd-agent-bm01 --context bm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_add_the_helm_chart_repository"&gt;Add the Helm Chart Repository&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Add the Helm repository:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;helm repo add openshift-helm-charts https://charts.openshift.io/
helm repo update&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_install_a_managed_agent_with_the_helm_chart"&gt;Install a managed Agent with the Helm Chart&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Install the agent in the &lt;strong&gt;managed&lt;/strong&gt; mode using the Helm Chart. The following parameters are used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;namespaceOverride&lt;/strong&gt; - The namespace where the Agent is running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;agentMode&lt;/strong&gt; - The mode of the Agent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;server&lt;/strong&gt; - The server URL of the Principal component. This is the spec.host setting of the Principal’s route.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;argoCdRedisSecretName&lt;/strong&gt; - The name of the Redis secret.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;argoCdRedisPasswordKey&lt;/strong&gt; - The key of the Redis password.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;redisAddress&lt;/strong&gt; - The address of the Redis instance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;helm install redhat-argocd-agent openshift-helm-charts/redhat-argocd-agent \
--set namespaceOverride=argocd-agent-bm01 \
--set agentMode=&amp;#34;managed&amp;#34; \
--set server=&amp;#34;serverURL of principal route&amp;#34; \
--set argoCdRedisSecretName=&amp;#34;agent-argocd-redis-initial-password&amp;#34; \
--set argoCdRedisPasswordKey=&amp;#34;admin.password&amp;#34; \
--set redisAddress=&amp;#34;agent-argocd-redis:6379&amp;#34; \
--kube-context &amp;#34;bm&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this chart a pod on the &lt;strong&gt;spoke&lt;/strong&gt; cluster will be created and will start to synchronize the Argo CD applications between the hub and the spoke cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verify_the_managed_agent"&gt;Verify the Managed Agent&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To verify the Agent in &lt;strong&gt;managed&lt;/strong&gt; mode we need to create an Argo CD Application on the &lt;strong&gt;hub&lt;/strong&gt; cluster. We can try the following Application. The Application is taken from the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" target="_blank" rel="noopener"&gt;openshift-clusterconfig-gitops&lt;/a&gt; repository and simply adds a banner to the top of the OpenShift UI. I typically use this as a quick test if GitOps is working.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: branding
namespace: argocd-agent-bm01 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
spec:
destination:
namespace: default
server: &amp;#39;https://hub-argocd-agent-principal-resource-proxy:9090?agentName=argocd-agent-bm01&amp;#39; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
project: default
source:
path: clusters/management-cluster/branding
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39;
targetRevision: main
syncPolicy:
automated:
prune: true
selfHeal: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The namespace of the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The server URL of the Principal component. This is the URL plus the port and the agentName. As an alternative you can also use &lt;strong&gt;name: argocd-agent-bm01&lt;/strong&gt; which is the name of the cluster and might be easier to read.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Apply the Application to the &lt;strong&gt;hub&lt;/strong&gt; cluster:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc apply -f application.yaml -n argocd-agent-bm01 --context aws&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since the application will try to automatically synchronize the configuration, the status will change to &lt;strong&gt;Synced&lt;/strong&gt; after a few seconds:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;status:
resources:
- group: console.openshift.io
kind: ConsoleNotification
name: topbanner
status: Synced
version: v1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;and the (top) banner will be visible on the UI:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/agent/banner-top.png" alt="Banner on the top of the UI"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Banner on the top of the UI&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;On the &lt;strong&gt;hub&lt;/strong&gt; cluster, the Argo CD Application will be Synced:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get applications --context aws -A
NAMESPACE NAME SYNC STATUS HEALTH STATUS
argocd-agent-bm01 branding-banner Synced Healthy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_install_an_autonomous_agent_with_the_helm_chart"&gt;Install an autonomous Agent with the Helm Chart&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s cleanup the first installation of the Chart (managed agent) in order to not have any conflicts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;helm uninstall redhat-argocd-agent --kube-context &amp;#34;bm&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Install the agent in the &lt;strong&gt;autonomous&lt;/strong&gt; mode using the Helm Chart. The following parameters are used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;namespaceOverride&lt;/strong&gt; - The namespace where the Agent is running.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;agentMode&lt;/strong&gt; - The mode of the Agent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;server&lt;/strong&gt; - The server URL of the Principal component. This is the spec.host setting of the Principal’s route.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;argoCdRedisSecretName&lt;/strong&gt; - The name of the Redis secret.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;argoCdRedisPasswordKey&lt;/strong&gt; - The key of the Redis password.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;redisAddress&lt;/strong&gt; - The address of the Redis instance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The only difference to the managed mode is the &lt;strong&gt;agentMode&lt;/strong&gt; parameter.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;helm install redhat-argocd-agent-autonomous openshift-helm-charts/redhat-argocd-agent \
--set namespaceOverride=argocd-agent-bm01 \
--set agentMode=&amp;#34;autonomous&amp;#34; \
--set server=&amp;#34;serverURL of principal route&amp;#34; \
--set argoCdRedisSecretName=&amp;#34;agent-argocd-redis-initial-password&amp;#34; \
--set argoCdRedisPasswordKey=&amp;#34;admin.password&amp;#34; \
--set redisAddress=&amp;#34;agent-argocd-redis:6379&amp;#34; \
--kube-context &amp;#34;bm&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this chart a pod on the &lt;strong&gt;spoke&lt;/strong&gt; cluster will be created and will start to synchronize the Argo CD applications between the hub and the spoke cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verify_the_autonomous_agent"&gt;Verify the Autonomous Agent&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To verify the Agent in &lt;strong&gt;autonomous&lt;/strong&gt; mode we need to create an Argo CD Application on the &lt;strong&gt;spoke&lt;/strong&gt; cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We can try the following Application. It is basically the same as we used for the test for the managed mode, except that this time we will add a banner on the bottom of the UI.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: branding-bottom-banner
namespace: argocd-agent-bm01 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
spec:
destination:
namespace: default
server: &amp;#39;https://kubernetes.default.svc&amp;#39; &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
project: default
source:
path: clusters/management-cluster/branding-bottom
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39;
targetRevision: main
syncPolicy:
automated:
prune: true
selfHeal: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The namespace of the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The server URL of the local cluster, since in autonomous mode the application is managed locally.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Apply the Application to the &lt;strong&gt;spoke&lt;/strong&gt; cluster:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc apply -f application.yaml -n argocd-agent-bm01 --context bm&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Like the managed mode, the status will change to &lt;strong&gt;Synced&lt;/strong&gt; after a few seconds and the (this time) bottom banner will be visible on the UI:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/agent/banner-bottom.png" alt="Banner on the bottom of the UI"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. Banner on the bottom of the UI&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Moreover, the Argo CD Application will appear on the &lt;strong&gt;hub&lt;/strong&gt; cluster:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get applications --context aws -A
NAMESPACE NAME SYNC STATUS HEALTH STATUS
argocd-agent-bm01 branding-bottom-banner Synced Healthy
argocd-agent-bm01 branding-banner Synced Healthy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_troubleshooting"&gt;Troubleshooting&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;During the installation and configuration of the Argo CD Agent, you might encounter some issues. Here are some issues I encountered during the tests:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_principal_pod_fails_to_start"&gt;Principal Pod Fails to Start&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If the Principal pod fails to start with errors about missing secrets, verify that all required secrets have been created:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc get secrets -n argocd-principal | grep argocd-agent&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You should see the following secrets:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;argocd-agent-ca&lt;/strong&gt; - The CA certificate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;argocd-agent-principal-tls&lt;/strong&gt; - The Principal’s TLS certificate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;argocd-agent-resource-proxy-tls&lt;/strong&gt; - The Resource Proxy’s TLS certificate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;argocd-agent-jwt&lt;/strong&gt; - The JWT signing key&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If any of these are missing, re-run the corresponding &lt;code&gt;argocd-agentctl&lt;/code&gt; command to create them.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_redis_errors_in_the_principal_pod"&gt;Redis Errors in the Principal Pod&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When you see errors like the following in the logs of the Principal pod, ensure that the Argo CD instance does not have the controller enabled in that namespace (set &lt;code&gt;spec.controller.enabled: false&lt;/code&gt;). Hopefully, this will change in the future.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;time=&amp;#34;2026-01-20T04:57:10Z&amp;#34; level=error msg=&amp;#34;unexpected lack of &amp;#39;_&amp;#39; namespace/name separate: &amp;#39;app|managed-resources|branding|1.8.3&amp;#39;&amp;#34; connUUID=3c13b30b-9f84-4af6-93d8-e1c03c4c7898 function=redisFxn module=redisProxy&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_limitations"&gt;Limitations&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While the Argo CD Agent brings significant improvements, there are some limitations to be aware of:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_separate_argo_cd_instance_required"&gt;Separate Argo CD Instance Required&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Currently, the Principal component &lt;strong&gt;cannot&lt;/strong&gt; be installed alongside an existing Argo CD instance where the application controller is enabled. You must create a separate Argo CD instance with the controller disabled (&lt;code&gt;spec.controller.enabled: false&lt;/code&gt;). To me, this is one of the biggest limitations. However, this will be addressed in the future and is tracked in the issue: &lt;a href="https://github.com/argoproj-labs/argocd-agent/issues/708" class="bare"&gt;https://github.com/argoproj-labs/argocd-agent/issues/708&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_manual_certificate_management"&gt;Manual Certificate Management&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The mTLS certificates must be created and managed manually by the user. There is no automatic certificate rotation or renewal. For production environments, you should integrate with your organization’s PKI infrastructure and implement a certificate rotation strategy.&lt;/p&gt;
&lt;/div&gt;
&lt;hr/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_summary"&gt;Summary&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Argo CD Agent provides a powerful solution for managing multiple clusters with Argo CD. By combining the benefits of centralized management with distributed application controllers, it addresses the scalability and single point of failure challenges of traditional deployment models.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While the initial setup requires several steps, especially around certificate management, the resulting architecture offers a robust foundation for GitOps at scale.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.14] Reusable Argo CD Application Template</title><link>https://blog.stderr.at/gitopscollection/2025-07-17-common-template-application/</link><pubDate>Thu, 17 Jul 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2025-07-17-common-template-application/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;When working with Argo CD at scale, you often find yourself creating similar Application manifests repeatedly. Each application needs the same basic structure but with different configurations for source repositories, destinations, and sync policies. Additionally, managing namespace metadata becomes tricky when you need to conditionally control whether Argo CD should manage namespace metadata based on sync options.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this article, I’ll walk you through a reusable Helm template that solves these challenges by providing a flexible, DRY (Don’t Repeat Yourself) approach to creating Argo CD Applications. This template is available in my public Helm Chart library and can easily be used by anyone.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_problem"&gt;The Problem&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Traditional Argo CD Application manifests suffer from several issues:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Repetitive Code&lt;/strong&gt;: Each application requires similar boilerplate YAML&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration Validation&lt;/strong&gt;: Manual validation of required fields across multiple applications&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Maintenance Overhead&lt;/strong&gt;: Changes to common patterns require updates across multiple files&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_but_i_can_do_this_manually_right"&gt;But I can do this manually, right?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Of course, nobody prevents you from creating an Argo CD Application manifest manually or using the UI to enter the values there, but sometimes you just want to get things done faster or help your team with a consistent way to create Argo CD Applications. Often teams do not want to learn about a new tool like Argo CD and maybe you want to automate the creation by using a CI/CD pipeline.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_solution_a_reusable_helm_template"&gt;The Solution: A Reusable Helm Template&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I already work with a common template library for all of my Helm Charts. The idea is to have repeatable snippets in my tpl charts and I can reuse them in all other charts. Recently, I started testing the templating of entire Kubernetes manifests.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To address the above issues, I’ve created a comprehensive Helm template that will render an Argo CD Application. But let’s start with the end result.
Your team wants to create a new Argo CD Application. They can do this either via the UI, via the CLI, by creating the YAML file manually, or through pull request or CI/CD integration. Ultimately, the minimum required information is:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The name of the application&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The namespace of the application&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The source repository&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The destination repository&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So something like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;argocd_applications:
my-app: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: &amp;#34;openshift-gitops&amp;#34;
source: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
repositoryURL: &amp;#34;https://github.com/argoproj/argocd-example-apps.git&amp;#34;
targetRevision: &amp;#34;HEAD&amp;#34;
path: &amp;#34;guestbook&amp;#34;
destination: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
server: &amp;#34;https://kubernetes.default.svc&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
namespace: &amp;#34;guestbook&amp;#34;
my-second-app:
....&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;This key will become the name of the Application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The source repository, this is the repository that contains the Helm chart or the Kubernetes manifests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The destination, that defines the cluster and namespace where the application will be deployed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Either &lt;strong&gt;server&lt;/strong&gt; or &lt;strong&gt;name&lt;/strong&gt; must be set, but not both.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With the above values multiple applications can be created at once.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_integrating_the_template"&gt;Integrating the Template&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The values from the above example can be used to create the Application manifest.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All the developers (or CI/CD pipelines) need to do is to define the values and create one template to include the source template.
This will look like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;{{- if .Values.argocd_applications }}
{{- range $name, $config := .Values.argocd_applications }} &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
{{- if $config.enabled | default false }} &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
---
{{- include &amp;#34;tpl.argocdApplication&amp;#34; (dict &amp;#34;name&amp;#34; $name &amp;#34;spec&amp;#34; $config) -}} &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
{{- end }}
{{- end }}
{{- end }}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Iterate over the applications, defining $name and $config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Only include the application if it is enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Include the template with the name of the application and the specification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is everything you need to do to create an Argo CD Application. Let’s take a look at the template.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_key_features_of_the_template"&gt;Key Features of the template&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_1_flexible_destination_configuration"&gt;1. Flexible Destination Configuration&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The template supports both server URL and cluster name destinations with &lt;strong&gt;validation&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;destination:
{{- if and ($spec.destination.server) ($spec.destination.name) }}
{{ fail &amp;#34;destination.server and destination.name cannot be set at the same time&amp;#34; }}
{{- else }}
{{- if $spec.destination.server }}
server: {{ $spec.destination.server }}
{{- else if $spec.destination.name }}
name: {{ $spec.destination.name }}
{{- else }}
server: https://kubernetes.default.svc
{{- end }}
{{- end }}
namespace: {{ $spec.destination.namespace | required &amp;#34;destination.namespace is required&amp;#34; }}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_2_comprehensive_sync_policy_support"&gt;2. Comprehensive Sync Policy Support&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The template handles all Argo CD sync policy features:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Automated sync with prune, selfHeal, and allowEmpty options&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Flexible sync options array&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Retry configuration with backoff strategies&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Conditional managed namespace metadata&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_3_conditional_namespace_management"&gt;3. Conditional Namespace Management&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The intelligent handling of &lt;code&gt;managedNamespaceMetadata&lt;/code&gt;. The template only includes this section when it &lt;strong&gt;CreateNamespace=&lt;/strong&gt; option is set to true:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;{{- if and $spec.syncPolicy.syncOptions (not (has &amp;#34;CreateNamespace=false&amp;#34; $spec.syncPolicy.syncOptions)) }}
{{- if $spec.syncPolicy.managedNamespaceMetadata }}
managedNamespaceMetadata:
{{- with $spec.syncPolicy.managedNamespaceMetadata.labels }}
labels:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $spec.syncPolicy.managedNamespaceMetadata.annotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_4_other_features"&gt;4. Other Features&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I created the template to be as flexible as possible. However, I did not include everything in this template, only the most important features (from my point of view). Currently, the following is possible:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create a template for an Argo CD Application using Git&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a template for an Argo CD Application using Helm defining all possible Helm parameters, like additional values files or other options.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using a &lt;strong&gt;single&lt;/strong&gt; source&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set required annotations and labels&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_5_not_possible_currently"&gt;5. Not possible (currently)&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Along with the supported features, there are some features that are currently not possible:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Defining multiple sources&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure Kustomize settings&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, if you feel this needs to be added, please let me know and create an issue. I can then try to add it.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_why_this_approach_works"&gt;Why This Approach Works&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_1_dry_principle"&gt;1. DRY Principle&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Instead of repeating the same YAML structure across multiple applications, you define it once and reuse it everywhere.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_2_intelligent_defaults"&gt;2. Intelligent Defaults&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The template provides sensible defaults (like &lt;code&gt;openshift-gitops&lt;/code&gt; namespace) while allowing customization when needed.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_3_validation"&gt;3. Validation&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Built-in validation ensures required fields are present and conflicting configurations are caught early.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_4_conditional_logic"&gt;4. Conditional Logic&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The template handles complex scenarios like namespace management automatically, reducing the chance of misconfigurations.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_real_world_benefits"&gt;Real-World Benefits&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In practice, this template has several advantages:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: All applications follow the same pattern&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;: Changes to common patterns are made in one place&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Safety&lt;/strong&gt;: Validation prevents common misconfigurations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: Supports the full range of Argo CD features&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Development Guidelines&lt;/strong&gt;: Ensure all developers are using the same process&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_real_world_examples"&gt;Real-World Examples&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_1_define_a_ui_branding"&gt;1. Define a UI Branding&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I would like to define a top banner in the OpenShift Console.
Everything is defined in the &lt;code&gt;clusters/management-cluster/branding&lt;/code&gt; folder in my git repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All I need to do is to define the following values:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;argocd_applications:
my-branding:
enabled: true
namespace: &amp;#34;openshift-gitops&amp;#34;
source:
repositoryURL: &amp;#34;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#34;
targetRevision: &amp;#34;main&amp;#34;
path: &amp;#34;clusters/management-cluster/branding&amp;#34;
destination:
name: in-cluster
namespace: &amp;#34;default&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_2_define_a_ui_branding_with_custom_helm_value"&gt;2. Define a UI Branding with custom Helm value&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Like above I would like to define a top banner in the OpenShift Console. This time I want to use a custom Helm value to define the background color of the banner.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;argocd_applications:
my-branding-custom-helm:
enabled: true
namespace: &amp;#34;openshift-gitops&amp;#34;
source:
repositoryURL: &amp;#34;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#34;
targetRevision: &amp;#34;main&amp;#34;
path: &amp;#34;clusters/management-cluster/branding&amp;#34;
helm:
parameters:
- name: generic-cluster-config.console.console_banners.topbanner.backgroundcolor
value: &amp;#39;#FF9843&amp;#39;
destination:
name: in-cluster
namespace: &amp;#34;default&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_3_full_blown_example"&gt;3. Full-Blown Example&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A full example with all features can be found in my Git repository at: &lt;a href="https://github.com/tjungbauer/helm-charts/blob/main/charts/tpl/values_example_ArgoCD-Application.yaml" target="_blank" rel="noopener"&gt;values_example_ArgoCD-Application.yaml&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_about_validation"&gt;What about Validation?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Above I mentioned that the template is able to validate the values.
This is true for the most important parts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example, try to define the following values:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;[...]
destination:
name: in-cluster
server: &amp;#34;https://kubernetes.default.svc&amp;#34;
namespace: &amp;#34;default&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;server&lt;/strong&gt; are not allowed to be set at the same time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Helm (and Argo CD which is using Helm) will validate the values and fail with an error.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;Error: destination.server and destination.name cannot be set at the same time&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This Argo CD Application template demonstrates how Helm’s templating capabilities can solve real-world GitOps challenges. By combining conditional logic, validation, and sensible defaults, we create a tool that’s both powerful and easy to use.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The conditional namespace management feature alone saves hours of debugging why Argo CD isn’t behaving as expected with namespace metadata. When you combine this with the DRY benefits and built-in validation, you get a robust foundation for managing Argo CD applications at scale.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Whether you’re managing a few applications or hundreds, this template pattern will help you maintain consistency, reduce errors, and improve your team’s GitOps experience.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.13] ApplicationSet with Matrix Generator</title><link>https://blog.stderr.at/gitopscollection/2025-04-17-applicationset-defining-namespaces/</link><pubDate>Thu, 17 Apr 2025 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2025-04-17-applicationset-defining-namespaces/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;During my day-to-day business, I am discussing the following setup with many customers: &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/"&gt;Configure App-of-Apps&lt;/a&gt;. Here I try to explain how I use an ApplicationSet that watches over a folder in Git and automatically adds a new Argo CD Application whenever a new folder is found. This works great, but there is a catch: The ApplicationSet uses the same Namespace &lt;strong&gt;default&lt;/strong&gt; for all Applications. This is not always desired, especially when you have different teams working on different Applications.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Recently I was asked by the customer if this can be fixed and if it is possible to define different Namespaces for each Application. The answer is yes, and I would like to show you how to do this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_current_situation"&gt;The Current Situation&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Currently, I am (or was) using the following ApplicationSet to watch over a folder in Git. The ApplicationSet uses the Matrix Generator to create a new Argo CD Application for each folder found in the Git repository. It also uses the list operator to define the targetCluster:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; generatormatrix:
# Git: Walking through the specific folder and take whatever is there.
- git:
directories:
- path: clusters/management-cluster/*
repoURL: *repourl
revision: *branch
# List: simply define the targetCluster. The name of the cluster must be known by Argo CD
- list:
elements:
# targetCluster is important, this will define on which cluster it will be rolled out.
# The cluster name must be known in Argo CD
- targetCluster: *mgmtclustername&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will create a new Application for any subfolder found in the &lt;strong&gt;clusters/management-cluster/&lt;/strong&gt; folder any every Application in Argo CD will be configured with the same target namespace: &lt;strong&gt;default&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Technically, this is not a problem, as I define the exact namespace in the different Helm Charts, but it is not always desired.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
I personally recommend defining the Namespace in the Helm Charts, since especially for the cluster configuration, sometimes there is no clear target Namespace or multiple Namespaces are modified.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_did_not_work"&gt;What did not work&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first idea was to use the &lt;strong&gt;Matrix&lt;/strong&gt; generator and define the &lt;strong&gt;targetNamespace&lt;/strong&gt; in list.elements and if the namespace is not defined, use a default one. So similar like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; generatormatrix:
# Git: Walking through the specific folder and take whatever is there.
- git:
directories:
- path: clusters/management-cluster/*
repoURL: *repourl
revision: *branch
# List: simply define the targetCluster. The name of the cluster must be known by Argo CD
- list:
elements:
# targetCluster is important, this will define on which cluster it will be rolled out.
# The cluster name must be known in Argo CD
- targetCluster: *mgmtclustername
path: clusters/management-cluster/cert-manager
targetNamespace: cert-manager
- targetCluster: *mgmtclustername
path: clusters/management-cluster/*
targetNamespace: default&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To make is short: &lt;strong&gt;This does not work&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The matrix operator walks over all folders and creates a cartesian product of the elements. This means, it will create a new Application for each folder and each element in the list. So if you have 10 folders and 2 elements in the list, you will end up with 20 Applications.
This is not what we want. We want to create a new Application for each folder and define the targetNamespace in the Git repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The second test was the use of the &lt;strong&gt;Merge&lt;/strong&gt; generator. This did not work as well, as it was not possible to define a default Namespace.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_solution"&gt;The Solution&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The solution is to use the &lt;strong&gt;Git FILES&lt;/strong&gt; generator and define the &lt;strong&gt;targetNamespace&lt;/strong&gt; in the Git repository. This is done by creating a file called &lt;strong&gt;config.json&lt;/strong&gt; in each subfolder. The content of the file is simple:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;namespace&amp;#34;: &amp;#34;default&amp;#34;,
&amp;#34;environment&amp;#34;: &amp;#34;in-cluster&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The advantage is that it is possible to define multiple parameters in that file. However, the disadvantage is that this file must be created, otherwise the ApplicationSet will ignore the folder and will not create a new Application.
I think this is a small disadvantage, and the file is easy to maintain.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Bringing everything together now opens two possibilities:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This will require &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-argocd"&gt;helper-argocd&lt;/a&gt; version 2.0.41 or higher.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_option_1_keep_matrix_generator_and_use_git_file_sub_generator"&gt;Option 1: Keep Matrix Generator and use Git File sub-generator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first option simply replaces the git directory generator with the git file generator. The rest of the ApplicationSet remains unchanged.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
I like this option somehow better than the second one, because I can keep everything as I had it before, the only thing is to create the &lt;strong&gt;config.json&lt;/strong&gt; file in each subfolder and change two lines in the ApplicationSet.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; # Switch to set the namespace to &amp;#39;.namespace&amp;#39; ... must be defined in config.json
use_configured_namespace: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# Definition of Matrix Generator. Only 2 generators are supported at the moment
generatormatrix:
# Git: Walking through the specific folder and take whatever is there.
- git:
files: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- path: clusters/management-cluster/**/config.json &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
repoURL: *repourl
revision: *branch
# List: simply define the targetCluster. The name of the cluster must be known by Argo CD
- list:
elements:
# targetCluster is important, this will define on which cluster it will be rolled out.
# The cluster name must be known in Argo CD
- targetCluster: *mgmtclustername&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Switch to use the configured namespace. This is important, otherwise the namespace is set to &amp;#34;default&amp;#34;. This was added for backward compatibility.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The git file generator is used instead of the git directory generator.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The path is changed to the config.json file. The ** is important, as it defines to look into every subfolder.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The config.json can be shortened to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;namespace&amp;#34;: &amp;#34;default&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_option_2_switch_to_plain_git_file_generator"&gt;Option 2: Switch to plain Git File Generator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The second option is to switch to the plain Git generator. This removes the Matrix generator, but also requires defining the targetCluster in the config.json file. This is not a problem, as the config.json file can be used to define multiple parameters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; generatorgit: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# Git: Walking through the specific folder and take whatever is there.
- files:
- clusters/management-cluster/**/config.json
repourl: *repourl
revision: *branch&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;No Matrix but Git generator instead.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Here the full config.json file is required, otherwise the targetCluster is not defined:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-json hljs" data-lang="json"&gt;{
&amp;#34;namespace&amp;#34;: &amp;#34;default&amp;#34;,
&amp;#34;environment&amp;#34;: &amp;#34;in-cluster&amp;#34;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_full_working_example"&gt;Full working example&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Source: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/base/argocd-resources-manager/values.yaml" class="bare"&gt;https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/base/argocd-resources-manager/values.yaml&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;applicationsets:
######################################
# MATRIX GENERATOR EXAMPLE Git Files #
######################################
# The idea behind the GIT Generate (File) is to walk over a folder, for example /clusters/management-cluster and fetch a config.json from each folder.
# This is more or less similar as the Matrix generator (see below), but reqires a bit more configuration ... the config.json.
# The advantage is that you can configure individual namespaces for example in this config.json and provide an additional information
mgmt-cluster-matrix-gitfiles:
enabled: true
# Description - always usful
description: &amp;#34;ApplicationSet that Deploys on Management Cluster Configuration (using Git Generator)&amp;#34;
# Any labels you would like to add to the Application. Good to filter it in the Argo CD UI.
labels:
category: configuration
env: mgmt-cluster
# Using go text template. See: https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/GoTemplate/
goTemplate: true
argocd_project: *mgmtclustername
environment: *mgmtclustername
# preserve all resources when the application get deleted. This is useful to keep that workload even if Argo CD is removed or severely changed.
preserveResourcesOnDeletion: true
# Switch to set the namespace to &amp;#39;.namespace&amp;#39; ... must be defined in config.json
use_configured_namespace: true
# Definition of Matrix Generator. Only 2 generators are supported at the moment
generatormatrix:
# Git: Walking through the specific folder and take whatever is there.
- git:
files:
- path: clusters/management-cluster/**/config.json
repoURL: *repourl
revision: *branch
# List: simply define the targetCluster. The name of the cluster must be known by Argo CD
- list:
elements:
# targetCluster is important, this will define on which cluster it will be rolled out.
# The cluster name must be known in Argo CD
- targetCluster: *mgmtclustername
syncPolicy:
autosync_enabled: false
# Retrying in case the sync failed.
retries:
# number of failed sync attempt retries; unlimited number of attempts if less than 0
limit: 5
backoff:
# the amount to back off. Default unit is seconds, but could also be a duration (e.g. &amp;#34;2m&amp;#34;, &amp;#34;1h&amp;#34;)
# Default: 5s
duration: 5s
# a factor to multiply the base duration after each failed retry
# Default: 2
factor: 2
# the maximum amount of time allowed for the backoff strategy
# Default: 3m
maxDuration: 3m&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this blog post I have shown you how to use the ApplicationSet with the Matrix generator and define individual Namespaces for each Application. This is done by using the Git File generator and defining a config.json file in each subfolder. The config.json file can be used to define multiple parameters, but it is required to create the file in each subfolder.
This is a small disadvantage, but I think it is worth the effort. The advantage is that you can define individual Namespaces for each Application, and you can use the same ApplicationSet for all your Applications.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I hope this blog post was helpful and you learned something new. If you have any questions or comments, please feel free to reach out to me.
I am happy to help you.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.12] Using Kustomize Post-Renderer</title><link>https://blog.stderr.at/gitopscollection/2024-10-13-using-post-renderer/</link><pubDate>Sun, 13 Oct 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-10-13-using-post-renderer/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Lately I came across several issues where a given Helm Chart must be modified after it has been rendered by Argo CD.
Argo CD does a &lt;strong&gt;helm template&lt;/strong&gt; to render a Chart. Sometimes, especially when you work with Subcharts or when a specific setting is not yet supported by the Chart, you need to modify it later …​ you need to post-render the Chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this very short article, I would like to demonstrate this on a real-live example I had to do. I would like to inject annotations to a Route objects, so that the certificate can be injected. This is done by the cert-utils operator.
For the post-rendering the Argo CD repo pod will be extended with a sidecar container, that is watching for the repos and patches them if required.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Everything below is using OpenShift Gitops Operator. This is based on Argo CD, but instead of directly modifying the repo Deployment, we will modify the Argo CD Custom Resource.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
In the future it will be easier to inject certificates into a Route, by defining a Secret. This as currently a TechPreview feature (OpenShift 4.17). &lt;a href="https://docs.openshift.com/container-platform/4.17/networking/routes/secured-routes.html#nw-ingress-route-secret-load-external-cert_secured-routes" target="_blank" rel="noopener"&gt;Creating a route with externally managed certificate
&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_route_object"&gt;The Route Object&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Imagine we have the following Route object, rendered via Helm template:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: my-route
namespace: my-namespace
spec:
host: my.route.apps.cluster.name
port:
targetPort: http
tls:
insecureEdgeTerminationPolicy: Redirect
termination: edge
to:
kind: Service
name: my-service
weight: 100
wildcardPolicy: None&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The cert-manager Operator requested a certificate which can be found in the Secret &amp;#34;my-certificate &amp;#34;.
To let the cert-utils Operator inject the data from the certificate automatically, we need to add annotations to that Route object.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This injection is usually a good idea, since we do not want to define certificate and (private) key directly in the Route object using our Chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: route.openshift.io/v1
kind: Route
metadata:
annotations: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
cert-manager.io/cluster-issuer: my-issuer
cert-utils-operator.redhat-cop.io/certs-from-secret: my-certificate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Two annotations shall be added to the Route object.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
In this example certificates have to be ordered. No wildcard certificate is available.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_post_rendering"&gt;Post-Rendering&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To modify the output after it has been rendered by Argo CD we will use &lt;strong&gt;Kustomize patch feature&lt;/strong&gt;. This means, after the template has been rendered, we send it to Kustomize and let it patch it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s go through the steps one-by-one:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Create a kustomization.yaml
Place the following file next to your Chart.yaml&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: my-namespace
resources:
- ./all.yaml &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
patches:
- patch: |
- op: add &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
path: /metadata/annotations
value:
cert-manager.io/cluster-issuer: my-issuer
cert-utils-operator.redhat-cop.io/certs-from-secret: my-certificate
target:
kind: Route
name: my-route &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The all.yaml file will be created by the helm template command.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Add the annotations to the Route object.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the Route object.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will patch the Route object. You can test this locally by execute the command:
&lt;strong&gt;helm template . &amp;gt; all.yaml &amp;amp;&amp;amp; kustomize build &amp;amp;&amp;amp; rm all.yaml&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic" start="2"&gt;
&lt;li&gt;
&lt;p&gt;Create an empty file called &lt;strong&gt;my-cmp-plugin&lt;/strong&gt; into the folder next to the Chart.yaml
I will explain in a bit why I chose to use this approach.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the following ConfigMap in the OpenShift GitOps namespace (for example openshift-gitops)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;kind: ConfigMap
apiVersion: v1
metadata:
name: my-cmp-plugin
namespace: openshift-gitops
data:
plugin.yaml: |-
apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: my-cmp-plugin &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
spec:
version: v1.0
init: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
command: [sh, -c, &amp;#39;echo &amp;#34;Initializing my-plugin-cmp...&amp;#34;&amp;#39;, &amp;#39;helm dependency build || true&amp;#39;]
generate: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
command: [sh, -c, &amp;#34;helm template . --name-template $ARGOCD_APP_NAME --namespace $ARGOCD_APP_NAMESPACE --include-crds &amp;gt; all.yaml &amp;amp;&amp;amp; kustomize build&amp;#34;]
discover: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
find:
glob: &amp;#34;**/my-cmp-plugin&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the plugin.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The init command will be executed once, when the plugin is loaded.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The generate command will be executed every time the plugin is called.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The discovery command will be executed to find the plugin.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will execute the command to generate a helm template, pipe the output into all.yaml and let Kustomize patch the output.
The &amp;#34;discovery&amp;#34; part is looking for a specific file in the repository. I thought this might be useful to pin down this plugin to specific repositories only.
However, there are other ways to implement this. You could omit this part and define the name of the plugin inside the Argo CD Application too for example.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic" start="4"&gt;
&lt;li&gt;
&lt;p&gt;Patching Argo CD Repo server&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now it is time to patch our repo server specification of the Argo CD custom resource.
The following should do it:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
As image for the sidecar container, I am using &lt;a href="https://quay.io/repository/gnunn/tools" target="_blank" rel="noopener"&gt;Gerald Nunn’s&lt;/a&gt; tool image. You can use your own image, as long as Helm and Kustomize are available.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1alpha1
kind: ArgoCD
metadata:
name: openshift-gitops
namespace: openshift-gitops
spec:
[...]
repo:
- configMap: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
name: my-cmp-plugin
name: my-cmp-plugin
sidecarContainers: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- name: my-cmp-plugin
command: [/var/run/argocd/argocd-cmp-server]
env:
- name: APP_ENV
value: prod
image: quay.io/gnunn/tools:latest &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
imagePullPolicy: Always
securityContext:
runAsNonRoot: true
volumeMounts: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- mountPath: /var/run/argocd
name: var-files
- mountPath: /home/argocd/cmp-server/plugins
name: plugins
- mountPath: /tmp
name: tmp
- mountPath: /home/argocd/cmp-server/config/plugin.yaml
subPath: plugin.yaml
name: my-cmp-plugin
volumes: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- configMap:
name: cloudbees-cmp-plugin
name: cloudbees-cmp-plugin&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the ConfigMap that was created in step 2.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The sidecar container specification.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The image that is used for the sidecar container.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The volume mounts for the sidecar container.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The volumes for the sidecar container.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As soon as the repo Pod has been patched a 2nd container inside the Pod will be started as a sidecar. This will take the ConfigMap that was created in step 2 and mount it. As soon as a repo is found where this patch shall be executed, Argo CD will perform the actions defined in the ConfigMap, resulting in the output of the helm template and the patched output of Kustomize.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: my-route
namespace: my-namespace
annotations: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
cert-manager.io/cluster-issuer: my-issuer
cert-utils-operator.redhat-cop.io/certs-from-secret: my-certificate
spec:
host: my.route.apps.cluster.name
port:
targetPort: http
tls:
insecureEdgeTerminationPolicy: Redirect
termination: edge
to:
kind: Service
name: my-service
weight: 100
wildcardPolicy: None&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The annotations that are added to the Route.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is it; this will patch our resource. Such post-renderer can be used for other patches as well. For example, to remove certain items from an object.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_2nd_example"&gt;2nd Example&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In my real-live example I had the problem that the &lt;strong&gt;path&lt;/strong&gt; was empty in the Helm Chart and OpenShift automatically removed that, which was shown as out-of-sync in Argo CD.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;So I am using the patch to remove the path.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Only do this if you are sure the element is really empty!
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I extended the kustomization.yaml with&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; - op: remove
path: /spec/path&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;so it looks like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: my-namespace
resources:
- ./all.yaml
patches:
- patch: |
- op: add
path: /metadata/annotations
value:
cert-manager.io/cluster-issuer: my-issuer
cert-utils-operator.redhat-cop.io/certs-from-secret: my-certificate
- op: remove &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
path: /spec/path
target:
kind: Route
name: my-route&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The patch that removes the path.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This 2nd patch will completely remove the /spec/path from the Route object named &lt;em&gt;my-route&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_further_information"&gt;Further information:&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Example, which was the base of my patch: &lt;a href="https://github.com/gitops-examples/argocd-operator-customization/tree/main/plugin-sidecar" target="_blank" rel="noopener"&gt;Plugin Sidecar&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;G.Nunn’s tools image (Thanks for everything): &lt;a href="https://quay.io/repository/gnunn/tools" class="bare"&gt;https://quay.io/repository/gnunn/tools&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.11] Managing Certificates</title><link>https://blog.stderr.at/gitopscollection/2024-07-04-managing-certificates-with-gitops/</link><pubDate>Thu, 04 Jul 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-07-04-managing-certificates-with-gitops/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;The article &lt;a href="https://blog.stderr.at/openshift/2023/02/ssl-certificate-management-for-openshift-on-aws/"&gt;SSL Certificate Management for OpenShift on AWS&lt;/a&gt; explains how to use the &lt;strong&gt;Cert-Manager Operator&lt;/strong&gt; to request and install a new SSL Certificate.
This time, I would like to leverage the GitOps approach using the Helm Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;cert-manager&lt;/a&gt; I have prepared to deploy the Operator and order new Certificates.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I will use an ACME Letsencrypt issuer with a DNS challenge. My domain is hosted at AWS Route 53.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, any other integration can be easily used.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_before_we_start"&gt;Before we start&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Before we start, be sure that Route 53 is configured correctly. The required settings and commands are described at &lt;a href="https://blog.stderr.at/openshift/2023/02/ssl-certificate-management-for-openshift-on-aws/#_configure_an_aws_user_for_accessing_route_53" target="_blank" rel="noopener"&gt;Configure an AWS User for Accessing Route 53&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_deploy_the_operator"&gt;Deploy the Operator&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first step is to deploy the Operator to our cluster. This is done using GitOps and the Helm Chart is located at my Helm repository: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/cert-manager" class="bare"&gt;https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/cert-manager&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The configuration looks like below. It takes care to:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Deploy the Operator &lt;strong&gt;cert-manager-operator&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify if the Operator has been deployed successfully&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure Cert-Manager&lt;/p&gt;
&lt;div class="olist loweralpha"&gt;
&lt;ol class="loweralpha" type="a"&gt;
&lt;li&gt;
&lt;p&gt;Create a ClusterIssuer using route53 integration. (You can configure any other configuration too)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Patch the Operator with &amp;#34;overrideArgs&amp;#34;. This is required for AWS Route 53 where we need to define which DNS resolvers shall be used.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All these settings are handed over to the appropriate sub-charts. Like &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;helper-operator&lt;/a&gt;, &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;helper-status-checker&lt;/a&gt; and &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;cert-manager&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# Install Operator Compliance Operator
# Deploys Operator --&amp;gt; Subscription and Operatorgroup
# Syncwave: 0
helper-operator: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
operators:
compliance-operator:
enabled: true
syncwave: &amp;#39;0&amp;#39;
namespace:
name: cert-manager-operator
create: true
subscription:
channel: stable-v1
approval: Automatic
operatorName: openshift-cert-manager-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
operatorgroup:
create: true
notownnamespace: false
helper-status-checker: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
enabled: true
checks:
- operatorName: cert-manager-operator
namespace:
name: cert-manager-operator
serviceAccount:
name: &amp;#34;status-checker-cert-manager&amp;#34;
cert-manager: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
certManager:
enable_patch: true
overrideArgs: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- &amp;#39;--dns01-recursive-nameservers-only&amp;#39;
- --dns01-recursive-nameservers=ns-362.awsdns-45.com:53,ns-930.awsdns-52.net:53
issuer: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- name: letsencrypt-prod
type: ClusterIssuer
enabled: true
syncwave: 20
acme:
email: tjungbau@redhat.com
solvers:
- dns01: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
route53:
accessKeyIDSecretRef:
key: access-key-id
name: prod-route53-credentials-secret
region: us-west-1
secretAccessKeySecretRef:
key: secret-access-key
name: prod-route53-credentials-secret
selector:
dnsZones:
- aws.ispworld.at&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Installing the Operator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Verify if the Operator has been successfully deployed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configure the Cert-Manager Operator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Override the DNS resolver args&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configure the ClusterIssuer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Use the solver dns01.route53.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the README of the Helm Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;cert-manager&lt;/a&gt; for additional possibilities in the configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;One additional piece is missing before we can finally start the deployment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see in the values file above the &lt;strong&gt;accessKey&lt;/strong&gt; and &lt;strong&gt;secretAccessKey&lt;/strong&gt; are stored in the secret named &lt;em&gt;prod-route53-credentials-secret&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This means, that a secret is required with the keys that have been provided by AWS when you configured the Route 53 access:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;kind: Secret
apiVersion: v1
metadata:
name: prod-route53-credentials-secret
namespace: cert-manager &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
data:
access-key-id: &amp;lt;AccessKey&amp;gt;
secret-access-key: &amp;lt;Secret Access Key&amp;gt;
type: Opaque&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace of the Secret, here the Operator is managing the Certificate Controller.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I stored this Secret as SealedSecret and put it into the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/cert-manager" target="_blank" rel="noopener"&gt;cluster configuration folder&lt;/a&gt;. From here, Argo CD will pick it up and deploy it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Never, never ever store a Secret object directly in Git. Secret objects are not encrypted but encoded. Everybody could decode the data. With Sealed Secrets or any other Secret Management, you are able to prepare these objects and store or retrieve them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Finally, with these settings, the Operator can be deployed. This is managed by OpenShift GitOps (Argo CD). As soon as the Operator is ready, we can start requesting certificates as we automatically created the &lt;strong&gt;ClusterIssuer&lt;/strong&gt; (letsencrypt-prod)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/cert-manager.png?width=640px" alt="Deploying and Configuring Cert-Manager Operator"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Deploying and Configuring Cert-Manager Operator&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Two certificates are of special interest :&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Default IngressController of OpenShift&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenShift’s API&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Therefore, let’s request and configure them.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_requesting_a_certificate"&gt;Requesting a Certificate&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;cert-manager&lt;/a&gt; can render a Certificate resource as well. I tried to support any possible setting. However, not everything, especially the non-stable ones, is available yet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The official Cert-Manager documentation explains how to create such &lt;a href="https://cert-manager.io/docs/usage/certificate/" target="_blank" rel="noopener"&gt;Certificate Resource&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The chart &lt;a href="https://github.com/tjungbauer/helm-charts/blob/main/charts/cert-manager/values.yaml#L112-L269" target="_blank" rel="noopener"&gt;README&lt;/a&gt; explains which settings are supported by the chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Not every setting is required and some will set default values. The minimum parameters are: name, namespace, secretName, dnsNames and reference to an issuer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_requesting_ingresscontroller_certificate"&gt;Requesting IngressController Certificate&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The default IngressController of OpenShift listens on the wildcard domain *.apps.clustername.basedomain. In my examples, you will see *.apps.ocp.aws.ispworld.at&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The IngressController configuration must be modified to reference the Secret object the cert-manager will generate once the Certificate has been successfully requested. The cluster configuration &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/ingresscontroller/values.yaml" target="_blank" rel="noopener"&gt;Ingresscontroller&lt;/a&gt; defines the required parameters:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;---
# -- Define ingressControllers
# Multiple might be defined.
ingresscontrollers: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# -- Name of the IngressController. OpenShift initial IngressController is called &amp;#39;default&amp;#39;.
- name: default
# -- Enable the configuration
# @default -- false
enabled: true
# -- Number of replicas for this IngressController
# @default -- 2
replicas: 3
# -- The name of the secret that stores the certificate information for the IngressController
# @default -- N/A
defaultCertificate: router-certificate &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
# -- Bind IngressController to specific nodes
# Here as example for Infrastructure nodes.
# @default -- empty
#nodePlacement:
# NodeSelector that shall be used.
# nodeSelector: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
# key: node-role.kubernetes.io/infra
# value: &amp;#39;&amp;#39;
# # -- Tolerations, required if the nodes are tainted.
# tolerations:
# - effect: NoSchedule
# key: node-role.kubernetes.io/infra
# operator: Equal
# value: reserved
# - effect: NoExecute
# key: node-role.kubernetes.io/infra
# operator: Equal
# value: reserved
certificates:
enabled: true
# List of certificates
certificate: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- name: router-certificate
enabled: true
namespace: openshift-ingress
syncwave: &amp;#34;0&amp;#34;
secretName: router-certificate &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
dnsNames: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- apps.ocp.aws.ispworld.at
- &amp;#39;*.apps.ocp.aws.ispworld.at&amp;#39;
# Reference to the issuer that shall be used.
issuerRef: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
name: letsencrypt-prod
kind: ClusterIssuer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration for the IngressController&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Reference to the Secret that will store the Certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Optional tolerations that can be configured for the IngressController&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of Certificates to order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of domainnames for the IngressController. Here 2 are used, the wildcard domain and the base domain of that wildcard.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Reference to the issuer (in this case ClusterIssuer)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will request the Certificate:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: router-certificate
namespace: openshift-ingress
spec:
dnsNames:
- apps.ocp.aws.ispworld.at
- &amp;#39;*.apps.ocp.aws.ispworld.at&amp;#39;
duration: 2160h0m0s
issuerRef:
kind: ClusterIssuer
name: letsencrypt-prod
privateKey:
algorithm: RSA
encoding: PKCS1
rotationPolicy: Always
secretName: router-certificate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
It may take a while until the Certificate request is approved.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The IngressController will update the reference to the secret and restart the ingress pods:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
name: default
namespace: openshift-ingress-operator
spec:
[...]
defaultCertificate:
name: router-certificate&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once all pods have been successfully restarted, open a new browser, or reload or open a private window to verify the certificate that is provided by the application.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_requesting_apiserver_certificate"&gt;Requesting APIServer Certificate&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Requesting the certificate for the OpenShift API follows the same rules as for the IngressController.
The example can be found at: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/clusterconfig-apiserver/values.yaml" target="_blank" rel="noopener"&gt;Clusterconfig APIServer&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The values file may look like the following for example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
In this case, not only the Certificate is requested but the ETCD encryption is also enabled. The reason for that is, that both settings are done in the same Kubernetes resource (apiserver). If we split this up into 2 Argo CD Applications one of them will always show a warning that the same resource is managed by another Argo CD Application.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;---
# -- Using subchart generic-cluster-config
generic-cluster-config:
apiserver: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
enabled: true
# audit configuration
audit:
profile: Default
# Configure a custom certificate for the API server
custom_cert:
enabled: true
cert_names: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- api.ocp.aws.ispworld.at
secretname: api-certificate &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
etcd_encryption: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
enabled: true
encryption_type: aesgcm &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
# -- Namespace where Job is executed that verifies the status of the encryption
namespace: kube-system
serviceAccount:
create: true
name: &amp;#34;etcd-encryption-checker&amp;#34; &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
cert-manager: &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
enabled: true
certificates:
enabled: true
# List of certificates
certificate:
- name: api-certificate
enabled: true
namespace: openshift-config
syncwave: &amp;#34;0&amp;#34;
secretName: api-certificate
dnsNames:
- api.ocp.aws.ispworld.at
# Reference to the issuer that shall be used.
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Settings for the APIServer object.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the domain the certificate will be responsible for.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Reference to the Secret that will store the Certificate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable ETCD encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Tpee of encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Service Account that will be created and used by a Job that will verify when and if the encryption has been finished successfully.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Settings for the Certificate. Similar to the settings of the IngressController.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The configuration is more or less similar to the IngressController. Again the APIServer will restart and once done, the Certificate is used by the cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this, very short, article I have tried to easily explain how to deploy the Cert-Manager Operator and request Certificates. Different Helm Charts are used, but the main one is &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/cert-manager" target="_blank" rel="noopener"&gt;cert-manager&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The cluster configuration repository &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" class="bare"&gt;https://github.com/tjungbauer/openshift-clusterconfig-gitops&lt;/a&gt; then use this chart to configure the required resources.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With the support of this Helm Chart anybody in the Cluster can request Certificates which are then managed by the Cert-Manager Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.10] Update Cluster Version</title><link>https://blog.stderr.at/gitopscollection/2024-06-07-update-cluster-version-with-gitops/</link><pubDate>Fri, 07 Jun 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-06-07-update-cluster-version-with-gitops/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;During a GitOps journey at one point, the question arises, how to update a cluster? Nowadays it is very easy to update a cluster using CLI or WebUI, so why bother with GitOps in that case? The reason is simple: Using GitOps you can be sure that all clusters are updated to the correct, required version and the version of each cluster is also managed in Git.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All you need is the &lt;strong&gt;channel&lt;/strong&gt; you want to use and the desired cluster &lt;strong&gt;version&lt;/strong&gt;. Optionally, you can define the exact image SHA. This might be required when you are operating in a restricted environment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_overview"&gt;Overview&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since OpenShift 4 it is very easy to update a cluster. It can either be done using the Web UI &lt;strong&gt;Administration → Cluster Settings&lt;/strong&gt; or via the command line using the command &lt;code&gt;oc adm upgrade&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;There are 2 main configurations for an upgrade:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;channel&lt;/strong&gt;: This declares the update strategy tied to versions of OpenShift.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;strong&gt;desired version&lt;/strong&gt;: This is the target version the cluster should be updated to.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To get the current clusterversion you can use the following command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;❯ oc get clusterversion
NAME VERSION AVAILABLE PROGRESSING SINCE STATUS
version 4.14.1 True False 4m38s Cluster version is 4.14.1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Here we can see that the version is currently &lt;strong&gt;4.14.1&lt;/strong&gt; and no upgrade is currently progressing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we want to update to the latest possible version.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_where_to_find_the_available_updates"&gt;Where to find the available updates?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before you start the update, you will need to fetch the possible available updates.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This information can be gathered with the command &lt;code&gt;oc adm upgrade&lt;/code&gt; or &lt;code&gt;oc get clusterversion/version -o yaml&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The output will look like the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;Cluster version is 4.14.1
Upstream is unset, so the cluster will use an appropriate default.
Channel: stable-4.14 (available channels: candidate-4.14, eus-4.14, fast-4.14, stable-4.14, candidate-4.15, fast-4.15, stable-4.15) &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
Recommended updates: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
VERSION IMAGE
4.14.27 quay.io/openshift-release-dev/ocp-release@sha256:4d30b359aa6600a89ed49ce6a9a5fdab54092bcb821a25480fdfbc47e66af9ec
4.14.26 quay.io/openshift-release-dev/ocp-release@sha256:4fe7d4ccf4d967a309f83118f1a380a656a733d7fcee1dbaf4d51752a6372890
[...]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Current Channel and available channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of available updates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The command describes the current channel (stable-4.14) and a list of possible updates.
(The list is much longer).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The latest available version is &lt;strong&gt;4.14.27&lt;/strong&gt; and the list of possible channels is all the 4.14 channels, but also 4.15 channels. This means we could change the channel here as well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
It is your responsibility to find the &lt;strong&gt;correct and supported upgrade paths&lt;/strong&gt;. Not all upgrades to any version are supported. Also, do not rely on consecutive path numbers, some versions were never available.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_channels"&gt;Channels&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Channels help users to define the timing and level of support for their environment. The following channels typically exist:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;fast&lt;/strong&gt;: This channel is fully supported but not yet fully tested and can be used to quickly update to the latest GA release. For example, when a certain bug is triggered in the current version.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;stable&lt;/strong&gt;: This is the latest stable version. Versions here are added after enough data points have been collected and therefore have some delay.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;candidate&lt;/strong&gt;: This channel offers &lt;strong&gt;unsupported&lt;/strong&gt; early access to specific versions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;eus&lt;/strong&gt;: All even-numbered versions offer an Externed Update Support (EUS) channel. They allow EUS-to-EUS updates and have a longer support phase.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_helm_chart_update_clusterversion"&gt;Helm Chart - update-clusterversion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now we want to update the version to the latest of the current channel &lt;strong&gt;4.14.27&lt;/strong&gt; and change the channel to &lt;strong&gt;stable-4.15&lt;/strong&gt;.
The Helm Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/update-clusterversion" target="_blank" rel="noopener"&gt;update-clusterversion&lt;/a&gt; has been created to help with a cluster update.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It will modify the clusterversion resource.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The required values are quite simple:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;channel: stable-4.15
desiredVersion: 4.14.27
image: &amp;#39;&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Be sure that you choose the correct and available updates.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is everything we need. As soon as this is synchronized into the cluster, the update process will be started by the cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_gitops_synchronization"&gt;GitOps Synchronization&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;When we verify the current version in the OpenShift UI, we will see the possibility of an upgrade:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/clusterversion.png?width=720px" alt="Cluster before the update process starts"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Cluster before the update process starts&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In OpenShift GitOps we have the Application to start the update process:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/clusterversion-sync.png?width=720px" alt="Syncing new version"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. Syncing new version&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After a few seconds OpenShift will start with the update:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/clusterversion-update-progressing.png?width=720px" alt="Progressing Cluster Update"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 3. Progressing Cluster Update&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This can also be verified via the command line&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;❯ oc get clusterversion
NAME VERSION AVAILABLE PROGRESSING SINCE STATUS
version 4.14.1 True True 95s Working towards 4.14.27: 116 of 860 done (13% complete), waiting on etcd, kube-apiserver&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Eventually, the cluster update process finishes successfully. We are now on version &lt;strong&gt;4.14.27&lt;/strong&gt; and using the channel &lt;strong&gt;stable-4.15&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We can see that the next upgrade with be to version 4.15.15. This means we can directly upgrade to this version. This is possible because we switched the channel to stable-4.15.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Other channels, like candidate-4.15 might offer different, but not recommended, versions.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;❯ oc adm upgrade
Cluster version is 4.14.27
Upstream is unset, so the cluster will use an appropriate default.
Channel: stable-4.15 (available channels: candidate-4.14, candidate-4.15, eus-4.14, fast-4.14, fast-4.15, stable-4.14, stable-4.15)
Recommended updates:
VERSION IMAGE
4.15.15 quay.io/openshift-release-dev/ocp-release@sha256:bb1182cd9001d6811dea8c5823235c17b9a316cce3bb13c51325250c14b46787&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_what_about_the_sha_for_the_image"&gt;What about the SHA for the image?&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The SHA for the image field in the ClusterVersion resource is required in certain scenarios to provide a precise reference to the container image that represents the OpenShift version you want to update to.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Such scenarios could be for example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Offline or Restricted Networks
In environments where clusters are running in offline or restricted network conditions, specifying the exact image SHA ensures that the cluster updates to a specific, known image that has been pre-pulled and is available within the network.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Precise Version Control
Using the SHA ensures that the exact image version is used for the update, providing a higher level of precision and control.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Custom or Private Registries
If you are using custom or private container registries, specifying the image SHA can help avoid ambiguities and ensure that the correct image is pulled from the correct registry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Specific Compliance or Security Requirements
Some regulations might require precise specification of container images, including SHAs, to ensure traceability and verifiability of the software components being deployed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this very simple method, it is easy to manage the version of multiple clusters via GitOps. Especially, where there is a bigger cluster fleet it will become essential to ensure which cluster has which version.
Disconnected environments can also use the &lt;strong&gt;image&lt;/strong&gt; setting to specify the exact SHA of an available update.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The current Helm Chart is very small and limited. It was created to quickly show the main use case: a simple and straightforward cluster upgrade.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Other possible options that might be required in the future. If you find anything missing, please let me know.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Please note, to always verify which version and channel is available and always consult the official documentation before an upgrade to find the latest release notes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;References: &lt;a href="https://docs.openshift.com/container-platform/4.15/updating/understanding_updates/intro-to-updates.html" target="_blank" rel="noopener"&gt;Updating Clusters&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.9] Multiple Sources for Applications</title><link>https://blog.stderr.at/gitopscollection/2024-06-02-multisources-for-application-in-argocd/</link><pubDate>Sun, 02 Jun 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-06-02-multisources-for-application-in-argocd/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Argo CD or OpenShift GitOps uses Applications or ApplicationSets to define the relationship between a source (Git) and a cluster. Typically, this is a 1:1 link, which means one Application is using one source to compare the cluster status. This can be a limitation. For example, if you are working with Helm Charts and a Helm repository, you do not want to re-build (or re-release) the whole chart just because you made a small change in the values file that is packaged into the repository. You want to separate the configuration of the chart with the Helm package.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The most common scenarios for multiple sources are (see: &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/multiple_sources/" target="_blank" rel="noopener"&gt;Argo CD documentation&lt;/a&gt;):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Your organization wants to use an external/public Helm chart&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want to override the Helm values with your own local values&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You don’t want to clone the Helm chart locally as well because that would lead to duplication and you would need to monitor it manually for upstream changes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This small article describes three different ways with a working example and tries to cover the advantages and disadvantages of each of them. They might be opinionated but some of them proved to be easier to use and manage.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_option_1_multisource_by_argo_cd"&gt;Option 1: Multisource by Argo CD&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Specifying multiple sources for an application is a &lt;strong&gt;beta feature&lt;/strong&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first option I would like to demonstrate is the &amp;#34;official way&amp;#34; by Argo CD. It was introduced in Argo CD version 2.6 and while this option is still in the Beta phase, it is one of the most requested features and was added to the release candidate of version 2.11. The &lt;a href="https://blog.argoproj.io/argo-cd-v2-11-release-candidate-b83ba3008ba5" target="_blank" rel="noopener"&gt;article by Argo CD&lt;/a&gt; gives more details of this release. The official documentation by Argo CD can be found &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/multiple_sources/" target="_blank" rel="noopener"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In my &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/base/init_app_of_apps/" target="_blank" rel="noopener"&gt;repository&lt;/a&gt; I am using this option as an example to create the App-of-Apps. The App-of-Apps created an Application, that uses multiple sources.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;The values files from &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/base/argocd-resources-manager/values.yaml" class="bare"&gt;https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/base/argocd-resources-manager/values.yaml&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Helm Chart &lt;strong&gt;helper-argocd&lt;/strong&gt; from &lt;a href="https://charts.stderr.at/" class="bare"&gt;https://charts.stderr.at/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s see the working example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd-resources-manager
namespace: openshift-gitops
spec:
destination:
namespace: openshift-gitops
server: &amp;#39;https://kubernetes.default.svc&amp;#39;
info:
- name: Description
value: &amp;gt;-
This is the starting point which will initialize all applicationsets or
argocd applications
project: default
sources: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
- chart: helper-argocd &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
helm:
valueFiles: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
- $values/base/argocd-resources-manager/values.yaml
repoURL: &amp;#39;https://charts.stderr.at/&amp;#39; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
targetRevision: 2.0.28 &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
- ref: values &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39;
targetRevision: main
syncPolicy:
automated:
prune: true
selfHeal: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Using &lt;strong&gt;sources&lt;/strong&gt; instead of singular source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;I want to use the Helm Chart &lt;strong&gt;&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-argocd" target="_blank" rel="noopener"&gt;helper-argocd&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The chart will use the values file(s) from github.com/tjungbauer/openshift-clusterconfig-gitops/base/argocd-resources-manager/values.yaml. $value (can only be specified at the beginning of the path) resolves to the root of the values file repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The URL of the Helm Chart repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The version of the Helm chart helper-argocd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The reference to the values file, defining the repository URL and target revision.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Argo CD does not currently support using another Helm chart as a source for value files.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this option, it is possible to separate the values files from the Helm Chart itself. Whenever I want to change something in the configuration, I simply change the values file without being required to release a new Chart version.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Allows easily to define an Application with multiple sources, using Argo CD features.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No additional tool (see other options below) is required&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Was added to the release candidate of Argo CD v2.11&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Currently a Beta feature and thus is not supported and has some limitations, such as lacking support of CLI and UI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Does not allow using additional local specifications. At least as far as I know.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can be complex to configure and manage&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_option_2_wrapper_helm_chart"&gt;Option 2: Wrapper Helm Chart&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this option, which I extensively use, a wrapper Helm Chart is used to define local values files while calling additional (sub) Helm Charts as a dependency. This wrapper could simply define the values files and nothing else (being &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/#_why_empty_helm_charts/" target="_blank" rel="noopener"&gt;empty instead&lt;/a&gt;) or even define additional files, such as Sealed Secrets or things that are not provided by the sub-charts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An example of an empty chart would be: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/setup-acs" target="_blank" rel="noopener"&gt;setup-acs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An example of a chart that defines additional local files would be: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/generic-clusterconfig" target="_blank" rel="noopener"&gt;generic-clusterconfig&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first one is using sub-charts to build the required specifications:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;dependencies:
- name: rhacs-setup
version: ~1.0.0
repository: https://charts.stderr.at/
- name: helper-operator
version: ~1.0.23
repository: https://charts.stderr.at/
- name: helper-status-checker
version: ~4.0.0
repository: https://charts.stderr.at/
condition: helper-status-checker.enabled&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-acs/values.yaml" target="_blank" rel="noopener"&gt;values file&lt;/a&gt; specifies the configuration for these sub-charts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The second example also uses sub-charts, but additionally defines local files such as a SealedSecret for htpasswd.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see throughout my &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster" target="_blank" rel="noopener"&gt;repository&lt;/a&gt; I am using this option almost all the time. It proved to be quite simple, especially if you prefer working with Helm Charts such as I do.
However, you must take care of the settings in the values file. Specifications you would like to be presented in a sub-chart must be put into the correct place.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Everything underneath&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-operator:
operators:
rhacs-operator:
[...]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;will be used by the chart &lt;strong&gt;helper-operator&lt;/strong&gt;. While everything underneath:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-status-checker:
enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;will be used by the chart &lt;strong&gt;helper-status-checker&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Easy to use, at least for myself&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allows defining additional, local files that are not provided by the sub-charts&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A wrapper Chart must be created, that at least defines: Chart.yaml, templates folder and values.yaml&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The configuration must be done correctly and all settings for a sub-chart must be forwarded to the sub-chart.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_option_3_using_kustomize_with_helm_enabled"&gt;Option 3: Using Kustomize with Helm enabled&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The third option I would like to show is using Kustomize. This tool can be used to call a Helm Chart when the option &lt;strong&gt;--enable-helm&lt;/strong&gt; is activated in Argo CD. I am using one example for the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/ingresscontroller" target="_blank" rel="noopener"&gt;IngressController&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Here the values file is placed into the local folder and the Kustomize.yaml is configured as:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
helmCharts:
- name: ingresscontroller
repo: https://charts.stderr.at
valuesFile: values.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This simply defines the use of the chart &lt;strong&gt;ingresscontroller&lt;/strong&gt; from the Helm repository with the local values file.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Unlike Option #2 you do not need to take care about the settings for sub-charts and which settings are passed to which chart. Like Option #2 you can also define additional files that shall be rendered using out-of-the-box Kustomize possibilities.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Easy to use&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allows defining additional, local files that are not provided by the sub-charts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No need to take care of sub-charts and correctly pass the settings to a sub-chart&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;combines two different tools, which might become confusing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;requires specific option to be enabled &lt;strong&gt;--enable-helm&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As this short article demonstrates, there are multiple ways to work with multiple sources and therefore to separate the values file from the actual Helm Chart. There might be even more options, but these are the ones I was seeing at customers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Which one do I use? When you look at my &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" target="_blank" rel="noopener"&gt;repository&lt;/a&gt; you see that I mainly use Option #2. Actually, I completely moved from Option #3 to Option #2 a few months ago, because this proved to be clearer for customers, especially when they are new to Kustomize and Helm. That way, only one tool is used and must be managed. Option #3 proved to be more complex in such a case.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;What about Option #1? While I am using it to showcase this feature it is still in a TechPreview phase. However, I do not think that it will completely replace the other options, because it is more complex to configure.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;However, in the end, it is all about personal preferences. Use the tool that you feel most comfortable with :).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.8] Installing OpenShift Logging</title><link>https://blog.stderr.at/gitopscollection/2024-05-24-install-openshift-logging/</link><pubDate>Fri, 24 May 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-05-24-install-openshift-logging/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;a href="https://docs.openshift.com/container-platform/4.15/observability/logging/logging_release_notes/logging-5-9-release-notes.html" target="_blank" rel="noopener"&gt;OpenShift Logging&lt;/a&gt; is one of the more complex things to install and configure on an OpenShift cluster. Not because the service or Operators are so complex to understand, but because of the dependencies logging has. Besides the logging operator itself, the Loki operator is required, the Loki operator requires access to an object storage, that might be configured or is already available.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this article, I would like to demonstrate the configuration of the full stack using an object storage from OpenShift Data Foundation. This means:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Installing the logging operator into the namespace openshift-logging&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Installing the Loki operator into the namespace openshift-operators-redhat&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creating a new BackingStore and BucketClass&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generating the Secret for Loki to authenticate against the object storage&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configuring the LokiStack resource&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configuring the ClusterLogging resource&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All steps will be done automatically. In case you have S3 storage available, or you are not using OpenShift Data Foundation, the setup will be a bit different. For example, you do not need to create a BackingStore or the Loki authentication Secret.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;OpenShift 4&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Argo CD (OpenShift GitOps) deployed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OpenShift Data Foundation (ODF) deployed and ready to provide object storage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enough available compute resources to deploy LokiStack. Verify the official &lt;a href="https://docs.openshift.com/container-platform/4.15/observability/logging/log_storage/installing-log-storage.html" target="_blank" rel="noopener"&gt;OpenShift Logging documentation&lt;/a&gt; to see which option might need which resources.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
For ODF it would be enough to deploy object storage only, instead of the full storage stack based on Ceph. In this case, the so-called &lt;strong&gt;MultiCloudObjectGateway&lt;/strong&gt; option is used, which creates (virtualizes) object storage on top of an existing StorageClass
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If ODF object storage based on Noobaa should be used, then it makes sense to think about the data retention process, which will take care of removing old data from the storage. It is recommended to configure this directly on the object storage, because this is much more compute-friendly, then letting OpenShift Logging take care of that. The configuration depends on the object storage vendor. In the case of Noobaa, I have prepared a separate article: &lt;a href="https://blog.stderr.at/openshift/2024/02/openshift-data-foundation-noobaa-bucket-data-retention-lifecycle/"&gt;Noobaa Bucket Data Retention Lifecycle&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_introduction"&gt;Introduction&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The main resources of OpenShift Logging are the three custom resources: &lt;strong&gt;ClusterLogging&lt;/strong&gt;, &lt;strong&gt;ClusterLogForwarder&lt;/strong&gt; and &lt;strong&gt;LokiStack&lt;/strong&gt;. The first two are provided by the OpenShift Logging Operator, the last one is provided by the Loki Operator. ClusterLogForwarder is an optional configuration. It allows us to forward logs to external destinations, such as Splunk, or to forward the OpenShift audit logs to Loki. (They are not stored by default). The LokiStack resource requires an available object storage to be able to start its workloads.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In my case, I would like to configure everything automatically. This means, that I also want to configure the object or S3 storage and create the required authentication secret for Loki without manual intervention. This can be easily done using ODF.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/"&gt;Configure App-of-Apps&lt;/a&gt; installed an Argo CD Application called &lt;strong&gt;in-cluster-setup-openshift-logging&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup-openshift-logging.png?width=720px" alt="Argo CD Application: setup-openshift-logging"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Argo CD Application: setup-openshift-logging&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This Argo CD Application uses the following path to find the Helm Chart: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-openshift-logging" target="_blank" rel="noopener"&gt;setup-openshift-logging&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This Helm chart is a &lt;strong&gt;wrapper chart&lt;/strong&gt; that uses sub-charts as dependencies to install and configure the operator as well as to do some OpenShift Jobs on top, for example, creating the required Secret for LokiStack.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The deployment workflow will go through the sub-charts and look like the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/logging-deployment-flow.png" alt="Deployment Workflow"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. Deployment Workflow&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While this looks quite huge and complex, the idea of the sub-charts is quite simple: Do a small specific task, that can be reused by other charts. For example, the NetworkObservability Operator also required an object storage and Loki. I can easily reuse the sub-charts without repeating the logic behind them.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installing_openshift_logging_stack"&gt;Installing OpenShift Logging Stack&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_analyzing_chart_yaml"&gt;Analyzing Chart.yaml&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s examine the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-openshift-logging/Chart.yaml" target="_blank" rel="noopener"&gt;Chart.yaml&lt;/a&gt; file to see which dependencies are used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The file looks like the following. The Chart has a lot of dependencies on sub-charts, that have been created to make specific, small and defined operations re-useable for multiple Charts. A total number of 6 sub-charts are used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;dependencies:
- name: helper-operator &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
version: ~1.0.18
repository: https://charts.stderr.at/
- name: helper-status-checker &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
version: ~4.0.0
repository: https://charts.stderr.at/
condition: helper-status-checker.enabled
- name: openshift-logging &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
version: ~2.0.0
repository: https://charts.stderr.at/
- name: helper-loki-bucket-secret &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
version: ~1.0.0
repository: https://charts.stderr.at/
condition: helper-loki-bucket-secret.enabled
- name: helper-objectstore &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
version: ~1.0.0
repository: https://charts.stderr.at/
condition: helper-objectstore.enabled
- name: helper-lokistack &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
version: ~1.0.0
repository: https://charts.stderr.at/
condition: helper-lokistack.enabled&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Status Checker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/openshift-logging" target="_blank" rel="noopener"&gt;OpenShift Logging&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-loki-bucket-secret" target="_blank" rel="noopener"&gt;Helper Loki Bucket Secret&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-objectstore" target="_blank" rel="noopener"&gt;Helper Objectstore&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-lokistack" target="_blank" rel="noopener"&gt;Helper Lokistack&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the READMEs of the different Charts for detailed information on how to configure them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configuration_of_the_chart"&gt;Configuration of the Chart&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To configure OpenShift Logging the &lt;strong&gt;&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-openshift-logging/values.yaml" target="_blank" rel="noopener"&gt;values file&lt;/a&gt;&lt;/strong&gt; of the wrapper Chart must be prepared accordingly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The important thing here is, that any value that should be bypassed to a sub-chart is defined under the name of the sub-chart. For example, everything under &lt;strong&gt;helper-operator:&lt;/strong&gt; will be sent to the helper-operator Chart and is used there for its configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s walk through the configuration for each sub-chart in the order they are required:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_installing_the_operator"&gt;Installing the Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first thing to do is to deploy the Operators themselves. For OpenShift Logging two Operators are required:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;OpenShift Logging&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Loki&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Loki might be installed already due to a different dependency. Maybe you have deployed the Network Observability Operator previously. In that case, OpenShift Logging is required only.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Helm Chart &lt;strong&gt;&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;helper-operator&lt;/a&gt;&lt;/strong&gt; is responsible for deploying the Operators. In the following example, I will deploy both Operators (Logging and Loki) and enable the console plugin for the OpenShift Logging operator:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The console plugin will only work when the whole stack, this means when Logging itself, has been rolled out.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-operator:
console_plugins: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
enabled: true
plugins: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- logging-view-plugin
operators:
cluster-logging-operator: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
syncwave: &amp;#39;0&amp;#39; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
namespace: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
name: openshift-logging
create: true
subscription: &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
channel: stable
source: redhat-operators
approval: Automatic
operatorName: cluster-logging
sourceNamespace: openshift-marketplace
operatorgroup: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
create: true
notownnamespace: false
loki-operator: &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
enabled: true
namespace: &lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;(10)&lt;/b&gt;
name: openshift-operators-redhat
create: true
subscription: &lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;(11)&lt;/b&gt;
channel: stable-5.8
approval: Automatic
operatorName: loki-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
operatorgroup: &lt;i class="conum" data-value="12"&gt;&lt;/i&gt;&lt;b&gt;(12)&lt;/b&gt;
create: true
notownnamespace: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Activate Console Plugin. This will trigger a Kubernetes Job, that will modify the current list of console plugins and add the new plugin to it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of plugins that should be added by the Job. The name of that plugin must be known. In the case of OpenShift Logging it is called &lt;strong&gt;logging-view-plugin&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Key of the first operator: &lt;strong&gt;cluster-logging-operator&lt;/strong&gt;. Everything below here will define the settings for the Logging Operator.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Is this Operator enabled yes/no.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Syncwave for the Operator deployment. (Subscription and OperatorGroup etc.) This should be early enough for other tasks.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Namespace where the Operator shall be deployed and if this namespace shall be created.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the Subscription resource. This defines the channel (version) that shall be used and whether the approval of the installPlan shall happen automatically or not.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the OperatorGroup. Typically, you will need one when you create a new Namespace. &lt;em&gt;Notownnamespace&lt;/em&gt; defines whether or not the targetNamespace is configured for this Operator or if the Operator is available in any Namespace.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Key of the second Operator: &lt;strong&gt;loki-operator&lt;/strong&gt;. Everything below here will define the settings for the Logging Operator.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Namespace where the Operator shall be deployed, must be &lt;strong&gt;openshift-operators-redhat&lt;/strong&gt; and if this namespace shall be created.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;11&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the Subscription resource. This defines the channel (version) that shall be used and whether the approval of the installPlan shall happen automatically or not.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="12"&gt;&lt;/i&gt;&lt;b&gt;12&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the OperatorGroup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The &lt;strong&gt;approval&lt;/strong&gt; setting can either be &lt;em&gt;Automatic&lt;/em&gt; or &lt;em&gt;Manual&lt;/em&gt;. If the Operator requires approval to be installed, then this must either be done manually (via WebUI or CLI) or using the &lt;strong&gt;helper-status-checker&lt;/strong&gt; chart which automatically can approve existing installPlans (explained in the next section). This is helpful, to automatically deploy the first version of the Operator without the need for manual intervention.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt; to find additional possible configurations. Also, verify the separate article &lt;a href="https://blog.stderr.at/openshift/2023/03/operator-installation-with-argo-cd/"&gt;Operator Installation with Argo CD&lt;/a&gt; to understand why I am verifying the status of the Operator installation.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verifying_the_operator_deployment"&gt;Verifying the Operator Deployment&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;An Operator deployment can take some time and before you continue to configure the operator’s CRDs you must be sure that the installation finished successfully. Otherwise, the synchronization in Argo CD will fail because the CRD is not ready.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;There are mainly two tactics to really verify the status of the Operator:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Simply retry a failed sync in Argo CD. This can be done automatically x-times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify if the Operator installation succeeded by starting a Kubernetes Job that monitors the status.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
(Custom) Health checks in Argo CD proved to be not 100% accurate because sometimes the Operator says it is &amp;#34;Ready&amp;#34; but the CRD still cannot be configured for some seconds. Looking at you Compliance Operator …​.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I chose the second option, simply because I could also add a second Job that approved pending installPlans in case the deployment was set to manual approval.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Helm Chart &lt;strong&gt;&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;helper-status-checker&lt;/a&gt;&lt;/strong&gt; has two main purposes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Start a Kubernetes Job to verify the status of one or multiple Operator installation(s)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optional: start a Kubernetes Job to approve the installPlan(s)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;An example configuration, that verifies two Operators, looks like the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-status-checker:
enabled: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
approver: false &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
# List of checks that shall be performed.
checks:
- operatorName: cluster-logging &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
# -- OPTIONAL: Name of subscription that shall be approved. In some cases the name of the Subscription is different to the name of the operator.
# @default --operatorName
subscriptionName: cluster-logging-operator &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
namespace: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
name: openshift-logging
serviceAccount: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
name: &amp;#34;status-checker-logging&amp;#34;
- operatorName: loki-operator &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
namespace:
name: openshift-operators-redhat
serviceAccount:
name: &amp;#34;status-checker-loki&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable the status checker.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable the installPlan approver. Only required if the approval strategy for an Operator is set to &lt;em&gt;Manual&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Verify the status of the first Operator &lt;strong&gt;cluster-logging&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Sometimes the name of the Subscription differs from the Operator name. Logging is such a case. To be able to find which Subscription should be verified, the subscriptionName must be defined here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace for OpenShift Logging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the ServiceAccount that will be created to verify the status of the logging operator.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Settings for the 2nd operator: Loki. This one is running in a different Namespace and must be verified there.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt; to find additional possible configurations.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At this stage, the Operators have been deployed and they have been verified if the deployment was finished successfully.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now the real complex part can start…​&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_creating_a_new_backingstore_for_openshift_data_foundation"&gt;Creating a new BackingStore for OpenShift Data Foundation&lt;/h3&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If you want to use a different storage solution or you have a bucket already, you can skip this section and simply create the LokiStack Secret manually.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the case that ODF is used and a BackingStore together with a BucketClass shall be created another sub-chart called &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-objectstore" target="_blank" rel="noopener"&gt;Helper ObjectStore&lt;/a&gt; can be used.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It will help you to create a:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;BackingStore&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BucketClass&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;StorageClass&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;BucketClaim&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This fully automates the creation of the bucket and the required Class when using ODF. As a prerequisite, OpenShift Data Foundation (ODF) must be configured and available of course.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This is completely optional. If you want to use a different storage solution and have the buckets ready, you can simply create the Secret that Loki requires to authenticate at the storage. In this case, you can ignore this and the next section.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following example will create a BackingStore with the size of 700Gi for our OpenShift Logging. A bucket named &lt;strong&gt;logging-bucket&lt;/strong&gt; is created and can be used to store the logs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-objectstore:
enabled: true
syncwave: 1 &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
backingstore_name: logging-backingstore &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
backingstore_size: 700Gi &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
limits_cpu: 500m &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
limits_memory: 2Gi
pvPool: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
numOfVolumes: 1
type: pv-pool
baseStorageClass: gp3-csi &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
storageclass_name: logging-bucket-storage-class &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
bucket: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
enabled: true
name: logging-bucket
namespace: openshift-logging
syncwave: 2
storageclass: logging-bucket-storage-class&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Syncwave to create the BackingStore.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Backingstore.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Size of the BackingStore. 700Gi is good enough for testing Logging. Keep in mind that data retention should be configured separately for &lt;a href="https://blog.stderr.at/openshift/2024/02/openshift-data-foundation-noobaa-bucket-data-retention-lifecycle/"&gt;Noobaa&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Limit for CPU and Memory for the Noobaa (BackingStore) pod. They might need to be adjusted since the original ones are quite small for bigger buckets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Pool of Persistent Volumes. Currently &lt;strong&gt;pv-pool&lt;/strong&gt; is supported by the chart only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The basic storage class that shall be used to virtualize ODF object storage on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the StorageClass that will be created and used by the BackingStore.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The configuration of the Bucket and its namespace and storageClass (defined at &amp;lt;7&amp;gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Eventually, the BackingClass and the BucketClaim are created and ready.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/logging-objectstore.png?width=720px" alt="Ready BackingStore and bound BucketClaim"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 3. Ready BackingStore and bound BucketClaim&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_custom_argo_cd_health_check_for_backingstore"&gt;Custom Argo CD Health Check for BackingStore&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The creation of the BackingStore is a process that will take several minutes. Storage must be prepared, and several services must be started. To let Argo CD wait until the BackingStore is fully operational, instead of blindly continuing with the deployment of Loki and Logging, a custom &lt;strong&gt;Health Check&lt;/strong&gt; in Argo CD might help.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following health check should be placed into the Argo CD resource. Be aware, that there might be others already defined.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The status of the BackingStore resource inside Argo CD will continue &lt;em&gt;progressing&lt;/em&gt; until the status of the resource becomes &lt;em&gt;Ready&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Due to different syncwaves, Argo CD will wait for the Ready-status before it continues deploying Loki and Logging.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; resourceHealthChecks:
- check: |
hs = {}
if obj.status ~= nil then
if obj.status.phase ~= nil then
if obj.status.phase == &amp;#34;Ready&amp;#34; then
hs.status = &amp;#34;Healthy&amp;#34;
hs.message = obj.status.phase
return hs
end
end
end
hs.status = &amp;#34;Progressing&amp;#34;
hs.message = &amp;#34;Waiting for BackinbgStore to complete&amp;#34;
return hs
group: noobaa.io
kind: BackingStore&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_generating_secret_for_lokistack"&gt;Generating Secret for LokiStack&lt;/h3&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If you want to use a different storage solution or you have a bucket already, you can skip this section and simply create the LokiStack Secret manually.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Creating the BackingStore and the BucketClaim will generate a Secret and a ConfigMap inside the target namespace. These hold the information about the connection to the object storage.
Both resources are named as the bucket.
The Secret contains the keys: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY while the ConfigMap stores the information about the URL, region etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While this is all we need to connect to the object store, Loki itself unfortunately requires a different Secret with a specific format.
Before Loki can be configured, this Secret must be created, containing the keys: access_key_id, access_key_secret, bucketnames, endpoint and region (could be empty)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To automate the process another Helm Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-loki-bucket-secret" target="_blank" rel="noopener"&gt;Helper Loki Bucket Secret&lt;/a&gt; has been created (we have too few charts) that has the only task to wait until the object store has been created, read the ConfigMap and the Secret and create the required Secret for Loki for us. Easy …​&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-loki-bucket-secret:
enabled: true
syncwave: 3
namespace: openshift-logging &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
secretname: logging-loki-s3 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
bucket:
name: logging-bucket &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace we are working in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the Secret that shall be created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The name of the bucket that was created in the previous step to find the source information.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A Kubernetes Job is created, that will mount the created Secret and ConfigMap, read their values and create the Secret we need. It will simply execute the following command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;oc create secret generic {{ .secretname }} --from-literal access_key_id=${bucket_user} \
--from-literal access_key_secret=${bucket_secret} \
--from-literal bucketnames=${bucket_name} \
--from-literal endpoint=https://${bucket_host} \
--from-literal region=${bucket_region} \&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This is completely optional. If you want to use a different storage solution and have the buckets ready, you can simply create the Secret (Sealed or inside a Vault) and put it into the wrapper chart. In this case, you can ignore this section.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configuring_the_lokistack"&gt;Configuring the LokiStack&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Up until now, all we did was the deployment of the Operators, verifying if they were ready, creating the object storage and the Secret that will be required by Loki. At this point, we can configure Loki by creating the resource LokiStack. This will start a lot of Pods (depending on your selected size). Loki itself then takes care to push the logs into the object store and to query them etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Believe it or not, but there is another Helm Chart called &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-lokistack" target="_blank" rel="noopener"&gt;Helper LokiStack&lt;/a&gt; this will configure the service as we need.
The configuration can become very big and the following example shows the main settings. Please consult the README of the Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-lokistack" target="_blank" rel="noopener"&gt;Helper LokiStack&lt;/a&gt; or the values file from our wrapper chart &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-openshift-logging/values.yaml#L234-L395" target="_blank" rel="noopener"&gt;setup-openshift-logging&lt;/a&gt;. Especially, the pod placement using tolerations might be interesting, as it must be set per component individually.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-lokistack:
enabled: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
name: logging-loki
namespace: openshift-logging
syncwave: 3
# -- This is for log streams only, not the retention of the object store. Data retention must be configured on the bucket.
global_retention_days: 4
storage: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
# -- Size defines one of the supported Loki deployment scale out sizes.
# Can be either:
# - 1x.demo
# - 1x.extra-small (Default)
# - 1x.small
# - 1x.medium
# @default -- 1x.extra-small
size: 1x.extra-small
# Secret for object storage authentication. Name of a secret in the same namespace as the LokiStack custom resource.
secret: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
name: logging-loki-s3
# -- Storage class name defines the storage class for ingester/querier PVCs.
# @default -- gp3-csi
storageclassname: gp3-csi &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
# -- Mode defines the mode in which lokistack-gateway component will be configured.
# Can be either: static (default), dynamic, openshift-logging, openshift-network
# @default -- static
mode: openshift-logging &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
# -- Control pod placement for LokiStack components. You can define a list of tolerations for the following components:
# compactor, distributer, gateway, indexGateway, ingester, querier, queryFrontend, ruler
podPlacements: {}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Basic settings, like Namespace, name of the resource and syncwave.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Size of the LokiStack. Depending on the selected size more or less compute resources will be required. &lt;strong&gt;1x.demo&lt;/strong&gt; is for testing only and is not supported for production workload.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Secret that was created in the previous step (or manually)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;StorageClass that is required for additional workload. This is NOT the object storage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Mode for the LokiStack Gateway to store the data. Possible values are static, dynamic, openshift-logging and openshift-network.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_custom_argo_cd_health_check_for_lokistack"&gt;Custom Argo CD Health Check for LokiStack&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As for the BackingStore resource, the LokiStack resource can take a couple of minutes before it is ready. Moreover, it can easily break when there are not enough computing resources available in the cluster. Therefore, I suggest creating another custom health check for Argo CD, to let it wait until the resource is ready. Only when it is ready, Argo CD will continue with the synchronization. Add the following to the &lt;strong&gt;resourceHealthChecks&lt;/strong&gt; in your Argo CD resource.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; - check: |
hs = {}
if obj.status ~= nil and obj.status.conditions ~= nil then
for i, condition in ipairs(obj.status.conditions) do
if condition.type == &amp;#34;Degraded&amp;#34; and condition.reason == &amp;#34;MissingObjectStorageSecret&amp;#34; then &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
hs.status = &amp;#34;Degraded&amp;#34;
hs.message = &amp;#34;Missing Bucket Secret&amp;#34;
end
if condition.type == &amp;#34;Pending&amp;#34; and condition.reason == &amp;#34;PendingComponents&amp;#34; and condition.status == &amp;#34;True&amp;#34; then &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
hs.status = &amp;#34;Progressing&amp;#34;
hs.message = &amp;#34;Some LokiStack components pending on dependencies&amp;#34;
end
if condition.type == &amp;#34;Ready&amp;#34; and condition.reason == &amp;#34;ReadyComponents&amp;#34; then &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
hs.status = &amp;#34;Healthy&amp;#34;
hs.message = &amp;#34;All components are ready&amp;#34;
end
end
return hs
end
hs.status = &amp;#34;Progressing&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
hs.message = &amp;#34;Waiting for LokiStack to deploy.&amp;#34;
return hs
group: loki.grafana.com
kind: LokiStack&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;In LokiStack resources, if the fields &amp;#39;status.conditions.condition.type&amp;#39; is &amp;#34;Degraded&amp;#34; and &amp;#39;status.conditions.condition.reason&amp;#39; is MissingObjectStoreSecret then set the synchronization in Argo CD to &lt;strong&gt;Degraded&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;In LokiStack resources, if the fields &amp;#39;status.conditions.condition.type&amp;#39; is &amp;#34;Pending&amp;#34; and &amp;#39;status.conditions.condition.reason&amp;#39; is PendingComponents and &amp;#39;status.conditions.condition.status&amp;#39; is True then set the synchronization in Argo CD to &lt;strong&gt;Progressing&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;In LokiStack resources, if the fields &amp;#39;status.conditions.condition.type&amp;#39; is &amp;#34;Ready&amp;#34; and &amp;#39;status.conditions.condition.reason&amp;#39; is ReadyComponents then set the synchronization in Argo CD to &lt;strong&gt;Healthy&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Per default set the status to &lt;strong&gt;Progressing&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configuring_clusterlogging"&gt;Configuring ClusterLogging&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Finally, the time …​ or should I say syncwave …​ has come to actually deploy the Logging components. The Operators are deployed, the object storage has been created and LokiStack is running.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following settings will start the deployment of the ClusterLogging resource. As usual, please read the README of the Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/openshift-logging" target="_blank" rel="noopener"&gt;OpenShift Logging&lt;/a&gt; to find additional settings, such as tolerations etc.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;openshift-logging:
loggingConfig:
enabled: true
syncwave: &amp;#39;4&amp;#39; &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
logStore: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
type: lokistack
lokistack: logging-loki
visualization: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
type: ocp-console
collection: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
type: vector&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The next syncwave, should be after LokiStack deployment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the logStore (LokiStack) and its type (Loki or Elasticsearch). Please note that Elasticsearch as storage is deprecated and will be removed in the future. In my chart, I already removed the support for Elasticsearch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Type of virtualisation: should be &lt;strong&gt;ocp-console&lt;/strong&gt; since Kibana and Elasticsearch are deprecated.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Type of collection: should be &lt;strong&gt;vector&lt;/strong&gt; since Fluentd and Elasticsearch are deprecated.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will deploy the ClusterLogging resource and OpenShift Logging is finally deployed. In the WebUI of OpenShift, you should now see at Observe &amp;gt; Logs the log files for the cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/logging-installed.png?width=940px" alt="OpenShift Logging"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 4. OpenShift Logging&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For individual Pods, a new tab called Aggregated Logs is available too:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/logging-podtab.png?width=940px" alt="Aggregated Logs tab"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 5. Aggregated Logs tab&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_custom_argo_cd_health_check_for_clusterlogging"&gt;Custom Argo CD Health Check for ClusterLogging&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;One last thing to mention is the 3rd health check for Argo CD I usually configure that provides a proper response in the UI when the Logging stack is in a healthy state. The following will verify if the status is &amp;#34;Ready&amp;#34;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; - check: |
hs = {}
hs.status = &amp;#34;Progressing&amp;#34;
hs.message = &amp;#34;Progressing ClusterLogging&amp;#34;
if obj.status ~= nil and obj.status.conditions ~= nil then
for i, condition in ipairs(obj.status.conditions) do
if condition.type == &amp;#34;Ready&amp;#34; then
hs.status = &amp;#34;Healthy&amp;#34;
hs.message = &amp;#34;ClusterLogging is ready&amp;#34;
end
end
return hs
end
return hs
group: logging.openshift.io
kind: ClusterLogging&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_tips_and_tricks"&gt;Tips and Tricks&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Anchors in yaml files&lt;/strong&gt;: Several parameters in the values file will repeat themselves. For example, the name of the LokiStack resource. Typically, I define this as an anchor on the top of the yaml files and then reference it inside the file. This way I see these anchors at the top and can easily change them there:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;lokistack: &amp;amp;lokistackname logging-loki
[...]
helper-lokistack:
[...]
name: *lokistackname
openshift-logging:
loggingConfig:
[...]
logStore:
lokistack: *lokistackname&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Object Storage Data Retention&lt;/strong&gt;: The object storage is configured with a size of 700Gi, but without any lifecycle management. For object storage, the lifecycle (or data retention) is done on the bucket itself, not by the service. Please read the article &lt;a href="https://blog.stderr.at/openshift/2024/02/openshift-data-foundation-noobaa-bucket-data-retention-lifecycle/"&gt;Noobaa Bucket Data Retention Lifecycle&lt;/a&gt; to find out how to configure the data retention.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;OpenShift Logging with all its dependencies, especially when you also want to use OpenShift Data Foundation and automate the bucket creation, is for sure one of the most complex Argo CD Applications I have created. I wanted to create one Application that completely deploys Logging for me, without manual interference. It will become much easier when you do not need to create the ODF bucket and the Secret for Loki. However, in such a case you define the Bucket somewhere else and must create the Secret manually (and put it into the wrapper Helm Chart for example). So probably the effort just shifts to somewhere else.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I hope this article was somehow understandable. I am always happy for Feedback, GitHub issues or Pull Requests.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;One last thing, OpenShift Logging also supports the &lt;strong&gt;forwarding of logs&lt;/strong&gt;. This is currently not supported by the Helm Chart per se. I would suggest creating such a resource and storing it in the wrapper Chart. Just be sure that the syncwave is after the ClusterLogging deployment and it will install the resource accordingly.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.7] Configure Buckets in MinIO</title><link>https://blog.stderr.at/gitopscollection/2024-05-17-configure-minio-buckets/</link><pubDate>Fri, 17 May 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-05-17-configure-minio-buckets/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;a href="https://min.io/" target="_blank" rel="noopener"&gt;MinIO&lt;/a&gt; is a simple, S3-compatible object storage, built for high-performance and large-scale environments. It can be installed as an Operator to Openshift. In addition, to a command line tool, it provides a WebUI where all settings can be done, especially creating and configuring new buckets. Currently, this is not possible in a declarative GitOps-friendly way.
Therefore, I created the Helm chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/minio-configurator" target="_blank" rel="noopener"&gt;minio configurator&lt;/a&gt;, that will start a Kubernetes Job, which will take care of the configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Honestly, when I say I have created it, the truth is, that it is based on an existing &lt;a href="https://github.com/bitnami/charts/tree/main/bitnami/minio" target="_blank" rel="noopener"&gt;MinIO Chart by Bitnami&lt;/a&gt;, that does much more than just set up a bucket. I took out the bucket configuration part, streamlined it a bit and added some new features, which I required.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This article shall explain how to achieve this.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Argo CD (OpenShift GitOps) deployed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MinIO including a deployed tenant that is waiting for buckets&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_introduction"&gt;Introduction&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After MinIO and the Tenant have been deployed, we can &lt;strong&gt;configure and update&lt;/strong&gt; a bucket, users, policies and more. Since I do not want to do this manually, the Helm Chart that will be described here creates a Kubernetes Job that leverages the &lt;strong&gt;mc command line tool&lt;/strong&gt; to execute certain tasks automatically. The chart will take care of:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;creating a lifecycle policy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;creating an access policy&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;creating a new user/group. User credentials might be added directly to the values file or, better, are imported as a secret&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;attaching policies to a user/group&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;creating a bucket&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;set a quota for a bucket&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;set tags for a bucket&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;enable versioning for a bucket&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;enable object locking for a bucket (&lt;strong&gt;be aware&lt;/strong&gt; that this can only be enabled during the bucket creation)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;enable bucket replication to a target cluster/bucket&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;execute possible extra commands that are configured in the values file&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To perform all these tasks Bitnami released a container image: &lt;strong&gt;docker.io/bitnami/minio:2024.5.1-debian-12-r0&lt;/strong&gt;
They are updating this image regularly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Actually, the image can be used to deploy the minio server. At this moment, we are interested in the command line tool only. Bitnami also managing a &lt;em&gt;minio-client&lt;/em&gt; image, that can be tested and used. However, I left the original image, which is working very well.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_values"&gt;The Values&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
All settings are explained in more detail at: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/minio-configurator" class="bare"&gt;https://github.com/tjungbauer/helm-charts/tree/main/charts/minio-configurator&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The Job and everything that is required, are executed inside the Tenant namespace. In the following examples, this will be &lt;strong&gt;minio-tenant-namespace&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_basic_settings"&gt;Basic Settings&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The basic settings are the following. They will define the namespace of the Tenant, the name of the ServiceAccount, the URL of the tenant, Argo CD Hook settings and the image that shall be used for the deployment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;name: minio-provisioner &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: minio-tenant-namespace &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
synwave: 5 &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
argoproj: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
hook: Sync
hook_delete_policy: HookSucceeded
image:
url: docker.io/bitnami/minio:2024.5.1-debian-12-r0 &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
# Information of the Minio Cluster
miniocluster: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
url: minio-tenant-api-url
port: 443
skip_tls_verification: true &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
# Specifies whether a ServiceAccount should be created
serviceAccount: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
create: true
name: &amp;#34;minio-provisioner&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Kubernetes provisioner Job resource.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace of the MinIO Tenant.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Syncwave of the provisioner Job.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Possible Argo CD hook configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The container image the provisioner Job will use.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The URL of the minio console. This will be used to set the &amp;#34;alias&amp;#34; for the mc command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Skip verification of TLS for the mc command. This will disable the TLS check for any mc command the Job will execute.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Information about the ServiceAccount&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_authentication_settings"&gt;Authentication Settings&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To be able to authenticate against MinIO credentials must be provided. This happens, typically, in the form of a Secret:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;auth:
useCredentialsFiles: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
secretName: minio-provisioner &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Shall a secret mounted as a file be used (preferred)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Secret&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Secret itself requires specific keys and should look like the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;kind: Secret
apiVersion: v1
metadata:
name: minio-provisioner &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: minio-tenant-namespace &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
data:
root-password: &amp;lt;base64 string&amp;gt; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
root-user: &amp;lt;base64 string&amp;gt; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
type: Opaque&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Secret as mentioned in the minio-configurator values files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Namespace as mentioned in the minio-configurator values files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Password to access MinIO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;User to access MinIO&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The Secret must exist upfront and is not created by the Helm Chart. Either pick it from a Vault or create a Sealed Secret to be able to store it in Git.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The credentials are called &lt;strong&gt;root-&lt;/strong&gt;. Any user that has permission to configure buckets is sufficient here. Still, the keys must be named that way.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_creating_minio_policies"&gt;Creating MinIO Policies&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;MinIO uses Policy-Based Access Control to define which actions can be performed on certain resources by an authenticated user.
A policy can be created by the command &lt;strong&gt;mc admin policy&lt;/strong&gt;. Our Kubernetes Job will take the configuration from the values file and mount the information as a JSON file, that will be imported into MinIO.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following specification shows the example for OpenShift Logging:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;provisioning:
enabled: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
policies:
- name: openshift-logging-access-policy &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
statements:
- resources: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
- &amp;#34;arn:aws:s3:::openshift-logging&amp;#34;
- &amp;#34;arn:aws:s3:::openshift-logging/*&amp;#34;
effect: &amp;#34;Allow&amp;#34; &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
actions:
- &amp;#34;s3:*&amp;#34; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;In general, enable the provisioning or not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the policy. Multiple can be defined and assigned to a user or group.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the resources the policy should manage access to.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the effect: Allow or Deny (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The actions that are allowed. Here: any s3: action&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Multiple policies can be defined in the values file, and it is very important to exactly define the resources, the effect and the actions.
The above configuration will allow the user that has the policy assigned:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;All s3 actions to the bucket openshift-logging and everything inside this bucket (thus two resources)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All actions are defined at: &lt;a href="https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html#minio-policy" target="_blank" rel="noopener"&gt;MinIO Access Management&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Another example would be the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; policies:
- name: custom-bucket-specific-policy
statements:
- resources:
- &amp;#34;arn:aws:s3:::my-bucket&amp;#34;
actions:
- &amp;#34;s3:GetBucketLocation&amp;#34;
- &amp;#34;s3:ListBucket&amp;#34;
- &amp;#34;s3:ListBucketMultipartUploads&amp;#34;
- resources:
- &amp;#34;arn:aws:s3:::my-bucket/*&amp;#34;
effect: &amp;#34;Allow&amp;#34;
actions:
- &amp;#34;s3:AbortMultipartUpload&amp;#34;
- &amp;#34;s3:DeleteObject&amp;#34;
- &amp;#34;s3:GetObject&amp;#34;
- &amp;#34;s3:ListMultipartUploadParts&amp;#34;
- &amp;#34;s3:PutObject&amp;#34;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This policy defines the actions in a fine granular way:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;To the bucket &lt;strong&gt;my-bucket&lt;/strong&gt; we have three allowed actions (GetBucketLocation, ListBucket and ListBucketMultipartUploads)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To everything inside the bucket (/*) we can also Delete, Get, Put objects etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_creating_a_user"&gt;Creating a User&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The policy that has been created must be assigned to a user (or a group) to be effective. Such a user requires a username, a password and a list of policies that shall be assigned.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The required information can be added directly to the values file like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
&lt;strong&gt;This is NOT the recommended way!&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; # users:
# - username: test-username &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# password: test-password &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
# disabled: false &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
# policies: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
# - readwrite
# - consoleAdmin
# - diagnostics
# # When set to true, it will replace all policies with the specified.
# # When false, the policies will be added to the existing.
# setPolicies: false
# @default -- []&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Username&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;clear text password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Shall the user be created or not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of policies that shall be assigned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As mentioned above: Defining a list of users directly in the values file is &lt;strong&gt;not recommended&lt;/strong&gt; as it would mean that the passwords are stored in clear text.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Instead, a list of Secrets can be defined:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; usersExistingSecrets:
- minio-users&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The defined Secrets require a specific structure and can be encrypted and stored in Git or a Vault.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The data structure is the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: v1
kind: Secret
metadata:
name: minio-users &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
type: Opaque
stringData:
username1: | &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
username=username &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
password=password &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
disabled=false &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
policies=openshift-logging-access-policy,readwrite,consoleAdmin,diagnostics &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
setPolicies=false &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Secret as referenced in the values file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of users, distinguished by the key &amp;#34;username1&amp;#34;, &amp;#34;username2&amp;#34;, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Username&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enabled or disabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of policies to assign to the user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Replace or add the policies to an (existing) user.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_built_in_policies"&gt;Built-In Policies&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;MinIO provides several &lt;a href="https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html#built-in-policies" target="_blank" rel="noopener"&gt;Built-In Policies&lt;/a&gt; that can be attached to a user or group.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following policies will always exist: (Please verify the official documentation for further information)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;consoleAdmin&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Grants complete access to all S3 and administrative API operations against all resources on the MinIO deployment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;s3:*&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin:*&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;readonly&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Grants read-only permissions on any object on the MinIO deployment. The GET action must apply to a specific object without requiring any listing.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;s3:GetBucketLocation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;s3:GetObject&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;readwrite&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Grants read and write permissions for all buckets and objects on the MinIO server.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;s3:*&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;diagnostics&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Grants permission to perform diagnostic actions on the MinIO deployment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;admin:ServerTrace&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin:Profiling&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin:ConsoleLog&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin:ServerInfo&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin:TopLocksInfo&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin:OBDInfo&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin:BandwidthMonitor&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;admin:Prometheus&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;writeonly&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Grants write-only permissions to any namespace (bucket and path to object) the MinIO deployment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;s3:PutObject&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_provisioning_groups"&gt;Provisioning Groups&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Users can be combined into groups and instead of assigning policies to individual users, we can assign them to a whole group.
The idea is the same as for users, except, that we define a list of members for that group:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; groups
- name: test-group &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
disabled: false &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
members: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
- username
policies: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- readwrite
# When set to true, it will replace all policies with the specified.
# When false, the policies will be added to the existing.
setPolicies: false &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the group.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enabled or disabled.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of users that are members of this group.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of policies that are assigned to this group.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Replace or add the policies to an (existing) user.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configure_the_bucket"&gt;Configure the Bucket&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Finally, we can configure the bucket itself. A bucket will have a specific configuration, a lifecycle a quota etc.
A list of buckets with different configurations can be defined in the values files.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The only mandatory information is the name of the bucket. It is not required to configure a lifecycle or quota etc.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let us analyse the following example, which tries to cover all possible settings:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; buckets:
- name: mybucket &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
region: my-region &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
versioning: Versioned &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
withLock: false &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
bucketReplication: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
enabled: true
targetClusterUrl: replication-target-cluster
targetClusterPort: 443
targetBucket: replication-target-bucket
replicationSettings: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
- existing-objects
credSecretName: replication-credentials &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
lifecycle:
- id: name-of-lifecycle &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
prefix: test-prefix &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
disabled: false
expiry: &lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;(10)&lt;/b&gt;
days: 30 # or date
nonconcurrentDays: 10
- id: name-of-second-lifecycle
disabled: false
expiry:
deleteMarker: true
nonconcurrentDays: 10
quota: &lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;(11)&lt;/b&gt;
type: set
size: 1024Gib
tags: &lt;i class="conum" data-value="12"&gt;&lt;/i&gt;&lt;b&gt;(12)&lt;/b&gt;
key1: value1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the bucket.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Region of the bucket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable versioning (&lt;a href="https://docs.min.io/docs/minio-client-complete-guide.html#ilm" class="bare"&gt;https://docs.min.io/docs/minio-client-complete-guide.html#ilm&lt;/a&gt;). Allowed options are: Versioned, Suspended or Unchanged.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable object Locking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configure bucket replication to a target cluster and a target bucket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the settings for the bucket replication can be: delete, delete-marker or existing-objects: &lt;a href="https://min.io/docs/minio/linux/administration/bucket-replication/enable-server-side-one-way-bucket-replication.html" class="bare"&gt;https://min.io/docs/minio/linux/administration/bucket-replication/enable-server-side-one-way-bucket-replication.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Secret that stores the credentials for the replication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define a list of lifecycle policies for the bucket: &lt;a href="https://min.io/docs/minio/linux/administration/object-management/object-lifecycle-management.html" class="bare"&gt;https://min.io/docs/minio/linux/administration/object-management/object-lifecycle-management.html&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A prefix that can be defined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the expiration. This can be defined as &lt;strong&gt;days&lt;/strong&gt; OR as a &lt;strong&gt;date&lt;/strong&gt;, for example &amp;#34;2021-11-11T00:00:00Z&amp;#34;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;11&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Set a quota for the bucket: &lt;a href="https://docs.min.io/docs/minio-admin-complete-guide.html#bucket" class="bare"&gt;https://docs.min.io/docs/minio-admin-complete-guide.html#bucket&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="12"&gt;&lt;/i&gt;&lt;b&gt;12&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define additional tags for the bucket &lt;a href="https://docs.min.io/docs/minio-client-complete-guide.html#tag" class="bare"&gt;https://docs.min.io/docs/minio-client-complete-guide.html#tag&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_replication_secret"&gt;Replication Secret&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The definition above defines a bucket replication. To authenticate at the target cluster, we need to provide a username and a password. This is stored inside a secret:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: v1
kind: Secret
metadata:
name: replication-user
type: Opaque
stringData:
username: username
password: password&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This defines a whole bunch of settings. Except for the bucket name, none is mandatory.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect3"&gt;
&lt;h4 id="_example_openshift_logging_bucket"&gt;Example OpenShift-Logging Bucket&lt;/h4&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following is a more realistic example, for defining a bucket used for OpenShift Logging:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It defines the bucket name, with a lifecycle of 4 days and a quota of 1TB:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; buckets:
- name: openshift-logging
lifecycle:
- id: logging-retention
disabled: false
expiry:
days: 4
quota:
type: set
size: 1024GiB&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_additional_settings"&gt;Additional Settings&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Finally, there are some additional settings, I would like to mention here. They are completely optional, but might be interesting:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Automatically clean up the provisioning job after it has finished:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; cleanupAfterFinished:
enabled: false
seconds: 600&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Define resources for the provisioning job. For example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;resources:
requests:
cpu: 2
memory: 512Mi
limits:
cpu: 3
memory: 1024Mi&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Typically, I leave this to &lt;strong&gt;resources: {}&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Take care of the pod placement and define a nodeSelector and tolerations, for example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; nodeSelector: {}
tolerations:
- effect: NoSchedule
key: infra
operator: Equal
value: reserved
- effect: NoExecute
key: infra
operator: Equal
value: reserved&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this Helm chart by Bitnami, with a little modification from my side, it is possible to &lt;strong&gt;create and update&lt;/strong&gt; buckets, policies, users etc. There is no need, to perform any modification manually in the MinIO WebUI.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I am currently using this chart for several bucket configurations, with sometimes more and sometimes fewer settings in the values file. Keep in mind, that many settings, especially for the bucket itself, are completely optional and are not required to create a new bucket. (For example, lifecycle). Please check out the source of the Helm Chart and the values file to get further information: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/minio-configurator" target="_blank" rel="noopener"&gt;minio configurator&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you have any feedback or miss something, feel free to create a pull request or an issue :)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.6] Setup &amp; Configure Advanced Cluster Security</title><link>https://blog.stderr.at/gitopscollection/2024-04-28-installing-advanced-cluster-security/</link><pubDate>Sun, 28 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-04-28-installing-advanced-cluster-security/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Today I want to demonstrate the deployment and configuration of &lt;strong&gt;Advanced Cluster Security&lt;/strong&gt; (ACS) using a GitOps approach. The required operator shall be installed, verified if it is running and then ACS shall be initialized. This initialization contains the deployment of several components:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Central - as UI and as a main component of ACS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SecuredClusters - installs a Scanner, Controller pods etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Console link into OpenShift UI - to directly access the ACS Central UI&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Job to create an initialization bundle to install the Secured Cluster&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Job to configure authentication using OpenShift&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s start …​&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Argo CD (OpenShift GitOps) deployed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;App-Of-Apps deployed&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_introduction"&gt;Introduction&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The main components of ACS are the two custom resources: &lt;strong&gt;Central&lt;/strong&gt; and &lt;strong&gt;SecuredCluster&lt;/strong&gt;. The recommended way to deploy ACS is to use the Operator (alternatives would be the command line or Helm Chart). When I first came across ACS I thought about how to automate the full deployment. I did not want to install the Operator, then the Central, then manually create a so-called init-bundle, then deploy the Secured Cluster, then find the route that has been used, then find the secret that stores the initial administrator credentials and then, finally, log into ACS and activate OpenShift authentication.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see there are a lot of tasks to do before I can start a customer demo.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At the same time, I started to dig into GitOps and I thought this would be a good option to create my very first Helm Chart.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Long story short, I have now a Helm Chart (actually three, because I outsourced some of the features into sub-charts) that automatically does all these things above. Once I am synchronizing my Argo CD Application everything will happen automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/"&gt;Configure App-of-Apps&lt;/a&gt; installed an Argo CD Application called &lt;strong&gt;in-cluster-setup-acs&lt;/strong&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup-acs.png?width=720px" alt="Argo CD Application: setup-acs"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Argo CD Application: setup-acs&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This Argo CD Application used the following path to find the Helm Chart: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-acs"&gt;setup-acs&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This Helm chart is a wrapper chart that uses sub-charts as dependencies to install and configure the operator as well as to do some OpenShift Jobs on top, for example, creating a ConsoleLink or creating an init-bundle.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Please check out the article &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/#_why_empty_helm_charts"&gt;Setup Compliance Operator&lt;/a&gt; on why I am using a wrapper chart.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installing_advanced_cluster_security"&gt;Installing Advanced Cluster Security&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_analysing_chart_yaml"&gt;Analysing Chart.yaml&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s examine the Chart.yaml file to see which dependencies are used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The file looks like the following. Three sub-charts are defined as required to deploy and configure the ACS. This is pretty much the same as it was for the &lt;a href="https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/#_analysing_chart_yaml"&gt;Setup Compliance Operator&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: v2
name: setup-acs
description: Deploys Advanced Cluster Security (ACS) on target cluster. If enabled Central will be deployed too.
version: 1.0.0
dependencies:
- name: rhacs-setup &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
version: ~1.0.0 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
repository: https://charts.stderr.at/
- name: helper-operator &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
version: ~1.0.23
repository: https://charts.stderr.at/
- name: helper-status-checker &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
version: ~4.0.0
repository: https://charts.stderr.at/
condition: helper-status-checker.enabled &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/rhacs-setup" target="_blank" rel="noopener"&gt;RHACS Setup&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Version that will be used. The &amp;#34;~&amp;#34; means that the latest version of 1.0.X will be used.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Status Checker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Only use this dependency when &amp;#34;enabled&amp;#34; is set&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the READMEs of the different Charts for detailed information on how to configure them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configuration_of_the_chart"&gt;Configuration of the Chart&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To configure Advanced Cluster Security the &lt;strong&gt;values file&lt;/strong&gt; of the wrapper Chart must be prepared accordingly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The important thing here is, that any value that should be bypassed to a sub-chart is defined under the name of the sub-chart. For example, everything under &lt;strong&gt;helper-operator:&lt;/strong&gt; will be sent to the helper-operator Chart and is used there for its configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Check out the example &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-acs/values.yaml" target="_blank" rel="noopener"&gt;values file&lt;/a&gt; I use to configure ACS and the
&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/rhacs-setup" target="_blank" rel="noopener"&gt;README&lt;/a&gt; to find further information about the possible settings that can be done.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s check the main example, to quickly start:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_installing_and_verifying_the_operator"&gt;Installing and verifying the Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first thing to do is to deploy the Operator and to verify if the Operator installation finished successfully.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The two Helm Charts &lt;strong&gt;helper-operator&lt;/strong&gt; and &lt;strong&gt;helper-status-checker&lt;/strong&gt; are responsible to do so.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;They are configured as follows:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-operator:
operators:
rhacs-operator: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
syncwave: &amp;#39;0&amp;#39; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
namespace:
name: rhacs-operator &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
create: true
subscription:
channel: stable &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
approval: Automatic
operatorName: rhacs-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
operatorgroup: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
create: true
# rhacs does not support to monitor own namespace,
# therefore the spec in the OperatorGroup must be empty
notownnamespace: true
# Subchart helper-status-checker
# checks if ACS operator is ready
helper-status-checker:
enabled: true &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
checks: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
- operatorName: rhacs-operator &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
namespace:
name: rhacs-operator &lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;(10)&lt;/b&gt;
syncwave: 3
serviceAccount:
name: &amp;#34;status-checker-acs&amp;#34; &lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;(11)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Key that can be freely defined. Theoretically, you can deploy multiple operators at once.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Is this Operator enabled yes/no.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Syncwave for the Operator deployment. (Subscription and OperatorGroup etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Namespace where the Operator shall be deployed and if this namespace shall be created.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the Subscription resource.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the OperatorGroup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable status checker or not. Default: false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of operators to check. Typically, only one is checked, but there could be more.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Operator to check (same as for helper-operator)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace where the Operator has been installed (same as for helper-operator)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;11&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the ServiceAccount that will be created to check the status.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the READMEs at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt; and &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt; to find additional possible configurations.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Also verify the separate article &lt;a href="https://blog.stderr.at/openshift/2023-03-20-operator-installation-with-argocd/"&gt;Operator Installation with Argo CD&lt;/a&gt; to understand why I am verifying the status of the Operator installation.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configuring_advanced_cluster_security"&gt;Configuring Advanced Cluster Security&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Besides the deployment of the Operator, the configuration of ACS is the most important part here. The ACS Operator provides two custom resources: Central and SecuredCluster. On the Central cluster both CRDs are required. On any other (spoke) cluster, the SecuredCluster resource is enough.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the following example, I am going to configure both Central and SecuredCluster. Since the values file is quite huge I removed most of the additional comments, to keep this article short and readable.
You can read the example values file or the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/rhacs-setup" target="_blank" rel="noopener"&gt;Advanced Cluster Security Chart&lt;/a&gt; to find additional possible configurations. Especially, if you like to add tolerations or set resource limits.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;#########################################
# Settings for Advanced Cluster Security
#########################################
rhacs-setup:
rhacs:
namespace: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
name: stackrox
syncwave: &amp;#39;0&amp;#39;
descr: &amp;#39;Red Hat Advanced Cluster Security&amp;#39;
################
# CENTRAL of ACS
################
# Settings for the Central of ACS
central: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;3&amp;#39;
egress:
connectivityPolicy: Online
###############
# CENTRAL DB
###############
# Settings for Central DB, which is responsible for data persistence.
db: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
# -- Set Central DB resources.requests for a DEMO environment to save resources.
resources:
requests:
cpu: &amp;#39;1&amp;#39;
memory: &amp;#39;1Gi&amp;#39;
# -- If you want this component to only run on specific nodes, you can
# configure tolerations of tainted nodes.
tolerations: {}
# - effect: NoSchedule
# key: infra
# operator: Equal
# value: reserved
# - effect: NoSchedule
# key: infra
# operator: Equal
# value: reserved
###############
# SCANNER
###############
scanner: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
enabled: true
analyzer:
# The following settings are NOT suitable for a production environment
autoscaling:
status: &amp;#34;Disabled&amp;#34;
max: 1
min: 1
# When autoscaling is disabled, the number of replicas will always be
# configured to match this value.
replicas: 1
tolerations: {}
###############
# SCANNER DB
###############
db:
tolerations: {}
#################
# SECURED CLUSTER
#################
secured_cluster: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;4&amp;#39;
clustername: local-cluster
sensor:
tolerations: {}
admissioncontrol:
listenOn:
creates: true
events: true
updates: true
tolerations: {}
# -- Basic settings for ACS authentication
# This configuration is done by a Job, that will configure the OpenShift oauth for ACS.
basic_acs_settings: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
auth_provider: &amp;#39;OpenShift&amp;#39;
auth_provider_type: &amp;#39;openshift&amp;#39;
min_access_role: &amp;#39;None&amp;#39;
syncwave: 5
####################################################
# Additional settings for Central and possible Jobs
####################################################
job_vars: &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
max_attempts: 20
job_init_bundle: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;3&amp;#39;
consolelink: &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;3&amp;#39;
location: ApplicationMenu
text: Advanced Cluster Security
section: Observability&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Create the Namespace &lt;strong&gt;stackrox&lt;/strong&gt; and install the ACS resources there.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable the Central during Syncwave 3 and set the connectivityPolicy to Online&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Central DB and its configuration. Here the resource requests are modified to allow a small installation on the DEMO environment. Also, tolerations might be set here, as well a PVCs etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Settings for the Scanner and its databases. Again, tolerations might be configurated here, but also, not shown in this example, resource limits and requests and other settings. Since I am configuring for a DEMO environment, I disabled the autoscaler and set the replica to 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The SecuredCluster is the 2nd CRD that is provided by ACS Operator. It is installed after the Central (thus a higher Syncwave). The most important setting here is the clustername. In our &amp;#34;local&amp;#34; example, the name is set to &lt;strong&gt;local-cluster&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Some basic settings, that will configure the OpenShift authentication and the minimum role for authenticated users (None)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Some default settings for Jobs that are started by this Helm chart.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Job that initializes the creation of the init-bundle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Job and its configuration to generate a direct link to ACS in the OpenShift UI.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this ACS is about to be installed on the cluster. Let’s see what will happen during the synchronization.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_deploying_advanced_cluster_security_acs"&gt;Deploying Advanced Cluster Security (ACS)&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s hit the sync button inside OpenShift GitOps. This will start the whole process, walking through the syncwaves and the hooks that have been defined.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/syncing-argocd.png?width=720px" alt="Syncing Argo CD"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. Syncing Argo CD&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Since hooks are used, you must sync the whole Argo CD Application. As you can see inside Argo CD, the hooks are not shown, because they will only appear when their time has come (and will disappear afterward again). This means, that if you perform a selective sync, Argo CD does not know when it should start such a hook and they are never triggered.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Operator installation is now started. At the moment the Operator has the status &lt;strong&gt;Installing&lt;/strong&gt;. Currently, no CRDs (Central or SecuredCluster) are available yet. If we would just let Argo CD continue, it would try to create the Central configuration, based on a CRD which does not yet exist. Thus, the syncing process will fail and therefore the &lt;strong&gt;status-checker&lt;/strong&gt; is going to verify if the installation was truly successful.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/installing-operator.png?width=720px" alt="Operator is installed"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 3. Operator is installed&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Status Checker is a simple Pod that is triggered by a Kubernetes Job. If waits until the status of the Operator is &lt;strong&gt;Succeeded&lt;/strong&gt;. Until this is the case, Argo CD waits before it continues with the synchronization. (It waits until the hook ends the Job)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/status-checker.png?width=720px" alt="Status Checker"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 4. Status Checker&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the logs file of the Pod, we can see that the Operator is ready.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/status-checker-logs.png?width=720px" alt="Status Checker Logs"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 5. Status Checker Logs&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;And indeed, the status of the Operator is now &lt;strong&gt;Succeeded&lt;/strong&gt;. Now it is time for Argo CD to continue the synchronization.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/operator-ready.png?width=720px" alt="Operator Ready"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 6. Operator Ready&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The next step is to create the Central CRD. This will deploy the UI of ACS and the local Scanner. You can also see two other Jobs that have been created by the Helm Charts &lt;strong&gt;create_cluster_link&lt;/strong&gt; and &lt;strong&gt;create_cluster_init_bundle&lt;/strong&gt;. They will finish when the Central becomes ready.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Until the Central becomes ready, these two additional Jobs may show errors. Do not worry, OpenShift will reschedule them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/installing-central.png?width=720px" alt="Installing Central"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 7. Installing Central&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;You can also see two other Jobs that have been created by the Helm Charts &lt;strong&gt;create_cluster_link&lt;/strong&gt; and &lt;strong&gt;create_cluster_init_bundle&lt;/strong&gt;. They will finish when the Central becomes ready.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Until the Central becomes ready, these two additional Jobs may show errors. Do not worry, OpenShift will reschedule them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/init-job-waits.png?width=720px" alt="Init Job is waiting for Central"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 8. Init Job is waiting for Central&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Once the Central has been deployed, the second CRD &lt;strong&gt;SecuredCluster&lt;/strong&gt; will be added. This will trigger the installation of the Collectors and other components.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/installing-securedcluster.png?width=720px" alt="Installing Secured Cluster"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 9. Installing SecuredCluster&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Eventually, all Pods are running at the end. The additional Jobs are completed and ACS is ready to take care of the cluster security.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/acs-all-pods-running.png?width=720px" alt="All Pods running"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 10. All Pods running&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;We can now use the console link that was created.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/acs-consolelink.png?width=720px" alt="ACS ConsoleLink"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 11. ACS ConsoleLink&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
If you disabled the creation of the ConsoleLink you would need to find the Route that ACS Operator created. Honestly, I do not know why the Operator does not create such ConsoleLink out of the box.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Since I am lazy I created another Job that automatically configures authentication via OpenShift for ACS. This way, we can simply use our OpenShift credentials to login.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/acs-login.png?width=720px" alt="ACS Login"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 12. ACS Login&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;And that’s it, we can now use ACS, which was deployed fully automatically.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup_acs/acs.png?width=720px" alt="Advanced Cluster Security"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 13. Advanced Cluster Security&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see Advanced Cluster Security was completely installed. This included the Operator, the Central, the creation and installation of an init-bundle, the creation of a ConsoleLink, the configuration of the SecuredCluster CRD and the initial configuration of the auth provider inside ACS. You can now start using ACS or add additional clusters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Speaking of additional clusters: The create-cluster-init-bundle created three certificates: collector-tls, sensor-tls and admission-control-tls. They are required so that the SecuredClusters can communicate with the Central. You could now create a separate init-bundle for each SecuredCluster, which is not really easy to automate, or you simply take these created secrets and put them into your GitOps and re-use them for any other SecuredCluster.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All these steps and configurations seem quite complicated but honestly, it is straightforward. I install any Operator using &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt; and in most cases. I also use &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt; to find additional possible configurations. Both require simple configuration only, which you would need to know anyway when you create the Subscription object manually. Once done, you can repeat this for any other Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The real magic happens when the Operator is configured at the same time because this is very individual to the Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.5] Setup &amp; Configure Compliance Operator</title><link>https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/</link><pubDate>Thu, 25 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;In the previous articles, we have discussed the &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/"&gt;Git repository folder structure&lt;/a&gt; and the configuration of the &lt;a href="gitopscollection/2024-04-02-configure_app_of_apps/"&gt;App-Of-Apps&lt;/a&gt;. Now it is time to deploy our first configuration. One of the first things I usually deploy is the &lt;a href="https://docs.openshift.com/container-platform/4.15/security/compliance_operator/co-overview.html" target="_blank" rel="noopener"&gt;Compliance Operator&lt;/a&gt;. This Operator is recommended for any cluster and can be deployed without any addition to the Subscription.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this article, I will describe how it is installed and how the Helm Chart is configured.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Argo CD (OpenShift GitOps) deployed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;App-Of-Apps deployed&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_introduction"&gt;Introduction&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As a reminder, at &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/"&gt;Git repository folder structure&lt;/a&gt; I described my preferred folder structure. I would like to deploy the Compliance Operator in the Management Cluster now. All my examples can be found at GitHub repository &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" target="_blank" rel="noopener"&gt;OpenShift Clusterconfig GitOps&lt;/a&gt;. The folder &lt;strong&gt;clusters/management-cluster/setup-compliance-operator&lt;/strong&gt; is the one I am interested in.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Inside this folder, you will find another Helm Chart. The Helm Chart has no local templates, instead, it uses dependencies to call other (sub-) charts. However, the &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-compliance-operator/values.yaml" target="_blank" rel="noopener"&gt;values.yaml&lt;/a&gt; is the main part to configure everything.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
In case you want to have any local template, that you do NOT want to integrate into one of the sub-charts, you can easily do so, by storing them in the templates folder.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_why_empty_helm_charts"&gt;Why &amp;#34;empty&amp;#34; Helm Charts?&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Actually, it would be possible to use the Helm Chart of the Chart repository directly, without creating a separate chart, that does nothing else than using dependency charts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The reasons why I am using such an &amp;#34;empty&amp;#34; Chart are the following (in no particular order):&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;With that way it is possible to add templates (i.e. SealedSecrets) and modify the values-file without packaging and releasing a new Chart version every time you change a small thing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Multi-Source Option, which allows you to use a Helm Chart from repository A and a values file from repository B is still a TechPreview feature (Argo CD 2.10). I am using this for the App-of-Apps already, but I did not do this for all charts. This feature is on the list for Argo CD version 2.11 to become globally available.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As an alternative, it is also possible to mix Kustomize and Helm. That way you only need a kustomization.yaml file and reference to a Helm Chart. In the folder &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/tree/main/clusters/management-cluster/ingresscontroller" target="_blank" rel="noopener"&gt;clusters/management-cluster/ingresscontroller&lt;/a&gt; I have such an example.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_installing_compliance_operator"&gt;Installing Compliance Operator&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_analysing_chart_yaml"&gt;Analysing Chart.yaml&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As any Helm Chart a Chart.yaml file exists, that stores the basic information. The most important ones for now are the dependencies.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The file looks like the following. Three sub-charts are defined as required to deploy and configure the Compliance Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: v2
name: setup-compliance-operator
description: Deploy and configure the Compliance Operator
version: 1.0.1
dependencies:
- name: compliance-operator-full-stack &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
version: ~1.0.0 &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
repository: https://charts.stderr.at/
- name: helper-operator &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
version: ~1.0.21
repository: https://charts.stderr.at/
- name: helper-status-checker &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
version: ~4.0.0
repository: https://charts.stderr.at/
condition: helper-status-checker.enabled &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/compliance-operator-full-stack" target="_blank" rel="noopener"&gt;Compliance Operator Full Stack&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Version that will be used. The &amp;#34;~&amp;#34; means that the latest version of 1.0.X will be used.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Dependency: &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Status Checker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Only use this dependency when &amp;#34;enabled&amp;#34; is set&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the READMEs of the different Charts for detailed information on how to configure them.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As you can see three other Helm Charts are used to actually deploy and configure the Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configuration_of_the_chart"&gt;Configuration of the Chart&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To configure the Compliance Operator, the values files must be prepared accordingly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The important thing here is, that any value that should be bypassed to a sub-chart is defined under the name of the sub-chart. For example, everything under &lt;strong&gt;helper-operator:&lt;/strong&gt; will be sent to the helper-operator Chart and is used there for its configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following is a full example of the values I typically use.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# Install Operator Compliance Operator
# Deploys Operator --&amp;gt; Subscription and Operatorgroup
helper-operator:
operators:
compliance-operator:
enabled: true
syncwave: &amp;#39;0&amp;#39;
namespace:
name: openshift-compliance
create: true
subscription:
channel: stable
approval: Automatic
operatorName: compliance-operator
source: redhat-operators
sourceNamespace: openshift-marketplace
operatorgroup:
create: true
notownnamespace: true
# Verify if the Operator has been successfully deployed
helper-status-checker:
enabled: true
checks:
- operatorName: compliance-operator
namespace:
name: openshift-compliance
serviceAccount:
name: &amp;#34;status-checker-compliance&amp;#34;
# Setting for the Compliance Operator
compliance-operator-full-stack:
compliance:
namespace:
name: openshift-compliance
syncwave: &amp;#39;0&amp;#39;
descr: &amp;#39;Red Hat Compliance&amp;#39;
scansettingbinding:
enabled: true
syncwave: &amp;#39;3&amp;#39;
profiles:
- name: ocp4-cis-node
kind: Profile # Could be Profile or TailedProfile
- name: ocp4-cis
kind: Profile
scansetting: default&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let us walk through the settings in more detail.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_installing_the_operator"&gt;Installing the Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first thing to do is to deploy the Operator. Two resources are relevant to install an Operator:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Subscription&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OperatorGroup&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Both objects should be deployed at the very beginning of Argo CD synchronisation. This is done by setting the Syncwave to 0.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The main settings are the operatorName, the channel (which is the version of the operator) and the approval (which defines if the Operator is updated automatically or manually).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In addition, a Namespace object is deployed, because this Operator should run in its very own namespace.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This will start the Operator installation process.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;helper-operator:
operators:
compliance-operator: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
syncwave: &amp;#39;0&amp;#39; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
namespace:
name: openshift-compliance &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
create: true
subscription: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
channel: stable # Version of the Operator
approval: Automatic # Automatic or Manual
operatorName: compliance-operator # Name of the Operator
source: redhat-operators
sourceNamespace: openshift-marketplace
operatorgroup: &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
create: true
notownnamespace: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Key that can be freely defined. Theoretically, you can deploy multiple operators at once.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Is this Operator enabled yes/no.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Syncwave for the Operator deployment. (Subscription and OperatorGroup etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Namespace where the Operator shall be deployed and if this namespace shall be created.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the Subscription resource.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the OperatorGroup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt; to find additional possible configurations.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_verify_the_status_of_the_operator"&gt;Verify the Status of the Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;After Argo CD creates the subscription and operatorgroup resources (and namespace), OpenShift will start the installation of the Operator. This installation will take a while but Argo CD does not see this. All it sees is that the Subscription resource is available and it tries to continue with the configuration of the Operator. Here it will fail because the CRDs are not available yet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Therefore, I created a mechanism to verify if an Operator is ready or not.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Also verify the separate article &lt;a href="https://blog.stderr.at/openshift/2023-03-20-operator-installation-with-argocd/"&gt;Operator Installation with Argo CD&lt;/a&gt; that addresses the problem in more detail.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;All it does is to start a small Job inside OpenShift and to verify the status of the Operator installation. If everything is fine, the Job will end successfully and Argo CD will continue with the next syncwave. Argo CD Hook and syncwaves are required here. The Job should be started &lt;em&gt;after&lt;/em&gt; the Subscription/OperatorGroup resources have been created, which means any syncwave after &amp;#34;0&amp;#34;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following annotations will be used by the Job:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; argocd.argoproj.io/hook: Sync &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
argocd.argoproj.io/hook-delete-policy: HookSucceeded &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
argocd.argoproj.io/sync-wave: {{ .syncwave | default 1 | quote }} &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Hooks are ways to run scripts before, during, and after a Sync operation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Deletes the OpenShift Job again. The hook resource is deleted after the hook succeeded (e.g. Job/Workflow completed successfully).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Syncwave: can be configured. Must be after helper-operator (default 0) and before the Operator is configured further. Default value is 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The configuration for &lt;strong&gt;hepler_status_checker&lt;/strong&gt; will look like the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# Verify if the Operator has been successfully deployed
helper-status-checker:
enabled: true &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
checks: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- operatorName: compliance-operator &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
namespace:
name: openshift-compliance &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
serviceAccount:
name: &amp;#34;status-checker-compliance&amp;#34; &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable status checker or not. Default: false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;List of operators to check. Typically, only one is checked, but there could be more.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Operator to check (same as for helper-operator)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace where the Operator has been installed (same as for helper-operator)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the ServiceAccount that will be created to check the status.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt; to find additional possible configurations.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_configuring_compliance_operator"&gt;Configuring Compliance Operator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Finally, the Operator has been deployed and has been verified. Now the time is right to configure the Operator with any configuration we would like. This means, using CRDs to do whatever the Operator offers.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is reflected in the following part of the values file. All these settings are handed over to the sub-chart &lt;strong&gt;compliance-operator-full-stack&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Verify the README at &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/compliance-operator-full-stack" target="_blank" rel="noopener"&gt;Compliance Operator Chart&lt;/a&gt; to find additional possible configurations. Especially, if you like to do Tailored Profiles.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The compliance operator requires a so-called ScanSettingBinding that uses Profiles which are used to check the cluster compliance once a day. In this case, I am using CIS Benchmarks. There are two profiles:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;ocp4-cis-node: will check the node operating system for missing but suggested configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ocp4-cis: will check the OpenShift cluster for missing but suggested configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;# Setting for the Compliance Operator
compliance-operator-full-stack: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
compliance:
namespace:
name: openshift-compliance &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
syncwave: &amp;#39;0&amp;#39;
descr: &amp;#39;Red Hat Compliance&amp;#39;
scansettingbinding: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
enabled: true
syncwave: &amp;#39;3&amp;#39;
profiles: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- name: ocp4-cis-node
kind: Profile # Could be Profile or TailedProfile
- name: ocp4-cis
kind: Profile
scansetting: default&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Handing everything that comes below to the sub-chart &lt;strong&gt;compliance-operator-full-stack&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Namespace where the configuration should be deployed. The Syncwave at this point could be omitted.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The configuration for the ScanSettingBinding. It is enabled (default = false) and has a Syncwave AFTER the helper-status-checker.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The list of profiles that shall be used. These must exist. The Compliance Operator offers several profiles. I usually use these two for full CIS compliance check.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;With this configuration, the Compliance Operator will not only be installed but also configured with the same Argo CD Application. All you need to do is to synchronize Argo CD and let the magic happen. After a few minutes, everything should be in sync.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/setup-compliance-operator.png?width=720px" alt="Sync Compliance Operator"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Sync Compliance Operator&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Inside OpenShift the Operator is configured and starts doing its job:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/configured-compliance-operator.png?width=720px" alt="Configured Compliance Operator"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. Configured Compliance Operator&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This concludes the deployment of the Compliance Operator. For further information about the Operator itself, please read the documentation or articles:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.openshift.com/container-platform/4.15/security/compliance_operator/co-overview.html" target="_blank" rel="noopener"&gt;Official Documentation: Compliance Operator&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/compliance/2021/07/compliance-operator/"&gt;Blog: Compliance Operator&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Also, be sure to check out the READMEs of the different Charts:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-operator" target="_blank" rel="noopener"&gt;Helper Operator&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-status-checker" target="_blank" rel="noopener"&gt;Helper Operator Status Checker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/compliance-operator-full-stack" target="_blank" rel="noopener"&gt;Compliance Operator Chart&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/clusters/management-cluster/setup-compliance-operator/" target="_blank" rel="noopener"&gt;Compliance Operator Setup&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;If you have any questions or problems, feel free to create a GitHub issue at any time.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.4] Configure App-of-Apps</title><link>https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/</link><pubDate>Tue, 02 Apr 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;In the article &lt;a href="https://blog.stderr.at/gitopscollection/2024-02-02-setup-argocd/"&gt;Install GitOps to the cluster&lt;/a&gt; OpenShift GitOps is deployed using a shell script. This should be the very first installation and the only deployment that is done manually on a cluster. This procedure automatically installs the so-called &lt;strong&gt;App-of-Apps&lt;/strong&gt; named &lt;strong&gt;Argo CD Resources Manager&lt;/strong&gt; which is responsible for all further Argo CD Applications and ApplicationSets. No other configuration should be done manually if possible.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This article will demonstrate how to configure the App-of-Apps in an easy and declarative way, using ApplicationSet mainly.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At this stage, the OpenShift cluster with the openshift-gitops operator and the App-of-Apps must be deployed. Your Argo CD should look somehow like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/initial-applications.png?width=1024px" alt="Initial Applications"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Argo CD: Initial Applications&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But how do all these Applications end up in Argo CD and how can you add additional ones?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_understanding_the_argo_cd_resources_manager"&gt;Understanding the Argo CD Resources Manager&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
For any further references, I am using the GitHub repository &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/" target="_blank" rel="noopener"&gt;OpenShift Clusterconfig GitOps&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;Argo CD Resources Manager&lt;/strong&gt; is, in fact, the App-of-Apps. Its configuration file can be found in the directory &lt;strong&gt;base/argocd-resources-manager&lt;/strong&gt;. It is simply a values file and uses the Helm Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-argocd" target="_blank" rel="noopener"&gt;helper-argocd&lt;/a&gt; to create additional Applications or ApplicationSets for Argo CD.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_analyzing_the_example_values_file"&gt;Analyzing the example values file&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The example values file seems to be huge and confusing at first look. But it is quite easy to understand …​ trust me :)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Let’s walk through the file bit by bit:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_defining_header_variables"&gt;Defining Header Variables&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At the top of the file, some variables are defined as so-called anchors. These definitions might be used multiple times and are defined at the top to allow us to find and change them easily.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I define here the clusters and the information about the GitHub repository for example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;mgmt-cluster: &amp;amp;mgmtcluster https://kubernetes.default.svc &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
mgmt-cluster-name: &amp;amp;mgmtclustername in-cluster &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
production-cluster: &amp;amp;prodcluster https://api.ocp.aws.ispworld.at:6443
production-cluster-name: &amp;amp;prodclustername prod
repourl: &amp;amp;repourl &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
repobranch: &amp;amp;branch main &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the API URL for the cluster as configured in Argo CD. (The local cluster where Argo CD is running might be called kubernetes.default.svc)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the short name of the cluster as configured in Argo CD. (The local cluster where Argo CD is running might be called in-cluster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the URL to the GitHub repository that is used in this file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Define the git branch that will be used.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Any additional anchor can be used to define values, that should show up at the very top and/or are used multiple times and you do not want to write them each time.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Whenever you see a value defined as &lt;strong&gt;*repourl&lt;/strong&gt; for example, such an anchor is used.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_understanding_naming_conventions"&gt;Understanding Naming Conventions&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Each Application or ApplicationSet must have a unique name inside Argo CD. Whenever Applications are generated by ApplicationSet a prefix with the name of the cluster is usually added.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the values file multiple Applications or ApplicationSets can be defined. They are all bypassed to the Helm Chart which takes care of everything. The name that will be used for an ApplicationSet for example will be the (yaml) &lt;strong&gt;key&lt;/strong&gt; of the definition.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For example, ApplicationSets are defined as:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;applicationsets:
mgmt-cluster:
...
enable-etcd-encryption:
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;keys&lt;/strong&gt;, for example, mgmt-cluster or enable-etcd-encryption are used as names of the ApplicationSets. This way you do not need to take care of unique names, as YAML will already complain if some kwy is used twice.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_enabledisable"&gt;Enable/Disable&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Like with all of my Helm Charts, I added a switch to enable (or disable) a certain configuration. This way, you can easily remove Applications without actually deleting the specification.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The default value is &lt;strong&gt;false&lt;/strong&gt;, so you actively need to set it to &lt;code&gt;true&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; mgmt-cluster:
# Is the ApplicationSet enabled or not
enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_supported_generators"&gt;Supported Generators&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The helm chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-argocd" target="_blank" rel="noopener"&gt;helper-argocd&lt;/a&gt; supports the following generators currently:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Matrix Generator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;List Generator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Git Generator (for files)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cluster Generator&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Additional generators might be added in the future (ping me or create a pull request), but I found these the most useful ones.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But what is the difference between these generators from the configuration point of view? The different generators require different configurations and therefore provide different placeholders for variables. While the Git generator might use variables that are defined in a file that it finds {{environment}} the List (or Cluster) generator is using {{url}} to define the target cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This might make the specification of an ApplicationSet quite complex …​ and that’s the whole reason for creating the &lt;strong&gt;helper-argocd&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_example_applicationset_matrix_generator"&gt;Example ApplicationSet - Matrix Generator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first ApplicationSet I would like to show is probably the most important one. As described in &lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/"&gt;GitOps Repository Structure&lt;/a&gt; I am using a folder structure like &lt;strong&gt;clusters/management-cluster/&lt;/strong&gt; and in this folder I am defining any configuration that is applicable for that specific cluster. If I want to add a new cluster, I simply create a new folder (and a new App-of-Apps configuration). With this, you will always see which settings a specific cluster has without much hassle.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The idea is to walk over this folder and automatically create a new Argo CD Application for any sub-folder that is found. This has the advantage, that whenever I want to create an additional configuration for a cluster, I simply add another sub-folder and the ApplicationSet will automatically create a new Argo CD application.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To achieve this the so-called &lt;strong&gt;Matrix Generator&lt;/strong&gt; is used. This generator combines two (currently two are possible only) generators. In our case, it combines:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;git generator: to walk over the folder and get and sub-folder&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;list generator: to define the target cluster&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The snippet of the configuration will look like the following:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; # Definition of Matrix Generator. Only 2 generators are supported at the moment
generatormatrix: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# Git: Walking through the specific folder and take whatever is there.
- git: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
directories:
- path: clusters/management-cluster/*
- path: clusters/management-cluster/waves
exclude: true
repoURL: *repourl
revision: *branch
# List: simply define the targetCluster. The name of the cluster must be known by Argo CD
- list: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
elements:
# targetCluster is important, this will define on which cluster it will be rolled out.
# The cluster name must be known in Argo CD
- targetCluster: *mgmtclustername&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Using matrix generator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The first generator is Git: It will observe any changes in the folder &lt;strong&gt;clusters/management-cluster&lt;/strong&gt; and will create a new Argo CD Application if a new sub-folder is found. However, it excludes the folder &lt;strong&gt;clusters/management-cluster/waves/&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The second generator is List: It simply defines the target cluster where the Application that is created by the ApplicationSet shall be deployed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now let us bring the whole example together:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; mgmt-cluster: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# Is the ApplicationSet enabled or not
enabled: true &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
# Description - always useful
description: &amp;#34;ApplicationSet that Deploys on Management Cluster Configuration (using Matrix Generator)&amp;#34; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
# Any labels you would like to add to the Application. Good to filter it in the Argo CD UI.
labels: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
category: configuration
env: mgmt-cluster
# Using go text template. See: https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/GoTemplate/
goTemplate: true &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
argocd_project: *mgmtclustername &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
# preserve all resources when the application get deleted. This is useful to keep that workload even if Argo CD is removed or severely changed.
preserveResourcesOnDeletion: true &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
# Definition of Matrix Generator. Only 2 generators are supported at the moment
generatormatrix: &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
# Git: Walking through the specific folder and take whatever is there.
- git:
directories:
- path: clusters/management-cluster/*
- path: clusters/management-cluster/waves
exclude: true
repoURL: *repourl
revision: *branch
# List: simply define the targetCluster. The name of the cluster must be known by Argo CD
- list:
elements:
# targetCluster is important, this will define on which cluster it will be rolled out.
# The cluster name must be known in Argo CD
- targetCluster: *mgmtclustername
syncPolicy: &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
autosync_enabled: false&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Key of the ApplicationSet inside the yaml specification, that will be used as object name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Is the ApplicationSet enabled or not (Default: false)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A useful description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Labels that can be used to filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Enable the usage of Go Template for this ApplicationSet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The Argo CD project (not OpenShift project) the ApplicationSet belongs to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Be sure that resources are not deleted when deleting the ApplicationSet. I found this quite useful …​ otherwise, all Applications the ApplicationSet created will be removed INCLUDING the resources they have created.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The specification of the matrix generator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Any kind of syncPolicy …​ in this case automatic synchronization of the Applications that are created is disabled.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Based on these settings the &lt;strong&gt;helper-argocd&lt;/strong&gt; helm chart will render an ApplicationSet object automatically. As mentioned above it will be called &lt;strong&gt;mgmt-cluster&lt;/strong&gt; and creates an Application for any sub-folder it finds in clusters/management-cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Any new folder that is added will automatically create a new Application. You do not need to configure anything else.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The full objects will look like this:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: mgmt-cluster &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
namespace: openshift-gitops
labels:
app.kubernetes.io/instance: argocd-resources-manager
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: helper-argocd
category: configuration
env: mgmt-cluster
helm.sh/chart: helper-argocd-2.0.28
spec:
generators: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
- matrix:
generators:
- git:
directories:
- path: clusters/management-cluster/*
- exclude: true
path: clusters/management-cluster/waves
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39;
revision: main
- list:
elements:
- targetCluster: in-cluster
goTemplate: true
goTemplateOptions:
- missingkey=error
syncPolicy:
preserveResourcesOnDeletion: true
template:
metadata:
name: &amp;#39;{{ .targetCluster }}-{{ .path.basenameNormalized }}&amp;#39; &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
spec:
destination: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
name: &amp;#39;{{ .targetCluster }}&amp;#39;
namespace: default
info:
- name: Description
value: ApplicationSet that Deploys on Management Cluster Configuration (using Matrix Generator)
project: in-cluster
source: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
path: &amp;#39;{{ .path.path }}&amp;#39;
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39;
targetRevision: main&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the object == name of the key in the values file definition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration of the matrix generator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Applications that this ApplicationSet will generate. In this case, it will concat the name of the target cluster and the name of the path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Target cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Definition of the source for the Application&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_example_applicationset_git_generator"&gt;Example ApplicationSet - Git Generator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Now let us take a look at a second example using the git generator. The basic idea is quite similar and just a few minor changes must be made to our configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In the following object, a git &lt;strong&gt;file&lt;/strong&gt; generator is used to observe a specific folder and look for the file named values.yaml. For each file that it found an Application is created.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This example is also explained in my article at &lt;a href="https://www.redhat.com/en/blog/project-onboarding-using-gitops-and-helm?channel=/en/blog/channel/hybrid-cloud-infrastructure" target="_blank" rel="noopener"&gt;Project onboarding using GitOps and Helm&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; # Tenant Onboarding (using Git Generator)
onboarding-tenant-workload: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
# Is the ApplicationSet enabled or not
enabled: true
# Description - always useful
description: &amp;#34;Onboarding Workload to the cluster&amp;#34;
# Any labels you would like to add to the Application. Good to filter it in the Argo CD UI.
labels:
catagory: tenant-onboarding
# Path to the Git repository. The default URL and revision are defined as anchors at the beginning of the file, but could be overwritten here.
path: clusters/all/project-onboarding &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
repourl: *repourl
targetrevision: *branch
# Using go text template. See: https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/GoTemplate/
goTemplate: true
# Helm configuration. A list of helm values files
helm: &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
per_cluster_helm_values: false
value_files:
- &amp;#39;/{{ .path.path }}/values.yaml&amp;#39;
- /tenants/values-global.yaml
# Generator: currently list, git and cluster are possible.
# either &amp;#34;generatorlist&amp;#34;, &amp;#34;generatorgit&amp;#34; or &amp;#34;generatorclusters&amp;#34;
# Define the repository that shall be checked for configuration file
generatorgit: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- repourl: *repourl
targetrevision: *branch
files:
- tenants/**/values.yaml &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
# preserve all resources when the application gets deleted. This is useful to keep that workload even if Argo CD is removed or severely changed.
preserveResourcesOnDeletion: true&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the ApplicationSet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The repo URL and path which shall be read for the ApplicationSet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;A list of values files, that shall be used.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The specification of the Git generator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The path that shall be observed by this ApplicationSet. ** will return all files and directories recursively.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Again, the Helm chart &lt;strong&gt;helper-argocd&lt;/strong&gt; will render an ApplicationSet for us.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: onboarding-tenant-workload
namespace: openshift-gitops
labels:
app.kubernetes.io/instance: argocd-resources-manager
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: helper-argocd
catagory: tenant-onboarding
helm.sh/chart: helper-argocd-2.0.28
spec:
generators:
- git:
files:
- path: tenants/**/values.yaml
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39;
revision: main
goTemplate: true
goTemplateOptions:
- missingkey=error
syncPolicy:
preserveResourcesOnDeletion: true
template:
metadata:
name: &amp;#39;{{ index .path.segments 1 | normalize }}-{{ .path.basename }}&amp;#39;
spec:
destination:
name: &amp;#39;{{ .environment }}&amp;#39;
namespace: default
info:
- name: Description
value: Onboarding Workload to the cluster
project: default
source:
helm:
valueFiles:
- &amp;#39;/{{ .path.path }}/values.yaml&amp;#39;
- /tenants/values-global.yaml
path: clusters/all/project-onboarding
repoURL: &amp;#39;https://github.com/tjungbauer/openshift-clusterconfig-gitops&amp;#39;
targetRevision: main&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_example_applicationset_list_generator"&gt;Example ApplicationSet - List Generator&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At this point, we have seen two examples of ApplicationSets defined for &lt;strong&gt;helper-argocd&lt;/strong&gt;. The List generator will be very easy to understand as it simply uses a list of target clusters to render the ApplicationSet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following snippet demonstrates that all you need to set are the &lt;strong&gt;clustername&lt;/strong&gt; and &lt;strong&gt;clusterurl&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Please also verify the article &lt;a href="https://www.redhat.com/en/blog/argo-cd-and-release-management-with-helm-charts-and-applicationsets?channel=/en/blog/channel/hybrid-cloud-infrastructure" target="_blank" rel="noopener"&gt;Argo CD and Release Management with Helm Charts and ApplicationSets&lt;/a&gt; to understand the usage of the setting &lt;strong&gt;chart_version&lt;/strong&gt;.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt; # List of clusters
# &amp;#34;clustername&amp;#34; (string): Is the name of the cluster a defined in Argo CD
# &amp;#34;clusterurl&amp;#34; (string): Is the URL of the cluster API
# &amp;#34;chart_version&amp;#34; (string, optional): Defines which chart version shall be deployed on each cluster.
generatorlist:
- clustername: *mgmtclustername
clusterurl: *mgmtcluster&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is all the magic. The rendered ApplicationSet will look like:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: install-sonarqube
namespace: openshift-gitops
labels:
app.kubernetes.io/instance: argocd-resources-manager
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: helper-argocd
category: project
helm.sh/chart: helper-argocd-2.0.28
spec:
generators:
- list:
elements:
- cluster: in-cluster
url: &amp;#39;https://kubernetes.default.svc&amp;#39;
template:
metadata:
name: &amp;#39;{{ cluster }}-install-sonarqube&amp;#39;
spec:
destination:
namespace: sonarqube
server: &amp;#39;{{ url }}&amp;#39;
info:
- name: Description
value: Install Sonarqube
project: &amp;#39;{{ cluster }}&amp;#39;
source:
chart: sonarqube
helm:
releaseName: sonarqube
repoURL: &amp;#39;https://charts.stderr.at/&amp;#39;
targetRevision: 1.0.1&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The list generator can be used to deploy on &lt;strong&gt;ALL&lt;/strong&gt; clusters too. Simply define &lt;strong&gt;generatorlist: []&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The example above also demonstrates how to use a Helm chart instead of a git repository.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_what_about_applications"&gt;What about Applications?&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The examples above show the usage of ApplicationSet and recently I migrated any specification of an Application to ApplicationSets as I believe this is easier to use, especially when the Chart is rendering it for you.
However, it is still possible to define Applications as well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following example defines such an Application. The configuration differs compared to the ApplicationSet, however, the main idea stays the same:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-yaml hljs" data-lang="yaml"&gt;applications: &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
node-labelling: &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
enabled: true
description: &amp;#34;Deploy Node Labels&amp;#34;
labels:
category: configuration
namespace:
name: default
create: false
server: *mgmtcluster &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
project: default
syncOptions: &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
- name: ServerSideApply
value: true
- name: Validate
value: false
source: &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
path: clusters/management-cluster/node-labels
helm:
valuesfiles:
- name: values.yaml
repourl: *repourl
targetrevision: *branch&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Defining Applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Name of the Application. Be sure that it is unique since this time no prefix will be added&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The target cluster for this Application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Different options for the synchronization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The specification of the source. This contains the path, URL and branch of the repository and (in this case) the definition of a Helm values file.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_summary"&gt;Summary&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I hope I was able to explain the usage of my chart &lt;strong&gt;helper-argocd&lt;/strong&gt; and how I configure it. You can also verify the &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/helper-argocd" target="_blank" rel="noopener"&gt;README&lt;/a&gt; to find additional possible settings and the example &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/base/argocd-resources-manager/values.yaml" target="_blank" rel="noopener"&gt;values.file&lt;/a&gt; that I use for all my clusters when I to a demo.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.3] Setup OpenShift GitOps/Argo CD</title><link>https://blog.stderr.at/gitopscollection/2024-02-02-setup-argocd/</link><pubDate>Fri, 02 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2024-02-02-setup-argocd/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;„&lt;em&gt;If it is not in GitOps, it does not exist&lt;/em&gt;“ - is a mantra I hear quite often and also try to practice at customer engagements. The idea is to have Git as the only source of truth on what happens inside the environment. That said, &lt;a href="https://openpracticelibrary.com/practice/everything-as-code/"&gt;Everything as Code&lt;/a&gt; is a practice that treats every aspect of the system as a code. Storing this code in Git provides a shared understanding, traceability and repeatability of changes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;While there are many articles about how to get GitOps into the deployment process of applications, this one rather sets the focus on the &lt;strong&gt;cluster configuration&lt;/strong&gt; and tasks system administrators usually have to do.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Also check out the article &lt;a href="https://blog.stderr.at/openshift/2020-08-06-argocd/"&gt;GitOps Argo CD&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It all begins with an OpenShift cluster. Such a cluster must be installed and while we will not discuss a bootstrap of the whole cluster … yes, it is possible to even automate the cluster deployment using Advanced Cluster Management as an example, we will simply assume that one cluster is up and running.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;For our setup, an OpenShift cluster 4.14 is deployed and we will use the repository &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops"&gt;OpenShift Cluster Configuration using GitOps&lt;/a&gt; to deploy our configuration onto this cluster. This repository shall act as the source of truth for any configuration. In the article &lt;a href="http://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/"&gt;Choosing the right Git repository structure&lt;/a&gt; I have explained the folder structure I am usually using. As tool I am usually using Helm Charts.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The &lt;strong&gt;openshift-clusterconfig-gitops&lt;/strong&gt; repository heavily uses the Helm Repository found at &lt;a href="https://charts.stderr.at/" class="bare"&gt;https://charts.stderr.at/&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_deploy_openshift_gitops"&gt;Deploy OpenShift-GitOps&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The first thing we need to do is to deploy &lt;a href="https://docs.openshift.com/gitops/1.11/understanding_openshift_gitops/what-is-gitops.html" target="_blank" rel="noopener"&gt;OpenShift-GitOps&lt;/a&gt;, which is based on the &lt;a href="https://argo-cd.readthedocs.io/en/stable/" target="_blank" rel="noopener"&gt;Argo CD&lt;/a&gt; project. OpenShift-GitOps comes as an Operator and is available to all OpenShift customers. The Operator will deploy and configure Argo CD and provide several custom resources to configure Argo CD &lt;strong&gt;Applications&lt;/strong&gt; or &lt;strong&gt;ApplicationSets&lt;/strong&gt; for example.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To automate the operator deployment the following shell script can be used: &lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops/blob/main/init_GitOps.sh" target="_blank" rel="noopener"&gt;init_GitOps.sh&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This Shell script is the only script that is executed manually. It installs and configures Argo CD. Any other operation on the cluster must then be done using GitOps processes. I am using this to quickly install a new Demo-cluster. There are alternatives and maybe better way, but for my purpose it works pretty well.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Clone the repository to your local machine&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;git clone https://github.com/tjungbauer/openshift-clusterconfig-gitops.git&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Be sure that you are logged in the the required cluster&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;oc whoami --show-server&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Execute the init_GitOps.sh&lt;/p&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;./init_GitOps.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The script will deploy the operator and configure/patch the Argo CD instance. In addition, it will create the so-called &lt;strong&gt;Application of Applications&lt;/strong&gt;, which acts as an umbrella Application, that automatically creates all other Argo CD Application(Sets).
For now, the App of Apps is the only Argo CD Application that automatically synchronizes all changes found in Git. This is for security, purposes so you can test the cluster configuration one after another.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Of course, it is up to you if you want to use the shell script. The Operator can also be installed manually, using Advanced Cluster Manager, or using Platform Operators and installing the Operating during the cluster installation (However, this feature is currently (v4.15) TechPreview)
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_what_will_this_script_do"&gt;What will this script do?&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I will not de-assemble the script line by line, but in general, the following will happen:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;Adding repository &lt;a href="https://charts.stderr.at/" class="bare"&gt;https://charts.stderr.at/&lt;/a&gt; and install the Chart &lt;a href="https://github.com/tjungbauer/helm-charts/tree/main/charts/openshift-gitops"&gt;openshift-gitops&lt;/a&gt;&lt;/p&gt;
&lt;div class="admonitionblock caution"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-caution" title="Caution"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
This FIRST OpenShift-GitOps will be deployed with &lt;strong&gt;cluster-admin&lt;/strong&gt; privileges since we want to manage the whole cluster configuration. This Argo CD instance should not be used for application deployment. For that, deploy additional instances of GitOps.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Waiting for Deployments to become ready&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy the &lt;strong&gt;Application of Applications&lt;/strong&gt; that is responsible for automatically deploying a set of Applications or ApplicationSets (see &lt;a href="#The Argo CD Object Manager Application"&gt;[The Argo CD Object Manager Application]&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following shows the output of the command:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;div class="expand"&gt;
&lt;div class="expand-label" style="cursor: pointer;" onclick="$h = $(this);$h.next('div').slideToggle(100,function () {$h.children('i').attr('class',function () {return $h.next('div').is(':visible') ? 'fas fa-chevron-down' : 'fas fa-chevron-right';});});"&gt;
&lt;i style="font-size:x-small;" class="fas fa-chevron-right"&gt;&lt;/i&gt;
&lt;span&gt;
&lt;a&gt;Expand me...&lt;/a&gt;
&lt;/span&gt;
&lt;/div&gt;
&lt;div class="expand-content" style="display: none;"&gt;
&lt;div class="literalblock"&gt;
&lt;div class="content"&gt;
&lt;pre&gt;❯ ./init_GitOps.sh
Starting Deployment
Deploying OpenShift GitOps Operator
Adding Helm Repo https://charts.stderr.at/
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /Users/tjungbau/openshift-aws/aws/auth/kubeconfig
&amp;#34;tjungbauer&amp;#34; has been added to your repositories
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /Users/tjungbau/openshift-aws/aws/auth/kubeconfig
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the &amp;#34;sealed-secrets&amp;#34; chart repository
...Successfully got an update from the &amp;#34;tjungbauer&amp;#34; chart repository
...Successfully got an update from the &amp;#34;apache-airflow&amp;#34; chart repository
...Successfully got an update from the &amp;#34;hashicorp&amp;#34; chart repository
...Successfully got an update from the &amp;#34;bitnami&amp;#34; chart repository
Update Complete. ⎈Happy Helming!⎈
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /Users/tjungbau/openshift-aws/aws/auth/kubeconfig
Release &amp;#34;openshift-gitops-operator&amp;#34; has been upgraded. Happy Helming!
NAME: openshift-gitops-operator
LAST DEPLOYED: Mon Sep 26 13:22:33 2022
NAMESPACE: openshift-operators
STATUS: deployed
REVISION: 2
TEST SUITE: None
Give the gitops-operator some time to be installed. Waiting for 45 seconds...
Waiting for operator to start. Chcking every 10 seconds.
NAME READY UP-TO-DATE AVAILABLE AGE
gitops-operator-controller-manager 1/1 1 1 4d4h
Waiting for openshift-gitops namespace to be created. Checking every 10 seconds.
NAME STATUS AGE
openshift-gitops Active 4d4h
Waiting for deployments to start. Checking every 10 seconds.
NAME READY UP-TO-DATE AVAILABLE AGE
cluster 1/1 1 1 4d4h
Waiting for all pods to be created
Waiting for deployment cluster
deployment &amp;#34;cluster&amp;#34; successfully rolled out
Waiting for deployment kam
deployment &amp;#34;kam&amp;#34; successfully rolled out
Waiting for deployment openshift-gitops-applicationset-controller
deployment &amp;#34;openshift-gitops-applicationset-controller&amp;#34; successfully rolled out
Waiting for deployment openshift-gitops-redis
deployment &amp;#34;openshift-gitops-redis&amp;#34; successfully rolled out
Waiting for deployment openshift-gitops-repo-server
deployment &amp;#34;openshift-gitops-repo-server&amp;#34; successfully rolled out
Waiting for deployment openshift-gitops-server
deployment &amp;#34;openshift-gitops-server&amp;#34; successfully rolled out
GitOps Operator ready
Lets use our patched Argo CD CRD
argocd.argoproj.io/openshift-gitops unchanged
clusterrolebinding.rbac.authorization.k8s.io/cluster-admin-0 unchanged
Waiting for deployment cluster
deployment &amp;#34;cluster&amp;#34; successfully rolled out
Waiting for deployment kam
deployment &amp;#34;kam&amp;#34; successfully rolled out
Waiting for deployment openshift-gitops-applicationset-controller
deployment &amp;#34;openshift-gitops-applicationset-controller&amp;#34; successfully rolled out
Waiting for deployment openshift-gitops-redis
deployment &amp;#34;openshift-gitops-redis&amp;#34; successfully rolled out
Waiting for deployment openshift-gitops-repo-server
deployment &amp;#34;openshift-gitops-repo-server&amp;#34; successfully rolled out
Waiting for deployment openshift-gitops-server
deployment &amp;#34;openshift-gitops-server&amp;#34; successfully rolled out
GitOps Operator ready... again
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /Users/tjungbau/openshift-aws/aws/auth/kubeconfig
Release &amp;#34;app-of-apps&amp;#34; has been upgraded. Happy Helming!
NAME: app-of-apps
LAST DEPLOYED: Mon Sep 26 13:23:59 2022
NAMESPACE: openshift-gitops
STATUS: deployed
REVISION: 2
TEST SUITE: None&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_logging_into_argo_cd"&gt;Logging into Argo CD&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;At this point, we have GitOps and the &amp;#34;&lt;strong&gt;App of Apps&lt;/strong&gt;&amp;#34; deployed.
Argo CD comes with a WebUI and a command line tool. The latter must installed to your local environment. In this article, we will use the WebUI.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;To access the WebUI use the applications menu of the top right corner in Openshift.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/gitops/images/argocd2/argocd-link.png?width=340px" alt="WebUI Link"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. Argo CD: WebUI Link&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Use the button &amp;#34;Login via OpenShift&amp;#34;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/gitops/images/argocd2/argocd-login.png?width=340px" alt="Authentication"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. Argo CD: Authentication&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_argo_cd_resources_manager_application"&gt;The Argo CD Resources Manager Application&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The &lt;strong&gt;Application of Applications&lt;/strong&gt; (short App of Apps) is called &lt;strong&gt;Argo CD Resources Manager&lt;/strong&gt; and it is the only Argo CD application that is deployed using the init script. This single Argo CD Application has the sole purpose of deploying other Argo CD objects, such as Applications, ApplicationSets and AppProjects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/openshift-platform/gitops/images/argocd2/argocd-app-of-apps.png?width=340px" alt="App of Apps"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 3. Argo CD: App of Apps&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It synchronizes everything that is found in the repository in the path:
&lt;em&gt;base/argocd-resources-manager&lt;/em&gt; (main branch)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Whenever you would like to create a new Argo CD application(set) it is supposed to be done using this App-of-Apps or to be more exact: in the path mentioned above.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
The App-of-Apps is the only Argo CD Application (at this moment) that has automatic synchronization enabled. Thus any changes in the App-of-Apps will be propagated automatically as soon as GitOps syncs with Git.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The current Applications or ApplicationSets that come with the bootstrap repository are for example:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Deployment of Advanced Cluster Security (RHACS)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deployment of Advanced Cluster Management (RHACM)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deployment of basic cluster configuration (i.e. etcd encryption, some UI tweaks …​)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deployment of Compliance Operator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and many more.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Check out the deployed Argo CD objects or the openshift-clusterconfig-gitops repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;A deep dive into the argocd-resources-manager will be topic of a different episode of this serie.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.2] Choosing the right Git repository structure</title><link>https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/</link><pubDate>Thu, 28 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;One of the most popular questions asked before adopting the GitOps approach is how to deploy an application to different environments (Test, Dev, Production, etc.) in a safe and repeatable way.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Each organisation has different requirements, and the choice will depend on a multitude of factors that also include non-technical aspects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Therefore, it is important to state: &amp;#34;&lt;strong&gt;There is no unique “right” way, there are common practices&lt;/strong&gt;&amp;#34;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
In this series, I will focus on cluster configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_git_repository_strategy_options"&gt;Git Repository Strategy - Options&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;As written in the introduction: There is no unique “right” way, there are common practices. But how shall the Git repository structure look like? How shall the folder structure look like? Multiple options might be considered. Each has advantages and disadvantages, some I would recommend, some I would not recommend.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It is important to understand that:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Git repository structure will depend heavily on how the organisation is laid out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The final repo and directory structure is unique for every organisation, as such the right one will be a discovery process within the organisation and the teams involved in the GitOps engineering process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Before I describe what I usually try to leverage, let’s see the different options.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_environment_per_branch"&gt;Environment-per-branch&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this case, there is a Git branch for each environment. A “Dev” branch holds the configuration for the DEV environments, a “production” branch for production and so on. This approach is very popular and will be familiar to people who have adopted git flow in the past. However, it is focused on application source code and not environment configuration and is best used when you need to support multiple versions of your application in production. I do not recommend this approach for GitOps, the main reasons are that pull requests and merges will be very complex and promotions between environments are a hurdle. The whole life cycle of a cluster configuration will be very complex.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_environment_per_folder_monorepo"&gt;Environment-per-folder - Monorepo&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this case, all environments are in a &lt;strong&gt;single&lt;/strong&gt; Git repository, and all are in the same branch. The filesystem has different folders that hold configuration files for each environment. The configuration of the “DEV” environment is described by a “DEV” folder, the “production” environment is found in a “production” folder and so on.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/2_repostructure/monorepo.png" alt="GitOps Monorepo Approach"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 1. GitOps Monorepo Approach&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This is the approach I usually recommend, especially when someone is new to the whole GitOps workflow and because of the simplicity of setting up such a repository.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following advantages and disadvantages should be considered:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Provides a central location for configuration changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This simplicity enabled straightforward Git workflows that will be centrally visible to the entire organisation, allowing a smoother and clearer approval process and merging.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Better suitable for small teams that are managing the cluster and easy to read and understand&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Easy to debug problems.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Scalability &amp;gt;&amp;gt; Increase complexity &amp;gt;&amp;gt; Management&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Performance for huge repositories&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Challenging to control access permissions on a single repository&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_environment_per_repository_multirepo"&gt;Environment-per-repository - Multirepo&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In this case, each environment is on its separate git repository. So, the DEV environment is in a git repository called “DEV”, the “production” environment is in a “production” git repository and so on. The GitOps agent (OpenShift GitOps) connects to multiple repositories and takes care to apply the correct configuration to the correct target cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="imageblock"&gt;
&lt;div class="content"&gt;
&lt;img src="https://blog.stderr.at/gitopscollection/images/2_repostructure/multirepo.png" alt="GitOps Multirepo Approach"/&gt;
&lt;/div&gt;
&lt;div class="title"&gt;Figure 2. GitOps Multirepo Approach&lt;/div&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Like the Monorepo approach, Multirepo comes with some advantages and disadvantages:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Allows separating concerns between different departments of organisations (a repository for the security team, a repository for the operations team, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;More complex to manage&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Harder to understand and read the configuration (what is coming from where)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Argo CD Application dependencies might not be solved (i.e., Security tries to manage the same object as the operating team. Who is the leader?)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_example_setup"&gt;Example Setup&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_the_approach_i_choose"&gt;The Approach I choose&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I usually use and recommend &lt;strong&gt;Monorepo&lt;/strong&gt; approach.
The &lt;strong&gt;environment-per-folder&lt;/strong&gt; approach is a very good way to organise your GitOps applications. Not only is it very simple to implement and maintain, but it is also the optimal method for promoting releases between different GitOps environments. This approach can also work for any number of environments without any additional effort. Cluster configurations (for multiple clusters) are typically done by one team, therefore controlling access permissions in Git is not a big issue.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_example_folder_structure"&gt;Example Folder Structure&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Over time the following folder structure evolved or my repository:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="listingblock"&gt;
&lt;div class="content"&gt;
&lt;pre class="highlightjs highlight"&gt;&lt;code class="language-bash hljs" data-lang="bash"&gt;├── base &lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;(1)&lt;/b&gt;
│   ├── argocd-resources-manager &lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;(2)&lt;/b&gt;
│   └── init_app_of_apps &lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;(3)&lt;/b&gt;
├── charts &lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;(4)&lt;/b&gt;
├── clusters &lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;(5)&lt;/b&gt;
│   ├── all &lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;(6)&lt;/b&gt;
│   │   ├── base-operators
│   │   ├── etcd-encryption
│   ├── management-cluster &lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;(7)&lt;/b&gt;
│   │   ├── branding
│   │   ├── generic-cluster-config
│   │   ├── management-gitops
│   │   ├── node-labels
│   │   ├── openshift-data-foundation
│   │   ├── setup-acm
│   │   ├── setup-acs
│   │   ├── setup-compliance-oeprator
│   │   ├── setup-openshift-logging
│   │   └── setup-quay
│   └── production-cluster &lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;(8)&lt;/b&gt;
│   │   ├── branding
│   │   ├── generic-cluster-config
│   │   ├── node-labels
│   │   ├── openshift-data-foundation
│   │   ├── setup-acs
│   │   ├── setup-compliance-oeprator
│   │   └── setup-openshift-logging
├── init_GitOps.sh &lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;(9)&lt;/b&gt;
├── scripts &lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;(10)&lt;/b&gt;
│   ├── example_htpasswd
│   ├── sealed_secrets
├── tenant-projects &lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;(11)&lt;/b&gt;
   ├── my-main-app
   └── my-second-app&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="colist arabic"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="1"&gt;&lt;/i&gt;&lt;b&gt;1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;base&lt;/code&gt; folder contains basic configurations or Argo CD itself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="2"&gt;&lt;/i&gt;&lt;b&gt;2&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;argocd-resources-manager&lt;/code&gt; is a Helm Chart that configures Applications and ApplicationSets or Argo CD using a single configuration file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="3"&gt;&lt;/i&gt;&lt;b&gt;3&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;init_app_of_apps&lt;/code&gt; is used during the initial installation of OpenShift GitOps and installs the App-of-Apps that manages other Applications or Argo CD. This Application automatically synchronises and watches for changes in the folder &lt;code&gt;argocd-resources-manager&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="4"&gt;&lt;/i&gt;&lt;b&gt;4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;charts&lt;/code&gt; folder is &lt;strong&gt;optional&lt;/strong&gt; and can store local Helm Charts. Usually, it is better to release the Charts in a Helm repository, where they can be managed independently to the cluster configuration repository.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="5"&gt;&lt;/i&gt;&lt;b&gt;5&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The folder for the different clusters.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="6"&gt;&lt;/i&gt;&lt;b&gt;6&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configurations that are equal for all clusters and simple to achieve without any deeper configuration. Currently, for example, the activation of the etcd encryption and the deployment of base Operators that every cluster will require. In this case, the Operators are installed only, without further configuration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="7"&gt;&lt;/i&gt;&lt;b&gt;7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration for the &lt;code&gt;management-cluster&lt;/code&gt;. For example, deploying ACM, ACS, Quay or any generic cluster configuration. Here we see immediately what is deployed and where I can modify the configuration for that cluster.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="8"&gt;&lt;/i&gt;&lt;b&gt;8&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Configuration for the &lt;code&gt;production-clusters&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="9"&gt;&lt;/i&gt;&lt;b&gt;9&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The deployment script to install and configure the OpenShift GitOps Operator. This might be replaced or at least modified in the future once &lt;a href="https://docs.openshift.com/container-platform/4.14/operators/admin/olm-managing-po.html#platform-operators_olm-managing-po" target="_blank" rel="noopener"&gt;PlatformOperators&lt;/a&gt; are generally available and not in a technology preview state anymore.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="10"&gt;&lt;/i&gt;&lt;b&gt;10&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;scripts&lt;/code&gt; folder simply contains some shell scripts that might be useful. For example, to backup a Sealed Secrets key or generate a htpasswd file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;i class="conum" data-value="11"&gt;&lt;/i&gt;&lt;b&gt;11&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;tenant-projects&lt;/code&gt; folder is a special folder to store the configuration or projects. Any project onboarding is configured here, such as Quota, LimitRanges, NetworkPolicies etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect2"&gt;
&lt;h3 id="_why_the_repeating_folders"&gt;Why the repeating folders?&lt;/h3&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Some may argue why certain folders are equal for management and production clusters, for example, &amp;#34;setup-compliance-operator&amp;#34;, when this could be done more easily by defining such folder only once and using different overlays (using Kustomize) or different values-files (using Helm Charts). However, while this is a very valid question, I personally, like to see immediately what is configured on each cluster. I see, based on the folders, what is configured on the management cluster and where I could modify the configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Using Kustomize overlays, for example, would mean recreating the overlays for each configuration (if you want to have a clean separation and not combine all manifests into one overlay). Using different values-files is again a valid option, but (also again), you do not see what is configured on which cluster with one look.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Therefore, I like this folder structure, even if it may look weird (especially if you are used to Kustomize overlays). However, everyone is invited to define their very own structure :)&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_managing_kubernetes_manifests"&gt;Managing Kubernetes Manifests&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The Kubernetes manifests (the yaml files) must be managed in a way Argo CD can read and synchronise them.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Three &lt;strong&gt;main&lt;/strong&gt; options are commonly used:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Helm&lt;/strong&gt;: Helm uses a packaging format called charts. A chart is a collection of files that describe a related set of Kubernetes resources.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kustomize&lt;/strong&gt;: A Template-free way to customise application configuration that simplifies the use of off-the-shelf applications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Plain Text&lt;/strong&gt;: Plain text Kubernetes objects provided in YAML of JSON format.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Argo CD also understands &lt;strong&gt;jsonnet&lt;/strong&gt; or even custom plugins. However, I had no customer up until now, who wanted to use something else than Kustomize or Helm.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The different tools are not explained in detail in this article, but the choice of the tool highly depends on the existing knowledge and individual preferences inside the company. Every option has advantages and disadvantages that will become visible when they are used.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;I have seen companies tend to use Helm Charts or Plain Text, especially when they are new to the tools.
However, no tool is better than the other. Instead, the tools can be combined which might be useful for some use cases.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock note"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-note" title="Note"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
Kustomize and Helm do not exclude each other and can be combined. However, for the start, a single tool should be selected.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>[Ep.1] Introducing the GitOps Approach</title><link>https://blog.stderr.at/gitopscollection/2023-12-11-gitops-intro/</link><pubDate>Fri, 01 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.stderr.at/gitopscollection/2023-12-11-gitops-intro/</guid><description>&lt;div class="paragraph"&gt;
&lt;p&gt;When managing one or more clusters, the question arises as to how cluster configurations and applications can be installed securely, regularly, and in the same way.
This is where the so-called GitOps approach helps, according to the mantra: &amp;#34;&lt;strong&gt;If it is not in Git, it does not exist&lt;/strong&gt;&amp;#34;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The idea is to have Git as the only source of truth on what happens inside the environment. While there are many articles about how to get GitOps into the deployment process of applications, this series of articles tries to set the focus on the &lt;strong&gt;cluster configuration&lt;/strong&gt; and tasks system administrators usually have to do, for example: Setup an Operator.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_the_gitops_approach"&gt;The GitOps Approach&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;This series includes the following articles:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="olist arabic"&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2023-12-28-gitops-repostructure/"&gt;Choosing the right Git repository structure&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-02-02-setup-argocd/"&gt;Install GitOps to the cluster&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-04-02-configure_app_of_apps/"&gt;Configure App-of-Apps&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-04-25-installing-compliance-operator/"&gt;Setup Compliance Operator&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-04-28-installing-advanced-cluster-security/"&gt;Setup &amp;amp; Configure Advanced Cluster Security using GitOps&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-05-17-configure-minio-buckets/"&gt;Configure Buckets in MinIO using GitOps&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-05-19-install-openshift-logging/"&gt;Installing OpenShift Logging using GitOps&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-06-02-multisources-for-application-in-argocd/"&gt;Multiple Sources for Applications in Argo CD&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-06-07-update-cluster-version-with-gitops/"&gt;Update Cluster Version&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-07-04-managing-certificates-with-gitops/"&gt;Managing Certificates&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://blog.stderr.at/gitopscollection/2024-10-13-using-post-renderer/"&gt;Using Kustomize to post render a Helm Chart&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
In this series, I will focus on cluster configuration.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The GitOps approach is a very common practice and the-facto &amp;#34;standard&amp;#34; as of today.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonitionblock warning"&gt;
&lt;table&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td class="icon"&gt;
&lt;i class="fa icon-warning" title="Warning"&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td class="content"&gt;
When I write standard, then be assured, that the approach itself should be followed, but HOW this is done can be a topic of many tough discussions.
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;But what is it and why should a company invest time to follow this approach?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;_GitOps is a declarative way to implement continuous deployment for cloud-native applications. It should be a repeatable process to manage multiple clusters.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;GitOps adds the following features to company processes:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Everything as code&lt;/strong&gt;: The entire state of the application, infrastructure and configuration is declaratively defined as code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Git and the single source of truth&lt;/strong&gt;: Every setting and every manifest is stored and versioned in Git. Any change must first be saved to Git.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Operations via Git workflows&lt;/strong&gt;: Standard Git procedures, such as pull or merge requests, should be used to track any changes to the applications or cluster configurations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;It is important that not only the manifests of the applications but also the cluster configuration is stored in Git. The goal should be to ensure that no manual changes are made directly to the cluster.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_benefitschallenges"&gt;Benefits/Challenges&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Deploying new versions of applications or cluster configurations with a high degree of confidence is a desirable goal as getting features reliably to production is one of the most important characteristics of fast-moving organisations.
GitOps is a set of &lt;strong&gt;common practices&lt;/strong&gt; where the entire code delivery process is controlled via Git, including infrastructure and application definition as code and automation to complete updates and rollbacks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;GitOps constantly watches for changes in Git repositories and compares them with the current state of the cluster. If there is a drift it will either automatically synchronise to the wanted state or warn accordingly (manual sync must then be performed).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The key GitOps advantages are:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Cluster and application configuration versioned in Git&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visualisation of desired system state&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automatically syncs configuration from Git to clusters (if enabled)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drift detection, visualisation, and correction&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rollback and roll-forward to any Git commit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Manifest templating support (Helm, Kustomize, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visual insight into sync status and history.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Role-Based access support&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pipeline integration&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Adopting GitOps has enormous benefits but does pose some challenges. Many teams will have to adjust their culture and way of working to support using Git as the single source of truth. Strictly adhering to GitOps processes will mean all changes will be committed. This may present a challenge when it comes to debugging a live environment. There may be times when that is necessary and will require suspending GitOps in some way.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Some other prerequisites for adopting GitOps include&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Good testing and CI processes are in place.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A strategy for dealing with promotions between environments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Strategy for Secrets management.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_used_tools"&gt;Used Tools&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following list of tools (or specifications) are used for our GitOps Approach.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.openshift.com/gitops/1.11/understanding_openshift_gitops/what-is-gitops.html#what-is-gitops" target="_blank" rel="noopener"&gt;OpenShift GitOps&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://helm.sh/docs/topics/charts/" target="_blank" rel="noopener"&gt;Helm&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="sect1"&gt;
&lt;h2 id="_used_repositories"&gt;Used Repositories&lt;/h2&gt;
&lt;div class="sectionbody"&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;The following two Git repositories are used throughout the series:&lt;/p&gt;
&lt;/div&gt;
&lt;div class="ulist"&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/tjungbauer/openshift-clusterconfig-gitops" target="_blank" rel="noopener"&gt;OpenShift Configuration&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://charts.stderr.at/" target="_blank" rel="noopener"&gt;Helm Repository&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item></channel></rss>