Thursday, March 05, 2026

An Agentic AI for Kubernetes Application Lifecycle Management: Complete Architecture





I. Introduction: The Holistic View of the Agentic AI


The Agentic AI for Kubernetes is conceived as a highly autonomous system, designed to act as an intelligent orchestrator for cloud-native applications. Its primary function is to bridge the gap between high-level user intent (expressed in natural language) and the low-level complexities of Kubernetes, effectively automating the entire application lifecycle from inception to ongoing management. This section outlines the complete architectural blueprint, detailing each core component and its responsibilities.


II. Core Architectural Components and Their Interactions


The Agentic AI is composed of several tightly integrated modules, each specializing in a particular aspect of Kubernetes application management. These modules communicate and cooperate to achieve the AI's overarching goals.


A. User Interface and Prompt Interpretation Module


This is the AI's primary interface with human users. It is responsible for receiving natural language requests, understanding user intent, and translating these requests into a structured, machine-readable format.


1. Natural Language Processing (NLP) Engine

The NLP engine processes incoming user prompts. It employs techniques such as named entity recognition (NER) to identify key elements like application names, service types, desired functionalities, and resource specifications. It also performs intent classification to determine whether the user wants to create, modify, delete, or query an application.


2. Intent and Entity Extractor

After initial NLP processing, this component extracts specific parameters and actions from the user's request. For example, if a user asks to "create a web application named 'MyWebApp' with a Python backend and a PostgreSQL database," the extractor would identify "MyWebApp" as the application name, "web application" as the type, "Python backend" as a component with language, and "PostgreSQL database" as another component with its technology.


Conceptual Go Code Snippet: User Request Data Structure

        

This Go struct represents the structured output after processing a user's natural language prompt.


        package prompt


        // ApplicationRequest represents the structured interpretation of a user's prompt

        // for an application.

        type ApplicationRequest struct {

            AppName     string             `json:"appName"`

            Action      string             `json:"action"` // e.g., "create", "update", "delete", "query"

            Description string             `json:"description"`

            Components  []ComponentRequest `json:"components"`

            GlobalSettings GlobalSettings `json:"globalSettings"`

            // ... other high-level parameters like desired architecture, security posture

        }


        // ComponentRequest describes a single service or part of the application.

        type ComponentRequest struct {

            Name           string            `json:"name"`

            Type           string            `json:"type"` // e.g., "webserver", "api-service", "database", "message-queue"

            Language       string            `json:"language"` // e.g., "Go", "Python", "Java"

            Framework      string            `json:"framework"` // e.g., "Gin", "Flask", "Spring Boot"

            Endpoints      []EndpointRequest `json:"endpoints"`

            Dependencies   []string          `json:"dependencies"` // Names of other components this one depends on

            ResourceSpec   ResourceSpec      `json:"resourceSpec"`

            EnvironmentVars map[string]string `json:"environmentVars"`

            // ... other specific configurations for the component

        }


        // EndpointRequest defines an API endpoint or web route.

        type EndpointRequest struct {

            Path        string `json:"path"`

            Method      string `json:"method"` // e.g., "GET", "POST"

            ContentType string `json:"contentType"`

            Response    string `json:"response"` // For simple static responses

            Logic       string `json:"logic"`    // For more complex business logic description

        }


        // ResourceSpec defines resource requirements for a component.

        type ResourceSpec struct {

            CPURequests    string `json:"cpuRequests"`

            CPULimits      string `json:"cpuLimits"`

            MemoryRequests string `json:"memoryRequests"`

            MemoryLimits   string `json:"memoryLimits"`

            Storage        string `json:"storage"` // e.g., "5Gi" for persistent storage

        }


        // GlobalSettings for the application.

        type GlobalSettings struct {

            IngressEnabled bool   `json:"ingressEnabled"`

            IngressHost    string `json:"ingressHost"`

            Namespace      string `json:"namespace"`

            // ... other global settings like security, logging, monitoring

        }


        // PromptProcessor defines the interface for processing user prompts.

        type PromptProcessor interface {

            Process(prompt string) (*ApplicationRequest, error)

        }


        // Example implementation sketch for Process:

        // func (p *MyNLPProcessor) Process(prompt string) (*ApplicationRequest, error) {

        //     // Placeholder for actual NLP/LLM integration

        //     // This would involve calling an NLP model, extracting entities,

        //     // and mapping them to the ApplicationRequest struct.

        //     // For demonstration, let's assume a hardcoded example:

        //     if strings.Contains(prompt, "GreetingService") {

        //         return &ApplicationRequest{

        //             AppName: "GreetingService",

        //             Action:  "create",

        //             // ... populate other fields based on advanced NLP

        //         }, nil

        //     }

        //     return nil, fmt.Errorf("could not understand prompt")

        // }


Explanation: The `ApplicationRequest` struct is the canonical representation of a user's desire. The `PromptProcessor` interface outlines the contract for any component (likely backed by an LLM and NLP techniques) that transforms raw text into this structured format.


B. Knowledge Base and State Management Module

This module is the memory of the Agentic AI. It stores the desired state of all managed applications, the observed state of the Kubernetes cluster, and crucial mappings between logical application components and their physical Kubernetes resources and generated files. This data is persisted to ensure continuity across AI restarts.


1. Desired State Store

Stores the `ApplicationRequest` (or a more refined internal model) for every application the AI is managing. This represents the user's intended configuration for the application.


2. Observed State Cache

A real-time, frequently updated cache of the current state of Kubernetes resources (Pods, Deployments, Services, etc.) within the cluster. This is populated by the Monitoring and Observability Engine.


3. Resource Association Mapping

This is a critical component that maintains a bidirectional mapping:

  • From logical application components (e.g., "GreetingService Frontend") to their corresponding Kubernetes resources (Deployment, Service, Ingress), Helm chart files, generated Go source code files, and Docker image names.
  • From Kubernetes resource UIDs or names back to the application component they belong to.

This mapping allows the AI to quickly identify which files or resources need modification when a user requests a change to a specific application component.


4. Resource Profiles and Best Practices

Stores predefined resource requirements (CPU, memory, storage) for common application types (e.g., a typical web server, a small database). It also holds best practices for Kubernetes configurations, security settings, and Go code patterns.


