<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kahiro Okina</title>
    <description>The latest articles on DEV Community by Kahiro Okina (@kahirokunn).</description>
    <link>https://dev.to/kahirokunn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F131958%2Fede295d7-2bc0-4a9b-b620-a354fa27fabd.png</url>
      <title>DEV Community: Kahiro Okina</title>
      <link>https://dev.to/kahirokunn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kahirokunn"/>
    <language>en</language>
    <item>
      <title>Running Istio Ambient Multicluster with kind</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Mon, 19 Jan 2026 05:45:32 +0000</pubDate>
      <link>https://dev.to/kahirokunn/running-istio-ambient-multicluster-with-kind-3m5k</link>
      <guid>https://dev.to/kahirokunn/running-istio-ambient-multicluster-with-kind-3m5k</guid>
      <description>&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create cluster1 / cluster2 using &lt;strong&gt;kind&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enable LoadBalancer support with &lt;strong&gt;cloud-provider-kind&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Configure &lt;strong&gt;Istio Ambient mode&lt;/strong&gt; in multi-primary / multi-network setup&lt;/li&gt;
&lt;li&gt;Verify cross-cluster load balancing with sample applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About This Guide
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Using Helm instead of istioctl&lt;/strong&gt;: Designed for integration with GitOps tools like ArgoCD/FluxCD&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration details&lt;/strong&gt;: For detailed information about Istio Ambient mode multicluster configuration, refer to the &lt;a href="https://istio.io/latest/blog/2025/ambient-multicluster/" rel="noopener noreferrer"&gt;official blog&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Working directory&lt;/strong&gt;: Execute all commands from the root directory of the Istio repository&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;macOS + Docker Desktop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; / &lt;code&gt;kind&lt;/code&gt; / &lt;code&gt;helm&lt;/code&gt; / &lt;code&gt;cloud-provider-kind&lt;/code&gt; installed&lt;/li&gt;
&lt;li&gt;Clone the Istio repository and work from its root directory:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  git clone https://github.com/istio/istio.git
  &lt;span class="nb"&gt;cd &lt;/span&gt;istio
  &lt;span class="c"&gt;# Execute all subsequent commands from this directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create Two kind Clusters
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; cluster1
kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; cluster2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install Gateway API CRDs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GATEWAY_API_VER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1.4.1

kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;--server-side&lt;/span&gt; &lt;span class="nt"&gt;--force-conflicts&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://github.com/kubernetes-sigs/gateway-api/releases/download/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GATEWAY_API_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/experimental-install.yaml"&lt;/span&gt;

kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;--server-side&lt;/span&gt; &lt;span class="nt"&gt;--force-conflicts&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://github.com/kubernetes-sigs/gateway-api/releases/download/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GATEWAY_API_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/experimental-install.yaml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Start cloud-provider-kind
&lt;/h3&gt;

&lt;p&gt;Launch in a separate terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;cloud-provider-kind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Configure the Same CA for Both Clusters
&lt;/h3&gt;

&lt;p&gt;For mTLS to work in a multicluster setup, we need to use the same root CA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create ns istio-system &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1
kubectl create ns istio-system &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2

&lt;span class="c"&gt;# Use sample certificates from samples/certs/&lt;/span&gt;
kubectl create secret generic cacerts &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;samples/certs/ca-cert.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;samples/certs/ca-key.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;samples/certs/root-cert.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;samples/certs/cert-chain.pem

kubectl create secret generic cacerts &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;samples/certs/ca-cert.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;samples/certs/ca-key.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;samples/certs/root-cert.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;samples/certs/cert-chain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Install Istio
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;cluster1:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm upgrade &lt;span class="nt"&gt;--install&lt;/span&gt; istio-base manifests/charts/base &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system &lt;span class="nt"&gt;--kube-context&lt;/span&gt; kind-cluster1

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' | helm upgrade --install istiod manifests/charts/istio-control/istio-discovery &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -n istio-system --kube-context kind-cluster1 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -f manifests/helm-profiles/ambient.yaml -f -
global:
  meshID: mesh1
  multiCluster:
    clusterName: cluster1
  network: network1
pilot:
  env:
    AMBIENT_ENABLE_MULTI_NETWORK: "true"
    ENABLE_WILDCARD_HOST_SERVICE_ENTRIES_FOR_TLS: "true"
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' | helm upgrade --install istio-cni manifests/charts/istio-cni &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -n istio-system --kube-context kind-cluster1 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -f manifests/helm-profiles/ambient.yaml -f -
global:
  meshID: mesh1
  multiCluster:
    clusterName: cluster1
  network: network1
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' | helm upgrade --install ztunnel manifests/charts/ztunnel &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -n istio-system --kube-context kind-cluster1 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -f manifests/helm-profiles/ambient.yaml -f -
global:
  meshID: mesh1
  multiCluster:
    clusterName: cluster1
  network: network1
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;kubectl label ns istio-system &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 topology.istio.io/network&lt;span class="o"&gt;=&lt;/span&gt;network1 &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;cluster2:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm upgrade &lt;span class="nt"&gt;--install&lt;/span&gt; istio-base manifests/charts/base &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system &lt;span class="nt"&gt;--kube-context&lt;/span&gt; kind-cluster2

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' | helm upgrade --install istiod manifests/charts/istio-control/istio-discovery &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -n istio-system --kube-context kind-cluster2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -f manifests/helm-profiles/ambient.yaml -f -
global:
  meshID: mesh1
  multiCluster:
    clusterName: cluster2
  network: network2
pilot:
  env:
    AMBIENT_ENABLE_MULTI_NETWORK: "true"
    ENABLE_WILDCARD_HOST_SERVICE_ENTRIES_FOR_TLS: "true"
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' | helm upgrade --install istio-cni manifests/charts/istio-cni &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -n istio-system --kube-context kind-cluster2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -f manifests/helm-profiles/ambient.yaml -f -
global:
  meshID: mesh1
  multiCluster:
    clusterName: cluster2
  network: network2
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' | helm upgrade --install ztunnel manifests/charts/ztunnel &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -n istio-system --kube-context kind-cluster2 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
  -f manifests/helm-profiles/ambient.yaml -f -
global:
  meshID: mesh1
  multiCluster:
    clusterName: cluster2
  network: network2
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;kubectl label ns istio-system &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 topology.istio.io/network&lt;span class="o"&gt;=&lt;/span&gt;network2 &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Exchange Remote Secrets
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Handle differences between macOS and Linux base64 commands&lt;/span&gt;
b64dec&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--help&lt;/span&gt; 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'--decode'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Get IP addresses of kind control-plane nodes&lt;/span&gt;
&lt;span class="nv"&gt;SERVER_CLUSTER1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker inspect &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'&lt;/span&gt; cluster1-control-plane&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:6443"&lt;/span&gt;
&lt;span class="nv"&gt;SERVER_CLUSTER2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker inspect &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s1"&gt;'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'&lt;/span&gt; cluster2-control-plane&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:6443"&lt;/span&gt;

&lt;span class="c"&gt;# Create service account token for cluster1&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: Secret
metadata:
  name: istio-reader-service-account-istio-remote-secret-token
  namespace: istio-system
  annotations:
    kubernetes.io/service-account.name: istio-reader-service-account
type: kubernetes.io/service-account-token
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Wait for token generation&lt;/span&gt;
&lt;span class="k"&gt;until &lt;/span&gt;kubectl get &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system secret istio-reader-service-account-istio-remote-secret-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.token}'&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; .&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nv"&gt;TOKEN_CLUSTER1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system secret istio-reader-service-account-istio-remote-secret-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.token}'&lt;/span&gt; | b64dec&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CA_B64_CLUSTER1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system secret istio-reader-service-account-istio-remote-secret-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.ca\.crt}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create secret in cluster2 to access cluster1&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Secret
metadata:
  name: istio-remote-secret-cluster1
  namespace: istio-system
  labels:
    istio/multiCluster: "true"
  annotations:
    networking.istio.io/cluster: cluster1
stringData:
  cluster1: |
    apiVersion: v1
    kind: Config
    clusters:
    - name: cluster1
      cluster:
        server: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVER_CLUSTER1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
        certificate-authority-data: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CA_B64_CLUSTER1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    contexts:
    - name: cluster1
      context:
        cluster: cluster1
        user: cluster1
    current-context: cluster1
    users:
    - name: cluster1
      user:
        token: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN_CLUSTER1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Create service account token for cluster2&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: Secret
metadata:
  name: istio-reader-service-account-istio-remote-secret-token
  namespace: istio-system
  annotations:
    kubernetes.io/service-account.name: istio-reader-service-account
type: kubernetes.io/service-account-token
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Wait for token generation&lt;/span&gt;
&lt;span class="k"&gt;until &lt;/span&gt;kubectl get &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system secret istio-reader-service-account-istio-remote-secret-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.token}'&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; .&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;1
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nv"&gt;TOKEN_CLUSTER2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system secret istio-reader-service-account-istio-remote-secret-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.token}'&lt;/span&gt; | b64dec&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;CA_B64_CLUSTER2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system secret istio-reader-service-account-istio-remote-secret-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.data.ca\.crt}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Create secret in cluster1 to access cluster2&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;--server-side&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Secret
metadata:
  name: istio-remote-secret-cluster2
  namespace: istio-system
  labels:
    istio/multiCluster: "true"
  annotations:
    networking.istio.io/cluster: cluster2
stringData:
  cluster2: |
    apiVersion: v1
    kind: Config
    clusters:
    - name: cluster2
      cluster:
        server: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVER_CLUSTER2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
        certificate-authority-data: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CA_B64_CLUSTER2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    contexts:
    - name: cluster2
      context:
        cluster: cluster2
        user: cluster2
    current-context: cluster2
    users:
    - name: cluster2
      user:
        token: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOKEN_CLUSTER2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Create East-West Gateways
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;--server-side&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: istio-eastwestgateway
  namespace: istio-system
  labels:
    topology.istio.io/network: "network1"
spec:
  gatewayClassName: "istio-east-west"
  listeners:
    - name: mesh
      port: 15008
      protocol: HBONE
      tls:
        mode: Terminate
        options:
          gateway.istio.io/tls-terminate-mode: ISTIO_MUTUAL
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;--server-side&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: istio-eastwestgateway
  namespace: istio-system
  labels:
    topology.istio.io/network: "network2"
spec:
  gatewayClassName: "istio-east-west"
  listeners:
    - name: mesh
      port: 15008
      protocol: HBONE
      tls:
        mode: Terminate
        options:
          gateway.istio.io/tls-terminate-mode: ISTIO_MUTUAL
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for EXTERNAL-IP assignment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;c &lt;span class="k"&gt;in &lt;/span&gt;kind-cluster1 kind-cluster2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== waiting EXTERNAL-IP for &lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;until &lt;/span&gt;kubectl &lt;span class="nt"&gt;--context&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system get svc istio-eastwestgateway &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.loadBalancer.ingress[0].ip}'&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Eq&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;2
  &lt;span class="k"&gt;done
  &lt;/span&gt;kubectl &lt;span class="nt"&gt;--context&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; istio-system get svc istio-eastwestgateway
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Verification
&lt;/h3&gt;

&lt;p&gt;Create sample namespace and enable ambient mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create ns sample &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1
kubectl create ns sample &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2

kubectl label ns sample &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 istio.io/dataplane-mode&lt;span class="o"&gt;=&lt;/span&gt;ambient &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
kubectl label ns sample &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 istio.io/dataplane-mode&lt;span class="o"&gt;=&lt;/span&gt;ambient &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy helloworld (v1 to cluster1, v2 to cluster2):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create Service in both clusters&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; sample &lt;span class="nt"&gt;-f&lt;/span&gt; samples/helloworld/helloworld.yaml &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;helloworld
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-n&lt;/span&gt; sample &lt;span class="nt"&gt;-f&lt;/span&gt; samples/helloworld/helloworld.yaml &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;helloworld

&lt;span class="c"&gt;# Create Deployment with different versions in each cluster&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; sample &lt;span class="nt"&gt;-f&lt;/span&gt; samples/helloworld/helloworld.yaml &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-n&lt;/span&gt; sample &lt;span class="nt"&gt;-f&lt;/span&gt; samples/helloworld/helloworld.yaml &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v2

