diff --git a/cmd/atenet/internal/app/router/agentgateway.go b/cmd/atenet/internal/app/router/agentgateway.go new file mode 100644 index 000000000..04c3b09df --- /dev/null +++ b/cmd/atenet/internal/app/router/agentgateway.go @@ -0,0 +1,141 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package router + +import ( + "context" + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +type agentgatewayProvider struct { + cfg RouterConfig +} + +func (p agentgatewayProvider) Name() string { + return NetworkingModeAgentgateway +} + +func (p agentgatewayProvider) RequiresXDS() bool { + return false +} + +func (p agentgatewayProvider) ConfigMapData() map[string]string { + return map[string]string{"config.yaml": p.localConfig()} +} + +func (p agentgatewayProvider) Container() corev1.Container { + ports := []corev1.ContainerPort{ + {Name: "http", ContainerPort: int32(p.cfg.HttpPort)}, + {Name: "readiness", ContainerPort: 15021}, + {Name: "metrics", ContainerPort: 15020}, + } + if p.cfg.HttpsPort > 0 && tlsCertPath(p.cfg) != "" { + ports = append(ports, corev1.ContainerPort{Name: "https", ContainerPort: int32(p.cfg.HttpsPort)}) + } + + return corev1.Container{ + Name: "agentgateway", + Image: p.cfg.AgentgatewayImage, + Args: []string{"-f", "/etc/agentgateway/config.yaml"}, + Ports: ports, + VolumeMounts: []corev1.VolumeMount{ + {Name: "proxy-config", MountPath: "/etc/agentgateway"}, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/healthz/ready", + Port: intstr.FromInt32(15021), + }, + }, + PeriodSeconds: 10, + }, + } +} + +func (p agentgatewayProvider) ServicePorts() []corev1.ServicePort { + ports := []corev1.ServicePort{ + {Name: "http", Port: int32(p.cfg.HttpPort), TargetPort: intstr.FromString("http")}, + } + if p.cfg.HttpsPort > 0 && tlsCertPath(p.cfg) != "" { + ports = append(ports, corev1.ServicePort{Name: "https", Port: int32(p.cfg.HttpsPort), TargetPort: intstr.FromString("https")}) + } + return ports +} + +func (p agentgatewayProvider) CheckReady(ctx context.Context) (bool, string) { + return checkHTTPReady(ctx, "http://127.0.0.1:15021/healthz/ready", "") +} + +func (p agentgatewayProvider) localConfig() string { + httpRoute := p.routeBlock("substrate-http") + config := fmt.Sprintf(`# yaml-language-server: $schema=https://agentgateway.dev/schema/config +config: + adminAddr: "127.0.0.1:15000" + readinessAddr: "0.0.0.0:15021" + statsAddr: "0.0.0.0:15020" +binds: +- port: %d + listeners: + - name: http + protocol: HTTP + routes: +%s`, p.cfg.HttpPort, indent(httpRoute, 4)) + + if p.cfg.HttpsPort > 0 && tlsCertPath(p.cfg) != "" { + cert := tlsCertPath(p.cfg) + key := tlsKeyPath(p.cfg) + config += fmt.Sprintf(`- port: %d + listeners: + - name: https + protocol: HTTPS + tls: + cert: %q + key: %q + routes: +%s`, p.cfg.HttpsPort, cert, key, indent(p.routeBlock("substrate-https"), 4)) + } + + return config +} + +func (p agentgatewayProvider) routeBlock(name string) string { + return fmt.Sprintf(`- name: %s + matches: + - path: + pathPrefix: / + policies: + extProc: + host: %q + failureMode: failClosed + backends: + - dynamic: {} +`, name, fmt.Sprintf("%s:%d", p.cfg.ExtprocAddr, p.cfg.ExtprocPort)) +} + +func indent(s string, spaces int) string { + prefix := strings.Repeat(" ", spaces) + lines := strings.Split(s, "\n") + for i, line := range lines { + if line != "" { + lines[i] = prefix + line + } + } + return strings.Join(lines, "\n") +} diff --git a/cmd/atenet/internal/app/router/controller.go b/cmd/atenet/internal/app/router/controller.go index 055751fbe..5701666ba 100644 --- a/cmd/atenet/internal/app/router/controller.go +++ b/cmd/atenet/internal/app/router/controller.go @@ -31,9 +31,10 @@ type Controller struct { cfg RouterConfig xdsSrv *XdsServer extprocSrv *ExtProcServer + provider proxyProvider atStore atStore - envoyRunner *envoyrunner + proxyRunner *proxyrunner } func NewController( @@ -42,8 +43,11 @@ func NewController( cfg RouterConfig, xdsSrv *XdsServer, extprocSrv *ExtProcServer, + provider proxyProvider, ) *Controller { - xdsSrv.SetConfig(cfg.HttpPort, cfg.ExtprocPort, cfg.ExtprocAddr) + if xdsSrv != nil { + xdsSrv.SetConfig(cfg.HttpPort, cfg.ExtprocPort, cfg.ExtprocAddr) + } var store atStore if cfg.TemplatesFile != "" { @@ -58,9 +62,10 @@ func NewController( cfg: cfg, xdsSrv: xdsSrv, extprocSrv: extprocSrv, + provider: provider, atStore: store, - envoyRunner: newEnvoyRunner(k8sClient, cfg), + proxyRunner: newProxyRunner(k8sClient, cfg, provider), } } @@ -92,16 +97,18 @@ func (c *Controller) reconcile(ctx context.Context) error { return err } - if err := c.xdsSrv.UpdateSnapshot(); err != nil { - slog.ErrorContext(ctx, "xDS Configuration generation problem", slog.String("err", err.Error())) - return err + if c.provider.RequiresXDS() { + if err := c.xdsSrv.UpdateSnapshot(); err != nil { + slog.ErrorContext(ctx, "xDS Configuration generation problem", slog.String("err", err.Error())) + return err + } } if !c.cfg.Standalone && c.cfg.TemplatesFile == "" { - // Reconcile Envoy router Deployment and Kubernetes cluster entities - err := c.envoyRunner.reconcile(ctx) + // Reconcile router proxy Deployment and Kubernetes cluster entities. + err := c.proxyRunner.reconcile(ctx) if err != nil { - slog.ErrorContext(ctx, "Error during Envoy router reconciliation", slog.String("err", err.Error())) + slog.ErrorContext(ctx, "Error during router proxy reconciliation", slog.String("err", err.Error())) return err } } diff --git a/cmd/atenet/internal/app/router/dashboard.html b/cmd/atenet/internal/app/router/dashboard.html index 083d0d019..50d00ef14 100644 --- a/cmd/atenet/internal/app/router/dashboard.html +++ b/cmd/atenet/internal/app/router/dashboard.html @@ -252,12 +252,16 @@

atenet Router Status

Namespace Context {{ .Namespace }} +
+ Networking Mode + {{ .NetworkingMode }} +
Component Network Allocation
- Workload Port (Http Envoy) + Workload HTTP Port {{ .HttpPort }}
@@ -278,8 +282,8 @@

atenet Router Status

System Component Health Checks
diff --git a/cmd/atenet/internal/app/router/envoy.go b/cmd/atenet/internal/app/router/envoy.go new file mode 100644 index 000000000..88ec95365 --- /dev/null +++ b/cmd/atenet/internal/app/router/envoy.go @@ -0,0 +1,118 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package router + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +type envoyProvider struct { + cfg RouterConfig +} + +func (p envoyProvider) Name() string { + return NetworkingModeEnvoy +} + +func (p envoyProvider) RequiresXDS() bool { + return true +} + +func (p envoyProvider) ConfigMapData() map[string]string { + envoyYaml := fmt.Sprintf(`admin: + address: + socket_address: + address: 0.0.0.0 + port_value: 9901 + +node: + id: %s + cluster: substrate-router-cluster + +dynamic_resources: + lds_config: + resource_api_version: V3 + ads: {} + cds_config: + resource_api_version: V3 + ads: {} + ads_config: + api_type: GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + +static_resources: + clusters: + - name: xds_cluster + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http2_protocol_options: {} + load_assignment: + cluster_name: xds_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: atenet-router + port_value: %d +`, NodeID, p.cfg.XdsPort) + + return map[string]string{"envoy.yaml": envoyYaml} +} + +func (p envoyProvider) Container() corev1.Container { + return corev1.Container{ + Name: "envoy", + Image: p.cfg.EnvoyImage, + Command: []string{ + "/usr/local/bin/envoy", + "-c", + "/etc/envoy/envoy.yaml", + "--component-log-level", + "upstream:debug,router:debug,ext_proc:debug", + }, + Ports: []corev1.ContainerPort{ + {Name: "http", ContainerPort: int32(p.cfg.HttpPort)}, + {Name: "https", ContainerPort: int32(p.cfg.HttpsPort)}, + {Name: "admin", ContainerPort: 9901}, + }, + VolumeMounts: []corev1.VolumeMount{ + {Name: "proxy-config", MountPath: "/etc/envoy"}, + }, + } +} + +func (p envoyProvider) ServicePorts() []corev1.ServicePort { + return []corev1.ServicePort{ + {Name: "http", Port: int32(p.cfg.HttpPort), TargetPort: intstr.FromString("http")}, + {Name: "https", Port: int32(p.cfg.HttpsPort), TargetPort: intstr.FromString("https")}, + } +} + +func (p envoyProvider) CheckReady(ctx context.Context) (bool, string) { + return checkHTTPReady(ctx, "http://127.0.0.1:9901/ready", "LIVE") +} diff --git a/cmd/atenet/internal/app/router/envoyrunner.go b/cmd/atenet/internal/app/router/envoyrunner.go deleted file mode 100644 index 8d38be29f..000000000 --- a/cmd/atenet/internal/app/router/envoyrunner.go +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2026 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package router - -import ( - "context" - "fmt" - "log/slog" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - EnvoyDeploymentName = "atenet-router-envoy" - EnvoyServiceName = "atenet-router-envoy" - EnvoyConfigMapName = "atenet-router-envoy-config" -) - -// envoyrunner manages the dynamic deployment and lifecycle of the underlying -// Envoy proxy instance running inside Kubernetes. -type envoyrunner struct { - k8sClient client.Client - cfg RouterConfig -} - -func newEnvoyRunner(k8sClient client.Client, cfg RouterConfig) *envoyrunner { - return &envoyrunner{ - k8sClient: k8sClient, - cfg: cfg, - } -} - -func (r *envoyrunner) reconcile(ctx context.Context) error { - if err := r.reconcileEnvoyConfigMap(ctx); err != nil { - return fmt.Errorf("failed configmap reconciliation: %w", err) - } - - if err := r.reconcileEnvoyDeployment(ctx); err != nil { - return fmt.Errorf("failed deployment reconciliation: %w", err) - } - - if err := r.reconcileEnvoyService(ctx); err != nil { - return fmt.Errorf("failed service reconciliation: %w", err) - } - - return nil -} - -func (r *envoyrunner) reconcileEnvoyConfigMap(ctx context.Context) error { - envoyYaml := fmt.Sprintf(`admin: - address: - socket_address: - address: 0.0.0.0 - port_value: 9901 - -node: - id: %s - cluster: substrate-router-cluster - -dynamic_resources: - lds_config: - resource_api_version: V3 - ads: {} - cds_config: - resource_api_version: V3 - ads: {} - ads_config: - api_type: GRPC - transport_api_version: V3 - grpc_services: - - envoy_grpc: - cluster_name: xds_cluster - -static_resources: - clusters: - - name: xds_cluster - connect_timeout: 0.25s - type: STRICT_DNS - lb_policy: ROUND_ROBIN - typed_extension_protocol_options: - envoy.extensions.upstreams.http.v3.HttpProtocolOptions: - "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions - explicit_http_config: - http2_protocol_options: {} - load_assignment: - cluster_name: xds_cluster - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: atenet-router - port_value: %d -`, NodeID, r.cfg.XdsPort) - - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: EnvoyConfigMapName, - Namespace: r.cfg.Namespace, - }, - Data: map[string]string{ - "envoy.yaml": envoyYaml, - }, - } - - var existing corev1.ConfigMap - err := r.k8sClient.Get(ctx, client.ObjectKey{Namespace: r.cfg.Namespace, Name: EnvoyConfigMapName}, &existing) - if err != nil { - if k8serrors.IsNotFound(err) { - slog.InfoContext(ctx, "Creating Envoy bootstrap ConfigMap", - slog.String("namespace", r.cfg.Namespace), - slog.String("name", EnvoyConfigMapName)) - return r.k8sClient.Create(ctx, cm) - } - return err - } - - existing.Data = cm.Data - return r.k8sClient.Update(ctx, &existing) -} - -func (r *envoyrunner) reconcileEnvoyDeployment(ctx context.Context) error { - replicas := int32(1) - - dep := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: EnvoyDeploymentName, - Namespace: r.cfg.Namespace, - Labels: map[string]string{ - "app": "atenet-router-envoy", - }, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "atenet-router-envoy", - }, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "atenet-router-envoy", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "envoy", - Image: r.cfg.EnvoyImage, - Command: []string{ - "/usr/local/bin/envoy", - "-c", - "/etc/envoy/envoy.yaml", - "--component-log-level", - "upstream:debug,router:debug,ext_proc:debug", - }, - Ports: []corev1.ContainerPort{ - { - Name: "http", - ContainerPort: int32(r.cfg.HttpPort), - }, - { - Name: "admin", - ContainerPort: 9901, - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "envoy-config", - MountPath: "/etc/envoy", - }, - }, - }, - }, - Volumes: []corev1.Volume{ - { - Name: "envoy-config", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: EnvoyConfigMapName, - }, - }, - }, - }, - }, - }, - }, - }, - } - - var existing appsv1.Deployment - err := r.k8sClient.Get(ctx, client.ObjectKey{Namespace: r.cfg.Namespace, Name: EnvoyDeploymentName}, &existing) - if err != nil { - if k8serrors.IsNotFound(err) { - slog.InfoContext(ctx, "Creating managed Envoy router Deployment", slog.String("namespace", r.cfg.Namespace)) - return r.k8sClient.Create(ctx, dep) - } - return err - } - - existing.Spec = dep.Spec - return r.k8sClient.Update(ctx, &existing) -} - -func (r *envoyrunner) reconcileEnvoyService(ctx context.Context) error { - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: EnvoyServiceName, - Namespace: r.cfg.Namespace, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeClusterIP, - Selector: map[string]string{ - "app": "atenet-router-envoy", - }, - Ports: []corev1.ServicePort{ - { - Name: "http", - Port: int32(r.cfg.HttpPort), - TargetPort: intstr.FromString("http"), - }, - }, - }, - } - - var existing corev1.Service - err := r.k8sClient.Get(ctx, client.ObjectKey{Namespace: r.cfg.Namespace, Name: EnvoyServiceName}, &existing) - if err != nil { - if k8serrors.IsNotFound(err) { - slog.InfoContext(ctx, "Creating managed Envoy router ClusterIP service", slog.String("namespace", r.cfg.Namespace)) - return r.k8sClient.Create(ctx, svc) - } - return err - } - - existing.Spec.Ports = svc.Spec.Ports - existing.Spec.Selector = svc.Spec.Selector - return r.k8sClient.Update(ctx, &existing) -} diff --git a/cmd/atenet/internal/app/router/health.go b/cmd/atenet/internal/app/router/health.go index 5d8d9449d..e743cd06c 100644 --- a/cmd/atenet/internal/app/router/health.go +++ b/cmd/atenet/internal/app/router/health.go @@ -17,10 +17,7 @@ package router import ( "context" "fmt" - "io" "log/slog" - "net/http" - "strings" "sync" "time" @@ -38,9 +35,10 @@ type ComponentHealth struct { } type RouterHealthReport struct { - Envoy ComponentHealth `json:"envoy"` - K8sAPI ComponentHealth `json:"k8s_api"` - AteAPI ComponentHealth `json:"ate_api"` + Proxy ComponentHealth `json:"proxy"` + Provider string `json:"provider"` + K8sAPI ComponentHealth `json:"k8s_api"` + AteAPI ComponentHealth `json:"ate_api"` } // routerHealth periodically checks the dependent services of router to track health @@ -54,9 +52,10 @@ type routerHealth struct { clientset kubernetes.Interface apiClient ateapipb.ControlClient cfg RouterConfig + provider proxyProvider } -func newRouterHealth(interval time.Duration, clientset kubernetes.Interface, apiClient ateapipb.ControlClient, cfg RouterConfig) *routerHealth { +func newRouterHealth(interval time.Duration, clientset kubernetes.Interface, apiClient ateapipb.ControlClient, cfg RouterConfig, provider proxyProvider) *routerHealth { if interval <= 0 { interval = time.Second } @@ -65,6 +64,7 @@ func newRouterHealth(interval time.Duration, clientset kubernetes.Interface, api clientset: clientset, apiClient: apiClient, cfg: cfg, + provider: provider, } } @@ -91,20 +91,25 @@ func (rh *routerHealth) check(ctx context.Context) { slog.InfoContext(ctx, "Checking health") - // 1. Check Envoy + // 1. Check configured proxy { - healthy, msg := rh.checkEnvoy(ctx) + providerName := "" + if rh.provider != nil { + providerName = rh.provider.Name() + rh.report.Provider = providerName + } + healthy, msg := rh.checkProxy(ctx) if healthy { - rh.report.Envoy.Healthy = true - rh.report.Envoy.Message = msg - rh.report.Envoy.LastSuccess = time.Now() - rh.report.Envoy.SuccessCount++ + rh.report.Proxy.Healthy = true + rh.report.Proxy.Message = msg + rh.report.Proxy.LastSuccess = time.Now() + rh.report.Proxy.SuccessCount++ } else { - rh.report.Envoy.Healthy = false - rh.report.Envoy.Message = msg - rh.report.Envoy.LastFailure = time.Now() - rh.report.Envoy.FailureCount++ - slog.ErrorContext(ctx, "Envoy health check failed", slog.String("msg", msg)) + rh.report.Proxy.Healthy = false + rh.report.Proxy.Message = msg + rh.report.Proxy.LastFailure = time.Now() + rh.report.Proxy.FailureCount++ + slog.ErrorContext(ctx, "Proxy health check failed", slog.String("provider", providerName), slog.String("msg", msg)) } } @@ -143,36 +148,11 @@ func (rh *routerHealth) check(ctx context.Context) { } } -func (rh *routerHealth) checkEnvoy(ctx context.Context) (bool, string) { - timeoutCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) - defer cancel() - - req, err := http.NewRequestWithContext(timeoutCtx, "GET", "http://127.0.0.1:9901/ready", nil) - if err != nil { - return false, err.Error() - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return false, err.Error() +func (rh *routerHealth) checkProxy(ctx context.Context) (bool, string) { + if rh.provider == nil { + return false, "No proxy provider" } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return false, fmt.Sprintf("unexpected status code %d", resp.StatusCode) - } - - bodyBytes, err := io.ReadAll(resp.Body) - if err != nil { - return false, err.Error() - } - - bodyStr := strings.TrimSpace(string(bodyBytes)) - if bodyStr != "LIVE" { - return false, fmt.Sprintf("expected LIVE but got %q", bodyStr) - } - - return true, "LIVE" + return rh.provider.CheckReady(ctx) } func (rh *routerHealth) checkK8s() (bool, string) { diff --git a/cmd/atenet/internal/app/router/provider.go b/cmd/atenet/internal/app/router/provider.go new file mode 100644 index 000000000..ccd68cf79 --- /dev/null +++ b/cmd/atenet/internal/app/router/provider.go @@ -0,0 +1,102 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package router + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" +) + +const ( + NetworkingModeEnvoy = "envoy" + NetworkingModeAgentgateway = "agentgateway" +) + +// proxyProvider owns proxy-specific configuration and runtime details. The +// Substrate router keeps actor lookup and ext_proc behavior shared above this +// provider boundary. +type proxyProvider interface { + Name() string + RequiresXDS() bool + ConfigMapData() map[string]string + Container() corev1.Container + ServicePorts() []corev1.ServicePort + CheckReady(ctx context.Context) (bool, string) +} + +func newProxyProvider(cfg RouterConfig) (proxyProvider, error) { + switch strings.ToLower(cfg.NetworkingMode) { + case "", NetworkingModeEnvoy: + return envoyProvider{cfg: cfg}, nil + case NetworkingModeAgentgateway: + return agentgatewayProvider{cfg: cfg}, nil + default: + return nil, fmt.Errorf("unsupported networking mode %q", cfg.NetworkingMode) + } +} + +func tlsCertPath(cfg RouterConfig) string { + return cfg.TLSCertPath +} + +func tlsKeyPath(cfg RouterConfig) string { + if cfg.TLSKeyPath != "" { + return cfg.TLSKeyPath + } + if cfg.TLSCertPath != "" { + return cfg.TLSCertPath + } + return "" +} + +func checkHTTPReady(ctx context.Context, url string, expectedBody string) (bool, string) { + timeoutCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) + defer cancel() + + req, err := http.NewRequestWithContext(timeoutCtx, "GET", url, nil) + if err != nil { + return false, err.Error() + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return false, err.Error() + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return false, fmt.Sprintf("unexpected status code %d", resp.StatusCode) + } + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return false, err.Error() + } + + bodyStr := strings.TrimSpace(string(bodyBytes)) + if expectedBody != "" && bodyStr != expectedBody { + return false, fmt.Sprintf("expected %s but got %q", expectedBody, bodyStr) + } + if bodyStr == "" { + return true, "ready" + } + return true, bodyStr +} diff --git a/cmd/atenet/internal/app/router/proxyrunner.go b/cmd/atenet/internal/app/router/proxyrunner.go new file mode 100644 index 000000000..d55913f1e --- /dev/null +++ b/cmd/atenet/internal/app/router/proxyrunner.go @@ -0,0 +1,183 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package router + +import ( + "context" + "fmt" + "log/slog" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + ProxyDeploymentName = "atenet-router-proxy" + ProxyServiceName = "atenet-router-proxy" + ProxyConfigMapName = "atenet-router-proxy-config" +) + +// proxyrunner manages the dynamic deployment and lifecycle of the configured +// networking proxy instance running inside Kubernetes. +type proxyrunner struct { + k8sClient client.Client + cfg RouterConfig + provider proxyProvider +} + +func newProxyRunner(k8sClient client.Client, cfg RouterConfig, provider proxyProvider) *proxyrunner { + return &proxyrunner{ + k8sClient: k8sClient, + cfg: cfg, + provider: provider, + } +} + +func (r *proxyrunner) reconcile(ctx context.Context) error { + if err := r.reconcileProxyConfigMap(ctx); err != nil { + return fmt.Errorf("failed configmap reconciliation: %w", err) + } + + if err := r.reconcileProxyDeployment(ctx); err != nil { + return fmt.Errorf("failed deployment reconciliation: %w", err) + } + + if err := r.reconcileProxyService(ctx); err != nil { + return fmt.Errorf("failed service reconciliation: %w", err) + } + + return nil +} + +func (r *proxyrunner) reconcileProxyConfigMap(ctx context.Context) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: ProxyConfigMapName, + Namespace: r.cfg.Namespace, + }, + Data: r.provider.ConfigMapData(), + } + + var existing corev1.ConfigMap + err := r.k8sClient.Get(ctx, client.ObjectKey{Namespace: r.cfg.Namespace, Name: ProxyConfigMapName}, &existing) + if err != nil { + if k8serrors.IsNotFound(err) { + slog.InfoContext(ctx, "Creating proxy ConfigMap", + slog.String("namespace", r.cfg.Namespace), + slog.String("name", ProxyConfigMapName), + slog.String("provider", r.provider.Name())) + return r.k8sClient.Create(ctx, cm) + } + return err + } + + existing.Data = cm.Data + return r.k8sClient.Update(ctx, &existing) +} + +func (r *proxyrunner) reconcileProxyDeployment(ctx context.Context) error { + replicas := int32(1) + labels := map[string]string{ + "app": "atenet-router-proxy", + "substrate.ate.dev/proxy": r.provider.Name(), + "substrate.ate.dev/router": "atenet-router", + } + + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: ProxyDeploymentName, + Namespace: r.cfg.Namespace, + Labels: labels, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{r.provider.Container()}, + Volumes: []corev1.Volume{ + { + Name: "proxy-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: ProxyConfigMapName, + }, + }, + }, + }, + }, + }, + }, + }, + } + + var existing appsv1.Deployment + err := r.k8sClient.Get(ctx, client.ObjectKey{Namespace: r.cfg.Namespace, Name: ProxyDeploymentName}, &existing) + if err != nil { + if k8serrors.IsNotFound(err) { + slog.InfoContext(ctx, "Creating managed router proxy Deployment", + slog.String("namespace", r.cfg.Namespace), + slog.String("provider", r.provider.Name())) + return r.k8sClient.Create(ctx, dep) + } + return err + } + + existing.Spec = dep.Spec + return r.k8sClient.Update(ctx, &existing) +} + +func (r *proxyrunner) reconcileProxyService(ctx context.Context) error { + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: ProxyServiceName, + Namespace: r.cfg.Namespace, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Selector: map[string]string{ + "app": "atenet-router-proxy", + "substrate.ate.dev/proxy": r.provider.Name(), + "substrate.ate.dev/router": "atenet-router", + }, + Ports: r.provider.ServicePorts(), + }, + } + + var existing corev1.Service + err := r.k8sClient.Get(ctx, client.ObjectKey{Namespace: r.cfg.Namespace, Name: ProxyServiceName}, &existing) + if err != nil { + if k8serrors.IsNotFound(err) { + slog.InfoContext(ctx, "Creating managed router proxy ClusterIP service", + slog.String("namespace", r.cfg.Namespace), + slog.String("provider", r.provider.Name())) + return r.k8sClient.Create(ctx, svc) + } + return err + } + + existing.Spec.Ports = svc.Spec.Ports + existing.Spec.Selector = svc.Spec.Selector + return r.k8sClient.Update(ctx, &existing) +} diff --git a/cmd/atenet/internal/app/router/router.go b/cmd/atenet/internal/app/router/router.go index c85fe4692..1c39e20be 100644 --- a/cmd/atenet/internal/app/router/router.go +++ b/cmd/atenet/internal/app/router/router.go @@ -61,21 +61,24 @@ func init() { // RouterConfig holds deployment setup and endpoint options for the router node instance. type RouterConfig struct { - Standalone bool - Namespace string - Kubeconfig string - AteapiAddr string - HttpPort int - XdsPort int - ExtprocPort int - ExtprocAddr string - EnvoyImage string - TemplatesFile string - StatusPort int - HealthInterval time.Duration - HttpsPort int - EnvoyCertPath string - LogLevel string + Standalone bool + Namespace string + Kubeconfig string + AteapiAddr string + HttpPort int + XdsPort int + ExtprocPort int + ExtprocAddr string + NetworkingMode string + EnvoyImage string + AgentgatewayImage string + TemplatesFile string + StatusPort int + HealthInterval time.Duration + HttpsPort int + TLSCertPath string + TLSKeyPath string + LogLevel string } // RouterServer instantiates and coordinates runtime threads executing system modules. @@ -140,17 +143,24 @@ func NewCmd() *cobra.Command { cmd.Flags().IntVar(&cfg.XdsPort, "port-xds", 18000, "TCP port listening for the xDS dynamic Envoy connections") cmd.Flags().IntVar(&cfg.ExtprocPort, "port-extproc", 50051, "Listen port for the Envoy dynamic External Processing (ext_proc) server") cmd.Flags().StringVar(&cfg.ExtprocAddr, "extproc-address", "127.0.0.1", "Host IP or address of the Envoy External Processing (ext_proc) server") + cmd.Flags().StringVar(&cfg.NetworkingMode, "networking-mode", NetworkingModeEnvoy, "Networking proxy mode: envoy or agentgateway") cmd.Flags().StringVar(&cfg.EnvoyImage, "envoy-image", "envoyproxy/envoy:v1.30-latest", "Image URI used for dynamically launched router instances") + cmd.Flags().StringVar(&cfg.AgentgatewayImage, "agentgateway-image", "cr.agentgateway.dev/agentgateway:v1.3.0-alpha.1", "Image URI used for Agentgateway router instances") cmd.Flags().StringVar(&cfg.TemplatesFile, "actor-templates-file", "", "Path to offline YAML configuration file listing ActorTemplates") cmd.Flags().IntVar(&cfg.StatusPort, "status-port", 4040, "Port to serve /statusz on (set <= 0 to disable serving status)") cmd.Flags().DurationVar(&cfg.HealthInterval, "health-interval", 1*time.Second, "Interval for checking health of dependent services") cmd.Flags().IntVar(&cfg.HttpsPort, "port-https", 8443, "TCP port for HTTPS workload traffic entering through the Envoy Router") - cmd.Flags().StringVar(&cfg.EnvoyCertPath, "envoy-cert-path", "", "Path to the Envoy certificate file (if empty, a self-signed cert will be generated for testing)") + cmd.Flags().StringVar(&cfg.TLSCertPath, "tls-cert-path", "", "Path to the proxy TLS certificate file") + cmd.Flags().StringVar(&cfg.TLSKeyPath, "tls-key-path", "", "Path to the proxy TLS private key file") return cmd } func NewRouterServer(cfg RouterConfig) (*RouterServer, error) { + if cfg.NetworkingMode == "" { + cfg.NetworkingMode = NetworkingModeEnvoy + } + var k8sClient client.Client var clientset kubernetes.Interface var err error @@ -211,26 +221,30 @@ func (s *RouterServer) Run(ctx context.Context) error { g, ctx := errgroup.WithContext(ctx) + provider, err := newProxyProvider(s.cfg) + if err != nil { + return err + } + xdsSrv := NewXdsServer(s.cfg.XdsPort) xdsSrv.SetConfig(s.cfg.HttpPort, s.cfg.ExtprocPort, s.cfg.ExtprocAddr) var certContent, keyContent string - if s.cfg.EnvoyCertPath == "" { - slog.InfoContext(ctx, "No Envoy certificate path provided, generating self-signed certificate for testing") - var err error + if provider.RequiresXDS() && tlsCertPath(s.cfg) == "" { + slog.InfoContext(ctx, "No proxy TLS certificate path provided, generating self-signed certificate for testing") certContent, keyContent, err = generateSelfSignedCert() if err != nil { return fmt.Errorf("failed to generate self-signed cert: %w", err) } } - xdsSrv.SetTlsConfig(s.cfg.HttpsPort, s.cfg.EnvoyCertPath, certContent, keyContent) + xdsSrv.SetTlsConfig(s.cfg.HttpsPort, tlsCertPath(s.cfg), certContent, keyContent) if s.extprocSrv == nil { s.extprocSrv = NewExtProcServer(s.cfg.ExtprocPort, s.apiClient) } - ctrl := NewController(s.k8sClient, s.clientset, s.cfg, xdsSrv, s.extprocSrv) + ctrl := NewController(s.k8sClient, s.clientset, s.cfg, xdsSrv, s.extprocSrv, provider) - s.health = newRouterHealth(s.cfg.HealthInterval, s.clientset, s.apiClient, s.cfg) + s.health = newRouterHealth(s.cfg.HealthInterval, s.clientset, s.apiClient, s.cfg, provider) // Start Controller / Watcher g.Go(func() error { @@ -245,17 +259,19 @@ func (s *RouterServer) Run(ctx context.Context) error { return nil }) - // Start xDS Server - g.Go(func() error { - slog.InfoContext(ctx, "Starting Envoy xDS Server", slog.Int("port", s.cfg.XdsPort)) - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.cfg.XdsPort)) - if err != nil { - return fmt.Errorf("failed to listen on port %d: %w", s.cfg.XdsPort, err) - } - defer lis.Close() + if provider.RequiresXDS() { + // Start xDS Server + g.Go(func() error { + slog.InfoContext(ctx, "Starting xDS Server", slog.Int("port", s.cfg.XdsPort), slog.String("provider", provider.Name())) + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.cfg.XdsPort)) + if err != nil { + return fmt.Errorf("failed to listen on port %d: %w", s.cfg.XdsPort, err) + } + defer lis.Close() - return xdsSrv.Serve(ctx, lis) - }) + return xdsSrv.Serve(ctx, lis) + }) + } // Start ExtProc Server g.Go(func() error { diff --git a/cmd/atenet/internal/app/router/status.go b/cmd/atenet/internal/app/router/status.go index c8e8be55c..da642f566 100644 --- a/cmd/atenet/internal/app/router/status.go +++ b/cmd/atenet/internal/app/router/status.go @@ -131,6 +131,7 @@ type DashboardContext struct { BuildTag string `json:"build_tag"` RouterClusterIP string `json:"router_cluster_ip"` Namespace string `json:"namespace"` + NetworkingMode string `json:"networking_mode"` HttpPort int `json:"port_http"` XdsPort int `json:"port_xds"` ExtprocPort int `json:"port_extproc"` @@ -238,6 +239,7 @@ func (s *RouterServer) handleStatusz(w http.ResponseWriter, req *http.Request) { BuildTag: buildInfo, RouterClusterIP: routerIP, Namespace: s.cfg.Namespace, + NetworkingMode: s.cfg.NetworkingMode, HttpPort: s.cfg.HttpPort, XdsPort: s.cfg.XdsPort, ExtprocPort: s.cfg.ExtprocPort, diff --git a/hack/install-ate.sh b/hack/install-ate.sh index 8e043e85f..9f26dd714 100755 --- a/hack/install-ate.sh +++ b/hack/install-ate.sh @@ -39,6 +39,8 @@ fi # ATE_DEMOS is an array that registers the prefix name of the demo functions. ATE_DEMOS=() +ATE_INSTALL_ATENET_ROUTER="${ATE_INSTALL_ATENET_ROUTER:-envoy}" + # Include demos. source "${ROOT}"/hack/install-demo-counter.sh source "${ROOT}"/hack/install-demo-sandbox.sh @@ -61,6 +63,7 @@ function usage() { echo "Overall infrastructure (all infrastructure components):" echo "" echo " --deploy-ate-system Deploy core system (CRDs, atelet, apiserver)" + echo " --router=envoy|agentgateway Select atenet-router implementation (default: envoy)" echo " --delete-ate-system Delete core system" echo " --delete-all Delete core system and all registered demos" echo "" @@ -108,6 +111,63 @@ run_ko() { -- ${KUBECTL_CONTEXT:+--context=${KUBECTL_CONTEXT}} } +set_atenet_router() { + case "$1" in + envoy|agentgateway) + ATE_INSTALL_ATENET_ROUTER="$1" + ;; + *) + echo "unsupported atenet router mode: $1" >&2 + exit 1 + ;; + esac +} + +atenet_router_manifest() { + case "${ATE_INSTALL_ATENET_ROUTER}" in + envoy) + echo "manifests/ate-install/atenet-router.yaml" + ;; + agentgateway) + echo "manifests/ate-install/atenet-router-agentgateway.yaml" + ;; + *) + echo "unsupported atenet router mode: ${ATE_INSTALL_ATENET_ROUTER}" >&2 + exit 1 + ;; + esac +} + +ate_install_kustomize_base_dir() { + case "${ATE_INSTALL_ATENET_ROUTER}" in + envoy) + echo "manifests/ate-install/base" + ;; + agentgateway) + echo "manifests/ate-install/base-agentgateway" + ;; + *) + echo "unsupported atenet router mode: ${ATE_INSTALL_ATENET_ROUTER}" >&2 + exit 1 + ;; + esac +} + +ate_install_kustomize_dir() { + case "${ATE_INSTALL_ATENET_ROUTER}" in + envoy) + echo "manifests/ate-install/kind" + ;; + agentgateway) + echo "manifests/ate-install/kind-agentgateway" + ;; + *) + echo "unsupported atenet router mode: ${ATE_INSTALL_ATENET_ROUTER}" >&2 + exit 1 + ;; + esac +} + create_valkey_ca_certs_secret() { log_step "create_valkey_ca_certs_secret" local ca_certs="" @@ -232,10 +292,10 @@ deploy_ate_system() { local manifests="" if [[ "${ATE_INSTALL_KIND:-false}" == "true" ]]; then # Build everything resolved with Kustomize for Kind - manifests=$(kubectl kustomize manifests/ate-install/kind --load-restrictor LoadRestrictionsNone | run_ko resolve -f -) + manifests=$(kubectl kustomize "$(ate_install_kustomize_dir)" --load-restrictor LoadRestrictionsNone | run_ko resolve -f -) else # Build everything resolved with base manifests for GKE - manifests=$(run_ko resolve -f manifests/ate-install) + manifests=$(kubectl kustomize "$(ate_install_kustomize_base_dir)" --load-restrictor LoadRestrictionsNone | run_ko resolve -f -) fi echo "${manifests}" | run_kubectl apply -f - @@ -305,7 +365,7 @@ deploy_atenet() { run_kubectl apply -f manifests/ate-install/ate-system-namespace.yaml \ && run_kubectl wait --for=jsonpath='{.status.phase}'=Active namespace/ate-system --timeout=60s - run_ko apply -f manifests/ate-install/atenet-router.yaml + run_ko apply -f "$(atenet_router_manifest)" run_ko apply -f manifests/ate-install/atenet-dns.yaml run_kubectl rollout status deployment/atenet-router -n ate-system --timeout=120s run_kubectl rollout status deployment/atenet-dns -n ate-system --timeout=120s @@ -324,7 +384,7 @@ delete_ate_system() { delete_atenet() { log_step "delete_atenet" - run_kubectl delete --ignore-not-found -f manifests/ate-install/atenet-router.yaml + run_kubectl delete --ignore-not-found -f "$(atenet_router_manifest)" } delete_all() { @@ -349,6 +409,9 @@ for arg in "$@"; do usage exit 0 ;; + --router=*) + set_atenet_router "${arg#--router=}" + ;; esac done @@ -367,6 +430,7 @@ while [[ "$#" -gt 0 ]]; do case $1 in --deploy-ate-system) deploy_ate_system ;; + --router=*) ;; --delete-ate-system) delete_ate_system ;; --delete-all) delete_all ;; diff --git a/manifests/ate-install/atenet-router-agentgateway.yaml b/manifests/ate-install/atenet-router-agentgateway.yaml new file mode 100644 index 000000000..d39972906 --- /dev/null +++ b/manifests/ate-install/atenet-router-agentgateway.yaml @@ -0,0 +1,197 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: atenet-router + namespace: ate-system + labels: + app: atenet-router +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: atenet-router +rules: +- apiGroups: + - "ate.dev" + resources: + - actortemplates + verbs: + - get + - watch + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: atenet-router +subjects: +- kind: ServiceAccount + name: atenet-router + namespace: ate-system +roleRef: + kind: ClusterRole + name: atenet-router + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: atenet-router-agentgateway-config + namespace: ate-system +data: + config.yaml: | + binds: + - port: 8080 + listeners: + - name: http + protocol: HTTP + routes: + - name: substrate-http + matches: + - path: + pathPrefix: / + policies: + extProc: + host: "127.0.0.1:50051" + processingOptions: + requestBodyMode: none + responseBodyMode: none + requestHeaderMode: send + responseHeaderMode: skip + requestTrailerMode: skip + responseTrailerMode: skip + backends: + - dynamic: {} + - port: 8443 + listeners: + - name: https + protocol: HTTPS + tls: + cert: "/run/servicedns.podcert.ate.dev/cert.pem" + key: "/run/servicedns.podcert.ate.dev/key.pem" + routes: + - name: substrate-https + matches: + - path: + pathPrefix: / + policies: + extProc: + host: "127.0.0.1:50051" + processingOptions: + requestBodyMode: none + responseBodyMode: none + requestHeaderMode: send + responseHeaderMode: skip + requestTrailerMode: skip + responseTrailerMode: skip + backends: + - dynamic: {} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: atenet-router + namespace: ate-system + labels: + app: atenet-router +spec: + replicas: 1 + selector: + matchLabels: + app: atenet-router + template: + metadata: + labels: + app: atenet-router + spec: + serviceAccountName: atenet-router + containers: + - name: atenet-router + image: ko://github.com/agent-substrate/substrate/cmd/atenet + args: + - "router" + - "--networking-mode=agentgateway" + - "--standalone" + - "--namespace=ate-system" + - "--port-http=8080" + - "--port-extproc=50051" + - "--extproc-address=127.0.0.1" + - "--ateapi-address=api.ate-system.svc:443" + - "--status-port=4040" + - "--port-https=8443" + - "--tls-cert-path=/run/servicedns.podcert.ate.dev/cert.pem" + - "--tls-key-path=/run/servicedns.podcert.ate.dev/key.pem" + ports: + - name: extproc + containerPort: 50051 + - name: status + containerPort: 4040 + - name: agentgateway + image: cr.agentgateway.dev/agentgateway:v1.3.0-alpha.1 + args: + - "-f" + - "/etc/agentgateway/config.yaml" + ports: + - name: http + containerPort: 8080 + - name: https + containerPort: 8443 + - name: readiness + containerPort: 15021 + - name: metrics + containerPort: 15020 + readinessProbe: + httpGet: + path: /healthz/ready + port: 15021 + periodSeconds: 10 + volumeMounts: + - name: agentgateway-config + mountPath: /etc/agentgateway + - name: "servicedns" + mountPath: "/run/servicedns.podcert.ate.dev" + volumes: + - name: agentgateway-config + configMap: + name: atenet-router-agentgateway-config + - name: "servicedns" + projected: + sources: + - podCertificate: + signerName: servicedns.podcert.ate.dev/identity + keyType: ECDSAP256 + certificateChainPath: cert.pem + keyPath: key.pem +--- +apiVersion: v1 +kind: Service +metadata: + name: atenet-router + namespace: ate-system +spec: + type: ClusterIP + selector: + app: atenet-router + ports: + - name: http + port: 80 + targetPort: 8080 + protocol: TCP + - name: https + port: 443 + targetPort: 8443 + protocol: TCP diff --git a/manifests/ate-install/atenet-router.yaml b/manifests/ate-install/atenet-router.yaml index d4269a59a..6bc84bb63 100644 --- a/manifests/ate-install/atenet-router.yaml +++ b/manifests/ate-install/atenet-router.yaml @@ -122,6 +122,7 @@ spec: image: ko://github.com/agent-substrate/substrate/cmd/atenet args: - "router" + - "--networking-mode=envoy" - "--standalone" - "--namespace=ate-system" - "--port-http=8080" @@ -131,7 +132,8 @@ spec: - "--ateapi-address=api.ate-system.svc:443" - "--status-port=4040" - "--port-https=8443" - - "--envoy-cert-path=/run/servicedns.podcert.ate.dev/credential-bundle.pem" + - "--tls-cert-path=/run/servicedns.podcert.ate.dev/credential-bundle.pem" + - "--tls-key-path=/run/servicedns.podcert.ate.dev/credential-bundle.pem" ports: - name: xds containerPort: 18000 diff --git a/manifests/ate-install/base-agentgateway/kustomization.yaml b/manifests/ate-install/base-agentgateway/kustomization.yaml new file mode 100644 index 000000000..d5883ba79 --- /dev/null +++ b/manifests/ate-install/base-agentgateway/kustomization.yaml @@ -0,0 +1,25 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../ate-api-server.yaml + - ../ate-controller.yaml + - ../atelet.yaml + - ../atenet-dns.yaml + - ../atenet-router-agentgateway.yaml + - ../valkey.yaml + - ../pod-certificate-controller.yaml diff --git a/manifests/ate-install/base/kustomization.yaml b/manifests/ate-install/base/kustomization.yaml new file mode 100644 index 000000000..61cde5ace --- /dev/null +++ b/manifests/ate-install/base/kustomization.yaml @@ -0,0 +1,25 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../ate-api-server.yaml + - ../ate-controller.yaml + - ../atelet.yaml + - ../atenet-dns.yaml + - ../atenet-router.yaml + - ../valkey.yaml + - ../pod-certificate-controller.yaml diff --git a/manifests/ate-install/kind-agentgateway/kustomization.yaml b/manifests/ate-install/kind-agentgateway/kustomization.yaml new file mode 100644 index 000000000..d9c9192b9 --- /dev/null +++ b/manifests/ate-install/kind-agentgateway/kustomization.yaml @@ -0,0 +1,43 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../ate-api-server.yaml + - ../ate-controller.yaml + - ../kind/atelet + - ../atenet-dns.yaml + - ../atenet-router-agentgateway.yaml + - ../valkey.yaml + - ../pod-certificate-controller.yaml + - ../kind/rustfs.yaml + - ../kind/otel-collector.yaml + +patches: + - patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ate-api-server-deployment + namespace: ate-system + spec: + template: + spec: + containers: + - name: ate-api-server + env: + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://opentelemetry-collector.otel-system.svc:4317