Conceptual Go Code Snippet: Knowledge Base Structures and Interface

       

These Go structs and interface illustrate how the AI would store and manage its internal knowledge.


        package knowledgebase


        import (

            "time"

            "k8s.io/apimachinery/pkg/types" // For Kubernetes object UIDs

            "github.com/your-org/agentic-ai/prompt" // Assuming prompt package is defined

        )


        // ApplicationState represents the AI's comprehensive knowledge about an application.

        type ApplicationState struct {

            AppName         string                     `json:"appName"`

            DesiredRequest  prompt.ApplicationRequest  `json:"desiredRequest"` // The last successfully processed user request

            CurrentHelmChart HelmChartInfo             `json:"currentHelmChart"`

            Components      map[string]ComponentStatus `json:"components"` // Key: component name

            LastReconciled  time.Time                  `json:"lastReconciled"`

            Status          string                     `json:"status"` // e.g., "Deployed", "Reconciling", "Error"

            // ... other metadata

        }


        // ComponentStatus details the state and associated resources for an application component.

        type ComponentStatus struct {

            Name             string                  `json:"name"`

            Type             string                  `json:"type"`

            GoSourceFilePath string                  `json:"goSourceFilePath"`

            Dockerfile       string                  `json:"dockerfile"`

            DockerImage      string                  `json:"dockerImage"`

            KubeResources    []KubeResourceReference `json:"kubeResources"`

            // ... other specific details like current pod count, health

        }


        // KubeResourceReference links to a specific Kubernetes object.

        type KubeResourceReference struct {

            Kind      string    `json:"kind"` // e.g., "Deployment", "Service", "Pod"

            Namespace string    `json:"namespace"`

            Name      string    `json:"name"`

            UID       types.UID `json:"uid"` // Kubernetes unique ID

        }


        // HelmChartInfo stores details about the generated Helm chart.

        type HelmChartInfo struct {

            Path      string `json:"path"` // Local path to the chart directory

            Version   string `json:"version"`

            LastAppliedRevision int `json:"lastAppliedRevision"`

        }


        // KnowledgeBaseStore defines the interface for interacting with the knowledge base.

        type KnowledgeBaseStore interface {

            SaveApplicationState(appState *ApplicationState) error

            GetApplicationState(appName string) (*ApplicationState, error)

            DeleteApplicationState(appName string) error

            GetAllApplicationNames() ([]string, error)


            // Methods for associating resources

            AssociateKubeResource(appName, componentName string, ref KubeResourceReference) error

            GetKubeResourcesForComponent(appName, componentName string) ([]KubeResourceReference, error)

            GetComponentForKubeResource(uid types.UID) (appName, componentName string, err error)

        }


        // Example implementation sketch for a simple in-memory store (for illustration, not production)

        // type InMemoryKnowledgeBase struct {

        //     apps map[string]*ApplicationState

        //     // ... other maps for resource associations

        // }

        // func (m *InMemoryKnowledgeBase) SaveApplicationState(appState *ApplicationState) error { /* ... */ }


Explanation: `ApplicationState` captures the full context of a managed application. `ComponentStatus` details each part, including links to generated code and Kubernetes resources. The `KnowledgeBaseStore` interface defines operations for persisting and retrieving this vital information. In a production system, this would be backed by a robust database (e.g., PostgreSQL, etcd).


C. Monitoring and Observability Engine

   

This module is the AI's "eyes and ears" on the Kubernetes cluster. It continuously gathers data about the cluster's actual state and feeds it to the Knowledge Base and the Planning Engine.


1. Kubernetes Event Listener

This component subscribes to the Kubernetes API server's watch endpoints for various resource types (Pods, Deployments, Services, Ingresses, PVCs, etc.). It receives real-time events (Added, Modified, Deleted) and pushes them to an event processing pipeline.


2. State Collector

Periodically, or in response to specific events, this component performs direct API calls to fetch the current state of resources. This complements event-driven updates and helps ensure the observed state is eventually consistent.


3. Health and Performance Monitor

Integrates with metrics systems (e.g., Prometheus) to collect health and performance indicators for pods and services. This data is used for anomaly detection and to inform scaling or remediation decisions.


4. Reconciliation Loop Trigger

Based on observed events or scheduled intervals, this component triggers the Planning and Decision-Making Engine to perform reconciliation if a discrepancy between desired and observed state is detected.


Conceptual Go Code Snippet: Kubernetes Event Watcher Interface and Processor