&lt;span class="c"&gt;# Mark as global service (enable cross-cluster communication)&lt;/span&gt;
kubectl label svc helloworld &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; sample istio.io/global&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
kubectl label svc helloworld &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-n&lt;/span&gt; sample istio.io/global&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--overwrite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; sample &lt;span class="nt"&gt;-f&lt;/span&gt; samples/curl/curl.yaml
kubectl apply &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-n&lt;/span&gt; sample &lt;span class="nt"&gt;-f&lt;/span&gt; samples/curl/curl.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify cross-cluster load balancing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Call helloworld from curl in cluster1&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..10&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; sample deploy/curl &lt;span class="nt"&gt;-c&lt;/span&gt; curl &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"helloworld.sample:5000/hello"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Also verify from cluster2&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..10&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster2 &lt;span class="nt"&gt;-n&lt;/span&gt; sample deploy/curl &lt;span class="nt"&gt;-c&lt;/span&gt; curl &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"helloworld.sample:5000/hello"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success if you see a mix of v1 and v2 responses.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;1..10&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--context&lt;/span&gt; kind-cluster1 &lt;span class="nt"&gt;-n&lt;/span&gt; sample deploy/curl &lt;span class="nt"&gt;-c&lt;/span&gt; curl &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"helloworld.sample:5000/hello"&lt;/span&gt;
&lt;span class="k"&gt;done
&lt;/span&gt;Hello version: v1, instance: helloworld-v1-696f8879d6-w6g89
Hello version: v2, instance: helloworld-v2-59fc9f4558-g8r8b
Hello version: v1, instance: helloworld-v1-696f8879d6-w6g89
Hello version: v2, instance: helloworld-v2-59fc9f4558-g8r8b
Hello version: v1, instance: helloworld-v1-696f8879d6-w6g89
Hello version: v2, instance: helloworld-v2-59fc9f4558-g8r8b
Hello version: v1, instance: helloworld-v1-696f8879d6-w6g89
Hello version: v2, instance: helloworld-v2-59fc9f4558-g8r8b
Hello version: v2, instance: helloworld-v2-59fc9f4558-g8r8b
Hello version: v2, instance: helloworld-v2-59fc9f4558-g8r8b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;cloud-provider-kind not running&lt;/strong&gt;: EXTERNAL-IP remains &lt;code&gt;&amp;lt;pending&amp;gt;&lt;/code&gt; and never gets assigned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CA mismatch&lt;/strong&gt;: Errors like &lt;code&gt;curl: (56) Recv failure: Connection reset by peer&lt;/code&gt; occur&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing &lt;code&gt;istio.io/global=true&lt;/code&gt; label&lt;/strong&gt;: Cross-cluster communication won't happen unless applied to Services in both clusters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running outside the Istio repository&lt;/strong&gt;: Errors occur when &lt;code&gt;samples/&lt;/code&gt; or &lt;code&gt;manifests/&lt;/code&gt; directories are not found&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ambientmesh.io/docs/setup/multicluster/" rel="noopener noreferrer"&gt;Run a multi-cluster mesh – Ambient Mesh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://istio.io/latest/blog/2025/ambient-multicluster/" rel="noopener noreferrer"&gt;Istio / Introducing multicluster support for ambient mode (alpha)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://istio.io/latest/docs/ambient/install/multicluster/" rel="noopener noreferrer"&gt;Istio / Install Multicluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gateway-api.sigs.k8s.io/" rel="noopener noreferrer"&gt;Gateway API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>istio</category>
      <category>servicemesh</category>
      <category>network</category>
    </item>
    <item>
      <title>Extending Knative Service with Envoy Gateway Integration</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Tue, 23 Dec 2025 01:51:15 +0000</pubDate>
      <link>https://dev.to/kahirokunn/extending-knative-service-with-envoy-gateway-integration-56ak</link>
      <guid>https://dev.to/kahirokunn/extending-knative-service-with-envoy-gateway-integration-56ak</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This article demonstrates how to run Knative Serving in combination with Envoy Gateway and how to extend its functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;The following tools must be installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kind&lt;/li&gt;
&lt;li&gt;kubectl&lt;/li&gt;
&lt;li&gt;helm&lt;/li&gt;
&lt;li&gt;ko&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Installing cloud-provider-kind
&lt;/h3&gt;

&lt;p&gt;cloud-provider-kind is a tool that enables LoadBalancer Services in kind clusters. Install it with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;sigs.k8s.io/cloud-provider-kind@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installing ko
&lt;/h3&gt;

&lt;p&gt;ko is a tool for building and deploying applications written in Go as container images. Install it with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/google/ko@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create a kind Cluster
&lt;/h3&gt;

&lt;p&gt;First, create a local Kubernetes cluster with kind.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind create cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Install Knative Serving
&lt;/h3&gt;

&lt;p&gt;Install the Knative Serving CRDs and core components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/knative/serving/releases/latest/download/serving-crds.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/knative/serving/releases/latest/download/serving-core.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Configure Knative Serving for Gateway API
&lt;/h3&gt;

&lt;p&gt;Modify the Knative Serving network configuration to use Gateway API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch configmap/config-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; knative-serving &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; merge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"data":{"ingress.class":"gateway-api.ingress.networking.knative.dev"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Create values File for Envoy Gateway
&lt;/h3&gt;

&lt;p&gt;Create a configuration file for Envoy Gateway. This configuration deploys Envoy Proxy in the same namespace where the Gateway is deployed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; values-eg.yaml
config:
  envoyGateway:
    provider:
      type: Kubernetes
      kubernetes:
        deploy:
          type: GatewayNamespace
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Install Envoy Gateway
&lt;/h3&gt;

&lt;p&gt;Install Envoy Gateway using Helm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ENVOY_GATEWAY_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1.6.1
helm &lt;span class="nb"&gt;install &lt;/span&gt;eg oci://docker.io/envoyproxy/gateway-helm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="nv"&gt;$ENVOY_GATEWAY_VERSION&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; envoy-gateway-system &lt;span class="nt"&gt;--create-namespace&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; ./values-eg.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Start cloud-provider-kind
&lt;/h3&gt;

&lt;p&gt;To enable LoadBalancer Services, open a &lt;strong&gt;separate terminal&lt;/strong&gt; and start cloud-provider-kind. This process needs to remain running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run in a separate terminal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;cloud-provider-kind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave this terminal running and return to the original terminal to continue with the following steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Clone the net-gateway-api Repository
&lt;/h3&gt;

&lt;p&gt;Clone the repository containing resources for integrating Knative Serving with Gateway API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/knative-extensions/net-gateway-api.git
&lt;span class="nb"&gt;cd &lt;/span&gt;net-gateway-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Deploy Gateway API Integration Components
&lt;/h3&gt;

&lt;p&gt;Use ko to deploy the components that integrate Knative Serving with Gateway API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KO_DOCKER_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kind.local
ko apply &lt;span class="nt"&gt;-f&lt;/span&gt; config/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If you created your KinD cluster with the &lt;code&gt;--name&lt;/code&gt; option (e.g., &lt;code&gt;kind create cluster --name my-cluster&lt;/code&gt;), you need to additionally set the &lt;code&gt;KIND_CLUSTER_NAME&lt;/code&gt; environment variable as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KO_DOCKER_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kind.local
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KIND_CLUSTER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-cluster
ko apply &lt;span class="nt"&gt;-f&lt;/span&gt; config/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  9. Deploy Gateway API Resources
&lt;/h3&gt;

&lt;p&gt;Deploy the Gateway resources for Knative Serving.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; ./third_party/envoy-gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  10. Apply Gateway Configuration ConfigMap
&lt;/h3&gt;

&lt;p&gt;Add configuration so that Knative Serving recognizes Envoy Gateway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: config-gateway
  namespace: knative-serving
  labels:
    app.kubernetes.io/component: net-gateway-api
    app.kubernetes.io/name: knative-serving
    serving.knative.dev/release: devel
data:
  external-gateways: |
    - class: eg-external
      gateway: eg-external/eg-external
      service: eg-external/knative-external
      supported-features:
      - HTTPRouteRequestTimeout

  # local-gateways defines the Gateway to be used for cluster local traffic
  local-gateways: |
    - class: eg-internal
      gateway: eg-internal/eg-internal
      service: eg-internal/knative-internal
      supported-features:
      - HTTPRouteRequestTimeout
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  11. Configure Domain
&lt;/h3&gt;

&lt;p&gt;Set up a test domain configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch configmap config-domain &lt;span class="nt"&gt;-n&lt;/span&gt; knative-serving &lt;span class="nt"&gt;--type&lt;/span&gt; merge &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"data":{"example.com":""}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  12. Deploy Sample Application
&lt;/h3&gt;

&lt;p&gt;Deploy a sample application for testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;' | kubectl apply -f -
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-go
spec:
  template:
    spec:
      containers:
      - image: gcr.io/knative-samples/helloworld-go
        env:
        - name: TARGET
          value: Go Sample v1
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Get LoadBalancer IP and Configure /etc/hosts
&lt;/h3&gt;

&lt;p&gt;Get the LoadBalancer IP address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LB_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; eg-external get svc knative-external &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.loadBalancer.ingress[*].ip}{"\n"}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$LB_IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the obtained IP address to &lt;code&gt;/etc/hosts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LB_IP&lt;/span&gt;&lt;span class="s2"&gt; helloworld-go.default.example.com"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Access Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://helloworld-go.default.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If deployed successfully, you should receive a response like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello Go Sample v1!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced: Extending Knative Service with Envoy Gateway
&lt;/h2&gt;

&lt;p&gt;One advantage of using Envoy Gateway is the ability to add functionality using resources like SecurityPolicy without modifying the Knative Service itself. Here's an example of adding Basic authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Basic Authentication
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Create Authentication Credentials
&lt;/h4&gt;

