<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="pretty-atom-feed.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Ascode Blog</title>
  <subtitle>Adventures from the cloud trenches</subtitle>
  <link href="https://ascode.nl/feed/feed.xml" rel="self" />
  <link href="https://ascode.nl/" />
  <updated>2026-01-30T00:00:00Z</updated>
  <id>https://ascode.nl/</id>
  <author>
    <name>Erik Christiaans</name>
  </author>
  <entry>
    <title>GitLab CI/CD for Cloudflare Pages: Automated Deployments with Preview Environments</title>
    <link href="https://ascode.nl/blog/gitlab-cicd-cloudflare-pages/" />
    <updated>2026-01-30T00:00:00Z</updated>
    <id>https://ascode.nl/blog/gitlab-cicd-cloudflare-pages/</id>
    <content type="html">&lt;p&gt;Remember when the &lt;a href=&quot;https://ascode.nl/blog/new-website-2025/&quot;&gt;previous post&lt;/a&gt; ended with &lt;em&gt;&amp;quot;Automating deployments — When I find some more time...&amp;quot;&lt;/em&gt;? Well, I found the time. Let me walk you through how I set up a GitLab CI/CD pipeline that deploys this website to Cloudflare Pages, complete with preview environments on merge requests.&lt;/p&gt;
&lt;h2 id=&quot;the-goal&quot;&gt;The Goal&lt;/h2&gt;
&lt;p&gt;Simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Push to a branch, create a merge request → &lt;strong&gt;preview deploy&lt;/strong&gt; on a unique URL&lt;/li&gt;
&lt;li&gt;Merge to main → &lt;strong&gt;production deploy&lt;/strong&gt; to &lt;a href=&quot;https://ascode.nl&quot;&gt;ascode.nl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;No manual steps. No clicking around in dashboards.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;You need two things from Cloudflare:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Account ID&lt;/strong&gt; — find this on the sidebar of any zone in your Cloudflare dashboard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Token&lt;/strong&gt; — create one at &lt;a href=&quot;https://dash.cloudflare.com/profile/api-tokens&quot;&gt;dash.cloudflare.com/profile/api-tokens&lt;/a&gt; with these permissions:&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Permission&lt;/th&gt;
&lt;th&gt;Access&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Account · Cloudflare Pages&lt;/td&gt;
&lt;td&gt;Edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zone · Zone&lt;/td&gt;
&lt;td&gt;Read&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Add both as CI/CD variables in your GitLab project (Settings → CI/CD → Variables):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt; — masked, protected&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLOUDFLARE_ACCOUNT_ID&lt;/code&gt; — masked, protected&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wrangler (the Cloudflare CLI) picks these up automatically, no extra configuration needed.&lt;/p&gt;
&lt;h2 id=&quot;the-pipeline&quot;&gt;The Pipeline&lt;/h2&gt;
&lt;p&gt;The full &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; is surprisingly short. Two stages: build the static site with Eleventy, then deploy it with Wrangler.&lt;/p&gt;
&lt;h3 id=&quot;build-stage&quot;&gt;Build Stage&lt;/h3&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;stages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; build
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; deploy

&lt;span class=&quot;token key atrule&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;CLOUDFLARE_PROJECT_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ascode&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;web
  &lt;span class=&quot;token key atrule&quot;&gt;NODE_VERSION&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;20&quot;&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build
  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;NODE_VERSION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;alpine
  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; npm ci
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; npx @11ty/eleventy
  &lt;span class=&quot;token key atrule&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; _site/
    &lt;span class=&quot;token key atrule&quot;&gt;expire_in&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1 hour
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_MERGE_REQUEST_IID
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing fancy. Install dependencies, run Eleventy, and pass the &lt;code&gt;_site/&lt;/code&gt; output as an artifact to the deploy stage. The &lt;code&gt;rules&lt;/code&gt; ensure it runs on both merge requests and pushes to main.&lt;/p&gt;
&lt;h3 id=&quot;preview-deploys&quot;&gt;Preview Deploys&lt;/h3&gt;
&lt;p&gt;This is where it gets nice:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;deploy:preview&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; deploy
  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;NODE_VERSION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;alpine
  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; build
  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; npx wrangler pages deploy _site/
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;project&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;name=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CLOUDFLARE_PROJECT_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;branch=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CI_MERGE_REQUEST_SOURCE_BRANCH_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;commit&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;hash=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CI_COMMIT_SHORT_SHA&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;
  &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; preview/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CI_MERGE_REQUEST_SOURCE_BRANCH_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;.$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CLOUDFLARE_PROJECT_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;.pages.dev
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_MERGE_REQUEST_IID&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every merge request gets its own preview URL based on the branch name. Create a branch called &lt;code&gt;new-blog-post&lt;/code&gt;, and you get &lt;code&gt;https://new-blog-post.ascode-web.pages.dev&lt;/code&gt;. The GitLab environment link shows up directly in the merge request, so reviewers can click through to the preview without having to figure out the URL.&lt;/p&gt;
&lt;p&gt;Wrangler handles everything — if the Cloudflare Pages project does not exist yet, it creates it. It uploads the files, configures the deployment, and gives you the URL. No need to pre-configure anything in the Cloudflare dashboard.&lt;/p&gt;
&lt;h3 id=&quot;stopping-preview-environments&quot;&gt;Stopping Preview Environments&lt;/h3&gt;
&lt;p&gt;When a merge request is merged or closed, the preview environment should be cleaned up in GitLab:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;stop:preview&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; deploy
  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;NODE_VERSION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;alpine
  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; echo &quot;Preview environment stopped&quot;
  &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; preview/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
    &lt;span class=&quot;token key atrule&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; stop
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_MERGE_REQUEST_IID
      &lt;span class=&quot;token key atrule&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; manual
      &lt;span class=&quot;token key atrule&quot;&gt;allow_failure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this only stops the GitLab environment — the Cloudflare preview deployment itself stays around until Cloudflare cleans it up (they do, eventually). If you want to actively delete it, you can add a &lt;code&gt;wrangler pages deployment delete&lt;/code&gt; command, but I have not found the need for that.&lt;/p&gt;
&lt;h3 id=&quot;production-deploy&quot;&gt;Production Deploy&lt;/h3&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;deploy:production&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; deploy
  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;NODE_VERSION&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;alpine
  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; build
  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; npx wrangler pages deploy _site/
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;project&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;name=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CLOUDFLARE_PROJECT_NAME&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;branch=&quot;main&quot;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;commit&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;hash=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CI_COMMIT_SHORT_SHA&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;
  &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; production
    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//ascode.nl
  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Merge to main → deploys to production. That&#39;s it. The &lt;code&gt;--branch=&amp;quot;main&amp;quot;&lt;/code&gt; flag tells Cloudflare this is the production deployment, which means it goes live on your custom domain.&lt;/p&gt;
&lt;h2 id=&quot;a-note-on-wrangler-and-npx&quot;&gt;A Note on Wrangler and npx&lt;/h2&gt;
&lt;p&gt;You might notice I am using &lt;code&gt;npx wrangler&lt;/code&gt; instead of installing it as a project dependency. This keeps the blog repository clean — Wrangler is a deployment tool, not a site dependency. The &lt;code&gt;npx&lt;/code&gt; command downloads it on the fly in the CI environment, which adds a few seconds to the deploy stage but keeps &lt;code&gt;package.json&lt;/code&gt; focused on what matters: Eleventy and its plugins.&lt;/p&gt;
&lt;p&gt;If the download time bothers you, add &lt;code&gt;wrangler&lt;/code&gt; to your &lt;code&gt;devDependencies&lt;/code&gt; and it will be installed during &lt;code&gt;npm ci&lt;/code&gt; in the build stage. Either way works.&lt;/p&gt;
&lt;h2 id=&quot;the-result&quot;&gt;The Result&lt;/h2&gt;
&lt;p&gt;The workflow is now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a branch and write content&lt;/li&gt;
&lt;li&gt;Push and open a merge request&lt;/li&gt;
&lt;li&gt;Pipeline builds and deploys a preview — check the result&lt;/li&gt;
&lt;li&gt;Merge to main&lt;/li&gt;
&lt;li&gt;Pipeline deploys to production — live on &lt;a href=&quot;https://ascode.nl&quot;&gt;ascode.nl&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No more manual uploads to Cloudflare Pages. No more &amp;quot;when I find some more time.&amp;quot; Just git push and go.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building a Raspberry Pi5 Cluster with Talos Linux and Cilium</title>
    <link href="https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/" />
    <updated>2025-10-23T00:00:00Z</updated>
    <id>https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/</id>
    <content type="html">&lt;p&gt;You know when you want to have an environment to play with, test some things, and do some general tinkering around?&lt;/p&gt;
&lt;p&gt;Of course you can run this on your laptop (Very Happy Macbook Pro M4 Max owner!) but where is the fun in that? So, I decided to build a Kubernetes cluster consisting of Raspberry Pi&#39;s. Now I am the kind of guy that does not go half way, so this cluster not only needs to be small and quiet, but also powerful. Which means I am building this with not a single Raspberry Pi board, but a full-fledged cluster with high-availability and everything.&lt;/p&gt;
&lt;p&gt;Here is a picture of the end result:&lt;/p&gt;
&lt;p&gt;Let me take you through how I build this!&lt;/p&gt;
&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#kit-list&quot;&gt;Kit List&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#putting-it-all-together&quot;&gt;Putting it all together&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#installing-talos&quot;&gt;Installing Talos&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#preparing-the-raspberry-pi&quot;&gt;Preparing the Raspberry Pi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#flashing-the-os-to-an-sd-card&quot;&gt;Flashing the OS to an SD Card&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#preparing-talos-config&quot;&gt;Preparing Talos Config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#install-talos-on-the-first-node&quot;&gt;Install Talos on the first node&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#install-talos-on-other-nodes&quot;&gt;Install Talos on other nodes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#install-cilium&quot;&gt;Install Cilium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/#whats-next&quot;&gt;What&#39;s next?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;kit-list&quot;&gt;Kit list&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to view complete shopping list&lt;/strong&gt;&lt;/summary&gt;
&lt;p&gt;For those of you that want to know and possibly want to build this themselves, here is the full kit list:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Amount&lt;/th&gt;
&lt;th&gt;Cost at time of writing of article&lt;/th&gt;
&lt;th&gt;Total Cost&lt;/th&gt;
&lt;th&gt;Where ordered?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Raspberry Pi 5 - 16GB&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;€ 131,50&lt;/td&gt;
&lt;td&gt;€ 526&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.raspberrystore.nl/PrestaShop/nl/raspberry-pi-5/639-raspberry-pi-5-16gb-5056561803333.html&quot;&gt;RaspberryPi Store&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;512GB Raspberry Pi NVMe SSD, 22x30mm&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;€ 54,95&lt;/td&gt;
&lt;td&gt;€ 219,8&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.raspberrystore.nl/PrestaShop/nl/storage-sd-kaarten-en-ssd-s/613-512gb-raspberry-pi-nvme-ssd-22x30mm-enkel-voor-de-raspberry-pi-5-5056561805030.html&quot;&gt;RaspberryPi Store&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandisk 32Gb MicroSDHC Extreme PRO&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;€ 16,95&lt;/td&gt;
&lt;td&gt;€ 16,95&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.raspberrystore.nl/PrestaShop/nl/storage-sd-kaarten-en-ssd-s/206-sandisk-32gb-microsdhc-extreme-pro-r100-w90-c10-u-0619659155414.html&quot;&gt;RaspberryPi Store&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geekworm Cooler for Raspberry Pi 5, passive aluminum heatsink&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;€ 9&lt;/td&gt;
&lt;td&gt;€ 36&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.amazon.nl/dp/B0DDPWYLY1&quot;&gt;Amazon&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M.2 NVME M-Key and PoE+ HAT for RPi5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;€ 36,29&lt;/td&gt;
&lt;td&gt;€ 145,16&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.kiwi-electronics.com/nl/m-2-nvme-m-key-en-poeplus-hat-voor-rpi5-20117?search=3362&quot;&gt;Kiwi El4ctronics&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubiquiti UniFi Flex 2.5G PoE&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;€ 199,65&lt;/td&gt;
&lt;td&gt;€ 199,65&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.kommago.nl/p/ubiquiti-unifi-flex-2-5g-poe-8-switch-compacte-8-poorts-managed-2-5g-switch-8x-poe-88004&quot;&gt;KommaGo&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubiquiti AC Adapter 210W&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;€ 105,88&lt;/td&gt;
&lt;td&gt;€ 105,88&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.kommago.nl/p/ubiquiti-ac-adapter-210w-voor-usw-ultra-en-usw-flex-2-5g-8-poe-88056&quot;&gt;KommaGo&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C4Labs 8-Slot Cloudlet Cluster Case&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;€ 72,74&lt;/td&gt;
&lt;td&gt;€ 72,74&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://thepihut.com/products/8-slot-cloudlet-cluster-case?variant=37531942387907&quot;&gt;The Pi Hut&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grand total&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;€ 1249,44 incl. BTW&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/details&gt;
&lt;p&gt;I realize this list does not fit everyone&#39;s budget, but it is what I went with. Yes, you could get a cheaper PoE switch, but I love Ubiquiti and it is my go-to choice for networking gear. You could get Raspberry Pi4 with just SD Cards in stead of SSD&#39;s, of course you can! That is the beauty of these projects, you can do it however you want! In this case, this is my blog and my project so this is what I used.&lt;/p&gt;
&lt;p&gt;The cluster case I ordered from a company in the UK, and if you are not located in the UK then, it gets a bit more expensive - like a lot more expensive. That was a bit of an oversight from my part, there is a good alternative here: https://www.amazon.com/GeeekPi-Cabinet-Equipment-RackMate-Rackmount/dp/B0DPGZPTPP?th=1. I don&#39;t own a 3D printer but if you do, there are some very good cluster cases out there for you to print yourself. I also removed the fans from the case cause I hate noise - that is where the passive heatsink comes in.&lt;/p&gt;
&lt;p&gt;Lastly I had to purchase some network cables to make it all look nice.&lt;/p&gt;
&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/B33gVAuLYw-600.avif 600w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/B33gVAuLYw-600.webp 600w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ascode.nl/blog/building-a-raspberrypi-kubernetes-cluster/B33gVAuLYw-600.png&quot; alt=&quot;Pi5 Cluster&quot; width=&quot;600&quot; height=&quot;800&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;installing-talos&quot;&gt;Installing Talos&lt;/h2&gt;
&lt;p&gt;For those of you that are unfamiliar with Talos: &lt;a href=&quot;https://docs.siderolabs.com/talos/v1.11/overview/what-is-talos&quot;&gt;Talos&lt;/a&gt; is a Linux distro that is optimized for Kubernetes and is secure, immutable and minimal by default, limiting your Kubernetes attack surface. All system management is done via an API - there is no shell or interactive console.&lt;/p&gt;
&lt;p&gt;If you are interested in the philosophy behind this, have a read &lt;a href=&quot;https://docs.siderolabs.com/talos/v1.11/learn-more/philosophy&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Installing Talos was a little less easy than I expected, mainly because the Raspberry Pi5 is not officially supported yet. Here is an overview of the (non-Pi5) supported Raspberry Pi series: (https://docs.siderolabs.com/talos/v1.11/platform-specific-installations/single-board-computers/rpi_generic). After a quick search I found a Github repo &lt;a href=&quot;https://github.com/talos-rpi5/talos-builder&quot;&gt;here&lt;/a&gt; that ported the offial Talos image to work on Raspberry Pi5. Give them a star to support their work!&lt;/p&gt;
&lt;h3 id=&quot;preparing-the-raspberry-pi&quot;&gt;Preparing the Raspberry Pi&lt;/h3&gt;
&lt;p&gt;I had a previous Raspberry Pi OS Lite installed with K3S so my Pi had to be cleared out first:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;umount&lt;/span&gt; /dev/nvme0n1p?
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; wipefs &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--force&lt;/span&gt; /dev/nvme0n1p?
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; wipefs &lt;span class=&quot;token parameter variable&quot;&gt;--all&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--force&lt;/span&gt; /dev/nvme0n1
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dd&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/dev/zero &lt;span class=&quot;token assign-left variable&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/dev/nvme0n1 &lt;span class=&quot;token assign-left variable&quot;&gt;bs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Also, you want to make sure the boot order is changed so that the Pi attempts NVMe boot first. This does assume you already have an OS on your Pi5:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Open the Raspberry Pi configuration editor&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; raspi-config