This snippet shows the core structure for watching Kubernetes events and processing them.


        package monitoring


        import (

            "context"

            "log"

            "time"


            corev1 "k8s.io/api/core/v1"

            metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

            "k8s.io/apimachinery/pkg/watch"

            "k8s.io/client-go/kubernetes"

            "k8s.io/client-go/rest"

            "github.com/your-org/agentic-ai/knowledgebase" // Assuming knowledgebase package is defined

        )


        // KubeEventProcessor defines how different event types are handled.

        type KubeEventProcessor interface {

            ProcessPodEvent(event watch.Event, pod *corev1.Pod) error

            // Add methods for other resource types like Deployments, Services, etc.

        }


        // KubeWatcher watches Kubernetes resources and dispatches events.

        type KubeWatcher struct {

            clientset *kubernetes.Clientset

            kbStore   knowledgebase.KnowledgeBaseStore

            processor KubeEventProcessor

            resyncPeriod time.Duration

        }


        func NewKubeWatcher(config *rest.Config, kbStore knowledgebase.KnowledgeBaseStore, processor KubeEventProcessor) (*KubeWatcher, error) {

            clientset, err := kubernetes.NewForConfig(config)

            if err != nil {

                return nil, err

            }

            return &KubeWatcher{

                clientset: clientset,

                kbStore:   kbStore,

                processor: processor,

                resyncPeriod: 5 * time.Minute, // Periodically re-list resources

            }, nil

        }


        func (kw *KubeWatcher) Start(ctx context.Context) {

            log.Println("Monitoring: Starting Kubernetes Pod watcher...")

            go kw.watchPods(ctx)

            // In a full system, this would start watchers for Deployments, Services, etc.

        }


        func (kw *KubeWatcher) watchPods(ctx context.Context) {

            for {

                select {

                case <-ctx.Done():

                    log.Println("Monitoring: Pod watcher stopped.")

                    return

                default:

                    // Loop to re-establish watch on disconnect or error

                }


                watcher, err := kw.clientset.CoreV1().Pods("").Watch(ctx, metav1.ListOptions{})

                if err != nil {

                    log.Printf("Monitoring: Error establishing Pod watch, retrying in 5s: %v", err)

                    time.Sleep(5 * time.Second)

                    continue

                }

                log.Println("Monitoring: Pod watch established.")


                for event := range watcher.ResultChan() {

                    pod, ok := event.Object.(*corev1.Pod)

                    if !ok {

                        log.Printf("Monitoring: Received unexpected object type %T from Pod watch", event.Object)

                        continue

                    }

                    if err := kw.processor.ProcessPodEvent(event, pod); err != nil {

                        log.Printf("Monitoring: Error processing Pod event: %v", err)

                    }

                }

                log.Println("Monitoring: Pod watch channel closed, re-establishing...")

                // Add a short delay before re-establishing to prevent tight loops on persistent errors

                time.Sleep(1 * time.Second)

            }

        }


        // SimplePodEventProcessor (example implementation for KubeEventProcessor)

        type SimplePodEventProcessor struct {

            kbStore knowledgebase.KnowledgeBaseStore

        }


        func NewSimplePodEventProcessor(kbStore knowledgebase.KnowledgeBaseStore) *SimplePodEventProcessor {

            return &SimplePodEventProcessor{kbStore: kbStore}

        }


        func (s *SimplePodEventProcessor) ProcessPodEvent(event watch.Event, pod *corev1.Pod) error {

            log.Printf("Monitoring: Received %s event for Pod %s/%s (Phase: %s)",

                event.Type, pod.Namespace, pod.Name, pod.Status.Phase)


            // Here, the processor would update the observed state in the knowledge base.

            // It might also trigger reconciliation if a critical state change occurs.

            // For example, if a pod fails, the AI needs to know to potentially restart it

            // or alert the user.

            //

            // In a real system, this would involve:

            // 1. Updating the observed state of the specific pod in the kbStore.

            // 2. Checking if this event signifies a deviation from the desired state.

            // 3. Potentially enqueueing a reconciliation request for the affected application.


            // Example: Update observed state in KB

            // appName, componentName, err := s.kbStore.GetComponentForKubeResource(pod.UID)

            // if err == nil {

            //     appState, _ := s.kbStore.GetApplicationState(appName)

            //     if appState != nil {

            //         // Update appState.Components[componentName].KubeResources with current pod status

            //         // and save appState back to kbStore.

            //     }

            // }


            return nil

        }


Explanation: The `KubeWatcher` sets up a continuous watch on Kubernetes Pods. Events received are dispatched to a `KubeEventProcessor`. The `SimplePodEventProcessor` demonstrates how the AI would log these events and, in a full system, update its internal knowledge base (`kbStore`) with the observed state, potentially triggering further actions.


D. Planning and Decision-Making Engine


This is the "brain" of the Agentic AI. It takes the user's desired state (from the Prompt Interpretation Module) and the current observed state (from the Monitoring Module and Knowledge Base) and formulates a concrete action plan to bridge any gaps.


1. Intent-to-Action Translator

Translates the high-level `ApplicationRequest` into a sequence of specific, atomic Kubernetes-related actions. This involves understanding dependencies between components (e.g., a service must exist before a deployment can use it).


2. Dependency Graph and Ordering

Constructs a dependency graph of all required Kubernetes resources and generated code. It then determines the correct order of operations (e.g., CRDs before Custom Resources, Secrets before Deployments that use them).


3. Reconciliation Logic

When a discrepancy is detected between the desired state (from the Knowledge Base) and the observed state (from the Monitoring Engine), this component calculates the minimal set of changes required to bring the system back into alignment. This is a core part of the "control loop" pattern common in Kubernetes.


4. Resource Provisioning Decisioner

Determines the appropriate Kubernetes resource types (Deployment, StatefulSet, Service, Ingress, PVC, ConfigMap, etc.) and their configurations based on the `ComponentRequest` and best practices from the Knowledge Base. This includes selecting appropriate storage classes for PVCs and setting resource requests/limits.


Conceptual Go Code Snippet: Plan Generator Interface and Action Plan


This snippet outlines how the AI would generate a plan of actions.


        package planning


        import (

            "fmt"

            "github.com/your-org/agentic-ai/knowledgebase"

            "github.com/your-org/agentic-ai/prompt"

        )


        // ActionType defines the type of action to be performed.

        type ActionType string


        const (

            ActionGenerateGoCode    ActionType = "GenerateGoCode"

            ActionGenerateHelmChart ActionType = "GenerateHelmChart"

            ActionBuildDockerImage  ActionType = "BuildDockerImage"

            ActionPushDockerImage   ActionType = "PushDockerImage"

            ActionApplyHelmChart    ActionType = "ApplyHelmChart"

            ActionDeleteHelmRelease ActionType = "DeleteHelmRelease"

            ActionUpdateKubeResource ActionType = "UpdateKubeResource"

            ActionCreateKubeResource ActionType = "CreateKubeResource"

            ActionDeleteKubeResource ActionType = "DeleteKubeResource"

            // ... other actions like generating CRDs, webhooks, RBAC

        )


        // Action represents a single step in the execution plan.

        type Action struct {

            Type        ActionType            `json:"type"`

            Description string                `json:"description"`

            Payload     map[string]interface{} `json:"payload"` // Dynamic payload for action-specific data

            Dependencies []int                 `json:"dependencies"` // Indices of actions that must complete first

        }


        // ActionPlan is an ordered list of actions to achieve the desired state.

        type ActionPlan struct {

            AppName string   `json:"appName"`

            Actions []Action `json:"actions"`

        }


        // PlanGenerator defines the interface for generating action plans.

        type PlanGenerator interface {

            GeneratePlan(

                desiredState *prompt.ApplicationRequest,

                currentState *knowledgebase.ApplicationState,

            ) (*ActionPlan, error)

        }


        // Example implementation sketch for GeneratePlan:

        // func (pg *MyPlanGenerator) GeneratePlan(

        //     desiredState *prompt.ApplicationRequest,

        //     currentState *knowledgebase.ApplicationState,

        // ) (*ActionPlan, error) {

        //     plan := &ActionPlan{AppName: desiredState.AppName}

        //

        //     // 1. Compare desiredState with currentState to identify differences.

        //     // 2. Determine necessary Go code changes/generation.

        //     // 3. Determine necessary Helm chart changes/generation.

        //     // 4. Add actions in dependency order.

        //     //

        //     // Example: If app doesn't exist, first generate code, then chart, then apply.

        //     if currentState == nil { // New application

        //         plan.Actions = append(plan.Actions, Action{

        //             Type: ActionGenerateGoCode,

        //             Description: "Generate Go code for all components",

        //             Payload: map[string]interface{}{"components": desiredState.Components},

        //         })

        //         // ... add actions for building/pushing images, generating/applying Helm chart

        //     } else { // Existing application, check for changes

        //         // ... logic to diff desiredState and currentState

        //         // ... add actions like ActionUpdateKubeResource, ActionGenerateHelmChart

        //     }

        //

        //     return plan, nil

        // }


