<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>marschall.systems</title><link>https://marschall.systems/</link><description>Recent content on marschall.systems</description><generator>Hugo - gohugo.io</generator><language>en</language><contact/><copyright>© marschall.systems {year}</copyright><atom:link href="https://marschall.systems/index.xml" rel="self" type="application/rss+xml"/><item><title>Janus: Multi-Tenant Access Control for Observability-Data</title><link>https://marschall.systems/blog/janus-release/</link><pubDate>Tue, 03 Feb 2026 18:01:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/janus-release/</guid><description>We built an open-source proxy that adds tenant isolation to Prometheus, Loki, and Tempo by rewriting queries based on user identity.</description><content>&lt;p&gt;If you&amp;rsquo;re running a shared observability platform, you&amp;rsquo;ve probably faced this question: &lt;em&gt;How do I stop Team A from seeing Team B&amp;rsquo;s data?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Grafana is fantastic for visualization, but the datasources behind it (Prometheus, Loki, Tempo, and their derivatives) weren&amp;rsquo;t designed with fine-grained multi-tenancy in mind. You typically get one of two options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Basic authentication&lt;/strong&gt;: Everyone with access sees everything&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate instances&lt;/strong&gt;: Operational overhead multiplied by N teams&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Neither scales well. Enterprise solutions exist, but they&amp;rsquo;re expensive and often locked to specific vendors. Building custom authorization middleware means maintaining bespoke code for each query language. Most organizations end up with some combination of &amp;ldquo;trust people not to query the wrong namespaces&amp;rdquo; and &amp;ldquo;hope for the best.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;We ran into this exact problem at work. Multiple teams sharing a single observability stack, and no good way to enforce boundaries without spinning up separate instances for everyone. So we built Janus.&lt;/p&gt;
&lt;p&gt;Together with &lt;a href="https://github.com/ricket-son"&gt;Erik Kaisler&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/irfan-hadzijusufovic-8b0157151/"&gt;Irfan Hadzijusufovic&lt;/a&gt;, we&amp;rsquo;ve been working on this for a while, and we&amp;rsquo;re now releasing it as open source under the AGPLv3 license. A big thank you to our employer, &lt;a href="https://evoila.com"&gt;evoila&lt;/a&gt;, for giving us the time to work on this.&lt;/p&gt;
&lt;h2 id="what-janus-does"&gt;What Janus Does&lt;/h2&gt;
&lt;p&gt;Janus sits between Grafana and your datasources, intercepting every query. It extracts identity from OAuth2 tokens, maps that identity to a set of allowed label values, and automatically injects label filters into queries before they reach the backend.&lt;/p&gt;
&lt;p&gt;Your users write queries like normal. They don&amp;rsquo;t need to know about tenant boundaries or remember to add namespace filters. Janus handles it transparently.&lt;/p&gt;
&lt;p&gt;The biggest win for us: we can now use a single dashboard for all teams, and everyone only sees data relevant to them. Janus even filters the label values returned by the API, so dropdowns in Grafana are only populated with values you&amp;rsquo;re actually allowed to query. No more scrolling through namespaces you can&amp;rsquo;t access anyway.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# User writes:
rate(http_requests_total[5m])
# Janus rewrites to:
rate(http_requests_total{namespace=~&amp;#34;team-a|team-b&amp;#34;}[5m])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The same principle applies across all three query languages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PromQL&lt;/strong&gt; for metrics (Prometheus, Thanos, Mimir, VictoriaMetrics)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LogQL&lt;/strong&gt; for logs (Loki)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TraceQL&lt;/strong&gt; for traces (Tempo)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-it-works"&gt;How It Works&lt;/h2&gt;
&lt;p&gt;Janus is a reverse proxy that does three things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Identity Extraction&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a request arrives, Janus extracts the user&amp;rsquo;s identity from the OAuth2 access token. This can come from JWT claims, token introspection, or userinfo endpoints, whatever your identity provider supports.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Policy Resolution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Based on the extracted identity (user, groups, roles, custom claims), Janus determines which label values the user is permitted to query. Policies are defined declaratively:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;thanos&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tenant-header-constraints&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;order-service-team&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;header&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;X-Scope-OrgID&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tenant1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;my-service-team&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;header&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;X-Scope-OrgID&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tenant2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;regex-team&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;header&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;X-Scope-OrgID&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tenant-regex&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;multi-group-user&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;header&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;X-Scope-OrgID&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tenant3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;admin&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;header&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;X-Scope-OrgID&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;admin-tenant&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;user-label-constraints&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Regex pattern: starts with &amp;#34;demo&amp;#34; (anchored)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;order-service-team&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;labels&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;~^demo.*$&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# multi-group-user has access to BOTH demo AND observability&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;multi-group-user&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;labels&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;demo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;observability&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;3. Query Rewriting&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before forwarding the request, Janus parses the query, injects the appropriate label matchers, and reconstructs it. This happens transparently, the datasource receives a valid, scoped query, the user receives results they&amp;rsquo;re authorized to see.&lt;/p&gt;
&lt;h2 id="why-label-based-filtering"&gt;Why Label-Based Filtering?&lt;/h2&gt;
&lt;p&gt;Labels are the universal primitive in modern observability. Kubernetes workloads are labeled by namespace, team, environment. Logs carry metadata about their source. Traces propagate context through service boundaries.&lt;/p&gt;
&lt;p&gt;By filtering at the label level, Janus works with your existing data model. No need to restructure your metrics or maintain separate Loki tenants. If you can express a boundary as a label selector, Janus can enforce it.&lt;/p&gt;
&lt;p&gt;This also means enforcement happens at query time, not ingestion time. You can retroactively apply access controls without re-ingesting historical data.&lt;/p&gt;
&lt;h2 id="deployment"&gt;Deployment&lt;/h2&gt;
&lt;p&gt;Janus is designed for Kubernetes-native deployment, though it runs anywhere you can run a container.&lt;/p&gt;
&lt;h3 id="without-janus"&gt;Without Janus&lt;/h3&gt;
&lt;p&gt;In a typical setup, Grafana connects directly to each datasource:&lt;/p&gt;
&lt;div class="mermaid-wrapper"&gt;
&lt;pre class="mermaid"&gt;
flowchart LR
G[Grafana] --&amp;gt; P[Prometheus/Mimir/Thanos]
G --&amp;gt; L[Loki]
G --&amp;gt; T[Tempo]
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Every user with Grafana access can query any data in the backend-no tenant isolation.&lt;/p&gt;
&lt;h3 id="with-janus"&gt;With Janus&lt;/h3&gt;
&lt;p&gt;Janus sits between Grafana and your datasources, enforcing access control transparently:&lt;/p&gt;
&lt;div class="mermaid-wrapper"&gt;
&lt;pre class="mermaid"&gt;
flowchart LR
G[Grafana] --&amp;gt; J[Janus]
J --&amp;gt; P[Prometheus/Mimir/Thanos]
J --&amp;gt; L[Loki]
J --&amp;gt; T[Tempo]
J &amp;lt;--&amp;gt; IDP[Identity Provider]
IDP &amp;lt;--&amp;gt; G
&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id="query-flow"&gt;Query Flow&lt;/h3&gt;
&lt;p&gt;Here&amp;rsquo;s what happens when a user runs a query:&lt;/p&gt;
&lt;div class="mermaid-wrapper"&gt;
&lt;pre class="mermaid"&gt;
sequenceDiagram
participant U as User
participant G as Grafana
participant J as Janus
participant DS as Datasource
U-&amp;gt;&amp;gt;G: Execute query
G-&amp;gt;&amp;gt;J: Forward query + OAuth2 token
J-&amp;gt;&amp;gt;J: Validate token / get claims
J-&amp;gt;&amp;gt;J: Resolve policy → allowed labels
J-&amp;gt;&amp;gt;J: Rewrite query with label filters
J-&amp;gt;&amp;gt;DS: Execute scoped query
DS--&amp;gt;&amp;gt;J: Filtered results
J--&amp;gt;&amp;gt;G: Return results
G--&amp;gt;&amp;gt;U: Display dashboard
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Configure Grafana to use Janus as the datasource URL instead of pointing directly at your backends. Janus handles the rest.&lt;/p&gt;
&lt;p&gt;Pre-built container images are available from the repository.&lt;/p&gt;
&lt;h2 id="the-query-rewriting-challenge"&gt;The Query Rewriting Challenge&lt;/h2&gt;
&lt;p&gt;The interesting part was supporting three different query languages. Each has different syntax, different semantics for label matching, and different edge cases.&lt;/p&gt;
&lt;p&gt;PromQL label matchers are relatively straightforward, inject &lt;code&gt;{label=~&amp;quot;value1|value2&amp;quot;}&lt;/code&gt; and merge with existing selectors. LogQL adds pipeline stages that need to be preserved. TraceQL has its own structural query syntax for spans and traces.&lt;/p&gt;
&lt;p&gt;Janus uses dedicated parsers for each language rather than regex manipulation. This ensures queries remain valid after rewriting and handles edge cases like nested expressions, subqueries, and aggregations correctly.&lt;/p&gt;
&lt;p&gt;To make sure we don&amp;rsquo;t break anything on updates, we&amp;rsquo;ve built extensive end-to-end tests that run against real Prometheus, Loki, and Tempo instances with example data. This way we catch regressions before they ship, not when someone&amp;rsquo;s dashboard stops working.&lt;/p&gt;
&lt;h2 id="what-it-doesnt-do"&gt;What It Doesn&amp;rsquo;t Do&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt;: Use your existing ingress or API gateway for that&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data masking&lt;/strong&gt;: Janus filters which data you can query, not which fields are visible within results&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write-path authorization&lt;/strong&gt;: This is query-side only; ingestion controls are a separate concern&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="getting-started"&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;The repository includes a pre-built container image and documentation for the policy syntax.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run Janus as a container (Docker, Podman, or Kubernetes)&lt;/li&gt;
&lt;li&gt;Configure your policies to map users/groups to allowed label values&lt;/li&gt;
&lt;li&gt;Point your Grafana datasources at Janus instead of directly at your backends&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One requirement: Users need to be authenticated via OAuth2. Janus extracts identity from the access token, so anonymous access won&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;Check out the repo for example configs.&lt;/p&gt;
&lt;h2 id="why-agplv3"&gt;Why AGPLv3?&lt;/h2&gt;
&lt;p&gt;We went with AGPLv3 to make sure improvements stay available to everyone. If you modify Janus and offer it as a service, those modifications need to be shared. We&amp;rsquo;ve seen too many useful infrastructure tools get absorbed into proprietary platforms and fragment the community.&lt;/p&gt;
&lt;p&gt;For most users, running Janus internally to secure their own observability stack, the license has no practical impact beyond ensuring the project stays open.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s Next&lt;/h2&gt;
&lt;p&gt;The initial release covers the core use case: OAuth2 identity, label-based policies, and PromQL/LogQL/TraceQL rewriting. Possible Roadmap features could be
:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Additional identity sources (mTLS, API keys)&lt;/li&gt;
&lt;li&gt;Configurable Audit logging&lt;/li&gt;
&lt;li&gt;Caching layer for policy decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Contributions welcome, whether it&amp;rsquo;s bug reports, docs, or new features, the GitHub issues are open.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Janus is available now at &lt;a href="https://github.com/evoila/janus"&gt;GitHub&lt;/a&gt;. If tenant isolation has been sitting on your backlog, give it a try.&lt;/p&gt;
&lt;p&gt;Questions, feedback, or war stories about observability access control? We&amp;rsquo;d love to hear them.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Why &amp;ldquo;Janus&amp;rdquo;? Every observability tool seems to be named after a god Prometheus, Loki, Thanos. We needed a gatekeeper, so we went with the &lt;a href="https://en.wikipedia.org/wiki/Janus"&gt;Roman god of doorways and gates&lt;/a&gt;. Plus, he has two faces, which felt appropriate for a proxy that sees both sides of every request.&lt;/em&gt;&lt;/p&gt;</content></item><item><title>MinIO OSS: Another Rug Pull in Open Source?</title><link>https://marschall.systems/blog/minio-oss-another-rug-pull-in-open-source/</link><pubDate>Thu, 23 Oct 2025 17:13:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/minio-oss-another-rug-pull-in-open-source/</guid><description/><content>&lt;p&gt;MinIO, once a darling of the open-source object storage world, has followed a path we&amp;rsquo;ve seen before: slowly pulling the rug on its OSS community.
It started subtly, features were stripped from the community edition, leaving users with a barebones experience. Now, MinIO has taken it a step further by ceasing distribution of pre-built binaries altogether. The project is now source-only, as confirmed in their &lt;a href="https://github.com/minio/minio?tab=readme-ov-file#source-only-distribution"&gt;GitHub README&lt;/a&gt;. This move significantly raises the barrier to entry for users who relied on the simplicity of ready-to-run binaries.
One of MinIO’s biggest selling points was its sleek UI. With that gone from the OSS version and enterprise pricing remaining steep, it&amp;rsquo;s time to consider alternatives.&lt;/p&gt;
&lt;h2 id="enter-ceph"&gt;Enter Ceph&lt;/h2&gt;
&lt;p&gt;A strong contender is &lt;a href="https://ceph.com"&gt;Ceph&lt;/a&gt;, especially when paired with &lt;a href="https://rook.io"&gt;Rook&lt;/a&gt; for Kubernetes-native deployments. Ceph supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multi-node HA setups&lt;/li&gt;
&lt;li&gt;Single-node deployments with replication or erasure coding&lt;/li&gt;
&lt;li&gt;CephFS for true RWX support&lt;/li&gt;
&lt;li&gt;RBD for block storage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Depending on your architecture, Ceph can scale impressively and offers a rich feature set that MinIO’s OSS version no longer matches.&lt;/p&gt;
&lt;h2 id="personal-usecase"&gt;Personal usecase&lt;/h2&gt;
&lt;p&gt;Personally, I use Ceph in my homelab as a single-host NAS setup. I’ve configured different pools for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VM storage&lt;/li&gt;
&lt;li&gt;Object storage&lt;/li&gt;
&lt;li&gt;File storage&lt;/li&gt;
&lt;li&gt;RWX-capable storage for my &lt;a href="https://talos.dev"&gt;talos&lt;/a&gt; Kubernetes cluster via the Ceph CSI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My hardware setup includes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;th&gt;drive&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4TB NVMe datacenter-grade drives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;8TB SAS drives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;16TB SATA drives (joining the pool soon)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Using CRUSH maps, I assign disks to different use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VMs reside on NVMe with three-way replication&lt;/li&gt;
&lt;li&gt;CephFS metadata also uses three-way replicated NVMe&lt;/li&gt;
&lt;li&gt;File and object storage use Erasure Coding (EC 18+3) on HDDs, yielding ~86% usable space while tolerating up to 3 disk failures&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Performance-wise, the HDD pool delivers around 2.6 GB/s read/write, bottlenecked by my Xeon E2640v4 CPU, as the EC 18+3 config is quite compute-intensive. Replication deliveres native NVMe speeds.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re exploring Ceph or Rook and have questions, feel free to reach out, happy to help!&lt;/p&gt;</content></item><item><title>Setting up Harbor as private Docker registry</title><link>https://marschall.systems/blog/set-up-harbor/</link><pubDate>Fri, 28 Mar 2025 00:19:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/set-up-harbor/</guid><description/><content>&lt;p&gt;Because I didn&amp;rsquo;t want to push all my custom images and sourcecode to public repos like hub.docker.com, I was looking for
a self-hosted solution. After checking out the features of the official Docker registry, I was a bit underwhelmed by the
missing repo based auth and the lack of combining the use as repository and cache.&lt;/p&gt;
&lt;h2 id="why-would-you-need-private-docker-registry"&gt;Why would you need private Docker registry?&lt;/h2&gt;
&lt;p&gt;First if you don&amp;rsquo;t feel comfortable in sharing all your images with a third party you&amp;rsquo;ll need to find a more
local way of distributing your images. Also Docker
&lt;a href="https://docs.docker.com/docker-hub/usage/"&gt;changed their policies&lt;/a&gt;
and limit the maximum pulls you can do to 10 or 100 pulls per hour (depending if your autheticated or not), and while this may seem like a big number, I can assure
you it&amp;rsquo;s easy to reach it. Imagine you got a kubernetes cluster, and you restart some nodes because of an update. Now
every node is pulling every image it needs if you&amp;rsquo;ve set your &lt;code&gt;imagePullPolicy&lt;/code&gt; to &lt;code&gt;Always&lt;/code&gt;. I really like to keep my
deployments configured that way, because it makes updates via &lt;code&gt;kubectl rollout restart deployment x&lt;/code&gt; really easy for
uncritical services.&lt;/p&gt;
&lt;p&gt;To mitigate the pull limits, we will use a cache in our private repository.&lt;/p&gt;
&lt;h2 id="candidates"&gt;Candidates&lt;/h2&gt;
&lt;p&gt;I spent quite some time on digging through the different options.
While doing so, I checked out the following projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/SUSE/Portus"&gt;Portus&lt;/a&gt; (made by SUSE)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jfrog.com/de/container-registry/"&gt;JFrog Container Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://goharbor.io/"&gt;Harbor&lt;/a&gt; (&lt;a href="https://www.cncf.io/"&gt;CNCF&lt;/a&gt; Graduated project)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="portus"&gt;Portus&lt;/h3&gt;
&lt;p&gt;After looking into Portus, I quickly realised that this project is quite dead. The last commit dated back to Mar 25,
2020 and was only a change in the &lt;code&gt;CHANGELOG.md&lt;/code&gt;, also the last release is from Mar 19, 2019.&lt;/p&gt;
&lt;h3 id="jfrog-container-registry"&gt;JFrog Container Registry&lt;/h3&gt;
&lt;p&gt;I know JFrog from work and know it works, but it&amp;rsquo;s closed source, and I think it&amp;rsquo;s quite overkill for my private
use-cases.&lt;/p&gt;
&lt;h3 id="harbor"&gt;Harbor&lt;/h3&gt;
&lt;p&gt;Checking out Harbor, I quickly noticed the &lt;a href="https://www.cncf.io/"&gt;CNCF&lt;/a&gt; banner on their website. The graduated state
in &lt;a href="https://www.cncf.io/"&gt;CNCF&lt;/a&gt; means it&amp;rsquo;s stable and should be quite easy to handle.&lt;/p&gt;
&lt;p&gt;Also, they have an official helm3 chart available.&lt;/p&gt;
&lt;h2 id="deploying"&gt;Deploying&lt;/h2&gt;
&lt;p&gt;To get the helm chart we will use the bitnami chart via oci.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# get default values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm show values oci://registry-1.docker.io/bitnamicharts/harbor &amp;gt; values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After fetching the values we need to change some stuff to match our infrastructure.
For example I needed to set the ingress to &lt;code&gt;LoadBalancer&lt;/code&gt; and change the StorageClass.&lt;/p&gt;
&lt;p&gt;You should walk through the default values and adjust everything to your needs, here I&amp;rsquo;ll share my values file without any secrets (you&amp;rsquo;ll need to change all those &lt;code&gt;xxx&lt;/code&gt;)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;registry&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;xxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;credentials&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;password&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;xxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobservice&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;xxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;core&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;xxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;xsrfKey&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;xxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;expose&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ingress&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tls&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;certSource&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;secret&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secret&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secretName&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;hub.marschall.systems-tls&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ingress&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;hosts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;core&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;hub.marschall.systems&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;controller&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;className&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;nginx-dmz&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;annotations&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ingress.kubernetes.io/ssl-redirect&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ingress.kubernetes.io/proxy-body-size&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nginx.ingress.kubernetes.io/ssl-redirect&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;nginx.ingress.kubernetes.io/proxy-body-size&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;cert-manager.io/cluster-issuer&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cloudflare-issuer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;externalURL&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://hub.marschall.systems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;ipFamily&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ipv6&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ipv4&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;persistence&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resourcePolicy&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;keep&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;persistentVolumeClaim&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;registry&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;storageClass&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;vsan-default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;accessMode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ReadWriteOnce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;200Gi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;jobservice&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;jobLog&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;storageClass&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;vsan-default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;accessMode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ReadWriteOnce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1Gi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;redis&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;storageClass&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;vsan-default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;accessMode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ReadWriteOnce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1Gi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;trivy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;storageClass&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;vsan-default&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;accessMode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ReadWriteOnce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5Gi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;imageChartStorage&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;disableredirect&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;filesystem&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;filesystem&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rootdirectory&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/storage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;imagePullPolicy&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;IfNotPresent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;updateStrategy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Recreate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;logLevel&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;info&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;existingSecretAdminPasswordKey&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;HARBOR_ADMIN_PASSWORD&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;harborAdminPassword&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;secretKey&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;database&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;external&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;external&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;host&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;db1.marschall.management&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;5432&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;username&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;password&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;coreDatabase&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;harbor&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;sslmode&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;require&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;maxIdleConns&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;maxOpenConns&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metrics&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;serviceMonitor&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;trace&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;enabled&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can install the application with&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install --namespace harbor --name harbor --create-namespace oci://registry-1.docker.io/bitnamicharts/harbor -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="configuring-pull-through-cache-for-dockerhub"&gt;Configuring pull through cache for DockerHub&lt;/h2&gt;
&lt;p&gt;To make use of the millions of public available images on DockerHub without running into the pull limits we no create a new upstream Registry in Harbor.&lt;/p&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/set-up-harbor/images/step01_hu_11bbc03621a70489.png" width="800" height="612" alt="Step 1"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/set-up-harbor/images/step02_hu_2e5c55fb7b3bbeff.png" width="800" height="742" alt="Step 2"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now we need to map the Registry to a Project.&lt;/p&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/set-up-harbor/images/step03_hu_43cefd5224c0adaa.png" width="800" height="546" alt="Step 3"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/set-up-harbor/images/step04_hu_b000d924fa7b115d.png" width="800" height="644" alt="Step 4"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Congratulations you now have your own docker registry that can be used as pull through proxy!
To do so just substitute &lt;code&gt;docker.io&lt;/code&gt; with &lt;code&gt;&amp;lt;yourregistry&amp;gt;/cache&lt;/code&gt; in my case &lt;code&gt;docker.io/hugomods/hugo:git&lt;/code&gt; becomes &lt;code&gt;hub.marschall.systems/cache/hugomods/hugo:git&lt;/code&gt;&lt;/p&gt;</content></item><item><title>Simplify logging into all your VKS Clusters with one Command</title><link>https://marschall.systems/blog/simplify-logging-into-all-your-vks-clusters-with-one-command/</link><pubDate>Tue, 25 Mar 2025 22:13:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/simplify-logging-into-all-your-vks-clusters-with-one-command/</guid><description/><content>&lt;p&gt;Having to log into all of your VKS clusters one by one sucks as you either have to export your vSphere Password as an environment variable (which is kinda insecure) or enter the Password for each of your Clusters over and over again.&lt;/p&gt;
&lt;p&gt;As we got annoyed with that one of my Customers (&lt;a href="https://www.linkedin.com/in/ralfdahmen9/"&gt;Ralf Dahmen&lt;/a&gt;) and I decided to fix this by developing a little bash script that automatically detects all clusters in your vSphere Namespaces and logs you in to them, to do so you only ned to enter your Password once.&lt;/p&gt;
&lt;h2 id="prequesits"&gt;Prequesits&lt;/h2&gt;
&lt;p&gt;To run the script you&amp;rsquo;ll need to have some binaries available, those are &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;jq&lt;/code&gt; and &lt;code&gt;expect&lt;/code&gt; this shouldn&amp;rsquo;t be a problem for any modern linux distro or MacOS.&lt;/p&gt;
&lt;p&gt;Here are some examples on how to get those commands..&lt;/p&gt;
&lt;p&gt;Ubuntu/Debian:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt-get install sed tail grep jq expect
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Arch:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pacman -Sy sed grep jq expect
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="script"&gt;Script&lt;/h2&gt;
&lt;p&gt;Just save the following script as &lt;code&gt;vks-login.sh&lt;/code&gt; file in your &lt;code&gt;PATH&lt;/code&gt; and change permissions &lt;code&gt;chmod +x vks-login.sh&lt;/code&gt; afterwards you can login with &lt;code&gt;vks-login.sh&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Ralf Dahmen&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Maximilian Marschall &amp;lt;maximilian.marschall@3xm.at&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# https://msta.cc https://marschall.systems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Requirements:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# apt-get install sed tail grep jq expect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# BEGIN USER SETTINGS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SERVER&lt;span style="color:#f92672"&gt;=&lt;/span&gt;svc.marschall.management
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;USERNAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Administrator@vsphere.local
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# END USER SETTINGS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# DO NOT MODIFY AFTER THIS LINE IF YOU&amp;#39;RE NOT 100% SURE WHAT YOUR DOING&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GREEN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;\033[032m&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NC&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;\033[0m&amp;#39;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# No Color&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;unset -v KUBECTL_VSPHERE_PASSWORD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;set +o allexport
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IFS&lt;span style="color:#f92672"&gt;=&lt;/span&gt; read -rsp &lt;span style="color:#e6db74"&gt;&amp;#34;Please enter password for &lt;/span&gt;$USERNAME&lt;span style="color:#e6db74"&gt;: &amp;#34;&lt;/span&gt; KUBECTL_VSPHERE_PASSWORD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;expect - &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; log_user 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; spawn kubectl vsphere login --server=$SERVER --insecure-skip-tls-verify --vsphere-username $USERNAME
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; expect Password:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; send -- &amp;#34;$KUBECTL_VSPHERE_PASSWORD\r&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; expect eof
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;namespaces&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;kubectl config get-contexts | tr -s &lt;span style="color:#e6db74"&gt;&amp;#34; &amp;#34;&lt;/span&gt; | sed -e &lt;span style="color:#e6db74"&gt;&amp;#34;s/ /,/g&amp;#34;&lt;/span&gt; | tail -n +2 | awk -F, &lt;span style="color:#e6db74"&gt;&amp;#39;{print $NF}&amp;#39;&lt;/span&gt; | grep -v &lt;span style="color:#e6db74"&gt;&amp;#39;^$&amp;#39;&lt;/span&gt; | sort | uniq&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -z &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$namespaces&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;No namespaces found&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ns_count&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$namespaces&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; | wc -l&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$ns_count&lt;span style="color:#e6db74"&gt; Namespaces found&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; namespace in $namespaces; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kubectl config use-context $SERVER &amp;gt; /dev/null 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tkcs&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;kubectl get cluster -n $namespace -o json | jq -r .items&lt;span style="color:#f92672"&gt;[]&lt;/span&gt;.metadata.name&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -z &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$tkcs&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;no clusters found in &lt;/span&gt;$namespace&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tkc_count&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$tkcs&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; | wc -l&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$tkc_count&lt;span style="color:#e6db74"&gt; clusters found in &lt;/span&gt;$namespace&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tkc in $tkcs; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kubectl config use-context $SERVER &amp;gt; /dev/null 2&amp;gt;&amp;amp;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo -e &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;GREEN&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;Login to &lt;/span&gt;$tkc&lt;span style="color:#e6db74"&gt; in &lt;/span&gt;$namespace&lt;span style="color:#e6db74"&gt; &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;NC&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expect - &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; log_user 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; spawn kubectl vsphere login --server=$SERVER --insecure-skip-tls-verify --vsphere-username $USERNAME --tanzu-kubernetes-cluster-namespace $namespace --tanzu-kubernetes-cluster-name $tkc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; expect Password:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; send -- &amp;#34;$KUBECTL_VSPHERE_PASSWORD\r&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; expect eof
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content></item><item><title>Scaling with NSX-T and ALB beyond vNIC limits</title><link>https://marschall.systems/blog/scaling-with-nsx-t-and-alb-beyond-vnic-limits/</link><pubDate>Thu, 11 Jul 2024 19:15:32 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/scaling-with-nsx-t-and-alb-beyond-vnic-limits/</guid><description/><content>&lt;p&gt;When using NSX-T for networking in combination with NSX ALB for load balancing in the vSphere IaaS Control Plane, the ALB Service Engines attach to each Tier-1 Router (vSphere Namespace) with one vNIC. Naturally, there&amp;rsquo;s a limit of 10 vNICs per Service Engine, meaning we can connect up to 8 Tier-1 routers to one Service Engine, as one vNIC is needed for management and another for frontend traffic.&lt;/p&gt;
&lt;p&gt;We were curious to see how NSX ALB would handle more than 8 vSphere Namespaces in this setup. My colleague &lt;a href="https://www.linkedin.com/in/steven-schramm-87083113a/"&gt;Steven Schramm&lt;/a&gt; and I decided to test this in our lab.&lt;/p&gt;
&lt;h2 id="test-setup"&gt;Test Setup&lt;/h2&gt;
&lt;p&gt;Our lab environment was based on the following components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vCenter 8.0.3 (24022515)&lt;/li&gt;
&lt;li&gt;ESXi 8.0.3 (24022510)&lt;/li&gt;
&lt;li&gt;NSX ALB 22.1.5 (22.1.5-2p6)&lt;/li&gt;
&lt;li&gt;NSX-T 4.1.2.4&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For testing, we created 9 vSphere Namespaces, each with one workload cluster using default settings and the default VM-Class &lt;code&gt;xsmall-best-effort&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="observations"&gt;Observations&lt;/h2&gt;
&lt;p&gt;We observed the behavior of NSX ALB as we created the ninth vSphere Namespace and the corresponding Workload Cluster. As expected, NSX ALB started to deploy a new Service Engine to accommodate the additional namespace.&lt;/p&gt;
&lt;p&gt;To visualize this procedure we created two drawings. The first drawing shows the number of Service Engines and the distribution of the running virtual services with nine vSphere Namespaces and one K8s Cluster per vSphere Namespace.
Based on the corresponding Service Engine Group each of those Service Engines is allowed to run 11 virtual services. Further the drawing shows eleven virtual services. One virtual service for the K8s APi of the Supervisor cluster, one for the CSI controller and nine for the running K8s clusters. The virtual services for the Supervisor K8s API and the CSI controller are running in the same VRF and the different K8s clusters are running in dedicated VRFs. One VRF for each vSphere Namespace.
As you can see we do have two Service Engines deployed, since the Service Engine Group is running in HA Mode N+M and one additional Service Engine is required to cover the outage of one Service Engines an still have enough capacity to maintain all running virtual Services.&lt;/p&gt;
&lt;p&gt;Based on the maximum allowed ratio of 11 virtual services per Service Engine two Service Engines would cover the requirement to maintain all running virtual services, but this is not true with 10 VRFs and the requirement of one vNic per VRF. Still we only have two Service Engines, because the number of used vNics will not be taken in consideration for the N+M formular.&lt;/p&gt;
&lt;p&gt;What does this mean in case of a SE failure? After one of the two SEs did fail the Controller trys to reschedule the virtual services, but is unable to get all ten VRFs/ ten vNics connected to the same SE, since one vNIC is already used for the management connection of the SE. Based on this missing capacity of available vNICs, a new SE needs to be created and one of the virtual Services is out of service until this SE is initialized.
This can be prevented by increasing the valued for the SEs which should be deployed as a buffer or by reducing the amount of accepted virtual services per SE, but this does also mean you will increase your used licenses and the corresponding costs.&lt;/p&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/scaling-with-nsx-t-and-alb-beyond-vnic-limits/images/se1_hu_d59ada8eb21ea2dd.png" width="800" height="906" alt="Step 1"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The second drawing shows the effect of further scaling. In the specific example we inreased the minimum scale per virtual service from one to two.
Based on this each virtual service have to run on at least two different SEs.
This will also increase the number of required service engines from two to three, but the problem regarding the capacity of available vNICs is still valid.&lt;/p&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/scaling-with-nsx-t-and-alb-beyond-vnic-limits/images/se2_hu_d97a12f304fb7aa2.png" width="800" height="607" alt="Step 2"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This test indicates that the limitation of vSphere Namespaces will not be reduced by the number of available vNIC interfaces per SE, but in case of an SE failure the recovery time can be much longer. This can be solved by changing the values responsible for the scaling of the number of SEs in the N+M HA mode for example, but this might have an impact on the number of licenses you will require to keep you workload running and reduce the impact of an SE failure.&lt;/p&gt;</content></item><item><title>vSphere 8.0.3 Nested on Proxmox/KVM</title><link>https://marschall.systems/blog/vsphere-8.0.3-nested-on-proxmox/kvm/</link><pubDate>Thu, 04 Jul 2024 09:15:32 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/vsphere-8.0.3-nested-on-proxmox/kvm/</guid><description/><content>&lt;p&gt;While I was testing Broadcom&amp;rsquo;s new Release 8.0.3, I encountered some issues getting my nested lab to work in a Proxmox environment. Despite enabling Nested Virtualization on all Proxmox nodes and setting the VM CPU type to &amp;lsquo;host,&amp;rsquo; the vCLS service wouldn&amp;rsquo;t start due to EVC issues, causing DRS to malfunction.&lt;/p&gt;
&lt;h2 id="troubleshooting-nested-virtualization"&gt;Troubleshooting Nested Virtualization&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, I checked whether nested virtualization was enabled on the Proxmox nodes. This can be verified with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@pve0:~# cat /sys/module/kvm_intel/parameters/nested
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, I ensured the CPU type was set to &amp;lsquo;host&amp;rsquo;:
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-8.0.3-nested-on-proxmox/kvm/images/vcls-1_hu_bdf0e44da22f4f0a.png" width="800" height="114" alt="CPU-Type"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt; &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, I checked if the ESXi node reported HV enabled:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;root@esx3:~&lt;span style="color:#f92672"&gt;]&lt;/span&gt; esxcfg-info|grep &lt;span style="color:#e6db74"&gt;&amp;#34;HV Support&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; |----HV Support............................................3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;0 - VT/AMD-V support is not available for this hardware.&lt;/li&gt;
&lt;li&gt;1 - VT/AMD-V might be available but is not supported for this hardware.&lt;/li&gt;
&lt;li&gt;2 - VT/AMD-V is available but is not enabled in the BIOS.&lt;/li&gt;
&lt;li&gt;3 - VT/AMD-V is enabled in the BIOS and can be used.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="deeper-look-into-vcls"&gt;Deeper Look into vCLS&lt;/h2&gt;
&lt;p&gt;Since version 8.0.3, vCLS 2.0 is active, meaning the vCLS VMs are now Pods instead of traditional VMs. The issue is that the Pods configuration cannot be altered in any documented way. Therefore, I looked into reverting to the &amp;ldquo;old&amp;rdquo; vCLS model.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;/h2&gt;
&lt;p&gt;To disable the new vCLS version and revert to the old one, I found an advanced vCenter option. Follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to &lt;code&gt;vCenter &amp;gt; Configure &amp;gt; Advanced Settings&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;config.vcls.disablePodCrx&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; (default is &lt;code&gt;false&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this change, vCLS is deployed as VMs again. You can now change the EVC settings of the VMs after upgrading their VM compatibility to vSphere 8.0.2.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope Broadcom addresses this problem or at least documents an official way to work around it. This issue is a significant roadblock for homelabbers like me who use non-ESXi based host systems on their hypervisors. I suspect the same problem might occur on unsupported CPUs, which are also widespread in the homelab community.&lt;/p&gt;</content></item><item><title>vSphere IaaS Control Plane - Consumption Interface</title><link>https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/</link><pubDate>Tue, 02 Jul 2024 18:15:32 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/</guid><description/><content>&lt;p&gt;VMware by Broadcom has recently released the &amp;ldquo;Consumption Interface&amp;rdquo; for the vSphere IaaS Control Plane (formerly known as vSphere with Tanzu or TKGS). This is a significant enhancement to the existing features of TKG, allowing for better management and visibility of your resources.&lt;/p&gt;
&lt;p&gt;With this new interface, you can now view and manage all your resources within vSphere namespaces directly from the vCenter GUI. This update simplifies the creation and updating of resources, enhancing the user experience.&lt;/p&gt;
&lt;h2 id="installation-guide"&gt;Installation Guide&lt;/h2&gt;
&lt;p&gt;To install the Consumption Interface, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;: Ensure your Supervisor Cluster is running vCenter 8.0.3 or later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Download Required Files&lt;/strong&gt;: Access the required files from the &lt;a href="https://github.com/vsphere-tmm/Supervisor-Services?tab=readme-ov-file#consumption-interface"&gt;Supervisor Services GitHub Repository&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import Configuration&lt;/strong&gt;: Download the necessary &lt;code&gt;.yml&lt;/code&gt; file and import it into the Workload Management Services tab in vCenter.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is a step-by-step guide with screenshots to assist you through the installation process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: Download the .yml file from the repository.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: Navigate to the Workload Management Services tab.
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-1_hu_7fddb972c6e1493e.png" width="800" height="698" alt="Step 1"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Import the .yml file.
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-2_hu_f0dd21d27f9e748b.png" width="800" height="206" alt="Step 2"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-3_hu_354055c0a6c16bd8.png" width="800" height="451" alt="Step 3"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: Configure the Supervisor Service.
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-5_hu_7c8568436467a1f4.png" width="800" height="438" alt="Step 5"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-6_hu_e18fd95534cd7916.png" width="800" height="339" alt="Step 6"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 5&lt;/strong&gt;: Adjust the package settings as needed, such as overriding the image registry to use your local registry.
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-7_hu_38a723f55fb05c65.png" width="800" height="365" alt="Step 7"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt; &lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="post-installation-overview"&gt;Post-Installation Overview&lt;/h2&gt;
&lt;p&gt;After enabling the Consumption Interface, it will appear in the resources tab of your vSphere namespace.&lt;/p&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-10_hu_f7e97d682a3e6600.png" width="1200" height="484" alt="CCI Overview"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;From this interface, you can create and manage your IaaS resources, such as Kubernetes Clusters.&lt;/p&gt;
&lt;p&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-11_hu_f3410c7b35913e68.png" width="1200" height="671" alt="Kubernetes Cluster Overview"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure style="padding: 0.25rem;"&gt;
&lt;img style="max-width: 100%; width: auto; height: auto; border-radius:;" src="https://marschall.systems/blog/vsphere-iaas-control-plane-consumption-interface/images/cci-12_hu_cf6ad64d35e7b63d.png" width="1200" height="646" alt="Create Kubernetes Cluster"&gt;
&lt;figcaption&gt;
&lt;small&gt;
&lt;/small&gt;
&lt;/figcaption&gt;
&lt;/figure&gt; &lt;/p&gt;
&lt;p&gt;You can even preview the generated YAML that will be deployed on your behalf.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With these steps, you can seamlessly integrate the Consumption Interface into your existing vSphere environment, enhancing your control and visibility over your IaaS resources and providing an easy-to-use UI for your vSphere Admins to create Kubernetes Clusters and other resources.&lt;/p&gt;
&lt;p&gt;This addon has proven to be highly beneficial for many of my customers who were seeking a simple interface for creating Kubernetes clusters.&lt;/p&gt;</content></item><item><title>Using Antrea Egress to Identify Your Workloads at an External Firewall</title><link>https://marschall.systems/blog/using-antrea-egress-to-identify-your-workloads-at-an-external-firewall/</link><pubDate>Tue, 26 Mar 2024 19:13:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/using-antrea-egress-to-identify-your-workloads-at-an-external-firewall/</guid><description/><content>&lt;p&gt;Identifying different applications running in the same Kubernetes cluster at an external firewall can be challenging. By default, all services running on a node are SNATed to the node&amp;rsquo;s IP address.&lt;/p&gt;
&lt;p&gt;With Antrea Egress we can make use of dedicated egress IPs for different Pods in a Cluster. If you just want to get to the quickstart, &lt;a href="#quickstart"&gt;click here&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="what-is-antrea"&gt;What is Antrea?&lt;/h1&gt;
&lt;p&gt;Antrea is a Kubernetes CNI which is currently a CNCF Sandbox Project. It is heavily used in Production by VMware and its Customers as it is the default CNI for VMware Tanzu.&lt;/p&gt;
&lt;p&gt;It leverages Open vSwitch as dataplane and thus is pretty performant.&lt;/p&gt;
&lt;h1 id="how-does-antrea-egress-work"&gt;How does Antrea Egress work&lt;/h1&gt;
&lt;p&gt;Instead of applying an SNAT to outgoing traffic to the Nodes IP Antrea applies a SNAT Policy to your custom Egress-IP.&lt;/p&gt;
&lt;div class="goat svg-container "&gt;
&lt;svg
xmlns="http://www.w3.org/2000/svg"
font-family="Menlo,Lucida Console,monospace"
viewBox="0 0 312 105"
&gt;
&lt;g transform='translate(8,16)'&gt;
&lt;path d='M 0,0 L 160,0' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 16,32 L 64,32' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 88,32 L 144,32' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 184,32 L 296,32' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 64,48 L 80,48' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 144,48 L 160,48' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 160,48 L 176,48' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 16,64 L 64,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 88,64 L 144,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 184,64 L 296,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 0,80 L 160,80' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 0,0 L 0,80' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 16,32 L 16,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 64,32 L 64,48' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 64,48 L 64,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 88,32 L 88,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 144,32 L 144,48' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 144,48 L 144,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 160,0 L 160,48' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 160,48 L 160,80' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 184,32 L 184,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;path d='M 296,32 L 296,64' fill='none' stroke='currentColor'&gt;&lt;/path&gt;
&lt;polygon points='88.000000,48.000000 76.000000,42.400002 76.000000,53.599998' fill='currentColor' transform='rotate(0.000000, 80.000000, 48.000000)'&gt;&lt;/polygon&gt;
&lt;polygon points='184.000000,48.000000 172.000000,42.400002 172.000000,53.599998' fill='currentColor' transform='rotate(0.000000, 176.000000, 48.000000)'&gt;&lt;/polygon&gt;
&lt;text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'&gt;P&lt;/text&gt;
&lt;text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'&gt;o&lt;/text&gt;
&lt;text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'&gt;d&lt;/text&gt;
&lt;text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'&gt;N&lt;/text&gt;
&lt;text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'&gt;o&lt;/text&gt;
&lt;text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'&gt;d&lt;/text&gt;
&lt;text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'&gt;e&lt;/text&gt;
&lt;text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'&gt;S&lt;/text&gt;
&lt;text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'&gt;N&lt;/text&gt;
&lt;text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'&gt;A&lt;/text&gt;
&lt;text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'&gt;T&lt;/text&gt;
&lt;text text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'&gt;e&lt;/text&gt;
&lt;text text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'&gt;x&lt;/text&gt;
&lt;text text-anchor='middle' x='216' y='52' fill='currentColor' style='font-size:1em'&gt;t&lt;/text&gt;
&lt;text text-anchor='middle' x='224' y='52' fill='currentColor' style='font-size:1em'&gt;e&lt;/text&gt;
&lt;text text-anchor='middle' x='232' y='52' fill='currentColor' style='font-size:1em'&gt;r&lt;/text&gt;
&lt;text text-anchor='middle' x='240' y='52' fill='currentColor' style='font-size:1em'&gt;n&lt;/text&gt;
&lt;text text-anchor='middle' x='248' y='52' fill='currentColor' style='font-size:1em'&gt;a&lt;/text&gt;
&lt;text text-anchor='middle' x='256' y='52' fill='currentColor' style='font-size:1em'&gt;l&lt;/text&gt;
&lt;text text-anchor='middle' x='272' y='52' fill='currentColor' style='font-size:1em'&gt;I&lt;/text&gt;
&lt;text text-anchor='middle' x='280' y='52' fill='currentColor' style='font-size:1em'&gt;P&lt;/text&gt;
&lt;/g&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;h1 id="quickstart"&gt;Quickstart&lt;/h1&gt;
&lt;p&gt;When already using Antrea as CNI it&amp;rsquo;s quite straight forward to make use of Antrea Egress:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For Antrea versions older than 1.0, you need to upgrade. For versions older than 1.6, you need to enable a FeatureGate. For newer versions, proceed to step 2.
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ConfigMap&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;antrea-config&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kube-system&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;data&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;antrea-agent.conf&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; featureGates:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; Egress: true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;antrea-controller.conf&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; featureGates:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; Egress: true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;Create an Egress Policy:
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;crd.antrea.io/v1beta1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Egress&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;example-egress&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;appliedTo&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespaceSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;matchLabels&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;env&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;custom-egress-ip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;podSelector&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;matchLabels&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;custom-egress-ip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;egressIP&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10.10.30.30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;With this policy, all pods labeled role: custom-egress-ip in namespaces labeled env: custom-egress-ip will use 10.10.30.30 as their egress IP when connecting to external IPs.&lt;/li&gt;
&lt;/ol&gt;</content></item><item><title>Drone CI/CD</title><link>https://marschall.systems/blog/drone-ci/</link><pubDate>Wed, 23 Mar 2022 03:08:41 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/drone-ci/</guid><description/><content>&lt;p&gt;As I wanted to get back to writing some more blog posts I was annoyed by the fact
that I need to build, push and deploy the updated posts everytime something changes.&lt;/p&gt;
&lt;p&gt;As I already managed my whole blog with &lt;a href="https://gohugo.io/"&gt;hugo&lt;/a&gt; and within a git repository
hosted on my very-own instance of &lt;a href="https://gitea.io/en-us/"&gt;Gitea&lt;/a&gt; I decided it&amp;rsquo;s time
to get into self-hosting CI/CD for my personal projects.&lt;/p&gt;
&lt;h2 id="why-drone"&gt;why drone?&lt;/h2&gt;
&lt;p&gt;I checked out multiple other solutions like &lt;a href="https://about.gitlab.com/"&gt;Gitlab&lt;/a&gt;, &lt;a href="https://www.jenkins.io/"&gt;Jenkins&lt;/a&gt;,
&lt;a href="https://www.atlassian.com/software/bamboo"&gt;Bamboo&lt;/a&gt; and &lt;a href="https://circleci.com/"&gt;Circle CI&lt;/a&gt; but all of them seemed a
bit overkill for my needs.&lt;/p&gt;
&lt;p&gt;Also drone has ready to use helm charts for both the server and the runner (I&amp;rsquo;ll explain both later on)
which is great considering I already have nearly all my services running inside a kubernetes cluster.&lt;/p&gt;
&lt;h2 id="what-makes-drone-different"&gt;what makes drone different&lt;/h2&gt;
&lt;p&gt;Drone is based on docker images, basically any action in your pipeline is a Docker-Container.
That makes the system incredibly flexible because you can simply build plugins with new Containers
or use existing containers for your build-steps, for example &lt;code&gt;golang&lt;/code&gt; to build your app.&lt;/p&gt;
&lt;h2 id="server-and-runner"&gt;server and runner&lt;/h2&gt;
&lt;p&gt;Basically the drone server is the component you are interacting with either via drone-cli oder the web-gui.
The server also receives the webhooks from your git repo and dispatches jobs to runners which do the actual work.&lt;/p&gt;
&lt;p&gt;Runners come in a variety of sizes and shapes, for example they can run..&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in kubernetes&lt;/li&gt;
&lt;li&gt;via SSH&lt;/li&gt;
&lt;li&gt;in Docker&lt;/li&gt;
&lt;li&gt;on AWS&lt;/li&gt;
&lt;li&gt;on DigitalOcean&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;..and some more you can find all of them in their &lt;a href="https://docs.drone.io/runner/overview/"&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Obviously I&amp;rsquo;ll be using the kubernetes runner.&lt;/p&gt;
&lt;h2 id="deploying-drone-to-kubernetes"&gt;deploying drone to kubernetes&lt;/h2&gt;
&lt;p&gt;I choose to deploy drone via &lt;a href="https://helm.sh/"&gt;helm3&lt;/a&gt; because it&amp;rsquo;s easy and supported.&lt;/p&gt;
&lt;p&gt;You can simply add the drone helm repos to your helm3 installation via the following commands:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;helm repo add drone https://charts.drone.io
helm repo update
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To deploy I&amp;rsquo;d suggest checking the chart docs over at &lt;a href="https://github.com/drone/charts#install-charts"&gt;Github&lt;/a&gt;.
Depending on your VCS the steps may vary slightly.&lt;/p&gt;
&lt;h2 id="droneyml"&gt;.drone.yml&lt;/h2&gt;
&lt;p&gt;This is the file which tells drone how to handle your repository, it contains a pipeline
with all steps needed to build your project.&lt;/p&gt;
&lt;p&gt;This website is built using the following &lt;code&gt;.drone.yml&lt;/code&gt; (I redacted confidential information)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;pipeline&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kubernetes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;generatetag&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hub.docker.com/alpine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;commands&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;echo &amp;#34;${DRONE_REPO_BRANCH}-${DRONE_COMMIT_SHA},latest&amp;#34; &amp;gt;&amp;gt; .tags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;submodules&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hub.docker.com/alpine/git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;commands&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;git submodule update --init --recursive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;dockerbuild&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;plugins/docker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;settings&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;username&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;from_secret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;docker-username&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;password&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;from_secret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;docker-password&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;repo&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hub.docker.com/youruser/yourimage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;registry&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hub.docker.com&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;myprivatedockerrepo/drone-plugins/k8s-deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;settings&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;token&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;from_secret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;k8stoken&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;server&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;from_secret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;k8sserver&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;from_secret&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;k8snamespace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;deployment&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;marschallsystems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;marschallsystems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;repo&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hub.docker.com/youruser/yourimage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;tag&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${DRONE_REPO_BRANCH}-${DRONE_COMMIT_SHA}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you see the file isn`t too complicated, every step has a name and an image.
The image is the Docker-Image used for the step.&lt;/p&gt;
&lt;p&gt;At first, we are using alpine to generate a &lt;code&gt;.tags&lt;/code&gt; file which contains all target tags
for the resulting Image.&lt;/p&gt;
&lt;p&gt;The Second Step is building the &lt;code&gt;Dockerfile&lt;/code&gt; from which this blog is created
(I will cover this in another post and put a link over here).
This step will also take the tags from &lt;code&gt;.tags&lt;/code&gt; and tag the image and push it.
All credentials are supplied from drone secrets, these can be either set via the
web gui or via drone-cli.&lt;/p&gt;
&lt;p&gt;At last, the image which is used in the kubernetes deployment is updated via a
little custom drone plugin.
Actually this &amp;ldquo;plugin&amp;rdquo; is really only a bash one-liner in a docker container.&lt;/p&gt;
&lt;h2 id="deployment-update-plugin"&gt;deployment update plugin&lt;/h2&gt;
&lt;p&gt;To update the image we just need to issue a
&lt;code&gt;kubectl set image deployment/deploymentname containername=newimagename:tag&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="building-the-image"&gt;building the image&lt;/h3&gt;
&lt;p&gt;As previously stated the image is no big deal. It consists of a &lt;code&gt;script.sh&lt;/code&gt;
and a corresponding &lt;code&gt;Dockerfile&lt;/code&gt;. These two files need to be in the following
places.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.
├── src
│ └── script.sh
└── Dockerfile
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;script.sh&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/bin/kubectl --token $PLUGIN_TOKEN --server $PLUGIN_SERVER --namespace $PLUGIN_NAMESPACE --insecure-skip-tls-verify set image deployment/$PLUGIN_DEPLOYMENT $PLUGIN_CONTAINER&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$PLUGIN_REPO:$PLUGIN_TAG
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dockerfile&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;alpine&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; apk add --no-cache curl ca-certificates&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; curl -LO https://storage.googleapis.com/kubernetes-release/release/&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;/bin/linux/amd64/kubectl &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; chmod &lt;span style="color:#ae81ff"&gt;755&lt;/span&gt; kubectl &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mv kubectl /bin/&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ADD&lt;/span&gt; src/script.sh /bin/&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; chmod +x /bin/script.sh&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENTRYPOINT&lt;/span&gt; /bin/script.sh&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can build the image.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker build . -t hub.docker.com/youruser/yourimage:tag&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="kubectl-auth"&gt;kubectl auth&lt;/h3&gt;
&lt;p&gt;Obviously we need some kind of authentication for the &lt;code&gt;kubectl&lt;/code&gt; command to work,
and we will leverage the power of drone secrets again.&lt;/p&gt;
&lt;p&gt;To get a token to use for &lt;code&gt;kubectl&lt;/code&gt; we use a &lt;code&gt;service-account&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Todo so we apply following yml in the cluster (change namespaces according to
your deployments needs)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ServiceAccount&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;drone-deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;marschallsystems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rbac.authorization.k8s.io/v1beta1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Role&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;drone-deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;marschallsystems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;rules&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;apiGroups&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;apps&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;resources&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;deployments&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;verbs&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;patch&amp;#34;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#34;update&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rbac.authorization.k8s.io/v1beta1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;RoleBinding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;drone-deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;marschallsystems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;subjects&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ServiceAccount&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;drone-deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;marschallsystems&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;roleRef&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Role&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;drone-deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;apiGroup&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will create the service account and the corresponding role
and bind them together. Now we need to get the exact name of the
secret which kubernetes created for the service account. To do so we use &lt;code&gt;kubectl get secrets -n yournamespace&lt;/code&gt;.
After that we can receive the token with the following command (replace the secret name to match yours).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;kubectl get secrets drone-deploy-token-tn8t2 -o=jsonpath=&amp;quot;{.data.token}&amp;quot; | base64 -d -i -&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Congratulations you now have a token which you can save in drone as a secret
with the name &lt;code&gt;k8stoken&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;conclusion&lt;/h1&gt;
&lt;p&gt;After just a few hours we have a working CI/CD solution with very little footprint.&lt;/p&gt;
&lt;p&gt;At first, it might seem a bit overwhelming to describe every step in the &lt;code&gt;yml&lt;/code&gt; but
after all it&amp;rsquo;s quite easy if you get the hang of it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been also surprised how easy it is to make new plugins, and I just love it!&lt;/p&gt;
&lt;p&gt;I hope I&amp;rsquo;ve been able to show you how amazing drone is.
Simple, fast and easy to learn!&lt;/p&gt;</content></item><item><title>Really nice hardware for a Firewall-Appliance</title><link>https://marschall.systems/blog/nice-hardware-for-a-firewall/</link><pubDate>Sun, 20 Mar 2022 23:34:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/nice-hardware-for-a-firewall/</guid><description/><content>&lt;p&gt;As my whole lab and network was getting to the 10Gbit era I was looking for a nice piece of hardware to run
OPNSense.&lt;/p&gt;
&lt;p&gt;Because I already have a Mikrotik 8-port SFP+ switch (&lt;a href="https://mikrotik.com/product/crs309_1g_8s_in"&gt;CRS309-1G-8S+IN&lt;/a&gt;),
I wanted SFP+ ports because DAC Cables are just ridiculous cheap.
I really can recommend the guys over at &lt;a href="https://www.fs.com/"&gt;fs.com&lt;/a&gt;, they have good quality and are very cheap. Also,
none of the equipment I ordered there let me down till now.&lt;/p&gt;
&lt;p&gt;At first, I was looking into Thin-Clients and also some SFF PCs like the Lenovo or Dell 1l variants but just wasn&amp;rsquo;t
satisfied with the performance.
The big drawback of mini PCs is the limited expandability with PCIe devices.&lt;/p&gt;
&lt;h2 id="supermicro-sys-e300-9d-4cn8tp"&gt;Supermicro SYS-E300-9D-4CN8TP&lt;/h2&gt;
&lt;p&gt;Finally, I settled on the
&lt;a href="https://www.supermicro.com/en/products/system/Mini-ITX/SYS-E300-9D-4CN8TP.cfm"&gt;Supermicro SYS-E300-9D-4CN8TP&lt;/a&gt;, CPU is
already on-board, and I had some ECC DDR4 memory lying around. I just needed to get a decent datacenter SSD for
reliability and settled on the Samsung PM883 240 GB.&lt;/p&gt;
&lt;p&gt;This little beast has 4 x 10Gbit, 2 of them being RJ45 and the other 2 SFP+. And with its Xeon D-2123IT 4c/8t it&amp;rsquo;s
beefy enough for anything you throw at it.&lt;/p&gt;
&lt;p&gt;Another really nice feature is the dedicated IPMI port which removes the need to ever plug a monitor into this bad boy.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I am really happy how the whole process went and OPNSense plays very well with the hardware.
Every update went fine, and I always felt good knowing I&amp;rsquo;d be able to remote into the machine without plugging a display
n it.&lt;/p&gt;
&lt;p&gt;Maybe the machine is a bit overpowered for my &amp;ldquo;home-use&amp;rdquo;, but at least I&amp;rsquo;m sure I&amp;rsquo;ll never need to be worried about a
bottleneck for my WireGuard VPN tunnels.&lt;/p&gt;
&lt;p&gt;IPMI was also great for first time setup as you can mount an ISO remote via the IPMI which eliminates the need of
flashing an installer to a flash drive.&lt;/p&gt;
&lt;p&gt;PS: 32 Gigs of memory are way too much for a lab firewall ;)&lt;/p&gt;</content></item><item><title>Using Systemwide Darkmode settings on your Website</title><link>https://marschall.systems/blog/using-systemwide-darkmode-settings-on-your-website/</link><pubDate>Mon, 27 Apr 2020 17:01:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/using-systemwide-darkmode-settings-on-your-website/</guid><description/><content>&lt;p&gt;Systemwide dark/lightmode settings are becoming an standard in most Operating Systems like Android, iOS, MacOS, Manjaro and many many more. So why not use it on your Website or in your Webapp?&lt;/p&gt;
&lt;h2 id="css-media-queries"&gt;CSS-Media Queries&lt;/h2&gt;
&lt;p&gt;To check whether the user prefers the dark mode, a CSS3 feature called Media Queries is used.
Media Queries are quite powerful, not only you can check for the preferred color-scheme. They are also used for responsive design based on the screen-size or specific styles for the printed versions.&lt;/p&gt;
&lt;h2 id="css-variables"&gt;CSS-Variables&lt;/h2&gt;
&lt;p&gt;For easy changes to the color theme you can utilize CSS Variables.
You can define them like that.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;html {
--variable-1: #dfdfdf;
--variable-2: url(https://example.com/example.png)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To use them just use &lt;code&gt;var(&amp;lt;varname&amp;gt;)&lt;/code&gt; where you want the value to be inserted.&lt;/p&gt;
&lt;h2 id="react-to-the-setting"&gt;React to the setting&lt;/h2&gt;
&lt;p&gt;To activate the different themes we will put following code in our CSS file&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;html {
--color: #42464c;
--background: #f8f8f8;
}
@media (prefers-color-scheme: dark) {
html {
--color: #dfdfdf;
--background: #333435;
--color-subtitle: #ddd;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the user activated darkmode the second definition will overrule the first one and the variables change, and so does your page.&lt;/p&gt;</content></item><item><title>Optimizing your rclone mount</title><link>https://marschall.systems/blog/optimizing-your-rclone-mount/</link><pubDate>Tue, 21 Apr 2020 19:13:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/optimizing-your-rclone-mount/</guid><description/><content>&lt;p&gt;Because your Internet speed is not always as fast as your needs for more storage space you should use some kind of an buffer.&lt;/p&gt;
&lt;h2 id="what-is-mergerfs"&gt;What is mergerfs?&lt;/h2&gt;
&lt;p&gt;Mergerfs is a tool to logicaly combine multiple drives or folders into one.&lt;/p&gt;
&lt;p&gt;On the offical &lt;a href="https://github.com/trapexit/mergerfs"&gt;Github&lt;/a&gt; theres the following example which pretty much describes everything you need to know about how mergerfs works.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;A + B = C
/disk1 /disk2 /merged
| | |
+-- /dir1 +-- /dir1 +-- /dir1
| | | | | |
| +-- file1 | +-- file2 | +-- file1
| | +-- file3 | +-- file2
+-- /dir2 | | +-- file3
| | +-- /dir3 |
| +-- file4 | +-- /dir2
| +-- file5 | |
+-- file6 | +-- file4
|
+-- /dir3
| |
| +-- file5
|
+-- file6
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="how-we-will-use-it"&gt;How we will use it&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ll merge our rclone mount and some local folder, after that every read will first be done on the local folder unless the file is only availabe on the rclone mount.
Similar rules apply to writes, files will be writen to the local folder unless the local folder runs out of space.&lt;/p&gt;
&lt;p&gt;To automaticaly empty our local folder we&amp;rsquo;ll be using a little script that will upload the files to our rclone mount and delete the local copies after that.&lt;/p&gt;
&lt;h2 id="installing-mergerfs"&gt;Installing mergerfs&lt;/h2&gt;
&lt;p&gt;First of all you need to grab the latest release for your distro over at &lt;a href="https://github.com/trapexit/mergerfs/releases"&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After installing you should be able to run &lt;code&gt;mergerfs -v&lt;/code&gt; in your shell to check the installed version and get output similar to mine.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mergerfs version: 2.28.2
FUSE library version: 2.9.7-mergerfs_2.28.0
fusermount version: 2.9.2
using FUSE kernel interface version 7.29
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="creating-the-mount"&gt;Creating the mount&lt;/h2&gt;
&lt;p&gt;To make sure the mount we will start as soon as the dependicies are ready we&amp;rsquo;ll be using a systemd service.&lt;/p&gt;
&lt;p&gt;Creating a service for systemd is as easy as creating a file under &lt;code&gt;/etc/systemd/system&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our file should look something like this.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Unit]
Description=mergerfs mount
Requires=rclone-merge.service
After=&amp;lt; your rclone mount service name goes here&amp;gt;.service
RequiresMountsFor=&amp;lt; your local folder goes here &amp;gt;
RequiresMountsFor=&amp;lt; your rclone mountpath goes here &amp;gt;
[Service]
Type=forking
ExecStart=/usr/bin/mergerfs &amp;lt; your local folder goes here &amp;gt;:&amp;lt; your rclone mountpath goes here &amp;gt; &amp;lt; your target mountpath goes here &amp;gt; \
-o rw,async_read=false,use_ino,allow_other,func.getattr=newest,category.action=all,category.create=ff,cache.files=partial,dropcacheonclose=true
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After that you need to reload your systemd with following command &lt;code&gt;systemctl daemon-reload&lt;/code&gt;.
Now you should be able to start and stop your mergerfs with &lt;code&gt;systemctl start rclone-merge&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-upload-script"&gt;The upload script&lt;/h2&gt;
&lt;p&gt;To automate the process of uploading local data to your remote we&amp;rsquo;ll use this fancy little script. The script itself checks if another instance is running and only runs if it&amp;rsquo;s the only one.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/bash
## RClone Config file
RCLONE_CONFIG=/etc/rclone/rclone.conf
export RCLONE_CONFIG
#exit if running
if [[ &amp;#34;`/usr/sbin/pidof -x $(basename $0) -o %PPID`&amp;#34; ]]; then exit; fi
## Move older local files to the cloud
/usr/bin/rclone move &amp;lt; your local folder goes here &amp;gt; &amp;lt; your remote name goes here &amp;gt;: \
--log-file /var/log/rclone-upload.log \
--exclude-from /opt/scripts/excludes \
--delete-empty-src-dirs \
--fast-list \
--bwlimit &amp;#34;6M&amp;#34; \
--min-age 2h
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can and should ajust the arguments acording to your needs, they are documented over &lt;a href="https://rclone.org/flags/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The most importend flags in my opinion are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--exclude-from&lt;/code&gt; to exclude some files/folders defined in a text file like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;*.rar
download/**
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--delete-empty-src-dirs&lt;/code&gt; to keep the local folder clean.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--fast-list&lt;/code&gt; to speed up the process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--bwlimit&lt;/code&gt; to limit the maximum upload&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;--min-age&lt;/code&gt; to upload only files that are older than some time (this helps to make sure only completed copys or downloads are moved)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="executing-the-script-with-cron"&gt;Executing the Script with cron&lt;/h2&gt;
&lt;p&gt;Last but not least we should make sure the script will be run in some interval. I choose to run the script every 30 minutes, to do so I use the command &lt;code&gt;crontab -e&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0,30 * * * * /opt/scripts/upload_gdrive.sh
&lt;/code&gt;&lt;/pre&gt;</content></item><item><title>Mounting Google Drive to Linux made easy</title><link>https://marschall.systems/blog/mounting-google-drive-to-linux-made-easy/</link><pubDate>Sat, 27 Jul 2019 10:13:01 +0200</pubDate><author>Maximilian Marschall</author><guid>https://marschall.systems/blog/mounting-google-drive-to-linux-made-easy/</guid><description/><content>&lt;p&gt;Because VPS with storage are quite expensive I was looking for a cheap way to store my backups and media in the cloud
without the fear of Google or Amazon having my whole data.&lt;/p&gt;
&lt;h2 id="google-drive-business"&gt;Google Drive Business&lt;/h2&gt;
&lt;p&gt;With google drive it&amp;rsquo;s pretty cheap to have &amp;ldquo;unlimited&amp;rdquo; online storage, their business plan includes unlimited storage
(1TB per user if fewer than 5 users). But the limit of 1TB per user is not enforcing. Here&amp;rsquo;s a link to their
&lt;a href="https://gsuite.google.com/intl/en/pricing.html"&gt;pricing page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="but-how-should-i-mount-it-rclone"&gt;But how should I mount it? rclone!&lt;/h2&gt;
&lt;p&gt;This is where an open source project comes in handy, &lt;a href="https://rclone.org/"&gt;rclone&lt;/a&gt;. It lets you FUSE mount google drive
and some other cloud providers (Amazon Drive, Amazon S3, Backblaze B2, Dropbox, &amp;hellip;) to use them as block sotrage.&lt;/p&gt;
&lt;p&gt;But the best part: it enables you to encrypt every file you store on this mount and even the file names.&lt;/p&gt;
&lt;h2 id="install-rclone"&gt;Install rclone&lt;/h2&gt;
&lt;p&gt;To install rclone on Linux just run following command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;curl https://rclone.org/install.sh | sudo bash&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t trust the Script you can also manually download and install rclone, you can find help over
&lt;a href="https://rclone.org/downloads/"&gt;here&lt;/a&gt; you can also find versions for other Platforms in their download section.&lt;/p&gt;
&lt;p&gt;Now you can test your install with &lt;code&gt;rclone version&lt;/code&gt; and you will get something like that:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rclone v1.48.0-099-gc2635e39-beta
- os/arch: linux/amd64
- go version: go1.12.7
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="configure-rclone"&gt;Configure rclone&lt;/h2&gt;
&lt;p&gt;To configure rclone you can use a config file or the config command.
We will use the config command as follows:
&lt;code&gt;rclone config&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It will now ask you to create a new remote, press &lt;code&gt;n&lt;/code&gt; to do so. First of all you need to choose a name for your remote,
I&amp;rsquo;ll be using &lt;code&gt;GD&lt;/code&gt; for my googledrive.&lt;/p&gt;
&lt;p&gt;Next you&amp;rsquo;ll be choosing the backend, in this case we&amp;rsquo;ll be using &lt;code&gt;drive&lt;/code&gt; which stands for Google Drive.&lt;/p&gt;
&lt;p&gt;For the next step we&amp;rsquo;ll need an API Key from Google, you can obtain one at your
&lt;a href="https://console.developers.google.com/"&gt;API Console&lt;/a&gt;. You need to create a project here, and enable the Google Drive
API under &amp;ldquo;ENABLE APIS AND SERVICES&amp;rdquo;. After that you&amp;rsquo;ll need to click on &amp;ldquo;Credentials&amp;rdquo; (not &amp;ldquo;Create credentials&amp;rdquo;), then
Create credentials, then &amp;ldquo;OAuth client ID&amp;rdquo;. You will be prompted to set the OAuth screen product name, if you haven&amp;rsquo;t
set one yet. Now choose application type &amp;ldquo;other&amp;rdquo; and &amp;ldquo;Create&amp;rdquo;. Now you will be shown a client ID and client secret use
this two values in your rclone config.&lt;/p&gt;
&lt;p&gt;You won&amp;rsquo;t need the advanced config, so skip this for now.&lt;/p&gt;
&lt;p&gt;If you are configuring rclone on a remote/headless machine you&amp;rsquo;ll need to use manual config so say now to auto config
and follow the steps described by rclone.&lt;/p&gt;
&lt;p&gt;We won&amp;rsquo;t configure this as a Team Drive so no to the next question.&lt;/p&gt;
&lt;p&gt;After that you&amp;rsquo;ll be shown the configfile and it will be OK, so just say yes.&lt;/p&gt;
&lt;h2 id="encrypting-your-data"&gt;Encrypting your data&lt;/h2&gt;
&lt;p&gt;To encrypt the data which is uploaded to your remote we need to make some changes to your rclone config.&lt;/p&gt;
&lt;p&gt;To do so we need to run &lt;code&gt;rclone config&lt;/code&gt; again and press &lt;code&gt;n&lt;/code&gt; to add a new remote, I&amp;rsquo;ll call it &lt;code&gt;gcrypt&lt;/code&gt;. Next we&amp;rsquo;ll need to choose &lt;code&gt;9&lt;/code&gt; (crypt) after that you need to enter the remote you created in the last step, in my case it&amp;rsquo;s &lt;code&gt;GD&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The next step is filename encryption, I&amp;rsquo;d suggest to use &lt;code&gt;2&lt;/code&gt; (standard) after that it&amp;rsquo;s all about directoryname encryption choose &lt;code&gt;1&lt;/code&gt; to encrypt directory names.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the important step either choose your own password to encrypt files or let rclone generate a random one. This password will be stored encrypted in your rclone.conf but store this password in a safe place, if you loose it you&amp;rsquo;ll lose access to your data. Same rules apply to the next step: Password for salt.&lt;/p&gt;
&lt;p&gt;Congratulations you made it!&lt;/p&gt;
&lt;h2 id="mount-on-startup"&gt;Mount on startup&lt;/h2&gt;
&lt;p&gt;To auto mount your drive on boot you can use a service file for systemd.
Just create a &lt;code&gt;.service&lt;/code&gt; file under &lt;code&gt;/etc/systemd/system&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you encrypted your data use the name of your encrypted remote.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/etc/systemd/system/rclone-gmedia.service&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[Unit]
Description=RClone Service
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
Environment=RCLONE_CONFIG=/etc/rclone/rclone.conf
ExecStart=/usr/bin/rclone mount &amp;lt;your remote name goes here&amp;gt;: &amp;lt;your mountpath goes here&amp;gt; \
--allow-other \
--log-level INFO \
--log-file /var/log/rclone.log \
--umask 002
Restart=on-failure
User=&amp;lt;your user goes here&amp;gt;
Group=&amp;lt;your group goes here&amp;gt;
[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After that you are able to start and stop your mount with &lt;code&gt;systemctl&lt;/code&gt; as follows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;action&lt;/th&gt;
&lt;th&gt;command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;start&lt;/td&gt;
&lt;td&gt;&lt;code&gt;systemctl start rclone-gmedia&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stop&lt;/td&gt;
&lt;td&gt;&lt;code&gt;systemctl stop rclone-gmedia&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;check status&lt;/td&gt;
&lt;td&gt;&lt;code&gt;systemctl status rclone-gmedia&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;add to autostart&lt;/td&gt;
&lt;td&gt;&lt;code&gt;systemctl enable rclone-gmedia&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you want to know how to make your setup even better check out my new &lt;a href="../optimizing-your-rclone-mount/"&gt;post&lt;/a&gt;.&lt;/p&gt;</content></item></channel></rss>