&lt;p&gt;First, create an authentication file with the htpasswd command (username: &lt;code&gt;foo&lt;/code&gt;, password: &lt;code&gt;bar&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;htpasswd &lt;span class="nt"&gt;-cbs&lt;/span&gt; .htpasswd foo bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Create Secret
&lt;/h4&gt;

&lt;p&gt;Register the created authentication file as a Kubernetes Secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; default create secret generic basic-auth &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.htpasswd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Apply SecurityPolicy
&lt;/h4&gt;

&lt;p&gt;Use Envoy Gateway's SecurityPolicy resource to add Basic authentication to the HTTPRoute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;' | kubectl apply -f -
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
  name: helloworld-go-basic-auth
  namespace: default
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: helloworld-go.default.example.com
  basicAuth:
    users:
      name: basic-auth
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Verification
&lt;/h4&gt;

&lt;p&gt;Accessing without authentication returns a 401 error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://helloworld-go.default.example.com
&lt;span class="c"&gt;# 401 Unauthorized&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accessing with authentication credentials returns a successful response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-u&lt;/span&gt; foo:bar http://helloworld-go.default.example.com
&lt;span class="c"&gt;# Hello Go Sample v1!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This demonstrates how Basic authentication can be easily added using Envoy Gateway's SecurityPolicy without modifying the Knative Service or HTTPRoute manifests at all. Similarly, various features such as rate limiting, CORS configuration, JWT verification, etc., can be added at the Gateway layer, so please refer to &lt;a href="https://gateway.envoyproxy.io/latest/tasks/security/" rel="noopener noreferrer"&gt;Security | Envoy Gateway&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This article introduced how to run Knative Serving in combination with Envoy Gateway.&lt;/p&gt;

&lt;p&gt;Envoy Gateway is a high-performance official Envoy gateway implementation, and its combination with Knative Serving enables flexible control and extension of serverless application routing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://knative.dev/docs/serving/" rel="noopener noreferrer"&gt;Knative Serving&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gateway.envoyproxy.io/latest/tasks/security/basic-auth/" rel="noopener noreferrer"&gt;Basic Authentication | Envoy Gateway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/knative-extensions/net-gateway-api" rel="noopener noreferrer"&gt;knative-extensions/net-gateway-api&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gateway-api.sigs.k8s.io/" rel="noopener noreferrer"&gt;Kubernetes Gateway API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  HTTPRoute Output When Traffic Splitting is Enabled
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networking.internal.knative.dev/rollout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"configurations":[{"configurationName":"helloworld-go","percent":50,"revisions":[{"revisionName":"helloworld-go-00005","percent":50}],"stepParams":{}},{"configurationName":"helloworld-go","tag":"qa","percent":100,"revisions":[{"revisionName":"helloworld-go-00005","percent":100}],"stepParams":{}}]}'&lt;/span&gt;
    &lt;span class="na"&gt;networking.knative.dev/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway-api.ingress.networking.knative.dev&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/creator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-admin&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/lastModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-admin&lt;/span&gt;
  &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-11-09T05:41:51Z"&lt;/span&gt;
  &lt;span class="na"&gt;generation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;33&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networking.knative.dev/visibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/routeNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go.default.example.com&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;ownerReferences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.internal.knative.dev/v1alpha1&lt;/span&gt;
    &lt;span class="na"&gt;blockOwnerDeletion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;controller&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
    &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;f15718d7-91ba-482f-8322-27968a218697&lt;/span&gt;
  &lt;span class="na"&gt;resourceVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;220984"&lt;/span&gt;
  &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;07fca6bb-1e7a-4a53-8e09-107df56fcb25&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hostnames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;helloworld-go.default.example.com&lt;/span&gt;
  &lt;span class="na"&gt;parentRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gateway&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eg-external&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eg-external&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;backendRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Knative-Serving-Namespace&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Knative-Serving-Revision&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go-00003&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestHeaderModifier&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go-00003&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Knative-Serving-Namespace&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Knative-Serving-Revision&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go-00005&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestHeaderModifier&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go-00005&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
    &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K-Network-Hash&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;f3149388225bc6465417757efc25ea3b739741e2037cf7a5fc49772260fc882f&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestHeaderModifier&lt;/span&gt;
    &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K-Network-Hash&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Exact&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;override&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PathPrefix&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;timeouts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0s&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;backendRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Knative-Serving-Namespace&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Knative-Serving-Revision&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go-00003&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestHeaderModifier&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go-00003&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Knative-Serving-Namespace&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Knative-Serving-Revision&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go-00005&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestHeaderModifier&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go-00005&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
    &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PathPrefix&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;timeouts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0s&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;parents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;lastTransitionTime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-11-10T04:00:42Z"&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Route is accepted&lt;/span&gt;
      &lt;span class="na"&gt;observedGeneration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;33&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Accepted&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True"&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Accepted&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;lastTransitionTime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-11-10T04:00:42Z"&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Resolved all the Object references for the Route&lt;/span&gt;
      &lt;span class="na"&gt;observedGeneration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;33&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ResolvedRefs&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True"&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ResolvedRefs&lt;/span&gt;
    &lt;span class="na"&gt;controllerName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.envoyproxy.io/gatewayclass-controller&lt;/span&gt;
    &lt;span class="na"&gt;parentRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gateway&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eg-external&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eg-external&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  HTTPRoute Output When Using DomainMapping
&lt;/h3&gt;

&lt;p&gt;Apply the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.internal.knative.dev/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterDomainClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test.com&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serving.knative.dev/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DomainMapping&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test.com&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serving.knative.dev/v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following is output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networking.knative.dev/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway-api.ingress.networking.knative.dev&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/creator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-admin&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/lastModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-admin&lt;/span&gt;
  &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-11-10T04:48:55Z"&lt;/span&gt;
  &lt;span class="na"&gt;generation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;networking.knative.dev/visibility&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/domainMappingNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/domainMappingUID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;88954e71-03c4-4004-81e1-2916c6ba976c&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test.com&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;ownerReferences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.internal.knative.dev/v1alpha1&lt;/span&gt;
    &lt;span class="na"&gt;blockOwnerDeletion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;controller&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test.com&lt;/span&gt;
    &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;d7351ea1-9e67-4ebc-80cb-4227195362c1&lt;/span&gt;
  &lt;span class="na"&gt;resourceVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;231722"&lt;/span&gt;
  &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;45f70e96-0f30-4cda-b1a2-f6f1a25dad0b&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hostnames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test.com&lt;/span&gt;
  &lt;span class="na"&gt;parentRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gateway&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eg-external&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eg-external&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;backendRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K-Original-Host&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test.com&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestHeaderModifier&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
    &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K-Network-Hash&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;244b84148c16d15938fd7f3ab2d377b0b40f6418229edee9af3fe0d961fd152a&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestHeaderModifier&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;URLRewrite&lt;/span&gt;
      &lt;span class="na"&gt;urlRewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go.default.svc.cluster.local&lt;/span&gt;
    &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K-Network-Hash&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Exact&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;override&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PathPrefix&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;timeouts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0s&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;backendRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;requestHeaderModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K-Original-Host&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test.com&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestHeaderModifier&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
    &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;URLRewrite&lt;/span&gt;
      &lt;span class="na"&gt;urlRewrite&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go.default.svc.cluster.local&lt;/span&gt;
    &lt;span class="na"&gt;matches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PathPrefix&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;timeouts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0s&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;parents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;lastTransitionTime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-11-10T04:48:55Z"&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Route is accepted&lt;/span&gt;
      &lt;span class="na"&gt;observedGeneration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Accepted&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True"&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Accepted&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;lastTransitionTime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-11-10T04:48:55Z"&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Resolved all the Object references for the Route&lt;/span&gt;
      &lt;span class="na"&gt;observedGeneration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ResolvedRefs&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;True"&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ResolvedRefs&lt;/span&gt;
    &lt;span class="na"&gt;controllerName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.envoyproxy.io/gatewayclass-controller&lt;/span&gt;
    &lt;span class="na"&gt;parentRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gateway&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eg-external&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eg-external&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;helloworld-go.default.svc.cluster.local&lt;/code&gt; points to the Envoy Proxy of the internal Envoy Gateway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/creator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-admin&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/lastModifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-admin&lt;/span&gt;
  &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-11-09T05:41:51Z"&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
    &lt;span class="na"&gt;serving.knative.dev/service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;ownerReferences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serving.knative.dev/v1&lt;/span&gt;
    &lt;span class="na"&gt;blockOwnerDeletion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;controller&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Route&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helloworld-go&lt;/span&gt;
    &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;24cc3f82-f7b3-40d9-89ce-ea516e46382f&lt;/span&gt;
  &lt;span class="na"&gt;resourceVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;56240"&lt;/span&gt;
  &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c6ea6306-abf9-49c0-8a28-9a71c9a3a99a&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;externalName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;knative-internal.eg-internal.svc.cluster.local&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;appProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes.io/h2c&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;sessionAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ExternalName&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;loadBalancer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, for example, when exposed via ALB, the path from external traffic would be as follows, which includes one additional hop through the Internal Envoy Proxy compared to the typical path exposed through External Envoy:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3tsbtf42kok4yzod6yx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3tsbtf42kok4yzod6yx.gif" alt=" " width="800" height="119"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  A["ALB"]

  subgraph K8S["Kubernetes"]
    direction LR

    subgraph EXT["Namespace: eg-external"]
      B["External Envoy Proxy"]
    end

    subgraph INT["Namespace: eg-internal"]
      C["Internal Envoy Proxy"]
    end

    subgraph KS["Namespace: knative-serving"]
      D["Knative Activator"]
    end

    subgraph P["Knative Revision Pod"]
      direction LR
      E["Queue Proxy (sidecar)"]
      F["User App Container"]
    end
  end

  A e1@==&amp;gt; B
  B e2@==&amp;gt; C
  C e3@==&amp;gt; D
  D e4@==&amp;gt; E
  E e5@==&amp;gt; F

  e1@{ animate: true }
  e2@{ animate: true }
  e3@{ animate: true }
  e4@{ animate: true }
  e5@{ animate: true }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>knative</category>
      <category>kubernetes</category>
      <category>serverless</category>
      <category>envoy</category>
    </item>
    <item>
      <title>[Gateway API] How to Extend Existing HTTPRoute Without Modification Using GEP-1294</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Thu, 23 Oct 2025 01:14:03 +0000</pubDate>
      <link>https://dev.to/kahirokunn/gateway-api-how-to-extend-existing-httproute-without-modification-using-gep-1294-5ddo</link>
      <guid>https://dev.to/kahirokunn/gateway-api-how-to-extend-existing-httproute-without-modification-using-gep-1294-5ddo</guid>
      <description>&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Please use a Service Mesh such as Istio that implements GEP-1294: xRoutes Mesh Binding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Without using &lt;a href="https://dev.to/kahirokunn/shadow-service-pattern-4246"&gt;Shadow Service&lt;/a&gt;, you can &lt;strong&gt;extend existing HTTPRoute without touching a single line of YAML&lt;/strong&gt; using &lt;strong&gt;only Gateway API standard features&lt;/strong&gt;. The key point is the specification that allows you to &lt;strong&gt;specify a Kubernetes &lt;code&gt;Service&lt;/code&gt; in the &lt;code&gt;parentRefs&lt;/code&gt; of HTTPRoute (&lt;a href="https://gateway-api.sigs.k8s.io/geps/gep-1294" rel="noopener noreferrer"&gt;GEP-1294&lt;/a&gt;)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;According to GEP-1294's &lt;em&gt;Route presence&lt;/em&gt;, when you create an &lt;code&gt;xRoute&lt;/code&gt; with a Service as the &lt;code&gt;parentRef&lt;/code&gt;, the implicit delivery (normal Service→Endpoints routing) for that Service is not "added" but &lt;strong&gt;can be replaced by the Route&lt;/strong&gt;. Furthermore, &lt;em&gt;Namespace boundaries&lt;/em&gt; states that a &lt;strong&gt;producer route&lt;/strong&gt; with a Service in the same Namespace as &lt;code&gt;parentRef&lt;/code&gt; SHOULD be applied to &lt;strong&gt;all inbound requests&lt;/strong&gt; to that Service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You can customize even when existing tools don't provide fine-grained HTTPRoute customization&lt;/li&gt;
&lt;li&gt;Can be achieved using only Gateway API specifications&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Illustrated Diagram
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cikdht2owdbyyekyv6r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4cikdht2owdbyyekyv6r.gif" alt="image" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Show Mermaid&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  classDef parent stroke-dasharray: 4 2;
  classDef note fill:#FFF9C4,stroke:#FBC02D,color:#5D4037,stroke-width:1px;

  subgraph Ingress
    GW[Gateway]
  end

  subgraph app-ns
    HR_a["HTTPRoute a (existing)"]
    SVC_a[(Service a)]
    DEP_a[Deployment a]
  end

  subgraph ext-ns
    HR_b["HTTPRoute b (new)"]
    SVC_b[(Service b)]
    DEP_b[Deployment b]
  end

  GW e1@--&amp;amp;gt; HR_a
  HR_a -.parent ref: registration.-&amp;amp;gt; GW
  HR_a e2@--&amp;amp;gt; SVC_a
  SVC_a -."selector: Note 1".-x DEP_a
  SVC_a e4@--&amp;amp;gt; HR_b
  HR_b -."parentRef: override".-&amp;amp;gt; SVC_a
  HR_b e5@--&amp;amp;gt; SVC_b
  SVC_b e6@--&amp;amp;gt; DEP_b

  subgraph Notes
    direction TB
    N1["Note 1: A Route that uses a Service as parentRef **replaces** the implicit routing of that Service (GEP-1294 'Route presence'). Therefore, the selector on the Service side does not affect the final destination determination."]:::note
  end

  e1@{ animate: true }
  e2@{ animate: true }
  e4@{ animate: true }
  e5@{ animate: true }
  e6@{ animate: true }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Without modifying HTTPRoute managed by a Controller, you can switch the destination Service or add necessary L7 controls (match/header manipulation/retry/split, etc.) using another HTTPRoute with &lt;code&gt;Service&lt;/code&gt; as parent&lt;/li&gt;