Explanation: The `ActionPlan` struct holds an ordered sequence of `Action`s. The `PlanGenerator` interface defines the core logic for translating a desired application state into these concrete steps, taking into account the current state to perform minimal, targeted changes.


E. Code and Configuration Generation Module


This module is responsible for producing all the necessary artifacts for deploying and running applications on Kubernetes.


1. Go Code Generator

Generates idiomatic, production-ready Go code for:

  • Microservices: HTTP/gRPC servers, clients, business logic based on `EndpointRequest` and `ComponentRequest`.
  • Custom Controllers: For managing Custom Resources, including boilerplate, API types, and the reconciliation loop logic.
  • Admission Webhook Servers: Go code that implements validation or mutation logic for Kubernetes API requests.

The generator adheres to clean code principles, includes structured logging, and handles common patterns like configuration loading and dependency injection.


2. Helm Chart Generator

Creates or updates Helm charts based on the `ApplicationRequest` and the generated Go code. This includes:

  • `Chart.yaml`, `values.yaml`, and `_helpers.tpl`.
  • Kubernetes manifests (`deployment.yaml`, `service.yaml`, `ingress.yaml`, `pvc.yaml`, `secret.yaml`, `configmap.yaml`, `crd.yaml`, `serviceaccount.yaml`, `role.yaml`, `rolebinding.yaml`, `validatingwebhookconfiguration.yaml`, `mutatingwebhookconfiguration.yaml`).

It dynamically injects resource requests/limits, image names, environment variables, and other configurations derived from the `ApplicationRequest` and best practices.


3. Dockerfile Generator

Generates optimized Dockerfiles for each Go microservice, ensuring efficient builds and small image sizes (e.g., using multi-stage builds with Alpine Linux).


Conceptual Go Code Snippet: Code and Helm Chart Generator Interfaces


These interfaces illustrate the contracts for generating various artifacts.


        package codegen


        import (

            "fmt"

            "github.com/your-org/agentic-ai/prompt"

            "github.com/your-org/agentic-ai/knowledgebase"

        )


        // GoCodeGenerator defines the interface for generating Go source files.

        type GoCodeGenerator interface {

            GenerateMicroservice(component *prompt.ComponentRequest) (filePath string, err error)

            GenerateCustomController(crdName string, component *prompt.ComponentRequest) (filePath string, err error)

            GenerateAdmissionWebhook(webhookName string, component *prompt.ComponentRequest) (filePath string, err error)

            // ... other code generation methods

        }


        // HelmChartGenerator defines the interface for generating and updating Helm charts.

        type HelmChartGenerator interface {

            GenerateChart(appRequest *prompt.ApplicationRequest, appState *knowledgebase.ApplicationState) (chartPath string, err error)

            UpdateChart(appRequest *prompt.ApplicationRequest, appState *knowledgebase.ApplicationState) (chartPath string, err error)

        }


        // DockerfileGenerator defines the interface for generating Dockerfiles.

        type DockerfileGenerator interface {

            GenerateDockerfile(componentName, goSourcePath string) (dockerfilePath string, err error)

        }


        // Example implementation sketch for GenerateMicroservice:

        // func (g *MyGoCodeGenerator) GenerateMicroservice(component *prompt.ComponentRequest) (string, error) {

        //     // This would involve templating Go code based on component.Type, component.Endpoints, etc.

        //     // For instance, for a webserver:

        //     // - Create main.go

        //     // - Add http.HandleFunc for each endpoint

        //     // - Include logging, error handling

        //     // - Add resource-specific configurations (e.g., Redis client if dependency exists)

        //     // This is a highly complex templating/code synthesis task.

        //     code := fmt.Sprintf(`

        // package main

        //

        // import (

        //     "fmt"

        //     "log"

        //     "net/http"

        //     "os"

        // )

        //

        // func main() {

        //     port := os.Getenv("PORT")

        //     if port == "" {

        //         port = "8080"

        //     }

        //     listenAddr := fmt.Sprintf(":%s", port)

        //

        //     http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        //         log.Printf("Component %s: Received request for path: %%s", r.URL.Path)

        //         fmt.Fprintf(w, "Hello from %s!", "%s") // Placeholder for component name

        //     })

        //

        //     log.Printf("Component %s: Starting server on %%s", listenAddr)

        //     if err := http.ListenAndServe(listenAddr, nil); err != nil {

        //         log.Fatalf("Component %s: Failed to start server: %%v", err)

        //     }

        // }

        // `, component.Name, component.Name, component.Name)

        //

        //     // Save code to file, update knowledge base with path

        //     return fmt.Sprintf("generated/%s/main.go", component.Name), nil

        // }


Explanation: These interfaces define the capabilities of the generation module. The `GenerateMicroservice` example sketch shows how the AI would construct Go code, typically using templates and dynamic insertions based on the `ComponentRequest`. The complexity lies in ensuring correctness, security, and adherence to Go best practices for diverse service types.


F. Deployment and Management Module


This module is the AI's "hands" on the Kubernetes cluster. It executes the plans formulated by the Planning Engine by interacting directly with the Kubernetes API.


1. Kubernetes API Client

Uses the `client-go` library to perform CRUD (Create, Read, Update, Delete) operations on Kubernetes resources. It handles authentication, error retries, and rate limiting.