&lt;span class=&quot;token comment&quot;&gt;# Navigate to Advanced Options &gt; Boot &gt; Boot Order&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Highlight &#39;NVMe/USB Boot&#39; and press enter&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Follow the prompts&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you do not have an OS on your Pi5 yet, you can flash your SD card using the &lt;a href=&quot;https://www.raspberrypi.com/software/&quot;&gt;Raspberry Pi Imager&lt;/a&gt;. For OS, choose &lt;code&gt;Misc utility images&lt;/code&gt;, &lt;code&gt;Bootloader (Pi5 Family)&lt;/code&gt;, &lt;code&gt;NVMe/USB Boot&lt;/code&gt; and start your Pi5 using this card. Once the green light flashes, the Pi5 is configured to boot from NVMe first.&lt;/p&gt;
&lt;h3 id=&quot;flashing-the-os-to-an-sd-card&quot;&gt;Flashing the OS to an SD Card&lt;/h3&gt;
&lt;p&gt;On a side note, make sure you use specific fast SD cards as the Pi5 does not like slow cards. The type from the shopping list is good. On another note, I used 1 card. You can get more cards if you are impatient and do not want to re-flash in between tasks.&lt;/p&gt;
&lt;p&gt;Here is where it gets a little more interesting. You would think that we flash the Talos OS onto the SD card, right? Well, no, we don&#39;t. After spending quite a few hours, I found out that even when you select the NVMe card as the install disk, it just would not install to it. Here is the associated Talos config (that, to be clear, did not work for me):&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;machine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;disk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /dev/nvme0n1 &lt;span class=&quot;token comment&quot;&gt;# The disk used for installations.&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ghcr.io/siderolabs/installer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;v1.11.1 &lt;span class=&quot;token comment&quot;&gt;# Allows for supplying the image used to perform the installation.&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;wipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Indicates if the installation disk should be wiped at installation time.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I worked around the problem by flashing the SD Card with Raspberry Pi OS using the Raspberry Pi Imager &lt;code&gt;Raspberry Pi OS (other)&lt;/code&gt;, &lt;code&gt;Raspberry Pi OS Lite (64 bit)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I then used this card to boot the first Pi into Pi OS and then downloading the Talos OS:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-LO&lt;/span&gt; https://github.com/talos-rpi5/talos-builder/releases/download/v1.11.1-pre/metal-arm64.raw.zst
unzstd metal-arm64.raw.zst&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You only need to do this once. Make sure you set the hostname and then use the downloaded image to flash your NVMe disk. Repeat this on each of your nodes:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; hostnamectl set-hostname rpi5-1
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dd&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;metal-arm64.raw &lt;span class=&quot;token assign-left variable&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/dev/nvme0n1 &lt;span class=&quot;token assign-left variable&quot;&gt;bs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;4M &lt;span class=&quot;token assign-left variable&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;progress&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Make sure you set a unique hostname for each node! Shutdown the node after this step is complete, remove the SD card and power on the node again; it should now boot from the SSD drive.&lt;/p&gt;
&lt;h3 id=&quot;preparing-talos-config&quot;&gt;Preparing Talos Config&lt;/h3&gt;
&lt;p&gt;To perform any kind of management on Talos, you need to communicate (securely) with the API. In this blog, I use &lt;code&gt;talosctl&lt;/code&gt; for management.&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; siderolabs/tap/talosctl&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Once you have talosctl installed, we can create the config to deploy the cluster. We are going to install Cilium, for which we need a Talos Patch to prepare Talos for it. Specifically, no cni should be enabled and kube-proxy will be disabled as it will be replaced by Cilium. The last line will allow workloads to be scheduled on control plane nodes, which is fine for this lab environment but in a production environment maybe not such a good idea.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;EOF &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ./cilium-patch.yaml
  machine:
    install:
      disk: /dev/nvme0
      image: ghcr.io/siderolabs/installer:v1.11.3
      wipe: &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    network:
      interfaces:
      - deviceSelector:
          physical: &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
        dhcp: &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# view more in the complete command sequence&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to see the complete command sequence&lt;/strong&gt;&lt;/summary&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;EOF&lt;span class=&quot;token bash punctuation&quot;&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ./cilium-patch.yaml&lt;/span&gt;
  machine:
    install:
      disk: /dev/nvme0
      image: ghcr.io/siderolabs/installer:v1.11.3
      wipe: true
    network:
      interfaces:
      - deviceSelector:
          physical: true
        dhcp: true
        vip:
          ip: 10.10.10.15
  cluster:
    network:
      cni:
        name: none
    proxy:
      disabled: true
  allowSchedulingOnControlPlanes: true
EOF&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;CONTROL_PLANE_IP&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.10&lt;/span&gt;.10.11
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; _out
talosctl gen config talosrpi5 https://&lt;span class=&quot;token variable&quot;&gt;$CONTROL_PLANE_IP&lt;/span&gt;$:6443 --output-dir _out --config-patch @cilium-patch.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;This will create 3 files in the &lt;code&gt;_out&lt;/code&gt; directory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;talosconfig - this is the unique context for your cluster that contains the certificates for authentication&lt;/li&gt;
&lt;li&gt;controlplane.yaml - for configuring control plane nodes&lt;/li&gt;
&lt;li&gt;worker.yaml - for configuring worker nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Note:&lt;/strong&gt; You want your cluster nodes to have a fixed IP address. I found out that starting with DHCP and then setting a fixed IP as part of configuration basically broke the setup. The solution was to make a DHCP reservation for each node in your cluster, making sure that the nodes will always get the same IP address from DHCP.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;install-talos-on-the-first-node&quot;&gt;Install Talos on the first node&lt;/h3&gt;
&lt;p&gt;We are now ready to configure the first cluster node. Make sure the node is up and has the correct IP address; if you hooked up a display to your RPi then you should see this on screen.&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;CONTROL_PLANE_IP&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.10&lt;/span&gt;.10.11
talosctl apply-config &lt;span class=&quot;token parameter variable&quot;&gt;--insecure&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--nodes&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$CONTROL_PLANE_IP&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--file&lt;/span&gt; _out/controlplane.yaml
&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;TALOSCONFIG&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;_out/talosconfig&quot;&lt;/span&gt;
talosctl config endpoint &lt;span class=&quot;token variable&quot;&gt;$CONTROL_PLANE_IP&lt;/span&gt;
talosctl config &lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$CONTROL_PLANE_IP&lt;/span&gt;
talosctl bootstrap &lt;span class=&quot;token comment&quot;&gt;## &amp;lt;-- this command only needs to be run ONCE on a control plane node!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If all went well, you can use &lt;code&gt;talosctl health&lt;/code&gt; to check the status of your Talos cluster. Get the Kubernetes config using the following command:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;talosctl kubeconfig &lt;span class=&quot;token parameter variable&quot;&gt;--nodes&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$CONTROL_PLANE_IP&lt;/span&gt;
kubectl get nodes&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# you should see something like this&lt;/span&gt;
NAME     STATUS   ROLES           AGE   VERSION
rpi5-1   Ready    control-plane   1h    v1.34.0&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;install-talos-on-other-nodes&quot;&gt;Install Talos on other nodes&lt;/h3&gt;
&lt;p&gt;I would recommend to take care of Cilium first, then install the rest of the nodes.&lt;/p&gt;
&lt;h2 id=&quot;install-cilium&quot;&gt;Install Cilium&lt;/h2&gt;
&lt;p&gt;For those of you that are unfamiliar with Cilium: &lt;a href=&quot;https://github.com/cilium/cilium&quot;&gt;Cilium&lt;/a&gt; is an open-source, eBPF-based networking, security, and observability platform designed for cloud-native environments such as Kubernetes clusters and containerized workloads. Cilium operates as a Container Network Interface (CNI) and provides an advanced, kernel-native framework for container networking, combining eBPF-based performance, granular security, and deep observability.&lt;/p&gt;
&lt;p&gt;Start by installing the Cilium CLI as described on https://docs.cilium.io/en/stable/gettingstarted/k8s-install-default/#install-the-cilium-cli&lt;/p&gt;
&lt;p&gt;Next I am going to use Helm to install Cilium onto the cluster using Terraform (actually, I used OpenTofu). For a complete list of values and how to further customize the Helm deployment, look &lt;a href=&quot;https://github.com/cilium/cilium/blob/main/install/kubernetes/cilium/values.yaml&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;terraform&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;required_providers&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;kubernetes&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;source&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hashicorp/kubernetes&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt;= 2.38.0&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;helm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;source&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hashicorp/helm&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt;= 3.0.2&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;required_version&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt;= 1.10.0&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# view more in the complete command sequence&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to see the complete Terraform configuration with all Helm values&lt;/strong&gt;&lt;/summary&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;terraform&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;required_providers&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;kubernetes&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;source&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hashicorp/kubernetes&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt;= 2.38.0&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;helm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;source&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hashicorp/helm&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt;= 3.0.2&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;required_version&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&gt;= 1.10.0&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;provider&lt;span class=&quot;token type variable&quot;&gt; &quot;kubernetes&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;config_path&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~/.kube/config&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;provider&lt;span class=&quot;token type variable&quot;&gt; &quot;helm&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;kubernetes&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;config_path&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~/.kube/config&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;helm_release&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cilium&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;chart&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cilium&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;             &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cilium&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;repository&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://helm.cilium.io/&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;version&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.18.2&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;create_namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;namespace&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;kube-system&quot;&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ipam.mode&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;kubernetes&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;kubeProxyReplacement&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;securityContext.capabilities.ciliumAgent&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;securityContext.capabilities.cleanCiliumState&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cgroup.autoMount.enabled&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cgroup.hostRoot&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/sys/fs/cgroup&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;k8sServiceHost&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;localhost&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;k8sServicePort&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7445&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatewayAPI.enabled&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatewayAPI.enableAlpn&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatewayAPI.enableAppProtocol&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;If you want to check what versions are available for Cilium, run the following commands:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm repo &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; cilium https://helm.cilium.io/
helm search repo cilium &lt;span class=&quot;token parameter variable&quot;&gt;--versions&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;/div&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;