&lt;li&gt;"I want to control all inbound traffic to that Service" → &lt;strong&gt;producer route&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;"I want to redirect only outbound traffic from my Namespace" → &lt;strong&gt;consumer route&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Additional Explanation
&lt;/h3&gt;

&lt;p&gt;In GEP-1294, the meaning changes based on the &lt;strong&gt;relationship between the Namespace of the Service pointed to by &lt;code&gt;parentRef&lt;/code&gt; and the Namespace of the Route&lt;/strong&gt;. There is no explicit field in the API; this is purely a distinction in &lt;strong&gt;behavior&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This enables the following scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you want to control the entire ingress point, define a &lt;strong&gt;producer route&lt;/strong&gt; on the target Service side&lt;/li&gt;
&lt;li&gt;When you want to control only outbound traffic from your Namespace, create a &lt;strong&gt;consumer route&lt;/strong&gt; in a different Namespace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In both cases, this can be achieved by &lt;strong&gt;adding routes without touching existing HTTPRoute&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  producer route (control ingress side)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Application conditions:&lt;/strong&gt;&lt;br&gt;
When &lt;code&gt;parentRef&lt;/code&gt; points to a Service in the &lt;strong&gt;same Namespace&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavior:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SHOULD be applied to &lt;strong&gt;all inbound requests&lt;/strong&gt; to that Service (including calls from other Namespaces)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route rules take precedence&lt;/strong&gt; over the Service's default routing&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Note:&lt;/em&gt; Some implementations apply this at the client-side proxy, so it may not affect clients outside the mesh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The diagram shown earlier illustrates this producer route.&lt;/p&gt;

&lt;h4&gt;
  
  
  consumer route (control egress side)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Application conditions:&lt;/strong&gt;&lt;br&gt;
When &lt;code&gt;parentRef&lt;/code&gt; points to a Service in a &lt;strong&gt;different Namespace&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavior:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applied only to &lt;strong&gt;egress traffic from within the Namespace where the Route exists&lt;/strong&gt; (limited scope)&lt;/li&gt;
&lt;li&gt;Provides boundaries to prevent unrelated third-party Namespaces from arbitrarily changing traffic between different Namespaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;① A certain namespace has &lt;code&gt;HTTPRoute a&lt;/code&gt; applied (consumer route / normal path)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw26kp6fadqxd5s18fq8m.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw26kp6fadqxd5s18fq8m.gif" alt="image" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  classDef note fill:#FFF9C4,stroke:#FBC02D,color:#5D4037,stroke-width:1px;

  subgraph app-ns
    SVC_a[(Service a)]
    DEP_a[Deployment a]
  end

  subgraph other-ns["other-ns (e.g., some ns)"]
    CALL_other["Pod/Sidecar (caller)"]
    HR_a["HTTPRoute a (consumer)"]
  end

  CALL_other e1@--&amp;amp;gt; HR_a
  HR_a -."parentRef: app-ns / Service a (different NS ↔ consumer)".-&amp;amp;gt; SVC_a
  HR_a e2@--&amp;amp;gt; SVC_a
  SVC_a e3@--&amp;amp;gt; DEP_a

  e1@{ animate: true }
  e2@{ animate: true }
  e3@{ animate: true }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;② A specific namespace has &lt;code&gt;HTTPRoute b&lt;/code&gt; applied (consumer route / destination redirect)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijalxgqibbd1hxk1s1ju.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fijalxgqibbd1hxk1s1ju.gif" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  classDef note fill:#FFF9C4,stroke:#FBC02D,color:#5D4037,stroke-width:1px;

  subgraph app-ns
    SVC_a[(Service a)]
  end

  subgraph ext-ns
    SVC_b[(Service b)]
    DEP_b[Deployment b]
  end

  subgraph dev-ns["dev-ns (specific ns)"]
    CALL_dev["Pod/Sidecar (caller)"]
    HR_b["HTTPRoute b (consumer)"]
  end

  CALL_dev e1@--&amp;amp;gt; HR_b
  HR_b -."parentRef: app-ns / Service a (different NS ↔ consumer)".-&amp;amp;gt; SVC_a
  HR_b e2@--&amp;amp;gt; SVC_b
  SVC_b e3@--&amp;amp;gt; DEP_b

  e1@{ animate: true }
  e2@{ animate: true }
  e3@{ animate: true }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Set up HTTPRoute normally with Gateway API
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gateway&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gw&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-ns&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gatewayClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;istio&lt;/span&gt;
  &lt;span class="na"&gt;listeners&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example.local&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;route-a&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-ns&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hostnames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;example.local&lt;/span&gt;
  &lt;span class="na"&gt;parentRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gw&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-ns&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;backendRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;svc-a&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;svc-a&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-ns&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Without modifying the above HTTPRoute or Service, &lt;strong&gt;override the entire ingress point with a producer route&lt;/strong&gt; (place &lt;code&gt;route-b&lt;/code&gt; in &lt;code&gt;app-ns&lt;/code&gt;)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HTTPRoute&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;route-b&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-ns&lt;/span&gt;  &lt;span class="c1"&gt;# Same NS, so this is a producer route&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;parentRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;svc-a&lt;/span&gt;
      &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-ns&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;backendRefs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;svc-b&lt;/span&gt;
          &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ext-ns&lt;/span&gt;  &lt;span class="c1"&gt;# Switch to destination in different NS&lt;/span&gt;
          &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;svc-b&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ext-ns&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;b&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Since the above is a &lt;strong&gt;producer route&lt;/strong&gt; (parent reference in the same NS), the rules of &lt;code&gt;route-b&lt;/code&gt; are applied to &lt;strong&gt;all inbound requests&lt;/strong&gt; to &lt;code&gt;svc-a&lt;/code&gt;, and the destination is redirected to &lt;code&gt;svc-b&lt;/code&gt; (depending on the mesh implementation, the application point may be on the client side).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Note on ReferenceGrant
&lt;/h2&gt;

&lt;p&gt;GEP-1294 explicitly states that in the Mesh (East/West) context, Routes with &lt;code&gt;Service&lt;/code&gt; as &lt;code&gt;parentRef&lt;/code&gt; can reference &lt;strong&gt;&lt;code&gt;backendRefs&lt;/code&gt; in different Namespaces&lt;/strong&gt; and are &lt;strong&gt;allowed without &lt;code&gt;ReferenceGrant&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kubernetes-sigs/gateway-api/blob/main/geps/gep-1294/index.md#route-presence:%7E:text=Routes%20of%20either,a%20broader%20scope" rel="noopener noreferrer"&gt;https://github.com/kubernetes-sigs/gateway-api/blob/main/geps/gep-1294/index.md#route-presence:~:text=Routes%20of%20either,a%20broader%20scope&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Routes of either type can send traffic to backendRefs in any namespace. Unlike Gateway bound routes, this is allowed without a ReferenceGrant. In Gateway-bound routes (North-South), routes are opt-in; by default, no Services are exposed (often to the public internet), and a service producer must explicitly opt-in by creating a route themselves, or allowing another namespace to via ReferenceGrant. For mesh, routes augment existing Services, rather than exposing them to a broader scope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Additionally, it notes that &lt;strong&gt;access control&lt;/strong&gt; should be handled by &lt;strong&gt;mechanisms such as NetworkPolicy&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kubernetes-sigs/gateway-api/blob/main/geps/gep-1294/index.md#route-presence:%7E:text=As%20a%20result%2C%20a%20ReferenceGrant%20is%20not%20required%20in%20most%20mesh%20implementations.%20Access%20control%2C%20if%20desired%2C%20is%20handled%20by%20other%20mechanism%20such%20as%20NetworkPolicy" rel="noopener noreferrer"&gt;https://github.com/kubernetes-sigs/gateway-api/blob/main/geps/gep-1294/index.md#route-presence:~:text=As%20a%20result%2C%20a%20ReferenceGrant%20is%20not%20required%20in%20most%20mesh%20implementations.%20Access%20control%2C%20if%20desired%2C%20is%20handled%20by%20other%20mechanism%20such%20as%20NetworkPolicy&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a result, a ReferenceGrant is not required in most mesh implementations. Access control, if desired, is handled by other mechanism such as NetworkPolicy&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Therefore, the configuration in this article where a &lt;strong&gt;producer route&lt;/strong&gt; in &lt;code&gt;app-ns&lt;/code&gt; routes to &lt;code&gt;svc-b&lt;/code&gt; in &lt;code&gt;ext-ns&lt;/code&gt; &lt;strong&gt;does not require &lt;code&gt;ReferenceGrant&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I introduced a method to change the reference destination of HTTPRoute generated by other controllers using GEP-1294, achieving the same result as if you had written additional configuration in the HTTPRoute, when you cannot customize that HTTPRoute directly.&lt;/p&gt;

&lt;p&gt;I hope this serves as a reference for someone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Route presence: &lt;a href="https://gateway-api.sigs.k8s.io/geps/gep-1294/#route-presence" rel="noopener noreferrer"&gt;https://gateway-api.sigs.k8s.io/geps/gep-1294/#route-presence&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Namespace boundaries (producer / consumer boundaries): &lt;a href="https://gateway-api.sigs.k8s.io/geps/gep-1294/#namespace-boundaries" rel="noopener noreferrer"&gt;https://gateway-api.sigs.k8s.io/geps/gep-1294/#namespace-boundaries&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>gatewayapi</category>
      <category>servicemesh</category>
      <category>gep</category>
    </item>
    <item>
      <title>Shadow Service Pattern</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Thu, 23 Oct 2025 01:13:47 +0000</pubDate>
      <link>https://dev.to/kahirokunn/shadow-service-pattern-4246</link>
      <guid>https://dev.to/kahirokunn/shadow-service-pattern-4246</guid>
      <description>&lt;h1&gt;
  
  
  Shadow Service Pattern
&lt;/h1&gt;

&lt;p&gt;The Shadow Service pattern is a mechanism for communication using a Service on Kubernetes that has "no actual destination." This guide explains the specific method of creating a Service with an assigned ClusterIP (Shadow Service) and the EndpointSlice that the Service references. The key feature is that it can be completed using only standard Kubernetes features, without needing to change kube-proxy settings.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is a Shadow Service?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A Service that is linked to an arbitrary IP rather than referencing Pods running on Nodes&lt;/li&gt;
&lt;li&gt;Utilizes the Kubernetes Service object mechanism as-is, and ClusterIP can be automatically assigned&lt;/li&gt;
&lt;li&gt;Can be implemented without modifying default Kubernetes components like kube-proxy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By creating what is essentially a "dummy" Service and defining the endpoint information (such as IP and port) that becomes the actual backing for that Service yourself using EndpointSlice, you can make "external resources" accessible via ClusterIP within Kubernetes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create the Shadow Service&lt;/li&gt;
&lt;li&gt;Create the EndpointSlice&lt;/li&gt;
&lt;li&gt;Associate the Service name with the EndpointSlice label&lt;/li&gt;
&lt;li&gt;Access external resources via the ClusterIP&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Creating the Shadow Service
&lt;/h3&gt;

&lt;p&gt;Since the Shadow Service doesn't have Pods as a backend, you don't specify or disable the selector and type (NodePort, LoadBalancer, etc.) in a typical Service definition. In the following example, the name is set to "dummy" and HTTPS (443) port is defined.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: dummy
  namespace: default
spec:
  ports:
    - port: 443
      protocol: TCP
      targetPort: 443
      name: https
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage, when you execute &lt;code&gt;kubectl apply -f shadow-service.yaml&lt;/code&gt;, a ClusterIP for the Service will be automatically assigned.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Creating the EndpointSlice
&lt;/h3&gt;

&lt;p&gt;Next, define an EndpointSlice as the endpoint linked to the Shadow Service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: dummy
  namespace: default
  labels:
    kubernetes.io/service-name: dummy
addressType: IPv4
ports:
- name: https
  protocol: TCP
  port: 443
endpoints:
- addresses:
  - "93.184.215.14" # Example: IP of example.com, specify external endpoint
  conditions:
    ready: true
  zone: zone-a
  nodeName: node-a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above, "93.184.215.14" is linked to the endpoint as an external resource. By specifying &lt;code&gt;dummy&lt;/code&gt; in the &lt;code&gt;kubernetes.io/service-name&lt;/code&gt; label, it is associated with the Service &lt;code&gt;dummy&lt;/code&gt; created earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Association