2. Helm Client Integration

Interacts with the Helm CLI or Go SDK to install, upgrade, and uninstall Helm charts. This ensures that application deployments are versioned, manageable, and can be rolled back.


3. Docker Build and Push Client

Builds Docker images from the generated Dockerfiles and Go code, then pushes these images to a configured container registry (e.g., Docker Hub, Harbor, GCR).


4. Rollout and Rollback Orchestrator

Manages application updates, ensuring smooth rollouts (e.g., using rolling updates for Deployments) and providing mechanisms for quick rollbacks to previous stable versions if issues arise.


Conceptual Go Code Snippet: Deployment Client Interface


This interface shows how the AI would interact with Kubernetes and Helm.


        package deployment


        import (

            "context"

            "github.com/your-org/agentic-ai/knowledgebase"

            "github.com/your-org/agentic-ai/planning"

        )


        // KubeDeployer defines the interface for deploying and managing resources on Kubernetes.

        type KubeDeployer interface {

            ApplyHelmChart(ctx context.Context, chartPath, namespace, releaseName string, values map[string]interface{}) error

            DeleteHelmRelease(ctx context.Context, releaseName, namespace string) error

            ApplyManifest(ctx context.Context, manifest string, namespace string) error // For raw Kube manifests

            DeleteResource(ctx context.Context, kind, name, namespace string) error

            // ... methods for building/pushing docker images

        }


        // ActionExecutor executes a single action from the action plan.

        type ActionExecutor interface {

            Execute(ctx context.Context, action planning.Action, appState *knowledgebase.ApplicationState) error

        }


        // Example implementation sketch for Execute for ActionApplyHelmChart:

        // func (e *MyActionExecutor) Execute(ctx context.Context, action planning.Action, appState *knowledgebase.ApplicationState) error {

        //     switch action.Type {

        //     case planning.ActionApplyHelmChart:

        //         chartPath := action.Payload["chartPath"].(string)

        //         namespace := action.Payload["namespace"].(string)

        //         releaseName := action.Payload["releaseName"].(string)

        //         values := action.Payload["values"].(map[string]interface{})

        //

        //         log.Printf("Executing: Applying Helm chart %s for release %s in namespace %s", chartPath, releaseName, namespace)

        //         err := e.kubeDeployer.ApplyHelmChart(ctx, chartPath, namespace, releaseName, values)

        //         if err != nil {

        //             return fmt.Errorf("failed to apply Helm chart: %w", err)

        //         }

        //         log.Printf("Successfully applied Helm chart %s", chartPath)

        //         // Update appState with new Helm release info

        //         return nil

        //

        //     case planning.ActionGenerateGoCode:

        //         // Call the GoCodeGenerator and update appState with file paths

        //         return nil

        //     // ... handle other action types

        //     default:

        //         return fmt.Errorf("unknown action type: %s", action.Type)

        //     }

        // }


Explanation: The `KubeDeployer` provides an abstraction over Kubernetes and Helm operations. The `ActionExecutor` interface defines how the AI processes each step in the `ActionPlan`. The example shows how it would call the underlying deployer to apply a Helm chart, and in a full system, it would also invoke code generation, Docker build/push, and direct Kubernetes API calls for other action types.


G. Advanced Kubernetes Capabilities Integration


The Agentic AI seamlessly integrates with and generates configurations for advanced Kubernetes features.


1. Admission Controllers

The AI can generate `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` resources. Crucially, it also generates the Go code for the webhook server that implements the actual validation or mutation logic, ensuring that policies are enforced before resources are persisted in the cluster.


2. Custom Resources (CRs) and Custom Controllers

Upon user request for higher-level abstractions, the AI generates `CustomResourceDefinition` (CRD) manifests. It then generates the Go code for a custom controller that watches instances of this CRD and reconciles them into standard Kubernetes resources (Deployments, Services, etc.), effectively extending the Kubernetes API.


3. Ingress Management

The AI can create and configure `Ingress` resources to expose applications to external traffic. This includes setting hostnames, path-based routing, and integrating with certificate managers (e.g., Cert-Manager) for automated TLS certificate provisioning, or generating self-signed certificates for development environments.


4. Security Credentials and RBAC

The AI manages Kubernetes `Secrets` for sensitive data, either by integrating with external secret management systems or by directly encoding user-provided values. It generates `ServiceAccount` resources for pods and automatically creates `Role` and `RoleBinding` resources to grant the necessary minimum permissions (Role-Based Access Control) for the application components to function securely.


5. Persistent Volumes (PVs) and Persistent Volume Claims (PVCs)

For stateful applications, the AI generates `PersistentVolumeClaim` (PVC) resources, specifying storage size, access modes, and storage class. It understands how to configure `StatefulSets` to ensure stable network identities and ordered deployments/scaling for stateful workloads. The AI uses its knowledge base to determine appropriate storage requirements.


III. The Agentic AI's Control Loop: Orchestrating Intelligence


The entire system operates on a continuous control loop, similar to how Kubernetes itself functions.


    1. Observe: The Monitoring and Observability Engine constantly watches the Kubernetes API for events and collects the current state of resources.

    2. Analyze: The collected observed state is compared against the desired state stored in the Knowledge Base. Any discrepancies or new user requests are identified.

    3. Plan: The Planning and Decision-Making Engine takes the identified discrepancies or new user intents and formulates an `ActionPlan` – a sequence of concrete steps.

    4. Act: The Deployment and Management Module executes the `ActionPlan` by generating code, building images, applying Helm charts, and interacting with the Kubernetes API.

    5. Update Knowledge: As actions are executed, the Knowledge Base is updated with the new desired state, observed state, and resource mappings.


This loop ensures that the Agentic AI is always working towards maintaining the user's desired application state, reacting to changes, and proactively managing the Kubernetes environment.


IV. Conclusion: A Vision for Autonomous Cloud-Native Operations


The Agentic AI for Kubernetes represents a profound shift towards autonomous application management. By integrating advanced NLP, AI planning, dynamic code generation, and a deep understanding of Kubernetes internals, it empowers users to define complex application architectures with natural language, while the AI handles the intricate details of implementation, deployment, and ongoing operation. While the full, production-ready implementation of such an AI is a massive undertaking, this architectural blueprint and the conceptual code snippets illustrate the foundational components and their interactions, paving the way for a future where Kubernetes management is intuitive, efficient, and intelligent.