```bash
&lt;span class=&quot;token comment&quot;&gt;# results are something like this&lt;/span&gt;
NAME            CHART VERSION   APP VERSION     DESCRIPTION
cilium/cilium   &lt;span class=&quot;token number&quot;&gt;1.18&lt;/span&gt;.2          &lt;span class=&quot;token number&quot;&gt;1.18&lt;/span&gt;.2          eBPF-based Networking, Security, and Observability
cilium/cilium   &lt;span class=&quot;token number&quot;&gt;1.18&lt;/span&gt;.1          &lt;span class=&quot;token number&quot;&gt;1.18&lt;/span&gt;.1          eBPF-based Networking, Security, and Observability
cilium/cilium   &lt;span class=&quot;token number&quot;&gt;1.18&lt;/span&gt;.0          &lt;span class=&quot;token number&quot;&gt;1.18&lt;/span&gt;.0          eBPF-based Networking, Security, and Observability
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apply the Terraform code and wait for Cilium to be fully deployed. This can take a bit, be patient.&lt;/p&gt;
&lt;p&gt;You can check the status of deployment with either kubectl or the cilium cli.&lt;/p&gt;
&lt;h3 id=&quot;install-talos-on-other-nodes-2&quot;&gt;Install Talos on other nodes&lt;/h3&gt;
&lt;p&gt;Now that Cilium is installed, we can install the other Talos nodes. When you install Cilium straight after the first Talos node is deployed, the rest of the nodes automatically have the correct Cilium config.&lt;/p&gt;
&lt;p&gt;Because of etcd cluster quorum (a two-node etcd cluster is not high availability (HA) because if one node is lost, the cluster will have quorum loss and enter read-only mode), we are going to deploy a total of 3 control plane nodes (hence the &lt;code&gt;allowSchedulingOnControlPlanes: true&lt;/code&gt; config, otherwise we would only have 1 worker node on which to deploy workloads).&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;CONTROL_PLANE_IP&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;10.10.10.12&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10.10.10.13&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token assign-left variable&quot;&gt;WORKER_IP&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;10.10.10.14&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;ip&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${CONTROL_PLANE_IP&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;@&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;}&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Applying config to worker node: &lt;span class=&quot;token variable&quot;&gt;$ip&lt;/span&gt;&quot;&lt;/span&gt;
  talosctl apply-config &lt;span class=&quot;token parameter variable&quot;&gt;--insecure&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--nodes&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$ip&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--file&lt;/span&gt; _out/controlplane.yaml
&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;
talosctl apply-config &lt;span class=&quot;token parameter variable&quot;&gt;--insecure&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--nodes&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${WORKER_IP}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--file&lt;/span&gt; _out/worker.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The end result will be a 4-node Talos cluster that looks something like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get nodes

NAME     STATUS   ROLES           AGE   VERSION
rpi5-1   Ready    control-plane   1h    v1.34.0
rpi5-2   Ready    control-plane   1h    v1.34.0
rpi5-3   Ready    control-plane   1h    v1.34.0
rpi5-4   Ready    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;none&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;          1h    v1.34.0

&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; cilium status

    /¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
 /¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;    Cilium:             OK
 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/    Operator:           OK
 /¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;    Envoy DaemonSet:    OK
 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/    Hubble Relay:       disabled
    &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/       ClusterMesh:        disabled

&lt;span class=&quot;token comment&quot;&gt;# view more in the complete command sequence&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to see complete cluster status and pod listings&lt;/strong&gt;&lt;/summary&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get nodes

NAME     STATUS   ROLES           AGE   VERSION
rpi5-1   Ready    control-plane   1h    v1.34.0
rpi5-2   Ready    control-plane   1h    v1.34.0
rpi5-3   Ready    control-plane   1h    v1.34.0
rpi5-4   Ready    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;none&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;          1h    v1.34.0

cilium status

    /¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
 /¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;    Cilium:             OK
 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/    Operator:           OK
 /¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;    Envoy DaemonSet:    OK
 &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/¯¯&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/    Hubble Relay:       disabled
    &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;__/       ClusterMesh:        disabled

DaemonSet              cilium                   Desired: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;, Ready: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;/4, Available: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;/4
DaemonSet              cilium-envoy             Desired: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;, Ready: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;/4, Available: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;/4
Deployment             cilium-operator          Desired: &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;, Ready: &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;/2, Available: &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;/2
Containers:            cilium                   Running: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
                       cilium-envoy             Running: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
                       cilium-operator          Running: &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
                       clustermesh-apiserver
                       hubble-relay
Cluster Pods:          &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;/20 managed by Cilium
Helm chart version:    &lt;span class=&quot;token number&quot;&gt;1.18&lt;/span&gt;.2
Image versions         cilium             quay.io/cilium/cilium:v1.18.2@sha256:858f807ea4e20e85e3ea3240a762e1f4b29f1cb5bbd0463b8aa77e7b097c0667: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
                       cilium-envoy       quay.io/cilium/cilium-envoy:v1.34.7-1757592137-1a52bb680a956879722f48c591a2ca90f7791324@sha256:7932d656b63f6f866b6732099d33355184322123cfe1182e6f05175a3bc2e0e0: &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
                       cilium-operator    quay.io/cilium/operator-generic:v1.18.2@sha256:cb4e4ffc5789fd5ff6a534e3b1460623df61cba00f5ea1c7b40153b5efb81805: &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;

talosctl health
discovered nodes: &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;10.10.10.11&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10.10.10.12&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10.10.10.13&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10.10.10.14&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; etcd to be healthy: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; etcd to be healthy: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; etcd members to be consistent across nodes: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; etcd members to be consistent across nodes: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; etcd members to be control plane nodes: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; etcd members to be control plane nodes: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; apid to be ready: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; apid to be ready: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all nodes memory sizes: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all nodes memory sizes: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all nodes disk sizes: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all nodes disk sizes: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; no diagnostics: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; no diagnostics: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; kubelet to be healthy: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; kubelet to be healthy: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all nodes to finish boot sequence: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all nodes to finish boot sequence: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all k8s nodes to report: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all k8s nodes to report: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all control plane static pods to be running: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all control plane static pods to be running: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all control plane components to be ready: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all control plane components to be ready: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all k8s nodes to report ready: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all k8s nodes to report ready: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; kube-proxy to report ready: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; kube-proxy to report ready: SKIP
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; coredns to report ready: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; coredns to report ready: OK
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all k8s nodes to report schedulable: &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.
waiting &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; all k8s nodes to report schedulable: OK

kubectl get pods &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt;

NAMESPACE       NAME                                               READY   STATUS    RESTARTS      AGE
kube-system     cilium-9tff5                                       &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     cilium-envoy-f8z4f                                 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   o             1h
kube-system     cilium-envoy-jnj94                                 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     cilium-envoy-qdwcn                                 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   o             1h
kube-system     cilium-envoy-ww9pq                                 &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   o             1h
kube-system     cilium-kxkxb                                       &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     cilium-mvggt                                       &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     cilium-operator-54d6f6cccb-bkn8k                   &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     cilium-operator-54d6f6cccb-cdvz9                   &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     cilium-w9fvm                                       &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     coredns-7bb49dc74c-nbrd2                           &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     coredns-7bb49dc74c-xjkw8                           &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-apiserver-rpi5-1                              &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-apiserver-rpi5-2                              &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-apiserver-rpi5-3                              &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-controller-manager-rpi5-1                     &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-controller-manager-rpi5-2                     &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-controller-manager-rpi5-3                     &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-scheduler-rpi5-1                              &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-scheduler-rpi5-2                              &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h
kube-system     kube-scheduler-rpi5-3                              &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;/1     Running   &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;             1h&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h2 id=&quot;whats-next&quot;&gt;What&#39;s next?&lt;/h2&gt;
&lt;p&gt;Now that Talos and Cilium are ready, start deploying other services to your cluster. Think about &lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/user-guide/helm/&quot;&gt;ArgoCD&lt;/a&gt;, &lt;a href=&quot;https://cert-manager.io/docs/installation/helm/&quot;&gt;cert-manager&lt;/a&gt;, &lt;a href=&quot;https://github.com/kubernetes-sigs/external-dns&quot;&gt;external-dns&lt;/a&gt; or get an intro to what Cilium can actually do with the &lt;a href=&quot;https://docs.cilium.io/en/stable/gettingstarted/demo/&quot;&gt;Star Wars Demo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Have fun!&lt;/p&gt;
&lt;/div&gt;</content>
  </entry>
  <entry>
    <title>Self-hosted Gitlab Runners on AKS, with Managed Identities</title>
    <link href="https://ascode.nl/blog/gitlab-runners-aks/" />
    <updated>2025-09-30T00:00:00Z</updated>
    <id>https://ascode.nl/blog/gitlab-runners-aks/</id>
    <content type="html">&lt;p&gt;This post is long overdue, I&#39;ve been meaning to write this for a long time but did not get around to doing it. So here goes!&lt;/p&gt;
&lt;p&gt;The code used in this post is located in &lt;a href=&quot;https://gitlab.com/ascodenl/gitlab-runner-aks&quot;&gt;https://gitlab.com/ascodenl/gitlab-runner-aks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The great thing about using Managed Identities in Azure is that they cannot be (ab)used to run elsewhere, like Service Principals can. Yes, you can use workload identities conditional access policies nowadays, but SP&#39;s are basically just another username/password combination that can be abused for other purposes than they were meant for. Managed Identities are a special type of Service Principal (which is an Azure Enterprise Application Registration under the hood) that can only be used when attached to an Azure resource. When connected to a Kubernetes service account, the managed identity permissions can be assumed by the pod that has that service account attached. This allows for fine-grained permissions for specific purposes, for specific Gitlab runners. For example, a runner that is purpose-built for building container images that pushes to Azure Container Registry - using a Managed Identity that only has the &lt;code&gt;AcrPush&lt;/code&gt; permission on the ACR(s) it needs to push to.&lt;/p&gt;
&lt;h2 id=&quot;the-case-for-self-hosted-gitlab-runners&quot;&gt;The case for self-hosted Gitlab Runners&lt;/h2&gt;
&lt;p&gt;&amp;quot;Why not use the Gitlab provided runners&amp;quot;? I hear that quite often. In summary, self-hosted GitLab runners are ideal when you need maximum control, security, and flexibility for your CI/CD jobs, want to optimize costs at scale, or have specific compliance and infrastructure requirements that (what Gitlab calls) &lt;code&gt;Instance runners&lt;/code&gt; cannot meet. Yes, it requires maintenance but it outweighs the added benefits - especially in environments with heavy compliance requirements like the Financial industry.&lt;/p&gt;
&lt;p&gt;Slightly more detailed, for several reasons:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Full Control Over the Build Environment&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Self-hosted runners give complete control over the (virtual) hardware, operating system, and software installed on the machines (in our case, Kubernetes) running CI/CD jobs. This allows you to customize environments to match your production setup, install proprietary or legacy tools, and fine-tune performance for specific workloads.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Security and Compliance&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;By running jobs on your own infrastructure, you can ensure sensitive code and data never leave your network. This is especially important for organizations with strict compliance, data residency, or privacy requirements, such as those in the public sector.&lt;/li&gt;
&lt;li&gt;Self-hosted runners can be placed behind firewalls or VPNs, further reducing exposure to potential threats.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Cost Efficiency and Scalability&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For teams with high CI/CD usage, self-hosted runners can be more cost-effective than paying for shared or cloud-hosted runners, especially at scale. You avoid per-minute billing and can utilize existing cloud resources as needed.&lt;/li&gt;
&lt;li&gt;You can scale your runners as your needs grow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Performance and Flexibility&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Self-hosted runners can be optimized for your specific workloads, providing faster builds and more reliable performance than shared runners.&lt;/li&gt;
&lt;li&gt;You can run jobs on specialized hardware (e.g., GPUs, large-memory machines) or in specific environments (e.g., on-premises, in particular cloud regions) that aren&#39;t available with GitLab-hosted runners.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Advanced Customization&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can create custom caching strategies, use local Docker registries, or integrate with internal systems and services that aren&#39;t accessible from public runners.&lt;/li&gt;
&lt;li&gt;Self-hosted runners allow for advanced monitoring, logging, and debugging, giving greater visibility into CI/CD processes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;types-of-runners&quot;&gt;Types of runners&lt;/h2&gt;
&lt;p&gt;Gitlab has 3 types of runners:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instance runners: Shared runners that are built, deployed and managed by Gitlab for generic use.&lt;/li&gt;
&lt;li&gt;Group runners: Deployed on Gitlab group level, inherited by all repositories (projects) that are part of that group. Self-hosted and self-
managed.&lt;/li&gt;
&lt;li&gt;Project runners: Deployed on a single repository. Self-hosted and self-managed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;types-of-runtime-environments&quot;&gt;Types of runtime environments&lt;/h2&gt;
&lt;p&gt;When registering a GitLab runner, you must select an &lt;strong&gt;executor&lt;/strong&gt;, which determines the environment in which your CI/CD jobs will run. Each executor offers different levels of isolation, scalability, and compatibility, making them suitable for various scenarios.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Executor&lt;/th&gt;
&lt;th&gt;Isolation&lt;/th&gt;
&lt;th&gt;Typical Use Case&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shell&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Simple, local jobs&lt;/td&gt;
&lt;td&gt;Easy, minimal setup&lt;/td&gt;
&lt;td&gt;Low isolation, less secure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Reproducible, isolated builds&lt;/td&gt;
&lt;td&gt;Clean, scalable, supports services&lt;/td&gt;
&lt;td&gt;Needs Docker, some limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Autoscaler&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Scalable cloud builds&lt;/td&gt;
&lt;td&gt;Auto-scales, cloud support&lt;/td&gt;
&lt;td&gt;Complex setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instance&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Full VM per job, high isolation&lt;/td&gt;
&lt;td&gt;Max isolation, flexibility&lt;/td&gt;
&lt;td&gt;Resource intensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Cloud-native, Kubernetes environments&lt;/td&gt;
&lt;td&gt;Scalable, cloud integration&lt;/td&gt;
&lt;td&gt;Needs Kubernetes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Remote, legacy, or custom environments&lt;/td&gt;
&lt;td&gt;Remote execution&lt;/td&gt;
&lt;td&gt;Limited support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VirtualBox/Parallels&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;VM-based isolation on local hardware&lt;/td&gt;
&lt;td&gt;Good isolation&lt;/td&gt;
&lt;td&gt;Slower, needs virtualization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Anything not covered above&lt;/td&gt;
&lt;td&gt;Flexible&lt;/td&gt;
&lt;td&gt;Requires custom scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Choosing the right executor depends on your project&#39;s requirements for isolation, scalability, environment, and available infrastructure&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Our default platform of choice is Kubernetes, this article covers the Azure implementation of Kubernetes called &lt;strong&gt;Azure Kubernetes Service (AKS)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Creating infrastructure in Azure (or any environment for that matter) is done using Infrastructure as Code (IaC). The tool of choice is Terraform or Tofu, whatever your preference. The idea is to let a CI/CD pipeline handle the creating, updating and destruction of Azure resources, using Gitlab Runners on AKS. For this, we need several resources to make that happen.&lt;/p&gt;
&lt;p&gt;Here is a quick overview of what we are building:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://ascode.nl/blog/gitlab-runners-aks/dXHmWkf9cF-4588.avif 4588w&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://ascode.nl/blog/gitlab-runners-aks/dXHmWkf9cF-4588.webp 4588w&quot;&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ascode.nl/blog/gitlab-runners-aks/dXHmWkf9cF-4588.png&quot; alt=&quot;Gitlab runners AKS&quot; width=&quot;4588&quot; height=&quot;1484&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;azure-configuration&quot;&gt;Azure configuration&lt;/h2&gt;
&lt;p&gt;The runner will use a Kubernetes Service account, which is &amp;quot;connected&amp;quot; to an Azure Managed Identity, that will be assigned roles with permissions to create resources in Azure. You will need an AKS cluster with OIDC issuer enabled. Read &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/aks/use-oidc-issuer&quot;&gt;here&lt;/a&gt; how to enable if not configured yet.&lt;/p&gt;
&lt;p&gt;The Managed Identity is created as a separate resource outside of any Terraform Module. The main reason for this is, we use RBAC to assign permissions on Azure Resources. If you want to allow a Managed Identity access to Entra ID (to read groups, primarily), assigning permissions in Entra ID requires elevated privileges that we do not want to delegate to a Managed Identity. To prevent (re)creation of a Managed Identity as part of a module, we create it separately.&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;locals&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;runners&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;tf&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;environment&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ss&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;environment_long&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shared&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;runner_type&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;group_type&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;runner_tags&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;azure&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;platform&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;ss&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;repo_path&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ascodenl/infra&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;run_privileged&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;purpose&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Runner for deploying Terraform resources in the Azure Landing Zone using Gitlab&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# view more in the complete command sequence&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to see complete Terraform code&lt;/strong&gt;&lt;/summary&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;locals&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;runners&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;tf&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;environment&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ss&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;environment_long&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shared&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;runner_type&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;group_type&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;runner_tags&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;azure&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;platform&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;ss&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;repo_path&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ascodenl/infra&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;run_privileged&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;purpose&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Runner for deploying Terraform resources in the Azure Landing Zone using Gitlab&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;packer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;environment&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ss&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;environment_long&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shared&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;runner_type&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;project_type&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;runner_tags&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;infra-packer-azure&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;ss&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;repo_path&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ascodenl/infra/base-images/azure&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;run_privileged&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;purpose&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Gitlab runner for deploying Custom images to Shared Imaged Gallery using Packer in Azure&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;azurerm_user_assigned_identity&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;for_each&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; local.runners
  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;                &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;region_code&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environment&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-msi-gitlab-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;resource_group_name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; azurerm_resource_group.gitlab&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;each.value.region_code&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
  &lt;span class=&quot;token property&quot;&gt;location&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; azurerm_resource_group.gitlab&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;each.value.region_code&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.location
  &lt;span class=&quot;token property&quot;&gt;tags&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; merge(local.tags, &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;purpose&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; each.value.purpose
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;)
  &lt;span class=&quot;token keyword&quot;&gt;lifecycle&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;ignore_changes&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;tags&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;CreatedOnDateTime&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;##&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;## Create the MSGraph application&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;##&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;data &lt;span class=&quot;token type variable&quot;&gt;&quot;azuread_application_published_app_ids&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;well_known&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;azuread_service_principal&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;msgraph&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;client_id&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph
  &lt;span class=&quot;token property&quot;&gt;use_existing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;##&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;## Assign the Permissions in Entra ID&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;##&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;azuread_app_role_assignment&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_api_permissions&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;for_each&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; toset(&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Group.Read.All&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;User.Read.All&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;GroupMember.Read.All&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;Directory.Read.All&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;)
  &lt;span class=&quot;token property&quot;&gt;app_role_id&lt;/span&gt;         &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; azuread_service_principal.msgraph.app_role_ids&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;each.key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;principal_object_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; azurerm_user_assigned_identity.gitlab_runner&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tf&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.principal_id
  &lt;span class=&quot;token property&quot;&gt;resource_object_id&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; azuread_service_principal.msgraph.object_id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;##&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;## Create a federated credential&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;##&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;azurerm_federated_identity_credential&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;for_each&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; local.runners
  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;                &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ascode-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;environment_long&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-msi-gitlab-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-cred&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;resource_group_name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; azurerm_resource_group.gitlab.name
  &lt;span class=&quot;token property&quot;&gt;audience&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;api://AzureADTokenExchange&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;issuer&lt;/span&gt;              &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; module.aks.oidc_issuer_url
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt;           &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; azurerm_user_assigned_identity.gitlab_runner&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;each.key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.id
  &lt;span class=&quot;token property&quot;&gt;subject&lt;/span&gt;             &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;system:serviceaccount:&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;runners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;k8s_sa_namespace&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;runners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;k8s_sa_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;##&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;## Assign permissions in Azure&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;##&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# MSI is owner on all subscriptions&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;azurerm_role_assignment&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner_platform_owner&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;for_each&lt;/span&gt;             &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; local.subscription_id
  &lt;span class=&quot;token property&quot;&gt;scope&lt;/span&gt;                &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/subscriptions/&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;role_definition_name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Owner&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;principal_id&lt;/span&gt;         &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; azurerm_user_assigned_identity.gitlab_runner&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tf&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.principal_id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Note that we make the MSI specific to deploying Terraform resources (&lt;code&gt;azurerm_user_assigned_identity.gitlab_runner[&amp;quot;tf&amp;quot;].principal_id&lt;/code&gt;) owner of the subscriptions. Contributor is not going to be enough as we also want to use the pipeline to do role assignments and RBAC, therefor it needs owner permissions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Warning:&lt;/strong&gt; This means that this MSI has very powerful privileges! Make sure you lock down your pipelines so that not just anyone can run them and make sure you do proper Merge Requests and code reviews!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;kubernetes-resources&quot;&gt;Kubernetes resources&lt;/h2&gt;
&lt;p&gt;Remember we are using Kubernetes as the Gitlab Executor. What we are deploying is what can be described as a &amp;quot;runner manager&amp;quot;, which will spin off containers (pods, actually) that will run the pipeline. Once the pipeline is finished, the pod is destroyed.&lt;/p&gt;
&lt;p&gt;The Gitlab Runner is deployed using Helm. Gitlab maintains a Helm chart that you can find on &lt;a href=&quot;https://gitlab.com/gitlab-org/charts/gitlab-runner/&quot;&gt;https://gitlab.com/gitlab-org/charts/gitlab-runner/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Gitlab Runner configuration is done in a &lt;code&gt;config.toml&lt;/code&gt; file that we deploy using a template.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;gitlabUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;${gitlab_url}&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;unregisterRunners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3600&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;concurrent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;checkInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;serviceAccountName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;rbac_service_account_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# view more in the complete configuration&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to see GitLab Runner Helm Values Configuration&lt;/strong&gt;&lt;/summary&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;gitlabUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;${gitlab_url}&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;unregisterRunners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;terminationGracePeriodSeconds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3600&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;concurrent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;checkInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;serviceAccountName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;rbac_service_account_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;rbac&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;serviceAccountAnnotations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;azure.workload.identity/client-id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;client_id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;azure.workload.identity/tenant-id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;tenant_id&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;metrics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;runners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
    [[runners]]
      [runners.kubernetes]
        pod_labels_overwrite_allowed = &quot;.*&quot;
        privileged = ${run_privileged}
        service_account = &quot;${service_account_name}&quot;
        namespace = &quot;${runner_namespace}&quot;
      [runners.kubernetes.pod_labels]
        &quot;azure.workload.identity/use&quot; = &#39;&quot;true&quot;&#39;
      [[runners.kubernetes.volumes.empty_dir]]
        name = &quot;docker-certs&quot;
        mount_path = &quot;/certs/client&quot;
        medium = &quot;Memory&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;${gitlab_name}&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;${gitlab_docker_image}&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;privileged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;run_privileged&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;serviceAccountName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;service_account_name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;pull_policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always
  &lt;span class=&quot;token key atrule&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;${runner_secret}&quot;&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;securityContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;runAsUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;deploymentLabels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;deployment_repository&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;deployment_repo_path&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;podLabels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;azure.workload.identity/use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;Then we use Terraform to tranform the template and deploy the Helm chart:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;locals&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;gitlab_runner_vars&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;gitlab_url&lt;/span&gt;                &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.gitlab_url
    &lt;span class=&quot;token property&quot;&gt;gitlab_docker_image&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.gitlab_docker_image
    &lt;span class=&quot;token property&quot;&gt;gitlab_name&lt;/span&gt;               &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-k8-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;runner_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# view more in the complete chart&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to see Terraform deploy Helm Chart&lt;/strong&gt;&lt;/summary&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;locals&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;gitlab_runner_vars&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;gitlab_url&lt;/span&gt;                &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.gitlab_url
    &lt;span class=&quot;token property&quot;&gt;gitlab_docker_image&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.gitlab_docker_image
    &lt;span class=&quot;token property&quot;&gt;gitlab_name&lt;/span&gt;               &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-k8-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;runner_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;run_privileged&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.run_privileged
    &lt;span class=&quot;token property&quot;&gt;service_account_name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_service_account_v1.gitlab_runner.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
    &lt;span class=&quot;token property&quot;&gt;rbac_service_account_name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_service_account_v1.gitlab_runner.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
    &lt;span class=&quot;token property&quot;&gt;runner_namespace&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_namespace_v1.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
    &lt;span class=&quot;token property&quot;&gt;client_id&lt;/span&gt;                 &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.mi_client_id
    &lt;span class=&quot;token property&quot;&gt;tenant_id&lt;/span&gt;                 &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.tenant_id
    &lt;span class=&quot;token property&quot;&gt;runner_secret&lt;/span&gt;             &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_secret_v1.gitlab_runner.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
    &lt;span class=&quot;token property&quot;&gt;deployment_repo_path&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.deployment_repo_path
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;helm_release&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;chart&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab-runner&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;             &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;customer_tla&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;runner_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;namespace&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_namespace_v1.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
  &lt;span class=&quot;token property&quot;&gt;repository&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://charts.gitlab.io&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;version&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.gitlab_runner_helm_release_version
  &lt;span class=&quot;token property&quot;&gt;create_namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    templatefile(&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;/templates/runner-values.yml.tpl&quot;&lt;/span&gt;, local.gitlab_runner_vars)
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;p&gt;To check for the latest version(s) of the chart:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;helm repo &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; gitlab-runner https://charts.gitlab.io
helm repo update
helm search repo-l gitlab/gitlab-runner &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-5&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The last part is to create the Kubernetes Service Account that &amp;quot;glues&amp;quot; the Managed Identity to the Gitlab Runner:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;kubernetes_service_account_v1&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab-runner-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;random_id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_namespace_v1.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
    &lt;span class=&quot;token property&quot;&gt;annotations&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;azure.workload.identity/client-id&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.msi_client_id
      &lt;span class=&quot;token property&quot;&gt;&quot;azure.workload.identity/tenant-id&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.tenant_id
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;labels&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;azure.workload.identity/use&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;true&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Finally some required Kubernetes resources to make this all work (I use &lt;code&gt;rbac.create = false&lt;/code&gt; in the config.toml because I like to be in control of what is created. Setting this to true means you have to annotate the Service Account with the correct values as it gets auto-created).&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;kubernetes_namespace_v1&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;random_id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# view more in the complete Kubernetes resources&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to see Terraform Kubernetes resources&lt;/strong&gt;&lt;/summary&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;kubernetes_namespace_v1&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;random_id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;kubernetes_role&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab-runner-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;random_id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_namespace_v1.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;rule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;api_groups&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;resources&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;configmaps&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;pods&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;pods/attach&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;secrets&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;services&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;namespaces&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;verbs&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;get&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;list&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;watch&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;create&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;patch&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;update&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;rule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;api_groups&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;resources&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pods/exec&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;verbs&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;create&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;patch&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;delete&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;rule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;api_groups&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;resources&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;serviceAccounts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;verbs&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;get&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;kubernetes_role_binding_v1&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab-runner-binding-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;random_id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_namespace_v1.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;role_ref&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;api_group&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rbac.authorization.k8s.io&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;kind&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Role&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_role.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;kind&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ServiceAccount&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_service_account_v1.gitlab_runner.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
    &lt;span class=&quot;token property&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_namespace_v1.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;kubernetes_cluster_role_binding_v1&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;runner_admin&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab-runner-admin-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;random_id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hex&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;role_ref&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;api_group&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rbac.authorization.k8s.io&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;kind&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ClusterRole&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cluster-admin&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;kind&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ServiceAccount&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_service_account_v1.gitlab_runner.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
    &lt;span class=&quot;token property&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_namespace_v1.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h2 id=&quot;runner-registration&quot;&gt;Runner registration&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;strong&gt;Note:&lt;/strong&gt; This article describes the new way of registering runners. Please see https://docs.gitlab.com/ci/runners/new_creation_workflow/ how to migrate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When deploying a runner, it needs to be registered against a group or repository. Each group or repository has its own unique token. You can do this from the &lt;strong&gt;CI/CD settings&lt;/strong&gt; of the repository or group, create a project or group runner, fill in the details and out comes a registration token. But who wants do do manual? Let&#39;s automate this.&lt;/p&gt;
&lt;p&gt;Terraform has a great provider for Gitlab, found on &lt;a href=&quot;https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs&quot;&gt;https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs&lt;/a&gt;. You can use it to fully automate your Gitlab environment, including repositories, groups, authorizations, integrations, etc. We&#39;ll focus on the &lt;a href=&quot;https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/user_runner&quot;&gt;gitlab_user_runner&lt;/a&gt; resource to get the registration token.&lt;/p&gt;
&lt;p&gt;Each group or project in Gitlab has a unique id, whiich is hard to find and even harder to remember. We use the path to find the id, which is a lot easier to remember. If you use Terraform to also create your groups and projects, you can even reference the Terraform resource!&lt;/p&gt;
&lt;p&gt;Terraform provider configuration is required for Gitlab and Kubernetes (the registration token is stored in a Kubernetes secret):&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;provider&lt;span class=&quot;token type variable&quot;&gt; &quot;kubernetes&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;config_path&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;~/.kube/config&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Need to create this file from the pipeline or run locally&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;provider&lt;span class=&quot;token type variable&quot;&gt; &quot;gitlab&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;base_url&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://gitlab.com/&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;token&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; data.azurerm_key_vault_secret.gitlab_token.value &lt;span class=&quot;token comment&quot;&gt;# this can be a Group token or a PAT token with the create_runner scope&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;First, we need to determine if we are deploying a group runner or a project runner:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;data &lt;span class=&quot;token type variable&quot;&gt;&quot;gitlab_group&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;group&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;group_type&quot;&lt;/span&gt; ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;full_path&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.repo_path
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;data &lt;span class=&quot;token type variable&quot;&gt;&quot;gitlab_project&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;project&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt;               &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;project_type&quot;&lt;/span&gt; ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;path_with_namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.repo_path
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;repo_path&lt;/code&gt; is the path to your repo, for example &lt;code&gt;ascodenl/infra/tools&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then, dependent on what type of runner you want, create a token and store it in a Kubernetes secret. Note the reference to a Kubernetes Service Account, this will become clear later on.&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;gitlab_user_runner&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner_project&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;project_type&quot;&lt;/span&gt; ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;runner_type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type
  &lt;span class=&quot;token property&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;kubernetes_service_account_v1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gitlab_runner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;region_code&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-aks&quot;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# view more code below&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;details&gt;
&lt;summary&gt;&lt;strong&gt;Click to see Terraform Runner Token Creation&lt;/strong&gt;&lt;/summary&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;gitlab_user_runner&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner_project&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;project_type&quot;&lt;/span&gt; ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;runner_type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type
  &lt;span class=&quot;token property&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;kubernetes_service_account_v1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gitlab_runner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;region_code&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-aks&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;project_id&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; data.gitlab_project.project&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.id
  &lt;span class=&quot;token property&quot;&gt;tag_list&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; flatten(&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;var.region_code&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;, &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;var.runner_name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;, var.runner_tags&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;)
  &lt;span class=&quot;token property&quot;&gt;untagged&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.run_untagged
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;gitlab_user_runner&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner_group&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;group_type&quot;&lt;/span&gt; ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;runner_type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type
  &lt;span class=&quot;token property&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;kubernetes_service_account_v1&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gitlab_runner&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;region_code&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-aks&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;group_id&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; data.gitlab_group.group&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.id
  &lt;span class=&quot;token property&quot;&gt;tag_list&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; flatten(&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;var.region_code&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;, &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;var.runner_name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;, var.runner_tags&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;)
  &lt;span class=&quot;token property&quot;&gt;untagged&lt;/span&gt;    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.run_untagged
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;kubernetes_secret_v1&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gitlab_runner&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;metadata&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_service_account_v1.gitlab_runner.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
    &lt;span class=&quot;token property&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; kubernetes_namespace_v1.gitlab.metadata&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.name
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Opaque&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;runner-registration-token&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# need to leave as an empty string for compatibility reasons&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;runner-token&lt;/span&gt;              &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.runner_type &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;project_type&quot;&lt;/span&gt; ? gitlab_user_runner.gitlab_runner_project&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.token : gitlab_user_runner.gitlab_runner_group&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;.token
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/details&gt;
&lt;h2 id=&quot;using-this-in-a-gitlab-pipeline&quot;&gt;Using this in a Gitlab pipeline&lt;/h2&gt;
&lt;p&gt;Now that the runner is deployed with the proper permissions, it is time to create a pipeline to implement this in CI/CD.&lt;/p&gt;
&lt;p&gt;Creating a full multi environment pipline is enough for a separate blog post, so here is the most important part:&lt;/p&gt;
&lt;div style=&quot;position: relative;&quot;&gt;
&lt;button onclick=&quot;navigator.clipboard.writeText(this.nextElementSibling.querySelector(&#39;code&#39;).textContent).then(() =&gt; this.textContent = &#39;Copied!&#39;).catch(() =&gt; alert(&#39;Copy failed - please select and copy manually&#39;))&quot; style=&quot;position: absolute; top: 10px; right: 10px; background: #007acc; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px;&quot;&gt;Copy&lt;/button&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;before_script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
    if ! [ -x &quot;$(command -v az)&quot; ]; then
      echo -e &quot;&#92;e[33mWarn: az is not installed.&#92;e[0m&quot;
      exit 1
    else
      echo &quot;Logging in to Azure using client_id $AZURE_CLIENT_ID...&quot;
      az login --service-principal -u $AZURE_CLIENT_ID --tenant $AZURE_TENANT_ID --federated-token $(cat $AZURE_FEDERATED_TOKEN_FILE)
      if [[ ! -z ${ARM_SUBSCRIPTION_NAME} ]]; then az account set -n ${ARM_SUBSCRIPTION_NAME}; fi
      export ARM_OIDC_TOKEN=$(cat $AZURE_FEDERATED_TOKEN_FILE)
      export ARM_CLIENT_ID=$AZURE_CLIENT_ID
      export ARM_TENANT_ID=$AZURE_TENANT_ID
    fi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If OIDC is working correctly, the Azure token is stored in a file that is referenced in &lt;code&gt;$AZURE_FEDERATED_TOKEN_FILE&lt;/code&gt; which usually points to &lt;code&gt;/var/run/secrets/azure/tokens/azure-identity-token&lt;/code&gt;. Enabling OIDC on AKS deploys something called a &amp;quot;Mutating Admission Webhook&amp;quot; which takes care of getting a token that has a limited lifetime, and refreshes the token on expiration. If you are interested in how this works under the hood, look &lt;a href=&quot;https://azure.github.io/azure-workload-identity/docs/installation/self-managed-clusters/oidc-issuer.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope you enjoyed this, thanks for sticking around till the end. Until the next!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>New Website, refactored</title>
    <link href="https://ascode.nl/blog/new-website-2025/" />
    <updated>2025-05-04T00:00:00Z</updated>
    <id>https://ascode.nl/blog/new-website-2025/</id>
    <content type="html">&lt;p&gt;Having neglected our web site for a long time, I read a post on X of someone that used &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; to create his web site. Being a guy that alwys likes to try out new technology, I thought I&#39;d give it a go. Our previous website was created with Hugo, which is also great but still required a lot of tweaking to get right. Since I am not a web developer by trade, reading up on Eleventy seemed like it could make the job easier.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;I used the blog template available on &lt;a href=&quot;https://github.com/11ty/eleventy-base-blog&quot;&gt;Github&lt;/a&gt; to get started, cloned it into my local git environment and started customizing it to the Ascode look and feel. One if the nice features of Eleventy (same with Hugo) is that you can run a local web server using &lt;code&gt;eleventy --serve&lt;/code&gt; that starts a web server on http://localhost:8081 together with a file system watcher that once a change is made to a file, immediately processes the change and makes your chanegs visible through the local web server. This is great for immediate feedback on the result of the changes that were made.&lt;/p&gt;
&lt;p&gt;There are a bunch of &lt;a href=&quot;https://www.11ty.dev/docs/plugins/community/&quot;&gt;plugins&lt;/a&gt; available, we wanted to get up and running quickly so I kept the first version really simple.&lt;/p&gt;
&lt;h2 id=&quot;hosting-on-cloudflare-pages&quot;&gt;Hosting on Cloudflare Pages&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://ascode.nl/blog/new-website-2025/img/new-startup-new-website.md&quot;&gt;previous web site&lt;/a&gt; was a really nice project to test out the serverless features of AWS, but it involved deploying and maintaining quite a bit of resources so we decided to keep it really simple this time and host the web site with Cloudflare Pages.&lt;/p&gt;
&lt;p&gt;You can generate the static content for upload to Cloudflare by running the command &lt;code&gt;npx @11ty/eleventy&lt;/code&gt; which generates all required files and stores them by default in the &lt;code&gt;_site&lt;/code&gt; folder.
Then, upload the contents of the _site folder to Cloudflare (in the Compute (Workers)/Workers &amp;amp; Pages section). The name you give the project is the name that is used to generate the default Cloudflare Pages url, in this case the name is &lt;code&gt;ascode-web&lt;/code&gt; which automatically generates the url https://ascode-web.pages.dev/. That&#39;s it for basics, pretty simple - love it.&lt;/p&gt;
&lt;p&gt;The first go for me resulted in none of the pictures being visible, they contained broken links. After some troubleshooting I found the --serve option does not clean up after itself; after removing the local _site folder and re-running &lt;code&gt;eleventy --serve&lt;/code&gt; all was looking fine again.&lt;/p&gt;
&lt;p&gt;Here is the documentation for the image plugin that might help you out: https://www.11ty.dev/docs/plugins/image/&lt;/p&gt;
&lt;h3 id=&quot;setting-a-custom-domain&quot;&gt;Setting a custom domain&lt;/h3&gt;
&lt;p&gt;We are hosting the ascode.nl at Cloudflare which makes it super-easy to add the custom domain &lt;code&gt;ascode.nl&lt;/code&gt; to Cloudflare Pages. Just add the custom domain on the Workers &amp;amp; Pages page and Cloudflare will take care of creating or updating the required DNS record, and generate an SSL certificate with the same name. Keep in mind that &lt;code&gt;ascode.nl&lt;/code&gt; and &lt;code&gt;www.ascode.nl&lt;/code&gt; are 2 separate custom domains in the Pages sense and need to be generated seperately.&lt;/p&gt;
&lt;h2 id=&quot;automating-deployments&quot;&gt;Automating deployments&lt;/h2&gt;
&lt;p&gt;When I find some more time...&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Shared VPC Subnet tagging</title>
    <link href="https://ascode.nl/blog/shared-vpc-subnet-tagging/" />
    <updated>2023-11-06T00:00:00Z</updated>
    <id>https://ascode.nl/blog/shared-vpc-subnet-tagging/</id>
    <content type="html">&lt;p&gt;&lt;em&gt;This post was previously published on https://www.lionsville.nl/blog/shared-vpc-subnet-tagging. Since it is no longer there, it is re-published on this site.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One of the services we provide for our customers is the deployment and management of an AWS Landing Zone. Part of the Landing Zone is a centralized networking environment that is used as the centralized VPC for ingress and egress network traffic. VPC sharing is used, implementation varies, for this article we implement a shared VPC per AWS account with 3 subnets per VPC.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ascode.nl/img/shared_subnets-1.png&quot; alt=&quot;&amp;quot;High level overview&amp;quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;vpc-sharing-overview&quot;&gt;VPC Sharing overview&lt;/h2&gt;
&lt;p&gt;Each AWS Account gets its own VPC with 3 type of subnets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Isolated&lt;/li&gt;
&lt;li&gt;Private&lt;/li&gt;
&lt;li&gt;TransitGateway&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With 1 subnet per Availability Zone, a total of 9 subnets are created. The subnets are then shared with Resource Access Manager (RAM) with the Team account.&lt;/p&gt;
&lt;p&gt;For the sake of simplicity, this article will only focus on the subnet sharing part of this implementation. One of the big downsides of RAM is that the shared resource in the Team acccount does not inherit tags. Especially for implementations like EKS or other AWS services that rely on tagging, this is a problem. As part of the VPC deployment in the Shared Network account, several tags are created of which a few of them we would like to make available in the Team account. The tags that are discussed in this post are Name, AccountID, subnetType and aws-cdk:subnet-type.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ascode.nl/img/shared_subnets-2.png&quot; alt=&quot;&amp;quot;VPC Sharing overview&amp;quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;process&quot;&gt;Process&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;EventBridge detects an &lt;strong&gt;AssociateResourceShare&lt;/strong&gt; Cloudwatch Event.&lt;/li&gt;
&lt;li&gt;EventBridge triggers the Lambda function with the Cloudwatch event json as input.&lt;/li&gt;
&lt;li&gt;The Lambda function looks up the subnet-id that is in the body of the Cloudwatch event and grabs the tags that are configured on that subnet.&lt;/li&gt;
&lt;li&gt;The Lambda function assumes a role in the team account.&lt;/li&gt;
&lt;li&gt;The Lambda function updates the tags on the subnets in the team account.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The process in the will be discussed in more detail, using code examples (Terraform and Lambda). The code can be found &lt;a href=&quot;https://gitlab.com/ascodenl/vpc-subnet-tagger&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want Eventbridge to be able to record events with a &lt;code&gt;detail-type&lt;/code&gt; value of &lt;code&gt;AWS API Call via CloudTrail&lt;/code&gt;, a CloudTrail trail with logging enabled is required.
The event that Eventbridge is configured to look for is the &lt;strong&gt;AssociateResourceShare&lt;/strong&gt; event. Check the &lt;a href=&quot;https://docs.aws.amazon.com/ram/latest/APIReference/API_AssociateResourceShare.html&quot;&gt;API Documentation&lt;/a&gt; for the different events that are available for RAM.
The target for the event is a Lambda function that was created by &lt;a href=&quot;https://www.linkedin.com/in/markusmuhr&quot;&gt;Markus Muhr&lt;/a&gt;. Two resources are created, an event rule and an event target.&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_cloudwatch_event_rule&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ram&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;        &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;capture-ram-creation&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Capture creation of RAM shares&quot;&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;event_pattern&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; jsonencode(&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;source&quot;&lt;/span&gt; : &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;aws.ram&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token property&quot;&gt;&quot;detail-type&quot;&lt;/span&gt; : &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;AWS API Call via CloudTrail&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
    &lt;span class=&quot;token property&quot;&gt;&quot;detail&quot;&lt;/span&gt; : &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;eventSource&quot;&lt;/span&gt; : &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ram.amazonaws.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,
      &lt;span class=&quot;token property&quot;&gt;&quot;eventName&quot;&lt;/span&gt; : &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;AssociateResourceShare&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;)
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_cloudwatch_event_target&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lambda&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;target_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lambda-function-target&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;rule&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws_cloudwatch_event_rule.ram.name
  &lt;span class=&quot;token property&quot;&gt;arn&lt;/span&gt;       &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws_lambda_alias.tagger_alias.arn
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;lambda-function&quot;&gt;Lambda function&lt;/h3&gt;
&lt;p&gt;The Lambda function contains several functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lookup the shared subnet&lt;/li&gt;
&lt;li&gt;assume the role&lt;/li&gt;
&lt;li&gt;tag the team subnet&lt;/li&gt;
&lt;li&gt;the main Lambda event handler&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://gitlab.com/ascodenl/vpc-subnet-tagger/-/blob/main/lambda/subnet-tagger.py?ref_type=heads&quot;&gt;example&lt;/a&gt; shows a simplified version of the actual function that was implemented, it should however be easy to extend upon this code.&lt;/p&gt;
&lt;h3 id=&quot;iam-role-in-team-account-s&quot;&gt;IAM role in Team account(s)&lt;/h3&gt;
&lt;p&gt;The team account requires an IAM role to be created that can be assumed by the Lambda function in order to create the tags. This is a very simple role, which is created using &lt;a href=&quot;https://github.com/org-formation/org-formation-cli&quot;&gt;org-formation&lt;/a&gt; In short, in order to use Terraform several resources like an S3 bucket for state backend and IAM roles are required. Org-formation uses Cloudformation under the hood for creating the resources that are required for Terraform and resources that need to be created in all accounts. When AWS Control Tower is not an option, we leverage org-formation as our account vending machine in AWS.
Note the &lt;code&gt;OrganizationBinding&lt;/code&gt; parameter, this is specific to org-formation to target or filter only specific accounts or OU&#39;s. If you don&#39;t use org-formation, omit this parameter.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;LambdaTaggerPolicy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AWS::IAM::ManagedPolicy&quot;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;OrganizationBinding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;!Ref&lt;/span&gt; TeamAccountBinding
  &lt;span class=&quot;token key atrule&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;ManagedPolicyName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; LambdaTaggerPolicy
    &lt;span class=&quot;token key atrule&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Policy to allow access from lz&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;net Lambda function
    &lt;span class=&quot;token key atrule&quot;&gt;PolicyDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token datetime number&quot;&gt;2012-10-17&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Allow
          &lt;span class=&quot;token key atrule&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ec2:CreateTags&quot;&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;Resource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;arn:aws:ec2:*:*:subnet/*&quot;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;arn:aws:ec2:*:*:vpc/*&quot;&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;Roles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;!Ref&lt;/span&gt; LambdaTaggerRole

&lt;span class=&quot;token key atrule&quot;&gt;LambdaTaggerRole&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AWS&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;IAM&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;Role
  &lt;span class=&quot;token key atrule&quot;&gt;OrganizationBinding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;!Ref&lt;/span&gt; TeamAccountBinding
  &lt;span class=&quot;token key atrule&quot;&gt;Properties&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;RoleName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; LambdaTaggerRole
    &lt;span class=&quot;token key atrule&quot;&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2012-10-17&quot;&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;Statement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Allow
          &lt;span class=&quot;token key atrule&quot;&gt;Principal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lambda.amazonaws.com
          &lt;span class=&quot;token key atrule&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sts:AssumeRole&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;Effect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Allow
          &lt;span class=&quot;token key atrule&quot;&gt;Principal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token key atrule&quot;&gt;AWS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;central network account ID&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;Action&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sts:AssumeRole&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s it! Even though the current Lambda is “only” used to tag resources, you can imagine this concept can be used for all sorts of implementations. Go nuts!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>AWS Account creation, fully automated - ascode style</title>
    <link href="https://ascode.nl/blog/aws-account-creation-automation/" />
    <updated>2020-03-04T09:58:02Z</updated>
    <id>https://ascode.nl/blog/aws-account-creation-automation/</id>
    <content type="html">&lt;h4 id=&quot;an-introduction-into-creating-accounts-in-aws-in-code-fully-automated-with-terraform&quot;&gt;An introduction into creating accounts in AWS, in code, fully automated with Terraform.&lt;/h4&gt;
&lt;hr&gt;
&lt;p&gt;We like AWS. In fact, it&#39;s our preferred public cloud provider. Why? Because AWS understands the need to automate things. We use Terraform for automating our cloud infrastructures, and Hashicorp usually has first day support for new services coming out of Amazon. If Terraform doesn&#39;t have support for something, you can always fall back to the AWS CLI (which can be called with Terraform using a null_resource). I was automating the AWS cost &amp;amp; usage report the other day, and found out there is no Athena support in the &amp;quot;aws_cur_report_definition&amp;quot; resource, so I had to fall back to the CLI to create the report definition, like so:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;null_resource&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;create_cur_report&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;provisioner&lt;span class=&quot;token type variable&quot;&gt; &quot;local-exec&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token heredoc string&quot;&gt;&amp;lt;&amp;lt;EOF
      AWS_DEFAULT_REGION=&quot;us-east-1&quot; aws cur put-report-definition --report-definition &#92;
      AdditionalArtifacts=[&#92;&quot;ATHENA&#92;&quot;],AdditionalSchemaElements=[&#92;&quot;RESOURCES&#92;&quot;],&#92;
      Compression=&#92;&quot;Parquet&#92;&quot;,Format=&#92;&quot;Parquet&#92;&quot;,RefreshClosedReports=true,&#92;
      ReportName=${var.report_name},ReportVersioning=&#92;&quot;OVERWRITE_REPORT&#92;&quot;,&#92;
      S3Bucket=${var.cf_bucket_name},S3Prefix=${var.s3_prefix},S3Region=${var.s3_region},&#92;
      TimeUnit=&#92;&quot;${var.time_unit}&#92;&quot;
      EOF&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The nice thing about using the null_resource is that you can use the variables that you have configured for your other resources, and use the output from other resources in your Terraform template.&lt;/p&gt;
&lt;p&gt;Anyway, I digress. Creating the cost &amp;amp; usage report is a good subject for another blog post. This post is about account creation automation.&lt;/p&gt;
&lt;h3 id=&quot;aws-organizations&quot;&gt;AWS Organizations&lt;/h3&gt;
&lt;p&gt;We use AWS Organizations to structure our AWS accounts. For every new client, we roll out a basic setup that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ascode.nl/img/AWSOrganizations.png&quot; alt=&quot;&amp;quot;AWS Organizations&amp;quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;By default, we create the following accounts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;svc: used for shared services that are used by the other accounts&lt;/li&gt;
&lt;li&gt;log: contains the centralised logs, only accessbile by a limited number of users&lt;/li&gt;
&lt;li&gt;DTAP: dev, tst, acc, prd&lt;/li&gt;
&lt;li&gt;sandbox: contains 1 or more sandbox accounts used for &amp;quot;playgrounds&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;This setup is sufficient to get a customer started in AWS. Because of AWS Organizations, consolidated billing is in place. Tagging is enforced so that resources (and therefore, costs) are identifiable by customer and/or environment. The Terraform code for this setup:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;data &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_organization&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;root&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;locals&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;dtap_accounts&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;dev&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;+-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-dev@&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_domain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;tst&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;+-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-tst@&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_domain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;acc&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;+-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-acc@&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_domain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;prd&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;+-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-prd@&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_domain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;core_accounts&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;log&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;+-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-log@&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_domain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;svc&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;+-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-svc@&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_domain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;sbx_accounts&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;sbx&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;+-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-sbx@&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_domain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_organizational_unit&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;customer_root&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.client_name
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; data.aws_organizations_organization.root.roots.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_organizational_unit&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;customer_dtap&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-environments&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws_organizations_organizational_unit.customer_root.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_organizational_unit&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;customer_sandbox&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-sandbox&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws_organizations_organizational_unit.customer_root.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_organizational_unit&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;customer_core&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-core&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws_organizations_organizational_unit.customer_root.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_account&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dtap&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;for_each&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? local.dtap_accounts : &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;email&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; each.value
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws_organizations_organizational_unit.customer_dtap.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_account&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;core&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;for_each&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? local.core_accounts : &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;email&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; each.value
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws_organizations_organizational_unit.customer_core.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_account&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sandbox&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;for_each&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? local.sbx_accounts : &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;client_short_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;each&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;email&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; each.value
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws_organizations_organizational_unit.customer_sandbox.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code is part of a module that we created for anything client-related. Creation is done when the variable &lt;code&gt;has_organizations&lt;/code&gt; is set to true. We did this so we can exclude this from testing the module, as creating AWS accounts is easy but removing them is not. When you remove the AWS account from Terraform, Terraform removes it from the state but does not actually delete the account - this must be done manually and can only be done after at least 7 days. When we test a module, we create and destroy the resources in a sandbox environment to make sure it works - doing this for AWS accounts would mean it would become a mess real soon with leftover accounts. Using the &lt;code&gt;has_organizations&lt;/code&gt; variable gives the added benefit that we can set this to false when a customer already has AWS Organizations in place, for example.&lt;/p&gt;
&lt;p&gt;The outputs that are defined look like this:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;output&lt;span class=&quot;token type variable&quot;&gt; &quot;customer_dtap_accounts_map&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;for environment, accountid in zipmap(keys(local.dtap_accounts), values(aws_organizations_account.dtap)&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;) : &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;environment&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; environment, &lt;span class=&quot;token property&quot;&gt;&quot;account_id&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; accountid &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; : null
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;output&lt;span class=&quot;token type variable&quot;&gt; &quot;customer_core_accounts_map&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;for environment, accountid in zipmap(keys(local.core_accounts), values(aws_organizations_account.core)&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;) : &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;environment&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; environment, &lt;span class=&quot;token property&quot;&gt;&quot;account_id&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; accountid &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; : null
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;output&lt;span class=&quot;token type variable&quot;&gt; &quot;customer_sandbox_accounts_map&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;for environment, accountid in zipmap(keys(local.sbx_accounts), values(aws_organizations_account.sandbox)&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;) : &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;environment&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; environment, &lt;span class=&quot;token property&quot;&gt;&quot;account_id&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; accountid &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; : null
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;output&lt;span class=&quot;token type variable&quot;&gt; &quot;customer_root_ou_id&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? aws_organizations_organizational_unit.customer_root.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id : &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;output&lt;span class=&quot;token type variable&quot;&gt; &quot;customer_dtap_ou_id&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? aws_organizations_organizational_unit.customer_dtap.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id : &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;output&lt;span class=&quot;token type variable&quot;&gt; &quot;customer_sandbox_ou_id&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? aws_organizations_organizational_unit.customer_sandbox.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id : &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;output&lt;span class=&quot;token type variable&quot;&gt; &quot;customer_core_ou_id&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? aws_organizations_organizational_unit.customer_core.&lt;span class=&quot;token number&quot;&gt;0.&lt;/span&gt;id : &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;output&lt;span class=&quot;token type variable&quot;&gt; &quot;all_account_ids_map&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.has_organizations ? &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;for environment, accountid in merge(zipmap(keys(local.dtap_accounts), values(aws_organizations_account.dtap)&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;), zipmap(keys(local.core_accounts), values(aws_organizations_account.core)&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;), zipmap(keys(local.sbx_accounts), values(aws_organizations_account.sandbox)&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;*&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;)) : &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;environment&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; environment, &lt;span class=&quot;token property&quot;&gt;&quot;account_id&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; accountid &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; : null
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What you see here is the Organizational Unit id&#39;s are configured as output so they can be used as parent_id when creating additional accounts. The account id&#39;s are configured as output so they can be used to automatically create the AWS CLI config, but I will get into that later on. For now, I will break up the export of the account id&#39;s into smaller pieces to explain what is done.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;value = var.has_organizations ?&lt;/code&gt; - the value is only exported if the variable &lt;code&gt;has_organizations&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt; (which is the default).&lt;br&gt;
&lt;code&gt;[for environment, accountid in zipmap(keys(local.dtap_accounts), values(aws_organizations_account.dtap)[*][&amp;quot;id&amp;quot;]) : { &amp;quot;environment&amp;quot; = environment, &amp;quot;account_id&amp;quot; = accountid }]&lt;/code&gt; - this links the environment name that is defined in locals to the account id that is created.&lt;br&gt;
&lt;code&gt;zipmap(keys(local.dtap_accounts), values(aws_organizations_account.dtap)[*][&amp;quot;id&amp;quot;])&lt;/code&gt; - this creates a map of environment with account_id. The result for the core accounts looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;svc&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;123456789&quot;&lt;/span&gt;,
    &lt;span class=&quot;token property&quot;&gt;&quot;log&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;123456788&quot;&lt;/span&gt;,
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, using the for loop &lt;code&gt;[for environment, accountid in zipmap(...) : { &amp;quot;environment&amp;quot; = environment, &amp;quot;account_id&amp;quot; = accountid }]&lt;/code&gt; a map is created that looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;account_id&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;123456788&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;environment&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;log&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;account_id&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;123456789&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;environment&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;svc&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All the outputs are merged together to get a nice overview of all the provisioned accounts.&lt;/p&gt;
&lt;h3 id=&quot;additional-aws-accounts&quot;&gt;Additional AWS accounts&lt;/h3&gt;
&lt;p&gt;So, we have the default setup in place. It could be possible that a client wants an additional account, for example an additional sandbox account or an isolated account for a high-secure application. We want the creation of this account to be automated, incorporated into AWS Organizations and set up with some default roles and resources that are required in each account.&lt;/p&gt;
&lt;p&gt;First, the account is created. It is possible to add the account to an existing Organizational Unit for AWS Organizations, like an additional sandbox account that goes into the sandbox OU. It is also possible to add an additional OU in which we then create the account. The code for the module looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_account&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;additional&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.create_account ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;customer_tla&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;aws_account_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;email&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.aws_account_email_address
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.aws_account_parent_id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;aws_organizations_organizational_unit&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ou&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.create_ou ? &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; : &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;      &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.ou_name
  &lt;span class=&quot;token property&quot;&gt;parent_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.aws_ou_parent_id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not too difficult, right? When you want to create an OU, &lt;code&gt;create_account&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; and &lt;code&gt;create_ou&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;, and vice versa.&lt;/p&gt;
&lt;p&gt;Using this in a module, with the example of creating an additional OU, looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;span class=&quot;token type variable&quot;&gt; &quot;new_aws_ou&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;source&lt;/span&gt;                    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;modules/aws/account&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;create_account&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;create_ou&lt;/span&gt;                 &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;ou_name&lt;/span&gt;                   &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;custom&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;aws_ou_parent_id&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; module.client_customer_name.customer_root_ou_id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;module&lt;span class=&quot;token type variable&quot;&gt; &quot;new_aws_account&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;source&lt;/span&gt;                    &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;modules/aws/account&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;customer_tla&lt;/span&gt;              &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.customer_tla
    &lt;span class=&quot;token property&quot;&gt;aws_account_name&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.additional_account_name
    &lt;span class=&quot;token property&quot;&gt;aws_account_email_address&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;+&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;customer_tla&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-{var.environment}@&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;email_domain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;aws_account_parent_id&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; module.new_aws_ou.organizational_unit_id
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a new OU named &lt;code&gt;custom&lt;/code&gt; with a new account created in this OU. Now we can create the AWS CLI config file with the output of all the resources that we created:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# can use specific config file with export AWS_CONFIG_FILE=/var/tmp/config-${var.customer_name} in CI/CD pipeline&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;resource &lt;span class=&quot;token type variable&quot;&gt;&quot;null_resource&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;aws_config&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;depends_on&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;module.client_customer_name, module.new_aws_account&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;provisioner&lt;span class=&quot;token type variable&quot;&gt; local-exec &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;working_dir&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/keybase/team/customer/customerbot/home/.aws&quot;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;command&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token heredoc string&quot;&gt;&amp;lt;&amp;lt;EOT
      echo &quot;##########################################################################&quot; &gt;| config-${var.customer_name}
      echo &quot;# AWS config file for customer ${var.customer_name}&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;[profile ${var.customer_name}-root-user]&#92;nregion=eu-west-1&#92;noutput=json&#92;n&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;# DTAP Accounts&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      %{for account in module.client_customer_name.customer_dtap_accounts_map}
        echo &quot;&#92;n[profile ${var.customer_name}-${account.environment}]&#92;nrole_arn=arn:aws:iam::${account.account_id}:role/AccountAssumeRole&#92;nsource_profile=${var.customer_name}-root-user&#92;nregion=eu-west-1&#92;noutput=json&#92;n&quot; &gt;&gt; config-${var.customer_name}
      %{endfor~}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;# Core Accounts&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      %{for account in module.client_${var.customer_name}.customer_core_accounts_map}
        echo &quot;&#92;n[profile ${var.customer_name}-${account.environment}]&#92;nrole_arn=arn:aws:iam::${account.account_id}:role/AccountAssumeRole&#92;nsource_profile=${var.customer_name}-root-user&#92;nregion=eu-west-1&#92;noutput=json&#92;n&quot; &gt;&gt; config-${var.customer_name}
      %{endfor~}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;# Sandbox Accounts&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      %{for account in module.client_${var.customer_name}.customer_sandbox_accounts_map}
        echo &quot;&#92;n[profile ${var.customer_name}-${account.environment}]&#92;nrole_arn=arn:aws:iam::${account.account_id}:role/AccountAssumeRole&#92;nsource_profile=${var.customer_name}-root-user&#92;nregion=eu-west-1&#92;noutput=json&#92;n&quot; &gt;&gt; config-${var.customer_name}
      %{endfor~}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;# Other Accounts&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;##########################################################################&quot; &gt;&gt; config-${var.customer_name}
      echo &quot;&#92;n[profile ${var.customer_name}-${module.new_aws_account.aws_account_id[&quot;environment&quot;]}]&#92;nrole_arn=arn:aws:iam::${module.new_aws_account.aws_account_id[&quot;account_id&quot;]}:role/AccountAssumeRole&#92;nsource_profile=${var.customer_name}-root-user&#92;nregion=eu-west-1&#92;noutput=json&#92;n&quot; &gt;&gt; config-${var.customer_name}
     EOT&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, we use Keybase with a bot account in the CI/CD pipeline that uses an AWS config file. I hate having to update this file every time a new account is created, so by using this resource the file is updated every time a new account is created! The (partial) result looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;##########################################################################&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# AWS config file for customer CUSTOMER&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;##########################################################################&lt;/span&gt;
&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;profile customer-root-user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;eu-west-1&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;json&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;##########################################################################&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# DTAP Accounts&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;##########################################################################&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;profile customer-acc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;role_arn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;arn:aws:iam::123456787:role/AccountAssumeRole&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;source_profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;customer-root-user&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;eu-west-1&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;json&lt;/span&gt;


&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;profile customer-dev&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;role_arn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;arn:aws:iam::123456786:role/AccountAssumeRole&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;source_profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;customer-root-user&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;eu-west-1&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;json&lt;/span&gt;
...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the file is updated, it can be used to provision resources in the new account. This is done by configuring a module that contains basic resources that we want deployed in all accounts. For example, Cloudtrail must be enabled, some IAM roles must be deployed, centralized logging must be set up and anything else that you can think of that you want to be part of your default AWS account rollout.&lt;/p&gt;
&lt;p&gt;First, we configure an AWS provider with an alias:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;provider&lt;span class=&quot;token type variable&quot;&gt; &quot;aws&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;region&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.aws_region
  &lt;span class=&quot;token property&quot;&gt;profile&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;customer-custom&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;alias&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;custom&quot;&lt;/span&gt;

  &lt;span class=&quot;token property&quot;&gt;skip_metadata_api_check&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;assume_role&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;role_arn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;arn:aws:iam::&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token type variable&quot;&gt;new_aws_account&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;aws_account_id&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;account_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;:role/AccountAssumeRole&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can provision the resources in the new account using this provider:&lt;/p&gt;
&lt;pre class=&quot;language-hcl&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-hcl&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;span class=&quot;token type variable&quot;&gt; &quot;new_aws_account_baseline&quot; &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;source&lt;/span&gt;                 &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../modules/aws-account-baseline&quot;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;aws_account_id&lt;/span&gt;         &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.aws_account_id
  &lt;span class=&quot;token property&quot;&gt;customer_tla&lt;/span&gt;           &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.customer_tla
  &lt;span class=&quot;token property&quot;&gt;environment&lt;/span&gt;            &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.environment
  &lt;span class=&quot;token property&quot;&gt;tags&lt;/span&gt;                   &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; local.tags
  &lt;span class=&quot;token property&quot;&gt;customer_name&lt;/span&gt;          &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; var.customer_name
  &lt;span class=&quot;token property&quot;&gt;cloudtrail_bucket_name&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; module.centralized_logging.cloud_trail_bucket_id
  &lt;span class=&quot;token property&quot;&gt;cloudtrail_kms_key_id&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; module.centralized_logging.log.outputs.cloud_trail_kms_arn
  &lt;span class=&quot;token property&quot;&gt;cloudtrail_s3_prefix&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; data.aws_caller_identity.current.account_id
  &lt;span class=&quot;token property&quot;&gt;providers&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;aws&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; aws.custom
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each client gets it&#39;s own Terraform template using a set of shared modules, that defines the base accounts and any possible additional accounts. The client module can be extended with whatever services or resources you want to be part of a new AWS account. That way, all accounts are provisioned in exactly the same way. You could also create different contexts with IAM permissions boundaries, allowing only certain services to be provisioned in the account. You can then apply those contexts to an account based on a variable. The possibilities are nearly endless...&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to build a serverless website, in AWS</title>
    <link href="https://ascode.nl/blog/new-startup-new-website/" />
    <updated>2020-01-29T00:00:00Z</updated>
    <id>https://ascode.nl/blog/new-startup-new-website/</id>
    <content type="html">&lt;p&gt;We needed a new website, we didn&#39;t need Wordpress. Our experience building our new site, serverless and secure, in AWS.&lt;/p&gt;
&lt;p&gt;We have our fresh new logo, stickers, and lots of content. Now we need a new website to begin sharing with our customers and the community. As a company whose mission it is to do &amp;quot;all the things as code,&amp;quot; our website obviously can not be some middle-of-the-road Wordpress site. Quite the opposite if you roll with our way of doing things. It must be clean, secure, serverless and fast. Plus, we are going to want a DTAP street so that we can experiment, test and ensure at least one environment which is production-like for testing. Naturally, it must run on the public cloud, in this instance, AWS. Infrastructure code in Terraform, Gitlab for CI/CD with automatic deployments to Dev and manual deployments to Acc and Prod.&lt;/p&gt;
&lt;p&gt;For the framework, we decided to use &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;. Hugo is a popular open-source static site generator. It is straightforward to run locally, so you get immediate feedback on your changes, and it has a built-in mode for building static pages. You can even deploy straight from Hugo to your favourite cloud provider! As we wanted to have a little more control over the deployment, we decided to write custom deployment scripts.&lt;/p&gt;
&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;
&lt;p&gt;As mentioned in the introduction, we wanted to go full serverless. Here is an overview of the architecture:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://ascode.nl/img/website_architecture.png&quot; alt=&quot;&amp;quot;ascode.nl serverless architecture&amp;quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;You would think that hosting a static website is easy enough, but there are a few things that need attention. First of all, hosting a website in S3 is relatively easy to set up. But, it does not support SSL and is HTTP-only. That could be perfectly fine for other companies, but not for us - we advocate to implement security in everything we do. The most common way of using SSL with an S3-hosted website is Cloudfront. Since we want this all automated, we leveraged AWS Certificate Manager to generate the SSL certificates that Cloudfront requires. Remember, whenever you want to use ACM Certificates or Lambda function in Cloudfront, they &lt;code&gt;must&lt;/code&gt; be created in &lt;code&gt;US-East-1&lt;/code&gt;! Also, &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html&quot;&gt;Origin Access Identity&lt;/a&gt; is used to make the S3 buckets only accessible to Cloudfront and nothing else. The S3 bucket cannot be encrypted. CloudFront currently does not support KMS server-side encryption for S3. The reason this does not work is that viewer requests through CloudFront do not have access to the KMS credentials used to encrypt the S3 objects.&lt;/p&gt;
&lt;p&gt;Route53 has Alias records for ascode.nl and www.ascode.nl pointing to the Cloudfront distribution.&lt;/p&gt;
&lt;p&gt;So far, so good. We started off with an English website, but want to leave the option open to add other languages (hence the &amp;quot;/en&amp;quot; extension when you open our site). With Cloudfront, it is not necessary to configure your S3 bucket (or S3 origin, in Cloudfront lingo) containing the static files in &amp;quot;public-read&amp;quot; mode, which makes it a little more secure. CloudFront does allow you to specify a default root object (index.html), but it only works on the root of the website (such as https://ascode.nl &amp;gt; https://ascode.nl/index.html). It does not work on any subdirectory (such as https://ascode.nl/about/). If you were to attempt to request this URL through CloudFront, CloudFront would do a S3 GetObject API call against a key that does not exist, throwing a &amp;quot;NoSuchKey&amp;quot; error message. Apache, for example, has the &lt;code&gt;DirecoryIndex&lt;/code&gt; setting to configure the default document to serve.&lt;/p&gt;
&lt;p&gt;With Cloudfront there is no easy way to configure this, at least not if you want to keep your S3 bucket private and use OAI. Enter &lt;a href=&quot;https://aws.amazon.com/lambda/edge/&quot;&gt;Lambda@Edge&lt;/a&gt;. With this, you can have a function running on the CloudFront edge nodes to request the appropriate object key from the S3 origin. We configured this in a Cloudfront distrbution as a Lambda function association with an &lt;code&gt;origin-request&lt;/code&gt; event. We also set the &lt;code&gt;viewer-profile&lt;/code&gt; to &lt;code&gt;redirect-to-https&lt;/code&gt; so that all requests are made using SSL but still allowing for our website audience to connect over HTTP. Lastly, we want to redirect people coming to our site via www.ascode.nl to be redirected to ascode.nl (also called &amp;quot;naked domain&amp;quot; or &amp;quot;apex&amp;quot;). We use another Lambda@Edge function for this implementation but now configured in Cloudfront as a Lambda function association with a &lt;code&gt;viewer-request&lt;/code&gt; event.&lt;/p&gt;
&lt;h3 id=&quot;the-challenges-when-running-a-static-website-server-side-code&quot;&gt;The challenges when running a static website - server-side code&lt;/h3&gt;
&lt;p&gt;Cool, we have our website up and running! Last thing to tackle is our contact form. And boy, was that a challenge! Since S3 doesn&#39;t support any server-side code, such as PHP - so you can&#39;t use server-side logic to send an email, this required a different solution. Yes, we could have just implemented a mailto: link that will cause the user to open their mail client when they click on it - but what&#39;s the fun in that? We wanted a form that allowed anyone to send an email without needing an email client. The function for sending the email is a Lambda function behind an API Gateway. Invoking the protected API requires an API key.&lt;/p&gt;
&lt;p&gt;The problem with this setup, however, is that the API Gateway is on a different domain (https://...execute-api...) than Cloudfront (in our case, &lt;a href=&quot;https://ascode.nl&quot;&gt;https://ascode.nl&lt;/a&gt;, but could also be https://,,,cloudfront.net). Since the domains are different, the browser will treat this as a cross-origin request. And that means CORS headers are required on the API side. The two most important headers are &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; and &lt;code&gt;Access-Control-Allow-Credentials&lt;/code&gt;. The former is required for every cross-origin request, the latter is only when the &lt;code&gt;{credentials: &amp;quot;include&amp;quot;}&lt;/code&gt; option is used. This can be solved in the API Gatway itself by configuring an &amp;quot;OPTIONS&amp;quot; mock request that returns &lt;code&gt;Access-Control-Allow-Origin = &amp;quot;*&amp;quot;&lt;/code&gt; to Cloudfront, but this proved to be very error-prone and took a lot of testing.&lt;/p&gt;
&lt;p&gt;After some research we found out we can use Cloudfront not only with S3 origins but also with API Gateway origins, eliminating the CORS problem as both the API request as the website request comes from the same domain.
There is a trick to this, however. It has to do with the way Cloudfront constructs the URL for the backend.
When CloudFront constructs the URL for the backend, you can specify three parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The domain_name&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The origin_path&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The path_pattern at the cache behavior&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CloudFront constructs the URL to the origin by replacing the distribution URL with the &lt;code&gt;domain_name+origin_path&lt;/code&gt;; then, it appends the &lt;code&gt;path&lt;/code&gt;. Because the web site is already on &lt;code&gt;&amp;quot;/&amp;quot;&lt;/code&gt;, the API needs configuration on a different path. Long story short, the &lt;code&gt;path_pattern&lt;/code&gt; you configure in Cloudfront must have the same name as the stage name in the API Gateway. For example, if you have configured a stage called &lt;code&gt;&amp;quot;prod&amp;quot;&lt;/code&gt; in API Gateway, the path pattern in Cloudfront will be &lt;code&gt;&amp;quot;/prod/*&amp;quot;&lt;/code&gt;. It took some time to figure that out. Victory is sweet!&lt;/p&gt;
&lt;h2 id=&quot;making-this-work-in-dtap&quot;&gt;Making this work in DTAP&lt;/h2&gt;
&lt;p&gt;We write all our infrastructure code in Terraform and Gitlab for CI/CD. For this website, we wanted a &amp;quot;DAP&amp;quot; environment - DEV to check quickly how the site behaves, Acc the same as Prd. Hugo also supports environments (look &lt;a href=&quot;https://gohugo.io/getting-started/configuration/#configuration-directory&quot;&gt;here&lt;/a&gt;), which means we can also parameterize Hugo for the different environments. For example, the API key differs per environment so we can use the respective environment .toml files to configure the various keys.&lt;/p&gt;
&lt;p&gt;We have split up the code over multiple repositories; infrastructure code is in a different repository than the actual web site. The same DAP-principles apply, however.&lt;/p&gt;
&lt;p&gt;With Gitlab, pipeline as code is relatively easy. It takes some time getting used to the YAML syntax, but once you got the hang of it, it works great. As soon as a branch is ready to be merged into master, a merge request is created. The code is validated and scanned for compliance using &lt;a href=&quot;https://terraform-compliance.com/&quot;&gt;terraform-compliance&lt;/a&gt;. Once the pipeline is passed, the MR can be merged after which a new version is created, and the code is automatically deployed to Dev. Build/plan is done automatically for Acc and Prd, but they have manual deployment approvals (for now).&lt;/p&gt;
&lt;p&gt;If you are interested in what infrastructure code we used, we created a public repository that contains all the code that we wrote to build the AWS environment. As mentioned, in practice, we have this code divided over more than one repository, but for ease of (re)use, we created a &lt;a href=&quot;https://gitlab.com/erik.ascode/ascode-website-public&quot; title=&quot;ascode website repo&quot;&gt;single public repo&lt;/a&gt;. There probably will be some room for improvement here and there, so if you see a better way to do it, let us know! Or even better, create a merge request against our public repo.&lt;/p&gt;
</content>
  </entry>
</feed>