&lt;/h3&gt;

&lt;p&gt;If the Shadow Service's metadata.name and the EndpointSlice's labels.kubernetes.io/service-name are the same, they are automatically associated. This connects the Service entry point (ClusterIP and port 443) with the actual external endpoint ("93.184.215.14").&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Access Overview
&lt;/h3&gt;

&lt;p&gt;Once complete, requests to the ClusterIP (e.g., 10.96.0.X) will be forwarded to the external IP specified in the EndpointSlice. From the application or user's perspective, it appears as if they are accessing a Service called "dummy," but the actual backing is an external address.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;You can test access using curl as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://dummy &lt;span class="nt"&gt;--insecure&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Host: www.example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Benefits and Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Completed within the scope of existing Kubernetes components&lt;/li&gt;
&lt;li&gt;Can obtain a ClusterIP even without having Pods&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Considerations
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Manage the availability and validity of the IP addresses specified in the EndpointSlice&lt;/li&gt;
&lt;li&gt;Verify whether other integrated Ingress Controllers or Gateway Controllers support such Shadow Services&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors" rel="noopener noreferrer"&gt;Services without selectors | Kubernetes&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>networking</category>
    </item>
    <item>
      <title>No More Self-Building Required! CoreDNS v1.12.2 Now Includes Standard Multicluster Support</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Mon, 08 Sep 2025 05:27:53 +0000</pubDate>
      <link>https://dev.to/kahirokunn/no-more-self-building-required-coredns-v1122-now-includes-standard-multicluster-support-b6b</link>
      <guid>https://dev.to/kahirokunn/no-more-self-building-required-coredns-v1122-now-includes-standard-multicluster-support-b6b</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multicluster support has been integrated into CoreDNS v1.12.2!

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/coredns/coredns/pull/7266" rel="noopener noreferrer"&gt;https://github.com/coredns/coredns/pull/7266&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;You can now resolve &lt;code&gt;clusterset.local&lt;/code&gt; by just configuring the Corefile and RBAC&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Previously, it was necessary to self-build CoreDNS by adding the &lt;a href="https://github.com/coredns/multicluster" rel="noopener noreferrer"&gt;coredns/multicluster&lt;/a&gt; plugin, but starting with &lt;strong&gt;CoreDNS v1.12.2&lt;/strong&gt;, it has been integrated into the Kubernetes plugin, allowing you to handle &lt;code&gt;clusterset.local&lt;/code&gt; with just the official image.&lt;/p&gt;

&lt;p&gt;Examples of Corefile configuration can be found in the &lt;a href="https://github.com/coredns/coredns/blob/master/plugin/kubernetes/README.md#examples" rel="noopener noreferrer"&gt;official README&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Method
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Image: Use &lt;code&gt;registry.k8s.io/coredns/coredns:v1.12.2&lt;/code&gt; or later versions&lt;/li&gt;
&lt;li&gt;Edit the &lt;strong&gt;Corefile&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Corefile Configuration
&lt;/h2&gt;

&lt;p&gt;Add &lt;code&gt;clusterset.local&lt;/code&gt; to the &lt;code&gt;kubernetes&lt;/code&gt; plugin and enable &lt;code&gt;multicluster&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubernetes cluster.local clusterset.local {
    multicluster clusterset.local
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command to directly patch the existing &lt;code&gt;kube-system/coredns&lt;/code&gt; ConfigMap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; get configmap &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system coredns &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'s/^([[:space:]]*)kubernetes cluster\.local (.*)$/\1kubernetes cluster.local clusterset.local \2\n\1   multicluster clusterset.local/'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; replace &lt;span class="nt"&gt;-f-&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Verification after application is recommended&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Granting Permissions
&lt;/h2&gt;

&lt;p&gt;Extend the ClusterRole so that CoreDNS can &lt;strong&gt;list/watch&lt;/strong&gt; &lt;code&gt;ServiceImport&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multicluster.x-k8s.io"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serviceimports"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Command to add permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch clusterrole system:coredns &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="nt"&gt;--patch&lt;/span&gt; &lt;span class="s1"&gt;'[
  {
    "op": "add",
    "path": "/rules/-",
    "value": {
      "apiGroups": ["multicluster.x-k8s.io"],
      "resources": ["serviceimports"],
      "verbs": ["list","watch"]
    }
  }
]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Rollout
&lt;/h2&gt;

&lt;p&gt;Update the CoreDNS image to v1.12.2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system &lt;span class="nb"&gt;set &lt;/span&gt;image deploy/coredns &lt;span class="nv"&gt;coredns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry.k8s.io/coredns/coredns:v1.12.2
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system rollout status deploy/coredns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check if ServiceImport is visible
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get serviceimports.multicluster.x-k8s.io &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check if &lt;code&gt;clusterset.local&lt;/code&gt; can be resolved&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From a debug pod or similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; default deploy/your-app &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'dig +short my-svc.my-namespace.svc.clusterset.local'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References &amp;amp; Extras
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CoreDNS Kubernetes Plugin Examples: &lt;a href="https://github.com/coredns/coredns/blob/master/plugin/kubernetes/README.md#examples" rel="noopener noreferrer"&gt;https://github.com/coredns/coredns/blob/master/plugin/kubernetes/README.md#examples&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: Clone &lt;a href="https://github.com/kubernetes-sigs/mcs-api/pull/126" rel="noopener noreferrer"&gt;https://github.com/kubernetes-sigs/mcs-api/pull/126&lt;/a&gt; and run &lt;code&gt;make demo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>coredns</category>
      <category>multicluster</category>
      <category>mcs</category>
    </item>
    <item>
      <title>Introduction to KEP-5339: Plugin for Credentials in ClusterProfile</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Tue, 02 Sep 2025 00:02:28 +0000</pubDate>
      <link>https://dev.to/kahirokunn/introduction-to-kep-5339-plugin-for-credentials-in-clusterprofile-36a4</link>
      <guid>https://dev.to/kahirokunn/introduction-to-kep-5339-plugin-for-credentials-in-clusterprofile-36a4</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
This article summarizes the key points of KEP-5339 "Plugin for Credentials in ClusterProfile" (SIG-Multicluster) and presents a simple implementation example using EKS.&lt;br&gt;
KEP: &lt;a href="https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/5339-clusterprofile-plugin-credentials" rel="noopener noreferrer"&gt;https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/5339-clusterprofile-plugin-credentials&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Project Introduction: ClusterProfile Credentials Plugins
&lt;/h2&gt;

&lt;p&gt;To make ClusterProfile &lt;strong&gt;Credentials Plugins&lt;/strong&gt; easily testable, I've released a repository containing ready-to-use plugins.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/labthrust/clusterprofile-credentials-plugins" rel="noopener noreferrer"&gt;labthrust/clusterprofile-credentials-plugins&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Included plugins (all implemented as single binaries in Go):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;eks-aws-auth-plugin&lt;/strong&gt;: Resolves clusters from EKS endpoints and CA, returns ExecCredential&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;secretreader-plugin&lt;/strong&gt;: Reads &lt;code&gt;data.token&lt;/code&gt; from Kubernetes Secrets, returns ExecCredential&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This repository aims to become an &lt;strong&gt;Awesome Plugins&lt;/strong&gt; style catalog. I plan to continue adding and introducing useful plugins, with &lt;strong&gt;SPIRE plugin&lt;/strong&gt; being one consideration for the future.&lt;/p&gt;

&lt;p&gt;The reason I started this project is that while &lt;strong&gt;Credentials Plugin&lt;/strong&gt; is an excellent specification, there's currently a significant hurdle of "having to create your own plugin first." Additionally, information about &lt;strong&gt;which plugins are available, how to use them, and where to find the binaries&lt;/strong&gt; is not well organized.&lt;br&gt;
Therefore, I've released this as a "collection of plugins you can immediately deploy and try" to lower the barrier for taking the first steps with ClusterProfile.&lt;/p&gt;
&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ClusterProfile's &lt;code&gt;status.credentialProviders[].cluster&lt;/code&gt; holds &lt;strong&gt;server / CA&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The controller registers &lt;strong&gt;exec plugins&lt;/strong&gt; in &lt;code&gt;cp-creds.json&lt;/code&gt; and obtains tokens using &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt; passed at runtime as input.&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;.spec.cluster.server&lt;/code&gt; as the starting point, it identifies EKS clusters and calls &lt;code&gt;aws eks get-token&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Input and output are in &lt;strong&gt;ExecCredential&lt;/strong&gt; format.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Overall Flow (Mermaid)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesc77e9i0cppfy8633rk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesc77e9i0cppfy8633rk.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
  autonumber
  participant Ctrl as Controller
  participant Prov as Credential Provider (cp-creds.json)
  participant Plugin as exec plugin&amp;lt;br/&amp;gt;(./eks-aws-auth-plugin.sh)
  participant AWS as AWS EKS API

  Ctrl-&amp;gt;&amp;gt;Prov: Load providers
  Ctrl-&amp;gt;&amp;gt;Plugin: exec providers[].execConfig.command&amp;lt;br/&amp;gt;env: KUBERNETES_EXEC_INFO
  Note right of Plugin: Extract region from server&amp;lt;br/&amp;gt;List EKS → resolve clusterName by exact endpoint match

  Plugin-&amp;gt;&amp;gt;AWS: eks list-clusters (region)
  Plugin-&amp;gt;&amp;gt;AWS: eks describe-cluster (each cluster's endpoint)
  AWS--&amp;gt;&amp;gt;Plugin: endpoint list
  Plugin-&amp;gt;&amp;gt;AWS: eks get-token (clusterName)
  AWS--&amp;gt;&amp;gt;Plugin: ExecCredential(JSON)&amp;lt;br/&amp;gt;status.token, expirationTimestamp

  Plugin--&amp;gt;&amp;gt;Ctrl: Return ExecCredential(JSON) via stdout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hands-On Example
&lt;/h2&gt;

&lt;p&gt;Complete sample: &lt;a href="https://github.com/kahirokunn/cluster-inventory-api/tree/eks-example" rel="noopener noreferrer"&gt;https://github.com/kahirokunn/cluster-inventory-api/tree/eks-example&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Start Controller
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go build  controller_example.go
./controller_example &lt;span class="nt"&gt;-clusterprofile-provider-file&lt;/span&gt; ./cp-creds.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) Contents of cp-creds.json
&lt;/h3&gt;