Addendum: Illustrative Code for Agentic AI Component and Running Example Application


As discussed, the following provides a fully functional example of the *output* an Agentic AI would generate (the `GreetingService` application) and a conceptual, illustrative piece of Go code for *one foundational component* of the Agentic AI itself (the Kubernetes Event Watcher).


A. Agentic AI Component: Kubernetes Event Watcher (Conceptual Go Code)


This Go program demonstrates how a core part of the Agentic AI would continuously watch for Pod events across all namespaces in a Kubernetes cluster. In a real AI, this would be a sophisticated component that feeds events into a larger decision-making and reconciliation engine.


package main


import (

    "context"

    "fmt"

    "log"

    "os"

    "time"


    corev1 "k8s.io/api/core/v1"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

    "k8s.io/apimachinery/pkg/watch"

    "k8s.io/client-go/kubernetes"

    "k8s.io/client-go/rest"

    "k8s.io/client-go/tools/clientcmd"

)


// AgenticAIEventProcessor is a conceptual interface for processing Kubernetes events

// In a full AI, this would interact with the Knowledge Base and Planning Engine.

type AgenticAIEventProcessor interface {

    ProcessPodEvent(event watch.Event, pod *corev1.Pod)

    // Add methods for other resource types (Deployments, Services, etc.)

}


// SimplePodEventProcessor implements AgenticAIEventProcessor for demonstration

type SimplePodEventProcessor struct{}


func (s *SimplePodEventProcessor) ProcessPodEvent(event watch.Event, pod *corev1.Pod) {

    log.Printf("AI Event Processor: Received %s event for Pod %s/%s (Phase: %s)",

        event.Type, pod.Namespace, pod.Name, pod.Status.Phase)


    // In a real AI, this would trigger more complex logic:

    // - Update internal state in the AI's knowledge base

    // - Check against desired state for reconciliation

    // - Trigger alerts if a critical pod fails

    // - Log detailed metrics

    switch event.Type {

    case watch.Added:

        log.Printf("  -> Pod %s/%s was added. Initializing monitoring.", pod.Namespace, pod.Name)

    case watch.Modified:

        log.Printf("  -> Pod %s/%s was modified. Current status: %s. Reason: %s",

            pod.Namespace, pod.Name, pod.Status.Phase, pod.Status.Reason)

        if pod.Status.Phase == corev1.PodFailed {

            log.Printf("  -> ALERT: Pod %s/%s has FAILED! Investigating...", pod.Namespace, pod.Name)

            // AI would initiate troubleshooting or re-deployment here

        }

    case watch.Deleted:

        log.Printf("  -> Pod %s/%s was deleted. Cleaning up associated internal state.", pod.Namespace, pod.Name)

    }

}


// getKubeConfig returns a Kubernetes client config, preferring in-cluster config

// but falling back to ~/.kube/config for local development.

func getKubeConfig() (*rest.Config, error) {

    // Try in-cluster config

    config, err := rest.InClusterConfig()

    if err == nil {

        log.Println("Using in-cluster Kubernetes config.")

        return config, nil

    }


    // Fallback to kubeconfig file for local development

    kubeconfigPath := os.Getenv("KUBECONFIG")

    if kubeconfigPath == "" {

        kubeconfigPath = clientcmd.RecommendedHomeFile

    }

    log.Printf("Using kubeconfig file: %s", kubeconfigPath)

    config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath)

    if err != nil {

        return nil, fmt.Errorf("could not get Kubernetes config: %w", err)

    }

    return config, nil

}


func main() {

    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

    log.Println("Agentic AI Kubernetes Event Watcher starting...")


    config, err := getKubeConfig()

    if err != nil {

        log.Fatalf("Failed to get Kubernetes config: %v", err)

    }


    clientset, err := kubernetes.NewForConfig(config)

    if err != nil {

        log.Fatalf("Failed to create Kubernetes clientset: %v", err)

    }


    eventProcessor := &SimplePodEventProcessor{}


    // Create a context that can be cancelled to stop the watcher gracefully

    ctx, cancel := context.WithCancel(context.Background())

    defer cancel()


    // Start watching for Pod events in all namespaces

    // In a real AI, this would be more sophisticated, potentially watching multiple resource types

    // and handling reconnects.

    go func() {

        for {

            select {

            case <-ctx.Done():

                log.Println("Kubernetes Pod watcher goroutine exiting.")

                return

            default:

                // Continue

            }


            log.Println("Establishing new watch for Pods...")

            watcher, err := clientset.CoreV1().Pods("").Watch(ctx, metav1.ListOptions{})

            if err != nil {

                log.Printf("Error establishing Pod watch, retrying in 5 seconds: %v", err)

                time.Sleep(5 * time.Second)

                continue

            }


            for event := range watcher.ResultChan() {

                pod, ok := event.Object.(*corev1.Pod)

                if !ok {

                    log.Printf("Received unexpected object type %T from watch", event.Object)

                    continue

                }

                eventProcessor.ProcessPodEvent(event, pod)

            }

            log.Println("Pod watch channel closed, re-establishing...")

            // If the channel closes, the watch might have timed out or encountered an error.

            // Loop to re-establish the watch.

            time.Sleep(1 * time.Second) // Prevent tight loop on immediate closure

        }

    }()


    log.Println("Agentic AI Kubernetes Event Watcher running. Press Ctrl+C to exit.")

    // Keep the main goroutine alive

    select {}

}


B. Running Example Application: GreetingService (Full Code)


This section contains the complete Go code for the frontend and backend microservices of the `GreetingService` application, along with its full Helm chart structure. This is the code that the Agentic AI would generate for the user.


1. GreetingService Frontend (main.go)


This Go application serves a simple "Hello from Frontend!" message.


package main


import (

    "fmt"

    "log"

    "net/http"

    "os"

)


func main() {

    // Get port from environment variable, default to 8080

    port := os.Getenv("PORT")

    if port == "" {

        port = "8080"

    }

    listenAddr := fmt.Sprintf(":%s", port)


    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        log.Printf("Frontend: Received request from %s for path: %s", r.RemoteAddr, r.URL.Path)

        fmt.Fprintf(w, "Hello from Frontend!")

    })


    log.Printf("Frontend: Starting server on %s", listenAddr)

    if err := http.ListenAndServe(listenAddr, nil); err != nil {

        log.Fatalf("Frontend: Failed to start server: %v", err)

    }

}


