<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Gaetan Semet on Medium]]></title>
        <description><![CDATA[Stories by Gaetan Semet on Medium]]></description>
        <link>https://medium.com/@gsemet?source=rss-962aefc92c3b------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*jynqQbrhJGvAl4H3YVmZ9Q.png</url>
            <title>Stories by Gaetan Semet on Medium</title>
            <link>https://medium.com/@gsemet?source=rss-962aefc92c3b------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 19 May 2026 19:29:42 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@gsemet/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Building Agentic Project Harness]]></title>
            <link>https://medium.com/@gsemet/building-agentic-project-harness-0254797a0236?source=rss-962aefc92c3b------2</link>
            <guid isPermaLink="false">https://medium.com/p/0254797a0236</guid>
            <category><![CDATA[claude-code]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[harness-engineering]]></category>
            <category><![CDATA[martin-fowler]]></category>
            <category><![CDATA[github-copilot]]></category>
            <dc:creator><![CDATA[Gaetan Semet]]></dc:creator>
            <pubDate>Wed, 13 May 2026 10:13:44 GMT</pubDate>
            <atom:updated>2026-05-13T10:13:44.992Z</atom:updated>
            <content:encoded><![CDATA[<p>Every coding agent (Claude Code, Copilot) ships with a built-in “Harness”:<br>tool routing, permission checks, context management, error recovery.<br>This harness accounts for roughly 98% of the coding agent’s codebase, while less than 2% is the “Agent Loop”, a “while” loop calling LLM until the LLM decide it work is finished.</p><p>When using a Coding Agent, any project needs its own layer on top of it: the project harness.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/1*C9_iR1BqXpYllfNNFePbxw.png" /></figure><p>This article presents a simple taxonomy for its components, openly inspired from <a href="https://martinfowler.com/articles/harness-engineering.html">Harness engineering for coding agent users</a> article: Guide, Sensors, Quality Gates and Maintenance Tasks.</p><h3>The economics changed</h3><p>In 2024, AI-assisted coding cost a few dollars per month per engineer.<br>Flat subscription were the norm, GitHub had Premium Request.</p><p>In 2026, serious agentic coding session can consume<br>$100 to $200 in tokens. That is per engineer, per day.<br>Flat plans are disappearing or highly limited,<br>without access to some models.</p><p>The relevant metric is no longer “lines of code AI generates”<br>but <em>outcome per token</em>:<br>how much verifiable engineering value<br>each dollar of model inference produces.</p><p>Optimising tokens consumption,<br>ensuring heavy agentic development workflow delivers<br>real value means Project has to keep the coding agent<br>under tight control.</p><p>SDD frameworks try to make this part is easy,<br>but I do not agree this should be outsourced.<br>Projects have to own their Harness and<br>then SDD frameworks, even Plan mode, will use it.</p><h3>Guides and sensors</h3><p>The Project Harness has two types of components:</p><ul><li><strong>Guides</strong>: shape agent behavor <strong>before</strong> execution.<br>They are feedforward:<br>rules and context the agent reads before writing any code.</li><li><strong>Sensors</strong>: detect deviation <strong>after</strong> execution.<br>They are feedback: signals the agent processes<br>to decide whether its output meets the project’s standards.</li></ul><p>Each type can be computational or inferential:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*M118OpOzAksbaavUxjJcvw.png" /></figure><p>Computational sensors are deterministic, fast, and cheap.</p><p>Inferential sensors use LLM calls: probabilistic, slower, and more expensive, but they catch semantic problems that scripts cannot.</p><p>For instance, AGENTS.md file is now widely supported and used, and should be a map of the important technologies, entrypoints, mandatory quality gates, pointers to conventions, guidelines, ADRs.</p><p>Keep it strictly tight, less than 200 lines for complex projects, 50 lines for simpler ones.</p><pre># AGENTS.md for My Project<br><br>## Commands<br><br>- Available commands: `just help`<br>- Install: `just dev`<br>- Run checks: `just check`<br>- Preflight validation: `just preflight`<br><br>## Constitution<br><br>See `CONSTITUTION.md` for inviolable rules.<br><br>## Guidelines<br><br>- REST API: `.agents/guidelines/rest-api.guideline.md`<br>- Commit messages: `.agents/guidelines/commits.guideline.md`</pre><p>AGENTS.md will be always in context, and resist compaction.</p><p>It is the minimal onboarding document that your agentic consultant<br>gets at each session.<br>It gives the important informations to always know and pointers.</p><p>Other types of in project guides:</p><ul><li><a href="http://constitution.md/">CONSTITUTION.md</a>: inviolable modification rules of the project</li><li>Guidelines: reusable rules accross project, pointed by AGENTS.md.</li><li>ADR: Architecture Decision Record</li><li>Skills: project specific capabilities.</li></ul><h3>Quality gates</h3><p>Guides tell the agent what to before coding.<br>Sensors tell the agent what went wrong after.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Cv55W1nJW8P0m7jKkemzZQ.png" /><figcaption>Quality Gates</figcaption></figure><p>Quality gates are when these sensors fire.</p><p>A quality gate blocks the agent from proceeding<br>until all attached sensors pass.</p><p>The simplest gate pattern can be called the “preflight check”: a single target (e.g. just preflight) that run all computational sensors: linting, type checking, unit tests, documentation generation, build, ...</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7ZIv1ePn9lqDAW2MLJc7JQ.png" /><figcaption>Preflight Harness</figcaption></figure><p>The agent is instructed (in AGENTS.md or hook) to run it after every task.<br>Failures feed directly into the agent&#39;s context as structured error output.<br>The agent reads the errors and fixes them before moving on.</p><p>When project grows, the “preflight” campaign can become too slow, taking more and more time and slowing down the complete agentic coding loop. For these complex workflows, several gates can be defined with different sensors, each with different scope, execution cost.</p><p>Here is an example of a complex autonomous coding workflow:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*qQ1pAXfNNg_vwEjcjqHk9Q.png" /><figcaption>Complex Quality Gates on a mature ProjectHarness</figcaption></figure><p>Each level runs less frequently but catches broader problems:</p><ul><li><strong>Task gate</strong> fire after every change. They run only computational sensors: lint, type checks, tests. They need to be fast (&lt;5 min) and deterministic.</li><li><strong>Phase gate</strong> fire after a group of related tasks. For instance, they can run the full e2e campaign, or the complete unit test suite<br>if selection logic was used at the task gate to accelerate execution.</li><li>At the <strong>end of development gate</strong>, the most complete performance tests run.</li><li><strong>CI gate</strong> fire after a push, and can allow agent to automatically fix minor issues when the Pull/Merge request is run in CI environment.</li><li><strong>Agentic Code Review gate</strong>can be triggered when MR/PR is created, a gate can allow the local coding workflow to directly react to agentic review (CodeRabbit, …)</li></ul><p><strong>Final Human review</strong> happens at the end, and verifies the complete outcome of the session (code, implementation reports, quality metrics,…)</p><p>For instance, you can have a ‘CI feedback loop’ skill to allow fixing CI failures:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/530/1*9qBzHxyueLzUEXw7x2736A.png" /><figcaption>CI Feedback loop</figcaption></figure><p>Gates are organised in a cost hierarchy. Computational sensors should be prefered over Inferential sensors but the later allow catching issues no amount of scripts can detect.</p><p>Engineering expertise is important to design these quality gates to balance between efficiency and accuracy.</p><p>Computational sensors should be prefered over Inferential sensors, but the later allow catching issues no amount of scripts can detect.</p><p>But letting the agent select its own checks is a recipe for failure: <strong>LLM will cheat.</strong></p><p>This has to be carefully designed by the project. Engineering expertise is important to manage these quality gates, find the right balance between efficiency and accuracy.</p><h3>Structural checks</h3><p>For important structural rules, agents might still silently violate guidelines even with careful planning.</p><p>Use the coding agent to write its own structural checks and add it into a Quality Gate.</p><p>For example, let’s say you have a Guideline that declares:</p><pre>Module interaction rules:<br>- `cli` can import `api`, `res`, `lib`, and `utils`<br>- `api` can import `lib`, `res`, and `utils`<br>- `lib` can import `res` and `utils`<br>...</pre><p>This rule can be easily verified by a dedicate, project-specific script.</p><p>Wire it into the preflight target. On error, display a clear message<br>to explain to the agent what needs to be fixed.</p><p>More complex structural rules might require to trigger a specific agent (“Inspector”).</p><h3>Entropy fights back</h3><p>Even with a strong harness, drift will accumulate silently, and entropy will slowly build up in the project.</p><p>Regular, off-cycle maintenance tasks can help control this drift:</p><ul><li><strong>Harness gardening</strong> — review <a href="http://agents.md/">AGENTS.md</a>, guidelines, quality gates structure for drift and performance, remove rules that no longer apply, and tighten rules that agents routinely violate.</li><li><strong>Documentation gardening</strong> — prune dead links, stale ADRs,<br>and obsolete glossary entries. An agent can scan recent code changes<br>and detect gaps in documentation.</li><li><strong>Dependency/Vulnerability updates</strong> — bump libraries<br>and verify that quality gates still pass.<br>This is a maintenance task that agents handle well, even when minor changes are required.</li></ul><p>Another good practice to set up is to consolidate feedback from coding agent itself on what when wrong during a session, in markdown, that can be compiled later into a specific “Harness Improvement Session”</p><h3>Conclusion</h3><p>Harness engineering exists because LLMs have hard limitations.</p><p>The context window is finite, sessions are stateless, output is probabilistic.<br>And especially inference now become extremely expensive.</p><p>The harness is how you work within these constraints instead of pretending they do not exist.</p><p>This article presented a very high level overview of how to organize guides to keep the context lean, how to use sensors in quality gates in your project to catch the mistakes that agent will make, and why setting up regular Maintenance Agentic Tasks allows to keep small harness drift under control.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0254797a0236" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducing Gitlab Project Configurator]]></title>
            <link>https://medium.com/grouperenault/introducing-gitlab-project-configurator-daa02b79183c?source=rss-962aefc92c3b------2</link>
            <guid isPermaLink="false">https://medium.com/p/daa02b79183c</guid>
            <category><![CDATA[infrastructure-as-code]]></category>
            <category><![CDATA[gitlab]]></category>
            <dc:creator><![CDATA[Gaetan Semet]]></dc:creator>
            <pubDate>Wed, 13 Jan 2021 13:47:18 GMT</pubDate>
            <atom:updated>2021-01-13T13:47:18.162Z</atom:updated>
            <content:encoded><![CDATA[<h4>Align settings for hundreds of Gitlab Projects</h4><p><strong><em>TL;DR</em></strong><em>: This article presents Gitlab-Project-Configurator (GPC) proudly released under MIT License by Groupe Renault. GPC is a “configuration as code” tool used internally to align settings of hundreds of Gitlab projects and groups.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MtxSr9ngKgygfRQXeh8FcQ.png" /><figcaption>A git project that configures hundreds of other projects using GPC</figcaption></figure><p>Gitlab is a great tool to store your code and run Continuous Integration Pipelines on it. It works fine for personal projects and is also a great tool for corporations as their main repository for source code.</p><p>It is even greater at experimenting new ways of doing things, project creation is easy (and it <em>should</em> be easy).</p><p>But as project number grows, managing them becomes harder and harder. Doing everything in the UI is great, but things get a bit harder when you want to apply the same settings to every projects in a single group.</p><p>It would be much scalable to write down the wanted settings for projects (and groups) in files and apply them automatically, using the Infrastructure as Code principle (or more precisely, “Configuration as Code”).</p><h3>Let’s start by an example</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P5NOVvkl46GdOaorybR8wQ.png" /><figcaption>We want to apply this setting to a set of project in Gitlab.</figcaption></figure><p>Let’s say we would like for a specific group to set all their project with the setting “Fast-forward merge”. Not the whole Gitlab instance, not only a few projects. Maybe a few hundreds of them.</p><p>And with some exceptions, let’s say in the middle of all of our project we have one where we do not want this settings applied for some obscure reason.</p><p>We can manage to use the API ourself, thanks to the amazing <a href="https://python-gitlab.readthedocs.io/en/stable/">Python-Gitlab</a> module. But if we add another settings, a few other exceptions, and we will finally end up with a really hardcoded script, hardly maintainable, and that cannot be used on other situations.</p><h3>Introducing Gitlab Project Configurator</h3><p>When we started really scaling up our usage of Gitlab, we wanted to ensure that we had a way to align some project settings following the “as code” paradigm:</p><ul><li>configuration is described in a text file</li><li>evolution of the configuration must be tracked like any other software change, ie, in Merge Requests</li><li>tracking who did a change, when and why is extremely important.<br>The git history provides it directly.</li></ul><p>We tried to work with <a href="https://github.com/egnyte/gitlabform">GitlabForm</a> at the end 2018, but we were not able to do what we wanted with it:</p><ul><li>we want to define a set of logically-related settings in “rules” or “policies”</li><li>several rules can be defined and inherit from each other</li><li>rules are to be applied on some specific projects, or on all projects of a Gitlab group, or matching a regular expression.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/968/1*rlbGM__9-oUiwAkvc3raKg.png" /><figcaption>Example of projects configuration and customization</figcaption></figure><p>Sometime a “generic” rule to be applied to a project needs some kind of customization.</p><p>Let’s say you want to configure 3 projects (P1, P2, P3) in a group with the following settings that all projects would follow:</p><ul><li>merge method set to fast-forward</li><li>define a variable “MY_VARIABLE” with a specific value “MY_VAL”</li></ul><p>But, one of the project (P3) is already using the variable “MY_VARIABLE” with another value “OTHER_VAL”, and changing may have side-effects.</p><p>So, we may want to apply these settings to P1 and P2, but on P3 to keep previous variable value for the moment. This becomes a “Technical Debt” which can be reimbursed later. This is an option often chosen when deadlines approaches.</p><p><strong>That’s why we created Gitlab-Project-Configurator</strong>, allowing to apply precisely project and group settings, with lot of possible customization.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hqG5fwDoV4cf5_58lGB5gQ.png" /><figcaption>Rules are defined, and then applied to projects.</figcaption></figure><p>GPC is command line tool, written in Python, that will take a description of what you want to set on Gitlab’s projects and group, and apply as much as possible. Key features are:</p><ul><li><strong>Security:</strong> all the configuration are centralized and it is easy to update credentials or password used in multiple projects.</li><li><strong>Traceability</strong>: If someone manually modify a setting manage by GPC, modified settings will appear in the GPC report.</li><li><strong>Reliability</strong>: GPC will apply settings defined in configuration files, and help to align multiple projects settings.</li><li><strong>Dry-Run:</strong> GPC can run in “dry-run” mode, for example in a merge request, and it will report what will be changed, but without applying the settings. This allow users to review and verify changes before applying the settings.</li><li><strong>Reports:</strong> GPC provides reports (HTML, email) of what has been done, and what failed, so post-mortem is easily done.</li></ul><p>The main way to use GPC is by using the docker image provided by the project and run it directly on your project:</p><pre>image: registry.gitlab.com/grouperenault/gitlab-project-configurator:latest</pre><p>Note: This command will only work on gitlab.com server.</p><h3>How it works?</h3><h4>Rules Definition</h4><p>Rules are defined in one or several files.</p><figure><img alt="Base rule" src="https://cdn-images-1.medium.com/max/1024/1*g0s85zAoIR4BNjTMCCeRyw.png" /><figcaption>Base rule</figcaption></figure><p>This is where you describe which settings your project should follow.</p><p>For example:<em> all projects of my team should have a default branch named ‘master’, protected and only allowed to merge (no one can “push on it”), with all tags protected.</em></p><p>And so on.</p><h4>Derivative rules</h4><p>Then we can imagine a derivative of this base rule that adds a badge and define other rules.</p><figure><img alt="Inherited rule" src="https://cdn-images-1.medium.com/max/830/1*bp2J2RlfSF4IVIBCoX-bLw.png" /><figcaption>Derivative rule</figcaption></figure><h4>Apply rules on projects</h4><p>Next step is to apply these rules on projects.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/787/1*QIl0CdcFsTtdhDsfDINSwA.png" /><figcaption>Rules applied on a project with customization</figcaption></figure><p>A special keyword named custom_rules allows to add exception to the rules you want to apply. It is probably a good idea to add comment on why this exception needs to be done in this case.</p><blockquote>Our experience shows that when transitioning from a completely freely managed project to a project managed under GPC, we often faced situations where we wanted to do only <em>part</em>s of the migration at a given moment: either all variables cannot be migrated to GPC <em>yet</em>, or it raises some minor incompatibilities on some projects that would require manual changes.</blockquote><blockquote>But when migrating hundreds of projects at once, we were happy to use some carefully crafted <em>custom_rules</em> for a few days or weeks until this technical debt can be reimbursed while keeping or release schedules untouched.</blockquote><h3>Recommendations</h3><p>We have a few recommendation when using GPC:</p><ul><li>you need to store your settings in a <strong>private project</strong>.</li><li><strong>Add a schedule</strong> to apply your change on a regular basis. We apply our change twice a day, so if someone alter some project settings under GPC, we know it will only last for a few hours before the settings are reset.</li></ul><h3>Project Information</h3><p><a href="https://gitlab.com/grouperenault/gitlab-project-configurator">Gitlab-Project-Configurator</a> is released under MIT license starting by version number 3.0.0. We chose to maintain the main codebase in a private Renault repository, and perform an automated synchronization with the Opensource project on a regular basis. We do not have any Renault-specific features, only intensive integrations tests still heavily customized and some internal configuration files.</p><p>The open source version is automatically updated using a <a href="https://github.com/google/copybara">CopyBara</a> script.</p><p>We are looking forward to contribution, and they will be merged internally first before being published back to open-source project, maintaining the original commit authorship.</p><p>You can have a look at a <a href="https://gitlab.com/grouperenault/gpc_demo/gpc-demo-config/">live demo project here</a>.</p><p>The main documentation is <a href="https://grouperenault.gitlab.io/gitlab-project-configurator/docs/index.html">accessible here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=daa02b79183c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/grouperenault/introducing-gitlab-project-configurator-daa02b79183c">Introducing Gitlab Project Configurator</a> was originally published in <a href="https://medium.com/grouperenault">grouperenault</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Secured CI/CD with “Tillerless” Helm]]></title>
            <link>https://medium.com/@gsemet/secured-ci-cd-with-tillerless-helm-e727fd19e312?source=rss-962aefc92c3b------2</link>
            <guid isPermaLink="false">https://medium.com/p/e727fd19e312</guid>
            <category><![CDATA[rbac]]></category>
            <category><![CDATA[tiller]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[helm]]></category>
            <category><![CDATA[tillerless]]></category>
            <dc:creator><![CDATA[Gaetan Semet]]></dc:creator>
            <pubDate>Sat, 08 Jun 2019 22:01:05 GMT</pubDate>
            <atom:updated>2019-06-08T22:13:49.997Z</atom:updated>
            <content:encoded><![CDATA[<p>I will present in this article our way of deploying complex applications in our RBAC-enabled Kubernetes cluster using Helm, without a running Tiller inside our cluster (aka “Tillerless” mode), and how to use the service account token to secure the connection between our CI and the Kubernetes.</p><p>I assume the reader of this article knows most common Continuous Integration (CI) technics, knows Kubernetes concepts, especially namespaces and RBAC, knows the basic Helm concepts, like <a href="https://helm.sh/docs/glossary/#tiller">Tiller</a>, <a href="https://helm.sh/docs/glossary/#chart">Chart</a> and <a href="https://helm.sh/docs/glossary/#values-values-files-values-yaml">value files</a> are.</p><p>Kubernetes makes it easy to deploy simple applications even with kubectl command line tool only. But things get more complex in real life, once RBAC is enabled in the cluster, or when several “flavors” of the same applications needs to be deployed (say, a “prod” and a “staging”). Something you may even want to create your own, on-demand environment to test your contribution.</p><p>Enters Helm, that, for me, gives <em>a satisfying answer </em>to our needs for deploying production-level applications.</p><p>But first, I need to clarify what I mean by “Helm gives satisfying answer”. Helm is not just the command line tool you can download from <a href="https://github.com/helm/helm/releases">https://github.com/helm/helm/releases</a>.</p><p>For me, “Helm” is all of the following points:</p><ul><li>the command line tool,</li><li>the Chart and its <em>weird</em> templating syntax (1),</li><li>the excellent Helm Charts available at <a href="https://github.com/helm/charts/">helm/charts</a>, that provides an reference database,</li><li>the documentation at <a href="https://helm.sh/docs/">helm.sh/docs</a>, that provides not only the description on how the Helm tool works, but also gives the <a href="https://helm.sh/docs/chart_best_practices/">best practices</a> on how to write a high quality Chart,</li><li>of course, the best friend of the helm command line tool: Tiller.</li></ul><p>Tiller is, sadly, the unloved friend of Helm. It is a small <em>pod </em>that is set to be always up and running inside your Kubernetes cluster and will accept the deployment requests (Chart + values) from the “helm” CLI, either from your development environment or your CI. If you want more control on it you can start one Tiller per namespace, or per team. But still it can do lot of damages if left unsecured.</p><p>Tiller can assist Kubernetes itself during some operations, like the rolling update of applications, providing features that Kubernetes does not provide yet: for example, redeploying all the pods that use a given ConfigMap, when this one is updated. <a href="https://github.com/helm/helm/blob/master/docs/charts_tips_and_tricks.md#automatically-roll-deployments-when-configmaps-or-secrets-change">This requires some tricks</a> with Helm, but at the end, it does the job.</p><p>But like said a few lines ago, having a special <em>pod </em>inside the Cluster that can alter almost anything inside it on-demand is a great security concern. <strong>You need to </strong><a href="https://github.com/helm/helm/blob/master/docs/securing_installation.md"><strong>secure your Helm installation</strong></a><strong> for production</strong>, but it could be complex. It basically requires the following 4 steps:</p><ul><li>enabling RBAC in your cluster</li><li>use secret to store Tiller configuration instead of ConfigMap</li><li>secure the Tiller gRPC endpoint by enabling TLS</li><li>install one Tiller per team</li></ul><p>Creating TLS certificate can be seen as a redundancy of RBAC configuration, since it means you rely on a service account linked to a RoleBind with enough permission to perform any action.</p><p>But the most important flaw of this method is that is yet another process to set up, that needs to be well understood and mastered in order not to have, in the end, less security than without RBAC.</p><p>And we already have done all this hard work of securing the connection to our Kubernetes cluster with RBAC. It provides everything that we need.</p><p>And it communicates through HTTP(S). gRPC is cool, but it makes things a little bit harder. For example, you can be in a case where you might not have easy access from outside your cluster to any service that uses ClusterIP, like… Tiller itself… so without any special trick, you cannot execute any helm command from outside the cluster… and so this special trick needs to be secured as well… again, ANOTHER security process to set up).</p><p>Enough is enough, let’s use only the Kubernetes API as sole and unique connectivity to Kubernetes.</p><p>We will also use the service account that already has Certificate Authority and a token, and we can use them to authenticate our deployment from our CI.</p><h3>Securing deployment using Tillerless Helm</h3><p>We will set up a secured “Tillerless” solution that follow this structure.</p><ol><li>RBAC is of course well configured in your cluster</li><li>Helm deployment information are stored as Kubernetes Secret</li><li>Have one service linked with role binding per user, team, or other organizational entity</li><li>We will use the service account token to authenticate our CI job</li></ol><p>The huge advantage of this process if that we only use the Kubernetes API to communicate from our CI. This also means you have kubectl alongside your helm commands in your CI and both will share the same security configuration.</p><p>It is expected that the future version of Helm, Helm v3, to work a little bit like this.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*59fPo4-BBuS3fJCeIt2svg.png" /><figcaption>Connection comparison between Helm 2 and Helm 3. (<a href="https://www.slideshare.net/mattfarina/helm-orchestructure">source</a>)</figcaption></figure><p>Using the latest version of Helm v2, you can still simulate this behavior with the excellent “Helm-Tiller” plugin for Helm: <a href="https://github.com/rimusz/helm-tiller">https://github.com/rimusz/helm-tiller</a>. You can even use it in your CI. What it does is it can start install and run a background Tiller process on your development environment (or CI).</p><h4>Connection to the Kubernetes API with the service account token</h4><p>You need to have a service account, for example named “tiller-sa”, in your namespace. Please ensure the Role Binding has the minimum permission possible, restricted to only the target namespace of your application. If for any reason, the service account get compromised, you need to limit as much as possible the blast radius.</p><p>Here is an example of the “tiller-sa” role:</p><pre>kind: Role<br>apiVersion: rbac.authorization.k8s.io/v1beta1<br>metadata:<br>  name: tiller-sa<br>rules:<br>- apiGroups: [&quot;&quot;, &quot;extensions&quot;, &quot;apps&quot;]<br>  resources: [&quot;*&quot;]<br>  verbs: [&quot;*&quot;]</pre><p>And its matching RoleBinding to the service accound “tiller-sa”:</p><pre>kind: RoleBinding<br>apiVersion: rbac.authorization.k8s.io/v1beta1<br>metadata:<br>  name: tiller-sa-binding<br>subjects:<br>- kind: ServiceAccount<br>  name: tiller-sa<br>roleRef:<br>  kind: Role<br>  name: tiller-sa<br>  apiGroup: rbac.authorization.k8s.io</pre><p>If you use helm-tiller plugin, you will see it automatically download the tiller executable matching your helm version, in “~/.helm/plugin/tiller/bin/tiller”.</p><p>But first, we need to set up the connection to the cluster. For example, on your development environment, you can rely directly on your RBAC configuration. You can use the Helm-tiller plugin directly.</p><p>But in our CI, we will use the service account token.</p><p>Just copy the token and ca.crt from the secret linked to the service account (you can find <a href="https://medium.com/@amimahloof/how-to-setup-helm-and-tiller-with-rbac-and-namespaces-34bf27f7d3c3">interesting functions here</a> for that), and store it as secret environment variables in your CI, alongside some other environment variables we need to define:</p><ul><li>$TILLER_SA_NAME: name of the service account. In our example, it is “tiller-sa”,</li><li>$TILLER_SA_CA_CRT: the content of ca.crt found in the secret related to the service account,</li><li>$TILLER_SA_TOKEN: the content of the token found in the secret related to the service account,</li><li>$TARGET_NAMESPACE: namespace where you want to deploy your application. Your service account must have the right role to deploy anything in it</li><li>$CLUSTER_URL: the URL to your cluster.</li></ul><p>In your CI job, simply execute the follow snippet to configure kubectl.</p><pre>$ echo $TILLER_SA_CA_CRT &gt; /etc/kube/ca.crt<br>$ export KUBE_CONFIG=/etc/kube<br>$ kubectl config set-cluster sa-cluster \<br>    --server=$CLUSTER_URL \<br>    --certificate-authority=/etc/kube/ca.crt<br>$ kubectl config set-credentials sa-user \<br>    --token=$TILLER_SA_TOKEN<br>$ kubectl config set-context sa-context \<br>    --cluster=sa-cluster \<br>    --user=sa-user \<br>    --namespace $TARGET_NAMESPACE<br>$ kubectl config use-context sa-context</pre><p>Test your connection to the cluster:</p><pre>$ kubectl get pods --namespace $TARGET_NAMESPACE</pre><p>If everything if fine, this means your CI job can access to the Kubernetes API using the Service Account Token. Great, now let’s start the real fun: Tillerless helming your application with a background tiller task.</p><h4>Tillerless Helming</h4><p>Instead of running Tiller inside the cluster, you will start it as a background job in your CI job. “Tillerless helm” only means running it on “localhost:44140”.</p><p>I consider you have set up the Helm-Tiller plugin in your CI.</p><p>You can <a href="https://github.com/rimusz/helm-tiller#usage">use the “start-ci” command</a> plugin with a bunch of environment variable, the one I recommend are:</p><pre>$ export HELM_TILLER_HISTORY_MAX=20<br>$ export CREATE_NAMESPACE_IF_MISSING=false<br>$ export HELM_TILLER_STORAGE=secret<br>$ helm tiller start-ci $TARGET_NAMESPACE</pre><p>Or if you want to start the process manually:</p><pre>$ ~/.helm/plugin/tiller/bin/tiller \<br>    --storage=secret<br>$ helm init \<br>    --client-only \<br>    --wait \<br>    --history-max 20</pre><p>Note that you do not link Tiller to a service account like when it is set up inside the cluster. Since Tiller is now running as background task in your CI, it will directly use the Kubernetes API, through your already configured Kube config file.</p><p>Now you can use helm as usual. Except, remember, you need to specify “--wait” at each command to tiller:</p><pre>$ helm upgrade --wait --install ...</pre><p>In bonus, you now see the tiller’s log during your deployment.</p><p>You can even execute any kubectl command you might want in your CI, such as:</p><pre>$ kubectl get pods</pre><p>Simply ensure to kill the tiller background after everything:</p><pre>$ helm tiller stop</pre><h4>Usage in Gitlab CI</h4><p>If you use Gitlab CI, you need to make sure to kill manually the background process at the end of your deployment, of your job will be stuck forever. There is <a href="https://gitlab.com/gitlab-org/gitlab-ce/issues/41581#note_53034410">a special trick</a> to be able to use a bash trap in your job. Or you can starts/stops tiller before/after each command with:</p><pre>helm tiller run $TARGET_NAMESPACE helm upgrade --wait ...</pre><p>This article is an adaptation of the following articles:</p><ul><li><a href="https://medium.com/@amimahloof/how-to-setup-helm-and-tiller-with-rbac-and-namespaces-34bf27f7d3c3">How to setup helm and tiller with RBAC and namespaces</a></li><li><a href="https://medium.com/faun/helm-basics-using-tillerless-dac28508151f">Helm Basics — Using Tillerless</a></li><li><a href="https://blog.tenx.tech/the-how-and-why-behind-tiller-less-helm-340a071d51c8">The How and Why Behind Tiller-less Helm</a></li></ul><p><em>My name is Gaetan Semet, and I work on a daily basis with Kubernetes. I am the maintainer of the </em><a href="https://github.com/helm/charts/tree/master/stable/airflow"><em>Airflow Chart</em></a><em>.</em></p><p>[1] I can have lot of complain about the flaws of Go Templating, for example the stupid <a href="https://www.calhoun.io/intro-to-templates-p3-functions/#the-and-function">boolean operations syntax</a>. Ok, Helm is <a href="https://www.reddit.com/r/kubernetes/comments/byd5jd/secured_cicd_with_tillerless_helm/">not only a template engine</a>. I don’t really know if <a href="http://jinja.pocoo.org/docs/2.10/">Jinja2</a> is the original or the copy, but to be honest Jinja is so much better.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e727fd19e312" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>