<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Gke on inetshell</title><link>https://inet.sh/en/tags/gke/</link><description>Recent content in Gke on inetshell</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/en/tags/gke/index.xml" rel="self" type="application/rss+xml"/><item><title>Vault Agent Injector on a private GKE cluster: no sidecar and pods take 30 s (the control plane cannot reach the webhook on 8080)</title><link>https://inet.sh/en/posts/kubernetes/vault-agent-injector-gke-private-cluster-no-sidecar/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/en/posts/kubernetes/vault-agent-injector-gke-private-cluster-no-sidecar/</guid><description>On a private GKE cluster, pods annotated with vault.hashicorp.com/agent-inject start without the vault-agent-init and vault-agent containers, take half a minute longer to be created, and the injector logs no request at all. The control plane can only open connections to nodes on ports 443 and 10250; the injector listens on 8080 and the webhook fails silently because failurePolicy is Ignore. Firewall rule from the control plane range, how to validate in the injector logs, and the same problem with any other admission webhook.</description><content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">CLUSTER</span><span class="o">=</span>my-cluster<span class="p">;</span> <span class="nv">REGION</span><span class="o">=</span>us-central1<span class="p">;</span> <span class="nv">NETWORK</span><span class="o">=</span>my-vpc
</span></span><span class="line"><span class="cl"><span class="nv">MASTER_CIDR</span><span class="o">=</span><span class="k">$(</span>gcloud container clusters describe <span class="nv">$CLUSTER</span> --region <span class="nv">$REGION</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --format<span class="o">=</span><span class="s1">&#39;value(privateClusterConfig.masterIpv4CidrBlock)&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">NODE_TAG</span><span class="o">=</span><span class="k">$(</span>gcloud compute firewall-rules list --filter<span class="o">=</span><span class="s2">&#34;name~^gke-</span><span class="nv">$CLUSTER</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --format<span class="o">=</span><span class="s1">&#39;value(targetTags.list())&#39;</span> <span class="p">|</span> head -1<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gcloud compute firewall-rules create gke-<span class="nv">$CLUSTER</span>-master-to-vault-injector <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --network<span class="o">=</span><span class="nv">$NETWORK</span> --direction<span class="o">=</span>INGRESS --action<span class="o">=</span>ALLOW <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --source-ranges<span class="o">=</span><span class="nv">$MASTER_CIDR</span> --target-tags<span class="o">=</span><span class="nv">$NODE_TAG</span> --rules<span class="o">=</span>tcp:8080
</span></span></code></pre></td></tr></table>
</div>
</div><p>Delete an annotated pod and check that the injector gets the call:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl -n app delete pod -l <span class="nv">app</span><span class="o">=</span>api
</span></span><span class="line"><span class="cl">kubectl -n vault logs deploy/vault-agent-injector <span class="p">|</span> grep mutate
</span></span><span class="line"><span class="cl"><span class="c1"># [INFO]  handler: Request received: Method=POST URL=/mutate?timeout=30s</span>
</span></span><span class="line"><span class="cl">kubectl -n app get pod -l <span class="nv">app</span><span class="o">=</span>api -o <span class="nv">jsonpath</span><span class="o">=</span><span class="s1">&#39;{.items[0].spec.initContainers[*].name} {.items[0].spec.containers[*].name}{&#34;\n&#34;}&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># vault-agent-init api vault-agent</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="the-symptom">The symptom</h2>
<ul>
<li>The pod carries <code>vault.hashicorp.com/agent-inject: &quot;true&quot;</code> and the rest of the annotations, but starts <strong>with the application container only</strong>: no <code>vault-agent-init</code>, no <code>vault-agent</code>.</li>
<li>Creating the pod <strong>takes about 30 seconds longer</strong> than usual.</li>
<li><code>kubectl get events</code> says nothing about the webhook. The injector logs do not have a single <code>Request received</code> line.</li>
<li>On other clusters (public zonal, kind, k3s) the same chart and the same annotations work.</li>
</ul>
<h2 id="why">Why</h2>
<p>On a <strong>private</strong> GKE cluster the control plane lives in a Google-owned VPC and reaches your nodes through the firewall rule GKE creates by itself (<code>gke-&lt;cluster&gt;-&lt;hash&gt;-master</code>). That rule only allows <strong>TCP 443 and 10250</strong> from the control plane range to the nodes. Everything else is closed.</p>
<p>An admission webhook does not go through the Service like a normal client would: the API server resolves the Service to its endpoints and <strong>connects straight to the pod IP on the <code>targetPort</code></strong>. The injector&rsquo;s Service (<code>vault-agent-injector-svc</code>) exposes 443, but the pod listens on <strong>8080</strong> (<code>injector.port</code> in the chart), so the actual connection goes to 8080 and the firewall drops it.</p>
<p>What happens next is explained by two chart defaults:</p>
<table>
  <thead>
      <tr>
          <th>Value</th>
          <th>Default</th>
          <th>Effect</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>injector.webhook.timeoutSeconds</code></td>
          <td><code>30</code></td>
          <td>The API server waits 30 s for the webhook&rsquo;s answer</td>
      </tr>
      <tr>
          <td><code>injector.webhook.failurePolicy</code></td>
          <td><code>Ignore</code></td>
          <td>If the webhook does not answer, the pod is created <strong>unmutated</strong>, with no error</td>
      </tr>
  </tbody>
</table>
<p>Hence the 30-second delay and the pod with no sidecar and no message. With <code>failurePolicy: Fail</code> you would have seen <code>failed calling webhook &quot;vault.hashicorp.com&quot;: … context deadline exceeded</code> in the ReplicaSet events, which at least points at the right place.</p>
<h2 id="not-just-vault">Not just Vault</h2>
<p>Any admission webhook, CRD conversion webhook or aggregated API listening on a port other than 443 or 10250 fails the same way on private GKE: the OpenTelemetry operator (9443), Kyverno (9443), Gatekeeper (8443)… cert-manager listens on <strong>10250</strong> precisely to avoid needing this rule. The GKE documentation lists it as a requirement for &ldquo;admission webhooks, aggregated API servers, webhook conversion&rdquo;: one rule per port, from <code>masterIpv4CidrBlock</code> to the node tags.</p>
<p>If the cluster lives in Terraform, keep the rule next to the cluster so it survives a rebuild:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;google_compute_firewall&#34; &#34;master_to_vault_injector&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  name</span>          <span class="o">=</span> <span class="s2">&#34;gke-${google_container_cluster.main.name}-master-to-vault-injector&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  network</span>       <span class="o">=</span> <span class="k">google_compute_network</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">name</span>
</span></span><span class="line"><span class="cl"><span class="n">  direction</span>     <span class="o">=</span> <span class="s2">&#34;INGRESS&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  source_ranges</span> <span class="o">=</span> <span class="p">[</span><span class="k">google_container_cluster</span><span class="p">.</span><span class="k">main</span><span class="p">.</span><span class="k">private_cluster_config</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="k">master_ipv4_cidr_block</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">  target_tags</span>   <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;gke-${google_container_cluster.main.name}&#34;</span><span class="p">]</span><span class="c1">   # the tag your node pools use
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">allow</span> {
</span></span><span class="line"><span class="cl"><span class="n">    protocol</span> <span class="o">=</span> <span class="s2">&#34;tcp&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    ports</span>    <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;8080&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="traps">Traps</h2>
<ul>
<li><strong>The wrong tag</strong>: nodes carry <code>gke-&lt;cluster&gt;-&lt;hash&gt;-node</code>; copy it from the existing <code>gke-&lt;cluster&gt;-*</code> rules instead of typing it. With several node pools, check that all of them have the tag.</li>
<li><strong>The control plane range changes if you recreate the cluster</strong>; a hand-written rule with the old CIDR silently stops working.</li>
<li><strong><code>failurePolicy: Fail</code></strong> makes the failure visible, but while the webhook is down <strong>no pod gets created</strong> in the namespaces matching the <code>namespaceSelector</code>; the chart excludes the injector itself, but not Vault if they share namespace and labels. Use it with <code>replicas: 2</code>.</li>
<li><strong>The injector does receive the request and there is still no sidecar</strong>: then it is no longer the network. Read the <code>vault-agent-init</code> logs: <code>permission denied</code> is the Kubernetes auth role (<code>bound_service_account_names</code>, <code>bound_service_account_namespaces</code>) or the policy; <code>connection refused</code> is <code>vault.hashicorp.com/service</code> or the Vault address.</li>
<li><strong>One port per rule does not scale</strong>: as webhooks pile up, one rule with <code>--rules=tcp:8080,8443,9443</code> from the control plane is easier to maintain than five.</li>
</ul>
<p>References: <a href="https://github.com/hashicorp/vault-k8s/issues/46">vault-k8s #46</a>, <a href="https://github.com/hashicorp/vault-k8s/issues/32">vault-k8s #32</a> and <a href="https://docs.cloud.google.com/kubernetes-engine/docs/how-to/private-clusters">firewall rules on private clusters</a> in the GKE documentation.</p>
<h2 id="see-also">See also</h2>
<ul>
<li><a href="/en/posts/kubernetes/cert-manager-http01-308-ingressclassname/">cert-manager: the HTTP-01 challenge returns 308</a> — another component that &ldquo;does nothing&rdquo; until you look in the right place.</li>
<li><a href="/en/posts/gcp/github-actions-workload-identity-federation/">GitHub Actions → Google Cloud without keys</a> — to deploy to that GKE without JSON keys.</li>
</ul>
]]></content:encoded></item></channel></rss>