2. GreetingService Backend (main.go)


This Go application provides a "/greet" API endpoint, returning "Hello from Backend!".


package main


import (

    "fmt"

    "log"

    "net/http"

    "os"

)


func main() {

    // Get port from environment variable, default to 8081

    port := os.Getenv("PORT")

    if port == "" {

        port = "8081"

    }

    listenAddr := fmt.Sprintf(":%s", port)


    http.HandleFunc("/greet", func(w http.ResponseWriter, r *http.Request) {

        log.Printf("Backend: Received request from %s for path: %s", r.RemoteAddr, r.URL.Path)

        fmt.Fprintf(w, "Hello from Backend!")

    })


    log.Printf("Backend: Starting server on %s", listenAddr)

    if err := http.ListenAndServe(listenAddr, nil); err != nil {

        log.Fatalf("Backend: Failed to start server: %v", err)

    }

}


3. Helm Chart for GreetingService


This is the complete Helm chart structure and content that the Agentic AI would generate for the `GreetingService` application.


Directory structure:


greeting-service/

|-- Chart.yaml

|-- values.yaml

|-- templates/

|   |-- _helpers.tpl

|   |-- frontend-deployment.yaml

|   |-- frontend-service.yaml

|   |-- backend-deployment.yaml

|   |-- backend-service.yaml

|   |-- ingress.yaml

|   |-- serviceaccount.yaml

|-- .helmignore


File: greeting-service/Chart.yaml


apiVersion: v2

name: greeting-service

description: A Helm chart for the GreetingService application

type: application

version: 0.1.0

appVersion: "1.0.0"


File: greeting-service/values.yaml


# Default values for greeting-service.

# This is a YAML-formatted file.

# Declare variables to be passed into your templates.


replicaCount: 1


frontend:

  image:

    repository: your-docker-registry/greeting-service-frontend # Replace with your registry

    pullPolicy: IfNotPresent

    tag: "1.0.0" # default to the AppVersion in Chart.yaml

  service:

    type: ClusterIP

    port: 80

    targetPort: 8080

  resources:

    limits:

      cpu: 100m

      memory: 128Mi

    requests:

      cpu: 50m

      memory: 64Mi

  containerPort: 8080 # The port your Go application listens on


backend:

  image:

    repository: your-docker-registry/greeting-service-backend # Replace with your registry

    pullPolicy: IfNotPresent

    tag: "1.0.0" # default to the AppVersion in Chart.yaml

  service:

    type: ClusterIP

    port: 80

    targetPort: 8081

  resources:

    limits:

      cpu: 100m

      memory: 128Mi

    requests:

      cpu: 50m

      memory: 64Mi

  containerPort: 8081 # The port your Go application listens on


ingress:

  enabled: true

  className: "" # Set to "nginx" or your ingress controller's class name

  annotations: {}

    # kubernetes.io/ingress.class: nginx

    # kubernetes.io/tls-acme: "true"

  host: greeting.example.com # Replace with your desired hostname

  paths:

    - path: /

      pathType: Prefix # Can be Exact, Prefix, or ImplementationSpecific


serviceAccount:

  create: true

  annotations: {}

  name: "" # If not set and create is true, a name is generated using the fullname template


podAnnotations: {}

podSecurityContext: {}

  # fsGroup: 2000

securityContext: {}

  # capabilities:

  #   drop:

  #   - ALL

  # readOnlyRootFilesystem: true

  # runAsNonRoot: true

  # runAsUser: 1000


nodeSelector: {}

tolerations: []

affinity: {}


File: greeting-service/templates/_helpers.tpl


{{/*

Expand the name of the chart.

*/}}

{{- define "greeting-service.name" -}}

{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}

{{- end -}}


{{/*

Create a default fully qualified app name.

We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).

If release name contains chart name it will be used as a full name.

*/}}

{{- define "greeting-service.fullname" -}}

{{- if .Values.fullnameOverride -}}

{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}

{{- else -}}

{{- $name := include "greeting-service.name" . -}}

{{- if contains $name .Release.Name -}}

{{- .Release.Name | trunc 63 | trimSuffix "-" -}}

{{- else -}}

{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}

{{- end -}}

{{- end -}}

{{- end -}}


{{/*

Create chart name and version as used by the chart label.

*/}}

{{- define "greeting-service.chart" -}}

{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}

{{- end -}}


{{/*

Common labels

*/}}

{{- define "greeting-service.labels" -}}

helm.sh/chart: {{ include "greeting-service.chart" . }}

{{ include "greeting-service.selectorLabels" . }}

{{- if .Chart.AppVersion }}

app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}

{{- end }}

app.kubernetes.io/managed-by: {{ .Release.Service }}

{{- end -}}


{{/*

Selector labels

*/}}

{{- define "greeting-service.selectorLabels" -}}

app.kubernetes.io/name: {{ include "greeting-service.name" . }}

app.kubernetes.io/instance: {{ .Release.Name }}

{{- end -}}


{{/*

Create the name of the service account to use

*/}}

{{- define "greeting-service.serviceAccountName" -}}

{{- if .Values.serviceAccount.create -}}

    {{- default (include "greeting-service.fullname" .) .Values.serviceAccount.name -}}

{{- else -}}

    {{- default "default" .Values.serviceAccount.name -}}

{{- end -}}

{{- end -}}



File: greeting-service/templates/frontend-deployment.yaml


apiVersion: apps/v1

kind: Deployment

metadata:

  name: {{ include "greeting-service.fullname" . }}-frontend

  labels:

    {{- include "greeting-service.labels" . | nindent 4 }}

    app.kubernetes.io/component: frontend