&lt;p&gt;This is the contents of the JSON file passed with the &lt;code&gt;-clusterprofile-provider-file&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "providers": [
    {
      "name": "eks",
      "execConfig": {
          "apiVersion": "client.authentication.k8s.io/v1beta1",
          "args": null,
          "command": "./eks-aws-auth-plugin.sh",
          "env": null,
          "provideClusterInfo": true
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3) ClusterProfile YAML State
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;multicluster.x-k8s.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterProfile&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-cluster-1&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-cluster-1&lt;/span&gt;
  &lt;span class="na"&gt;clusterManager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EKS-Fleet&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;credentialProviders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eks&lt;/span&gt;
    &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://xxx.gr7.ap-northeast-1.eks.amazonaws.com&lt;/span&gt;
      &lt;span class="na"&gt;certificate-authority-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1J...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ExecCredential and exec plugin Flow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ExecCredential&lt;br&gt;
Kubernetes exec authentication plugin protocol. At runtime, &lt;strong&gt;input&lt;/strong&gt; is provided via the &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt; environment variable, and &lt;strong&gt;output&lt;/strong&gt; returns &lt;strong&gt;ExecCredential&lt;/strong&gt; to stdout.&lt;br&gt;
Specification: &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Usage with kubectl / client-go&lt;br&gt;
When you configure a command in &lt;code&gt;kubeconfig&lt;/code&gt;'s &lt;code&gt;users[].exec&lt;/code&gt; or in &lt;strong&gt;credentials provider&lt;/strong&gt;'s &lt;code&gt;providers[].execConfig&lt;/code&gt; as in this article, the command is launched during authentication. &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt; is passed to the plugin at that time, and kubectl/client-go consumes the ExecCredential from the plugin's stdout.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Current Configuration&lt;br&gt;
&lt;code&gt;./eks-aws-auth-plugin.sh&lt;/code&gt; is specified in &lt;code&gt;providers[].execConfig.command&lt;/code&gt;. The plugin uses &lt;strong&gt;&lt;code&gt;.spec.cluster.server&lt;/code&gt; and CA&lt;/strong&gt; from &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt; as clues to identify the cluster and ultimately outputs &lt;strong&gt;ExecCredential&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background: "Compatibility" of &lt;code&gt;aws eks update-kubeconfig&lt;/code&gt; / &lt;code&gt;aws eks get-token&lt;/code&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;aws eks update-kubeconfig&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Function: Command that generates/updates local &lt;code&gt;kubeconfig&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Input: Requires explicit cluster name specification via &lt;code&gt;--name&lt;/code&gt;, etc. No mechanism to receive &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt; (ExecCredential's &lt;code&gt;spec.cluster.server&lt;/code&gt; / CA).&lt;/li&gt;
&lt;li&gt;Output: Does not return ExecCredential JSON to stdout. Only modifies &lt;code&gt;kubeconfig&lt;/code&gt; files.
→ Conclusion: Not compatible with exec plugin input/output requirements of "input = &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt;, output = ExecCredential(JSON)/stdout".&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;aws eks get-token&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Output: Returns ExecCredential-compatible JSON to stdout.&lt;/li&gt;
&lt;li&gt;Input: Requires explicit specification of &lt;code&gt;--cluster-name&lt;/code&gt;. Cannot directly receive &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt; as input (lacks functionality to resolve cluster names from just &lt;code&gt;server&lt;/code&gt;/&lt;code&gt;CA&lt;/code&gt;).
→ Conclusion: Output is compatible, but input is not. A separate resolution layer from &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt; (&lt;code&gt;server&lt;/code&gt;/&lt;code&gt;CA&lt;/code&gt;) → cluster-name is needed.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  EKS exec plugin Implementation
&lt;/h2&gt;

&lt;p&gt;Processing flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get &lt;code&gt;.spec.cluster.server&lt;/code&gt; from &lt;code&gt;KUBERNETES_EXEC_INFO&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extract &lt;strong&gt;region&lt;/strong&gt; from hostname&lt;/li&gt;
&lt;li&gt;List EKS in that region and identify cluster name by &lt;strong&gt;exact endpoint match&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Execute &lt;code&gt;aws eks get-token&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env bash
set -euo pipefail

# -------- utils --------
err() { printf "[eks-exec-credential] %s\n" "$*" &amp;gt;&amp;amp;2; }
need() { command -v "$1" &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || { err "missing dependency: $1"; exit 1; }; }
normalize_host() { sed -E 's#^https?://##; s#/$##; s#:443$##'; }

need jq
need aws

# --- read ExecCredential ---
if [[ -z "${KUBERNETES_EXEC_INFO:-}" ]]; then
  err "KUBERNETES_EXEC_INFO is empty. set provideClusterInfo: true"
  exit 1
fi

REQ_API_VERSION="$(jq -r '.apiVersion // empty' &amp;lt;&amp;lt;&amp;lt;"$KUBERNETES_EXEC_INFO")"
SERVER="$(jq -r '.spec.cluster.server // empty' &amp;lt;&amp;lt;&amp;lt;"$KUBERNETES_EXEC_INFO")"
if [[ -z "$SERVER" || "$SERVER" == "null" ]]; then
  err "spec.cluster.server is missing in KUBERNETES_EXEC_INFO"
  exit 1
fi

NORM_SERVER="$(printf "%s" "$SERVER" | normalize_host)"

# --- region: infer from server hostname ---
HOST="${NORM_SERVER%%/*}"
REGION="$(printf "%s\n" "$HOST" \
  | sed -nE 's#.*\.([a-z0-9-]+)\.eks(-fips)?\.amazonaws\.com(\.cn)?$#\1#p')"
if [[ -z "$REGION" ]]; then
  err "failed to parse region from server hostname: ${SERVER}"
  err "expected something like ...&amp;lt;random&amp;gt;.&amp;lt;suffix&amp;gt;.&amp;lt;region&amp;gt;.eks.amazonaws.com"
  exit 1
fi

# --- tiny cache: endpoint -&amp;gt; cluster name ---
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/eks-exec-credential"
mkdir -p "$CACHE_DIR"
MAP_CACHE="$CACHE_DIR/endpoint-map-${REGION}.json"
if [[ ! -s "$MAP_CACHE" ]] || ! jq -e . &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;lt;"$MAP_CACHE"; then
  echo '{}' &amp;gt;"$MAP_CACHE"
fi

lookup_cache() { jq -r --arg k "$NORM_SERVER" '.[$k] // empty' &amp;lt;"$MAP_CACHE"; }
update_cache() {
  local tmp; tmp="$(mktemp)"
  jq --arg k "$NORM_SERVER" --arg v "$1" '.[$k]=$v' "$MAP_CACHE" &amp;gt;"$tmp" &amp;amp;&amp;amp; mv "$tmp" "$MAP_CACHE"
}

match_endpoint() {
  local name="$1"
  local ep norm_ep
  ep="$(aws eks describe-cluster --region "$REGION" --name "$name" \
        --query 'cluster.endpoint' --output text 2&amp;gt;/dev/null || true)"
  [[ -z "$ep" || "$ep" == "None" ]] &amp;amp;&amp;amp; return 1
  norm_ep="$(printf "%s" "$ep" | normalize_host)"
  [[ "$norm_ep" == "$NORM_SERVER" ]]
}

CLUSTER_NAME=""
# 1) cache hit?
CACHED="$(lookup_cache || true)"
if [[ -n "$CACHED" ]] &amp;amp;&amp;amp; match_endpoint "$CACHED"; then
  CLUSTER_NAME="$CACHED"
fi

# 2) enumerate if needed
if [[ -z "$CLUSTER_NAME" ]]; then
  err "resolving cluster in ${REGION} for ${NORM_SERVER}"
  found=""
  while IFS= read -r name; do
    [[ -z "$name" ]] &amp;amp;&amp;amp; continue
    if match_endpoint "$name"; then
      found="$name"
      break
    fi
  done &amp;lt; &amp;lt;(aws eks list-clusters --region "$REGION" --output json | jq -r '.clusters[]?')

  if [[ -z "$found" ]]; then
    err "no matching EKS cluster for endpoint: ${SERVER} (region=${REGION})"
    exit 1
  fi
  CLUSTER_NAME="$found"
  update_cache "$CLUSTER_NAME" || true
fi

# --- fetch ExecCredential via aws CLI ---
TOKEN_JSON="$(aws eks get-token --region "$REGION" --cluster-name "$CLUSTER_NAME" --output json)"

printf "%s\n" "$TOKEN_JSON"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Controller Usage Example
&lt;/h2&gt;

&lt;p&gt;Sample code that generates &lt;code&gt;rest.Config&lt;/code&gt; and creates a Clientset.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "context"
    "encoding/base64"
    "flag"
    "log"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1"
    "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1"
    "sigs.k8s.io/cluster-inventory-api/pkg/credentials"
)

func main() {
    credentialsProviders := credentials.SetupProviderFileFlag()
    flag.Parse()

    cpCreds, err := credentials.NewFromFile(*credentialsProviders)
    if err != nil {
        log.Fatalf("Got error reading credentials providers: %v", err)
    }

    caPEMBase64 := `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1J...`
    caPEM, err := base64.StdEncoding.DecodeString(caPEMBase64)
    if err != nil {
        log.Fatalf("CA PEM base64 decode failed: %v", err)
    }

    // normally we would get this clusterprofile from the local cluster (maybe a watch?)
    // and we would maintain the restconfigs for clusters we're interested in.
    exampleClusterProfile := v1alpha1.ClusterProfile{
        Spec: v1alpha1.ClusterProfileSpec{
            DisplayName: "My Cluster",
        },
        Status: v1alpha1.ClusterProfileStatus{
            CredentialProviders: []v1alpha1.CredentialProvider{
                {
                    Name: "eks",
                    Cluster: clientcmdv1.Cluster{
                        Server: "https://xxx.gr7.ap-northeast-1.eks.amazonaws.com",
                        CertificateAuthorityData: caPEM,
                    },
                },
            },
        },
    }

    restConfigForMyCluster, err := cpCreds.BuildConfigFromCP(&amp;amp;exampleClusterProfile)
    if err != nil {
        log.Fatalf("Got error generating restConfig: %v", err)
    }
    log.Printf("Got credentials: %v", restConfigForMyCluster)
    // I can then use this rest.Config to build a k8s client.

    // Build a client and list Pods in the default namespace
    clientset, err := kubernetes.NewForConfig(restConfigForMyCluster)
    if err != nil {
        log.Fatalf("failed to create clientset: %v", err)
    }
    ctx := context.Background()
    pods, err := clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
    if err != nil {
        log.Fatalf("failed to list pods: %v", err)
    }
    log.Printf("default namespace has %d pods", len(pods.Items))
    for i, p := range pods.Items {
        if i &amp;gt;= 10 {
            log.Printf("... (truncated)")
            break
        }
        log.Printf("pod: %s", p.Name)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;no matching EKS cluster&lt;/code&gt; → Check results, permissions, and profiles for &lt;code&gt;aws eks list-clusters --region &amp;lt;r&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x509: certificate signed by unknown authority&lt;/code&gt; → Verify &lt;code&gt;certificate-authority-data&lt;/code&gt; (base64)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Additional Notes
&lt;/h2&gt;

&lt;p&gt;ExecCredential allows passing additional values using extensions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KEP-5339: ClusterProfile's Credentials Plugin does not define specifications for using &lt;code&gt;ExecCredential.extensions&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
Related code: &lt;a href="https://github.com/kubernetes-sigs/cluster-inventory-api/blob/main/pkg/credentials/config.go#L133" rel="noopener noreferrer"&gt;https://github.com/kubernetes-sigs/cluster-inventory-api/blob/main/pkg/credentials/config.go#L133&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;KEP-5339 (SIG-Multicluster)
&lt;a href="https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/5339-clusterprofile-plugin-credentials" rel="noopener noreferrer"&gt;https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/5339-clusterprofile-plugin-credentials&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ExecCredential Input and Output Formats
&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/reference/access-authn-authz/authentication/#input-and-output-formats&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>aws</category>
      <category>eks</category>
      <category>go</category>
    </item>
    <item>
      <title>🚀 Kubernetes v1.33's Image Volume β is on Fire! "Code-Only" Images are Changing the Game!</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Wed, 11 Jun 2025 02:32:20 +0000</pubDate>
      <link>https://dev.to/kahirokunn/kubernetes-v133s-image-volume-b-is-on-fire-code-only-images-are-changing-the-game-2a4o</link>
      <guid>https://dev.to/kahirokunn/kubernetes-v133s-image-volume-b-is-on-fire-code-only-images-are-changing-the-game-2a4o</guid>
      <description>&lt;p&gt;Kubernetes v1.33 has finally brought &lt;strong&gt;Image Volume&lt;/strong&gt; to &lt;strong&gt;β&lt;/strong&gt; status! (※disabled by default)&lt;br&gt;
This feature allows you to mount OCI images directly as &lt;strong&gt;read-only volumes&lt;/strong&gt; - isn't this &lt;strong&gt;revolutionary&lt;/strong&gt;?&lt;/p&gt;
&lt;h2&gt;
  
  
  🧠 What Makes Image Volume So Amazing?
&lt;/h2&gt;

&lt;p&gt;Say goodbye to the old way of "cramming everything into one container", and hello to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application code&lt;/li&gt;
&lt;li&gt;Runtime&lt;/li&gt;
&lt;li&gt;Libraries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...all &lt;strong&gt;split into separate images and mounted independently&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is especially powerful for scripting language users (Node.js/Python/etc.)!&lt;/strong&gt;&lt;br&gt;
Example: Mount a "code-only" image containing your app, and run it with &lt;code&gt;node:22.15.1&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧱 Building an Ultra-Lightweight "Code-Only" Image
&lt;/h2&gt;

&lt;p&gt;The beauty of Image Volume is creating &lt;strong&gt;ultra-lightweight OCI images containing only code and config files&lt;/strong&gt;.&lt;br&gt;
Let's build a simple example with just Node.js application code.&lt;/p&gt;


&lt;h3&gt;
  
  
  📁 &lt;code&gt;index.js&lt;/code&gt; – The App
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello from OCI volume!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A simple HTTP server in Node.js that returns "Hello from OCI volume!"&lt;/p&gt;


&lt;h3&gt;
  
  
  🐳 &lt;code&gt;Dockerfile&lt;/code&gt; – The Shocking "FROM scratch"
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; app /&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Starting &lt;code&gt;FROM scratch&lt;/code&gt; (= nothing) and just &lt;code&gt;COPY&lt;/code&gt;ing the code.&lt;br&gt;
That's right - &lt;strong&gt;not even Node.js is included.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What does this mean? &lt;strong&gt;The runtime (Node.js) is provided by a separate container&lt;/strong&gt;,&lt;br&gt;
and this image is used purely as a "code repository."&lt;/p&gt;


&lt;h3&gt;
  
  
  📏 The Size? A Mere &lt;strong&gt;134 Bytes&lt;/strong&gt;!
&lt;/h3&gt;

&lt;p&gt;This OCI image, composed of just these 3 files, weighs in at only &lt;strong&gt;134 bytes&lt;/strong&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker images | &lt;span class="nb"&gt;grep &lt;/span&gt;hello-app
localhost:5001/hello-app                                                799466d0                 c4bd064f6fff   About a minute ago   134B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Builds in an instant ✅&lt;/li&gt;
&lt;li&gt;Pushes to registry almost instantly ✅&lt;/li&gt;
&lt;li&gt;Minimal CI/CD load ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As long as the code doesn't change, this image can be reused, &lt;strong&gt;improving cache hit rates&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;This approach of "packaging only application code as an image" brings a completely different worldview&lt;br&gt;
from traditional "all-in-one containers."&lt;/p&gt;

&lt;p&gt;With Image Volume, you can &lt;strong&gt;completely separate&lt;/strong&gt; the &lt;strong&gt;application code lifecycle&lt;/strong&gt; from the &lt;strong&gt;runtime lifecycle&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  📦 Instant Base Image Patches! See the Diff
&lt;/h2&gt;

&lt;p&gt;With ImageVolume, following security patches for base images becomes surprisingly easy.&lt;/p&gt;
&lt;h3&gt;
  
  
  ✅ Pattern 1: Fixed Tags with Manual Updates
&lt;/h3&gt;

&lt;p&gt;For example, when a vulnerability patch is released for the &lt;code&gt;node:22.15.0&lt;/code&gt; base image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-    image: mirror.gcr.io/node:22.15.0
&lt;/span&gt;&lt;span class="gi"&gt;+    image: mirror.gcr.io/node:22.15.1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just update the &lt;code&gt;Pod&lt;/code&gt;'s &lt;code&gt;image&lt;/code&gt; and redeploy - that's it!&lt;br&gt;
&lt;strong&gt;No changes needed to the "app code" image!&lt;/strong&gt;&lt;br&gt;
No CI rebuilds needed. &lt;strong&gt;Just change one line of YAML&lt;/strong&gt;, and security patches are applied instantly.&lt;/p&gt;


&lt;h3&gt;
  
  
  🔄 Pattern 2: Floating Tags (&lt;code&gt;node:22&lt;/code&gt;) for Automatic Updates
&lt;/h3&gt;

&lt;p&gt;For a more "fully automated" approach, use major version tags (&lt;code&gt;node:22&lt;/code&gt;) instead of minor versions,&lt;br&gt;
and set &lt;code&gt;imagePullPolicy: Always&lt;/code&gt;. &lt;strong&gt;Patches are applied with just a Pod restart.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:22&lt;/span&gt;
&lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, updates from &lt;code&gt;22.15.0 → 22.15.1&lt;/code&gt; → &lt;code&gt;22.16.0&lt;/code&gt; are &lt;strong&gt;automatically applied with just Pod restarts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;:::caution ⚠️ Caution&lt;br&gt;
While this method &lt;strong&gt;always incorporates the latest patches&lt;/strong&gt;,&lt;br&gt;
there's also the risk of &lt;strong&gt;immediately hitting regressions&lt;/strong&gt; (bugs in new versions).&lt;br&gt;
&lt;strong&gt;Careful consideration is needed for production environments where stability is paramount.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explicit tags are safer for production.&lt;/strong&gt;&lt;br&gt;
:::&lt;/p&gt;




&lt;h3&gt;
  
  
  💡 Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Benefits&lt;/th&gt;
&lt;th&gt;Considerations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed tags (&lt;code&gt;node:22.15.1&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Stable, predictable behavior&lt;/td&gt;
&lt;td&gt;Manual updates required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Floating tags + Pull Always (&lt;code&gt;node:22&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Latest patches auto-applied&lt;/td&gt;
&lt;td&gt;May upgrade to unintended versions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Choose a &lt;strong&gt;tag strategy that balances risk and automation&lt;/strong&gt; based on your situation and environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Try It Now! Complete Automated Script Included
&lt;/h2&gt;

&lt;p&gt;For those eager to experience the Image Volume world, here's a &lt;strong&gt;one-shot verification script&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Full Verification Script:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euox&lt;/span&gt; pipefail

&lt;span class="c"&gt;# Create a temporary file for the upstream script&lt;/span&gt;
&lt;span class="nv"&gt;TMP_SCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Downloading upstream kind-with-registry.sh to &lt;/span&gt;&lt;span class="nv"&gt;$TMP_SCRIPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Download the upstream script&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://kind.sigs.k8s.io/examples/kind-with-registry.sh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_SCRIPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Patch and execute the script&lt;/span&gt;
&lt;span class="c"&gt;# This adds the ImageVolume feature gate after the apiVersion line&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Patching and executing the script with ImageVolume feature gate enabled"&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'
  /^apiVersion: kind.x-k8s.io\/v1alpha4/ {
    print
    print "featureGates:\n  ImageVolume: true"
    next
  }
  {print}
'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP_SCRIPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | bash &lt;span class="nt"&gt;-s&lt;/span&gt;

&lt;span class="c"&gt;##############################################################################&lt;/span&gt;
&lt;span class="c"&gt;# 2. Build &amp;amp; push "code-only" OCI image&lt;/span&gt;
&lt;span class="c"&gt;##############################################################################&lt;/span&gt;
&lt;span class="nv"&gt;WORK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Workdir: &lt;/span&gt;&lt;span class="nv"&gt;$WORK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK&lt;/span&gt;&lt;span class="s2"&gt;/app"&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK&lt;/span&gt;&lt;span class="s2"&gt;/app/index.js"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;JS&lt;/span&gt;&lt;span class="sh"&gt;'
const http = require('http');
http.createServer((_, res) =&amp;gt; res.end('Hello from OCI volume!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'))
    .listen(process.env.PORT || 80);
&lt;/span&gt;&lt;span class="no"&gt;JS

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK&lt;/span&gt;&lt;span class="s2"&gt;/Dockerfile"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;DOCKER&lt;/span&gt;&lt;span class="sh"&gt;'
FROM scratch
COPY app /
&lt;/span&gt;&lt;span class="no"&gt;DOCKER

&lt;/span&gt;&lt;span class="nv"&gt;RAND_TAG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 4&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;FULL_IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"localhost:5001/hello-app:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RAND_TAG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

docker build &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK&lt;/span&gt;&lt;span class="s2"&gt;/Dockerfile"&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FULL_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
docker push &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FULL_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;##############################################################################&lt;/span&gt;
&lt;span class="c"&gt;# 4. Apply Pod manifest (referencing ImageVolume)&lt;/span&gt;
&lt;span class="c"&gt;##############################################################################&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK&lt;/span&gt;&lt;span class="s2"&gt;/node-demo.yaml"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: v1
kind: Pod
metadata:
  name: node-demo
spec:
  containers:
  - name: runtime
    image: mirror.gcr.io/node:22-slim
    command: ["node", "/usr/src/app/index.js"]
    workingDir: /usr/src/app
    ports: [{containerPort: 80}]
    volumeMounts:
    - mountPath: /usr/src/app
      name: app-src
  volumes:
  - name: app-src
    image:
      reference: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FULL_IMAGE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
      pullPolicy: IfNotPresent
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;10
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK&lt;/span&gt;&lt;span class="s2"&gt;/node-demo.yaml"&lt;/span&gt;
kubectl &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="nt"&gt;--for&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Ready pod/node-demo &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120s

&lt;span class="c"&gt;##############################################################################&lt;/span&gt;
&lt;span class="c"&gt;# 5. Verify it works&lt;/span&gt;
&lt;span class="c"&gt;##############################################################################&lt;/span&gt;
kubectl port-forward pod/node-demo 3000:80 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &amp;amp;
&lt;span class="nv"&gt;PF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;2
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"curl → "&lt;/span&gt;
curl http://localhost:3000
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nv"&gt;$PF&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOM&lt;/span&gt;&lt;span class="sh"&gt;

✅ Done! Node.js is running with code mounted via ImageVolume.

Cleanup:
  kind delete cluster
  rm -rf &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WORK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
&lt;/span&gt;&lt;span class="no"&gt;EOM
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just copy-paste and run this script to &lt;strong&gt;launch a Node.js app using Image Volume&lt;/strong&gt; in your local environment!&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Use Cases: Where This Shines
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Traditional Method&lt;/th&gt;
&lt;th&gt;With Image Volume&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Node.js code distribution&lt;/strong&gt;&lt;br&gt;(e.g., &lt;code&gt;node:22.15.1&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Rebuild with code baked into base&lt;/td&gt;
&lt;td&gt;Separate code image, just mount it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Python library sharing&lt;/strong&gt;&lt;br&gt;(e.g., &lt;code&gt;python:3.11-slim&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Build for each app&lt;/td&gt;
&lt;td&gt;Fix common env as base, manage apps separately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI models or certificate deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pack everything into huge images&lt;/td&gt;
&lt;td&gt;Share models/certs as Image Volumes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  ✅ Conclusion: This is the Future Default
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"Code-only" images mean &lt;strong&gt;faster builds&lt;/strong&gt; &amp;amp; &lt;strong&gt;better cache hits&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Base image updates = &lt;strong&gt;instant patches&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slimmer CI/CD, improved security&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if "mounting images" didn't quite click before,&lt;br&gt;
experience firsthand how this mechanism will become the &lt;strong&gt;next-generation best practice&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Kubernetes Actions Runner Controller: A Thorough Explanation of PR #4059 Enabling Self-Healing for EphemeralRunner</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Thu, 15 May 2025 02:04:38 +0000</pubDate>
      <link>https://dev.to/kahirokunn/kubernetes-x-actions-runner-controller-a-thorough-explanation-of-pr-4059-enabling-self-healing-1l88</link>
      <guid>https://dev.to/kahirokunn/kubernetes-x-actions-runner-controller-a-thorough-explanation-of-pr-4059-enabling-self-healing-1l88</guid>
      <description>&lt;h2&gt;
  
  
  1. Background ― Why was this PR necessary?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Previous Implementation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Spot instances&lt;/strong&gt; terminate when their time is up, and EphemeralRunner mistakenly judges this as a failure.&lt;/td&gt;
&lt;td&gt;Failures accumulated in EphemeralRunner's &lt;code&gt;status.failures&lt;/code&gt; &lt;strong&gt;one by one&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When using Spot instances with &lt;code&gt;minRunners ≥ 1&lt;/code&gt;, you inevitably encounter Spot instance interruptions.&lt;/td&gt;
&lt;td&gt;After &lt;strong&gt;5&lt;/strong&gt; failures (interruptions), the &lt;strong&gt;Runner CR remains&lt;/strong&gt; with &lt;code&gt;Phase=Failed&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;From the Workflow side, jobs stall in a "Runner not found" state.&lt;/td&gt;
&lt;td&gt;Reported about 2 years ago in Issue &lt;a href="https://dev.to[GitHub][1]"&gt;#2721&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  2. Previous Limitations and Challenges
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;failure limit of 5 times&lt;/strong&gt; constant existed &lt;em&gt;before&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;After reaching the limit, it only &lt;strong&gt;changed the Phase with &lt;code&gt;markAsFailed()&lt;/code&gt;&lt;/strong&gt;, so:

&lt;ul&gt;
&lt;li&gt;EphemeralRunnerSet determined "Desired=1 but object already exists"&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;couldn't generate a new EphemeralRunner&lt;/strong&gt; and became "zombified"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. What Changed with PR #4059?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Type change&lt;/strong&gt; for &lt;code&gt;status.failures&lt;/code&gt; &lt;br&gt;&lt;code&gt;map[string]bool&lt;/code&gt; → &lt;code&gt;map[string]metav1.Time&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Records failure time to &lt;strong&gt;track the latest failures chronologically&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Introduction of &lt;code&gt;failedRunnerBackoff&lt;/code&gt; array&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Exponential backoff&lt;/strong&gt;: 0 s → 5 s → 10 s → 20 s → 40 s → 80 s (&lt;a href="https://github.com/actions/actions-runner-controller/pull/4059/files#diff-5c7193f43d9efbfc1fce789d32897a294678f0f551d5cde01b837496d519bdf3R56-R63" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Added &lt;code&gt;LastFailure()&lt;/code&gt; helper&lt;/td&gt;
&lt;td&gt;Retrieves the &lt;strong&gt;most recent failure time&lt;/strong&gt; from Status (&lt;a href="https://github.com/actions/actions-runner-controller/pull/4059/files#diff-66f5adca3ea1470f589b19e084eaba79d1ff4eb5542ebacaf84ed3332d290e8eR140-R153" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Requeue control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;For failure count n ≤ 5, process equivalent to &lt;code&gt;RequeueAfter(backoff[n])&lt;/code&gt; (&lt;a href="https://github.com/actions/actions-runner-controller/pull/4059/files#diff-5c7193f43d9efbfc1fce789d32897a294678f0f551d5cde01b837496d519bdf3R202-R211" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Changed handling of limit exceedance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Instead of &lt;em&gt;changing Phase&lt;/em&gt;, immediately &lt;code&gt;Delete()&lt;/code&gt; the EphemeralRunner CR.&lt;br&gt;EphemeralRunnerSet can then regenerate a new one (&lt;a href="https://github.com/actions/actions-runner-controller/pull/4059/files#diff-5c7193f43d9efbfc1fce789d32897a294678f0f551d5cde01b837496d519bdf3R190-R192" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Point&lt;/strong&gt;&lt;br&gt;
The constant &lt;em&gt;limit of 5 times&lt;/em&gt; itself hasn't changed.&lt;br&gt;
&lt;strong&gt;"What happens after reaching the limit"&lt;/strong&gt; changed from &lt;em&gt;Phase=Failed → object deletion&lt;/em&gt;, and&lt;br&gt;
&lt;strong&gt;Self-healing&lt;/strong&gt; of EphemeralRunner became possible&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. Actual Behavior in Spot × minRunners Scenario
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    autonumber
    participant ARC as ARC Controller
    participant ER as EphemeralRunner CR
    participant Set as EphemeralRunnerSet
    participant Node as Spot Node

    Node--&amp;gt;&amp;gt;ER: Interruption warning (2 minutes in advance)
    Node--x ER: Pod Terminated
    ARC-&amp;gt;&amp;gt;ER: failures+=1 / RequeueAfter(backoff[1])
    loop ~Repeats up to 5 times~
        Node--x ER: Terminated again
        ARC-&amp;gt;&amp;gt;ER: failures+=1 / Backoff
    end
    ARC-&amp;gt;&amp;gt;ER: failures=6 (&amp;gt;5)\nDelete(ER)
    Set-&amp;gt;&amp;gt;ARC: Desired=1 but no CR
    Set--&amp;gt;&amp;gt;ARC: **Create new EphemeralRunner**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F273odr8rqx83e7s6njmf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F273odr8rqx83e7s6njmf.png" alt=" " width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On the 6th time, it &lt;strong&gt;deletes CR → regenerates&lt;/strong&gt;, so "zombies" don't remain&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Code Explanation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Status Field for Recording Failures
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;EphemeralRunnerStatus&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// ...existing fields&lt;/span&gt;
    &lt;span class="n"&gt;Failures&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"failures,omitempty"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;EphemeralRunnerStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;LastFailure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Helper that scans the map and returns the latest timestamp&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key&lt;/strong&gt;: Pod's .metadata.uid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value&lt;/strong&gt;: Time when the failure occurred&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Exponential Backoff &amp;amp; Requeue
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;failedRunnerBackoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;maxFailures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;er&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Failures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;maxFailures&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;failedRunnerBackoff&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;er&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Failures&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RequeueAfter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Retry with 0 seconds → 5 s → 10 s ...&lt;/li&gt;
&lt;li&gt;On the 6th attempt (&lt;code&gt;len &amp;gt; maxFailures&lt;/code&gt;), it proceeds to the next section&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Immediate Deletion on 6th Time → EphemeralRunnerSet Recreates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Final phase&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;er&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Failures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxFailures&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Maximum failures reached – deleting EphemeralRunner"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;er&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Since it &lt;strong&gt;Deletes the object itself&lt;/strong&gt;, the Controller's retry ends&lt;/li&gt;
&lt;li&gt;The parent resource &lt;strong&gt;EphemeralRunnerSet&lt;/strong&gt; generates a new EphemeralRunner to meet the Desired count → Pod → job resumes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Operation Flow Summary
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[Pod Failed] --&amp;gt; B{Failures &amp;lt;= 5?}
    B -- Yes --&amp;gt; C[Record failure &amp;amp; requeue after backoff]
    B -- No --&amp;gt; D[Delete EphemeralRunner CR]
    D --&amp;gt; E[EphemeralRunnerSet creates new EphemeralRunner]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstlebqxqvpukmrefwde3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstlebqxqvpukmrefwde3.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Conclusion
&lt;/h2&gt;

&lt;p&gt;PR #4059 solves the long-standing issue (Issue &lt;a href="https://github.com/actions/actions-runner-controller/issues/2721" rel="noopener noreferrer"&gt;#2721&lt;/a&gt;) of &lt;strong&gt;"getting stuck after 5 interruptions"&lt;/strong&gt; that's particularly frequent in Spot × minRunners environments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manages failures chronologically and retries with exponential backoff&lt;/li&gt;
&lt;li&gt;Deletes the EphemeralRunner CR upon exceeding the limit, allowing EphemeralRunnerSet to recreate it&lt;/li&gt;
&lt;li&gt;This achieves &lt;strong&gt;self-healing close to fully managed&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;However, &lt;strong&gt;the risk of job execution being disrupted by Spot interruptions&lt;/strong&gt; still remains, so caution is needed when using Spot instances&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>githubactions</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Actions Runner Controller and ArgoCD Integration Guide</title>
      <dc:creator>Kahiro Okina</dc:creator>
      <pubDate>Thu, 06 Mar 2025 08:45:58 +0000</pubDate>
      <link>https://dev.to/kahirokunn/actions-runner-controller-and-argocd-integration-guide-3a81</link>
      <guid>https://dev.to/kahirokunn/actions-runner-controller-and-argocd-integration-guide-3a81</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;This guide addresses compatibility issues between Actions Runner Controller and ArgoCD, prepared in response to requirements from this PR:&lt;br&gt;
&lt;a href="https://github.com/actions/actions-runner-controller/pull/3575#issuecomment-2594906700" rel="noopener noreferrer"&gt;https://github.com/actions/actions-runner-controller/pull/3575#issuecomment-2594906700&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Create a kind Cluster
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind create cluster &lt;span class="nt"&gt;--image&lt;/span&gt; mirror.gcr.io/kindest/node:v1.32.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  2. Install ArgoCD
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note: To access the UI, use port forwarding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/argocd-server &lt;span class="nt"&gt;-n&lt;/span&gt; argocd 8080:443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Create GitHub PAT Secret
&lt;/h2&gt;

&lt;p&gt;Execute the following after the namespace is automatically created via ArgoCD Application syncOptions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create secret generic github-pat-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; default &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;github_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;YOUR_PAT&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Replace &lt;code&gt;&amp;lt;YOUR_PAT&amp;gt;&lt;/code&gt; with your actual Personal Access Token.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. ArgoCD Application: gha-runner-scale-set-controller
&lt;/h2&gt;

&lt;p&gt;Save the following content as &lt;code&gt;gha-runner-scale-set-controller-app.yaml&lt;/code&gt;.&lt;br&gt;
This uses Helm Chart version &lt;strong&gt;0.10.1&lt;/strong&gt;, specifies the image.repository while using the default tag, and adds excludeLabelPropagationPrefixes array settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gha-runner-scale-set-controller&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
  &lt;span class="na"&gt;finalizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;resources-finalizer.argocd.argoproj.io&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ghcr.io/actions/actions-runner-controller-charts"&lt;/span&gt;
    &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gha-runner-scale-set-controller"&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.10.1"&lt;/span&gt;
    &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;image.repository&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quay.io/kahirokunn/gha-runner-scale-set"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;githubConfigSecret&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github-pat-secret"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;serviceAccount.name&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gha-runner-scale-set-controller"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flags.excludeLabelPropagationPrefixes[0]&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;argocd.argoproj.io/instance"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flags.excludeLabelPropagationPrefixes[1]&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.kubernetes.io/component"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flags.excludeLabelPropagationPrefixes[2]&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.kubernetes.io/instance"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flags.excludeLabelPropagationPrefixes[3]&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.kubernetes.io/managed-by"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flags.excludeLabelPropagationPrefixes[4]&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.kubernetes.io/name"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flags.excludeLabelPropagationPrefixes[5]&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.kubernetes.io/part-of"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flags.excludeLabelPropagationPrefixes[6]&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app.kubernetes.io/version"&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://kubernetes.default.svc"&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arc-systems&lt;/span&gt;
  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;syncOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CreateNamespace=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ServerSideApply=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying, it will look like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpj7a24boas5ozmzrugp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpj7a24boas5ozmzrugp7.png" alt="image.png" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. ArgoCD Application: demo (Runner Scale Set)
&lt;/h2&gt;

&lt;p&gt;Save the following content as &lt;code&gt;gha-runner-scale-set-app.yaml&lt;/code&gt; (filename is arbitrary):&lt;br&gt;
This uses Helm Chart version &lt;strong&gt;0.10.1&lt;/strong&gt;, sets minimum runners to &lt;strong&gt;1&lt;/strong&gt;, and configures the following Helm parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;controllerServiceAccount.name: &lt;code&gt;"gha-runner-scale-set-controller"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;controllerServiceAccount.namespace: &lt;code&gt;"arc-systems"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;githubConfigSecret: &lt;code&gt;"github-pat-secret"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;githubConfigUrl: &lt;code&gt;"https://github.com/kahirokunn/actions-runner-controller"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
  &lt;span class="na"&gt;finalizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;resources-finalizer.argocd.argoproj.io&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ghcr.io/actions/actions-runner-controller-charts"&lt;/span&gt;
    &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gha-runner-scale-set"&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.10.1"&lt;/span&gt;
    &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minRunners&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;controllerServiceAccount.name&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gha-runner-scale-set-controller"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;controllerServiceAccount.namespace&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arc-systems"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;githubConfigSecret&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github-pat-secret"&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;githubConfigUrl&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/kahirokunn/actions-runner-controller"&lt;/span&gt;
  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://kubernetes.default.svc"&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;selfHeal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;syncOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CreateNamespace=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ServerSideApply=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying, it will appear in ArgoCD as follows:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf5mcr9tc98a2gd9g4h0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvf5mcr9tc98a2gd9g4h0.png" alt="image.png" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>githubactions</category>
      <category>argocd</category>
    </item>
  </channel>
</rss>