spec:

  replicas: {{ .Values.replicaCount }}

  selector:

    matchLabels:

      {{- include "greeting-service.selectorLabels" . | nindent 6 }}

      app.kubernetes.io/component: frontend

  template:

    metadata:

      {{- with .Values.podAnnotations }}

      annotations:

        {{- toYaml . | nindent 8 }}

      {{- end }}

      labels:

        {{- include "greeting-service.selectorLabels" . | nindent 8 }}

        app.kubernetes.io/component: frontend

    spec:

      {{- with .Values.imagePullSecrets }}

      imagePullSecrets:

        {{- toYaml . | nindent 8 }}

      {{- end }}

      serviceAccountName: {{ include "greeting-service.serviceAccountName" . }}

      securityContext:

        {{- toYaml .Values.podSecurityContext | nindent 8 }}

      containers:

        - name: frontend

          securityContext:

            {{- toYaml .Values.securityContext | nindent 12 }}

          image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag | default .Chart.AppVersion }}"

          imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}

          ports:

            - name: http

              containerPort: {{ .Values.frontend.containerPort }}

              protocol: TCP

          livenessProbe:

            httpGet:

              path: /

              port: http

            initialDelaySeconds: 5

            periodSeconds: 10

          readinessProbe:

            httpGet:

              path: /

              port: http

            initialDelaySeconds: 5

            periodSeconds: 10

          resources:

            {{- toYaml .Values.frontend.resources | nindent 12 }}

      {{- with .Values.nodeSelector }}

      nodeSelector:

        {{- toYaml . | nindent 8 }}

      {{- end }}

      {{- with .Values.affinity }}

      affinity:

        {{- toYaml . | nindent 8 }}

      {{- end }}

      {{- with .Values.tolerations }}

      tolerations:

        {{- toYaml . | nindent 8 }}

      {{- end }}


File: greeting-service/templates/frontend-service.yaml


apiVersion: v1

kind: Service

metadata:

  name: {{ include "greeting-service.fullname" . }}-frontend

  labels:

    {{- include "greeting-service.labels" . | nindent 4 }}

    app.kubernetes.io/component: frontend

spec:

  type: {{ .Values.frontend.service.type }}

  ports:

    - port: {{ .Values.frontend.service.port }}

      targetPort: {{ .Values.frontend.service.targetPort }}

      protocol: TCP

      name: http

  selector:

    {{- include "greeting-service.selectorLabels" . | nindent 4 }}

    app.kubernetes.io/component: frontend



File: greeting-service/templates/backend-deployment.yaml


apiVersion: apps/v1

kind: Deployment

metadata:

  name: {{ include "greeting-service.fullname" . }}-backend

  labels:

    {{- include "greeting-service.labels" . | nindent 4 }}

    app.kubernetes.io/component: backend

spec:

  replicas: {{ .Values.replicaCount }}

  selector:

    matchLabels:

      {{- include "greeting-service.selectorLabels" . | nindent 6 }}

      app.kubernetes.io/component: backend

  template:

    metadata:

      {{- with .Values.podAnnotations }}

      annotations:

        {{- toYaml . | nindent 8 }}

      {{- end }}

      labels:

        {{- include "greeting-service.selectorLabels" . | nindent 8 }}

        app.kubernetes.io/component: backend

    spec:

      {{- with .Values.imagePullSecrets }}

      imagePullSecrets:

        {{- toYaml . | nindent 8 }}

      {{- end }}

      serviceAccountName: {{ include "greeting-service.serviceAccountName" . }}

      securityContext:

        {{- toYaml .Values.podSecurityContext | nindent 8 }}

      containers:

        - name: backend

          securityContext:

            {{- toYaml .Values.securityContext | nindent 12 }}

          image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag | default .Chart.AppVersion }}"

          imagePullPolicy: {{ .Values.backend.image.pullPolicy }}

          ports:

            - name: http

              containerPort: {{ .Values.backend.containerPort }}

              protocol: TCP

          livenessProbe:

            httpGet:

              path: /greet

              port: http

            initialDelaySeconds: 5

            periodSeconds: 10

          readinessProbe:

            httpGet:

              path: /greet

              port: http

            initialDelaySeconds: 5

            periodSeconds: 10

          resources:

            {{- toYaml .Values.backend.resources | nindent 12 }}

      {{- with .Values.nodeSelector }}

      nodeSelector:

        {{- toYaml . | nindent 8 }}

      {{- end }}

      {{- with .Values.affinity }}

      affinity:

        {{- toYaml . | nindent 8 }}

      {{- end }}

      {{- with .Values.tolerations }}

      tolerations:

        {{- toYaml . | nindent 8 }}

      {{- end }}


File: greeting-service/templates/backend-service.yaml


apiVersion: v1

kind: Service

metadata:

  name: {{ include "greeting-service.fullname" . }}-backend

  labels:

    {{- include "greeting-service.labels" . | nindent 4 }}

    app.kubernetes.io/component: backend

spec:

  type: {{ .Values.backend.service.type }}

  ports:

    - port: {{ .Values.backend.service.port }}

      targetPort: {{ .Values.backend.service.targetPort }}

      protocol: TCP

      name: http

  selector:

    {{- include "greeting-service.selectorLabels" . | nindent 4 }}

    app.kubernetes.io/component: backend



File: greeting-service/templates/ingress.yaml


{{- if .Values.ingress.enabled -}}

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

  name: {{ include "greeting-service.fullname" . }}

  labels:

    {{- include "greeting-service.labels" . | nindent 4 }}

  {{- with .Values.ingress.annotations }}

  annotations:

    {{- toYaml . | nindent 4 }}

  {{- end }}

spec:

  {{- if .Values.ingress.className }}

  ingressClassName: {{ .Values.ingress.className }}

  {{- end }}

  rules:

    - host: {{ .Values.ingress.host | quote }}

      http:

        paths:

          {{- range .Values.ingress.paths }}

          - path: {{ .path }}

            pathType: {{ .pathType }}

            backend:

              service:

                name: {{ include "greeting-service.fullname" $ }}-frontend # Ingress points to frontend service

                port:

                  number: {{ $.Values.frontend.service.port }}

          {{- end }}

{{- end }} 


File: greeting-service/templates/serviceaccount.yaml


{{- if .Values.serviceAccount.create -}}

apiVersion: v1

kind: ServiceAccount

metadata:

  name: {{ include "greeting-service.serviceAccountName" . }}

  labels:

    {{- include "greeting-service.labels" . | nindent 4 }}

  {{- with .Values.serviceAccount.annotations }}

  annotations:

    {{- toYaml . | nindent 4 }}

  {{- end }}

{{- end }}