From 6ccacc187161a3d529fc217fb9c3d25520f691cc Mon Sep 17 00:00:00 2001 From: Amine Date: Mon, 11 Nov 2024 00:38:47 -0800 Subject: [PATCH] refactor(examples): Update and expand ResourceGroup examples - Reorganize examples into Basic and Advanced sections - Add new examples for deploying CoreDNS, controllers, AWS resources - Rename some examples for clarity (e.g. web-app instead of deploymentservice) - Remove outdated EKSCluster example - Add an index page summarizing the available examples docs(concepts): Improve ResourceGroup and schema documentation - Rewrite ResourceGroup concepts to be more clear and approachable - Expand schema documentation to cover markers, status fields, conditions - Clarify Instance lifecycle, status fields, and best practices - Fix typos and formatting issues --- .../cachecluster/cachecluster-instance.yaml | 4 +- examples/cachecluster/cachecluster.yaml | 112 +++---- .../cachecluster/simple-cachecluster.yaml | 24 +- .../docs/docs/concepts/00-resource-groups.md | 218 +++++++------ .../docs/docs/concepts/10-simple-schema.md | 226 ++++++------- website/docs/docs/concepts/15-instances.md | 186 +++++------ .../02-deploy-a-resource-group.md | 9 - .../img/docsVersionDropdown.png | Bin 25427 -> 0 bytes .../getting-started/img/localeDropdown.png | Bin 27841 -> 0 bytes website/docs/examples/ack-eks-cluster.md | 222 +++++++++++++ website/docs/examples/ack-networking-stack.md | 79 +++++ .../docs/examples/ack-valkey-cachecluster.md | 76 +++++ website/docs/examples/deploying-controller.md | 297 ++++++++++++++++++ website/docs/examples/deploying-coredns.md | 189 +++++++++++ website/docs/examples/ekscluster.md | 154 --------- website/docs/examples/examples.md | 49 +++ website/docs/examples/{empty.md => noop.md} | 6 +- ...entdbinstance.md => pod-rds-dbinstance.md} | 2 +- website/docs/examples/web-app-ingress.md | 92 ++++++ .../{deploymentservice.md => web-app.md} | 4 +- website/docusaurus.config.ts | 4 +- 21 files changed, 1395 insertions(+), 558 deletions(-) delete mode 100644 website/docs/docs/getting-started/img/docsVersionDropdown.png delete mode 100644 website/docs/docs/getting-started/img/localeDropdown.png create mode 100644 website/docs/examples/ack-eks-cluster.md create mode 100644 website/docs/examples/ack-networking-stack.md create mode 100644 website/docs/examples/ack-valkey-cachecluster.md create mode 100644 website/docs/examples/deploying-controller.md create mode 100644 website/docs/examples/deploying-coredns.md delete mode 100644 website/docs/examples/ekscluster.md create mode 100644 website/docs/examples/examples.md rename website/docs/examples/{empty.md => noop.md} (76%) rename website/docs/examples/{deploymentdbinstance.md => pod-rds-dbinstance.md} (97%) create mode 100644 website/docs/examples/web-app-ingress.md rename website/docs/examples/{deploymentservice.md => web-app.md} (96%) diff --git a/examples/cachecluster/cachecluster-instance.yaml b/examples/cachecluster/cachecluster-instance.yaml index 6197f88..a0cc839 100644 --- a/examples/cachecluster/cachecluster-instance.yaml +++ b/examples/cachecluster/cachecluster-instance.yaml @@ -1,6 +1,6 @@ apiVersion: kro.run/v1alpha1 -kind: Redis +kind: Valkey metadata: name: my-cache-subnetgroup spec: - name: my-cache-subnetgroup \ No newline at end of file + name: my-cache-subnetgroup diff --git a/examples/cachecluster/cachecluster.yaml b/examples/cachecluster/cachecluster.yaml index ac8c602..45fd74f 100644 --- a/examples/cachecluster/cachecluster.yaml +++ b/examples/cachecluster/cachecluster.yaml @@ -1,68 +1,68 @@ apiVersion: kro.run/v1alpha1 kind: ResourceGroup metadata: - name: redis.kro.run + name: valkey.kro.run spec: schema: apiVersion: v1alpha1 - kind: Redis + kind: Valkey spec: name: string status: csgARN: ${cacheSubnetGroup.status.ackResourceMetadata.arn} subnets: ${cacheSubnetGroup.status.subnets} - clusterARN: ${redis.status.ackResourceMetadata.arn} + clusterARN: ${valkey.status.ackResourceMetadata.arn} resources: - - name: networkingStack - template: - apiVersion: kro.run/v1alpha1 - kind: NetworkingStack - metadata: - name: ${schema.spec.name}-networking-stack - spec: - name: ${schema.spec.name}-networking-stack - - name: cacheSubnetGroup - template: - apiVersion: elasticache.services.k8s.aws/v1alpha1 - kind: CacheSubnetGroup - metadata: - name: ${schema.spec.name}-redis-subnet-group - spec: - cacheSubnetGroupDescription: "Redis ElastiCache subnet group" - cacheSubnetGroupName: ${schema.spec.name}-redis-subnet-group - subnetIDs: - - ${networkingStack.status.networkingInfo.subnetAZA} - - ${networkingStack.status.networkingInfo.subnetAZB} - - ${networkingStack.status.networkingInfo.subnetAZC} - - name: sg - template: - apiVersion: ec2.services.k8s.aws/v1alpha1 - kind: SecurityGroup - metadata: - name: ${schema.spec.name}-redis-sg - spec: - name: ${schema.spec.name}-redis-sg - description: "Redis ElastiCache security group" - vpcID: ${networkingStack.status.networkingInfo.vpcID} - ingressRules: - - fromPort: 6379 - toPort: 6379 - ipProtocol: tcp - ipRanges: - - cidrIP: 0.0.0.0/0 - - name: redis - template: - apiVersion: elasticache.services.k8s.aws/v1alpha1 - kind: CacheCluster - metadata: - name: ${schema.spec.name}-redis - spec: - cacheClusterID: vote-redis-cluster - cacheNodeType: cache.t3.micro - cacheSubnetGroupName: ${schema.spec.name}-redis-subnet-group - engine: redis - engineVersion: "7.1" - numCacheNodes: 1 - port: 6379 - securityGroupIDs: - - ${sg.status.id} \ No newline at end of file + - name: networkingStack + template: + apiVersion: kro.run/v1alpha1 + kind: NetworkingStack + metadata: + name: ${schema.spec.name}-networking-stack + spec: + name: ${schema.spec.name}-networking-stack + - name: cacheSubnetGroup + template: + apiVersion: elasticache.services.k8s.aws/v1alpha1 + kind: CacheSubnetGroup + metadata: + name: ${schema.spec.name}-valkey-subnet-group + spec: + cacheSubnetGroupDescription: "Valkey ElastiCache subnet group" + cacheSubnetGroupName: ${schema.spec.name}-valkey-subnet-group + subnetIDs: + - ${networkingStack.status.networkingInfo.subnetAZA} + - ${networkingStack.status.networkingInfo.subnetAZB} + - ${networkingStack.status.networkingInfo.subnetAZC} + - name: sg + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: SecurityGroup + metadata: + name: ${schema.spec.name}-valkey-sg + spec: + name: ${schema.spec.name}-valkey-sg + description: "Valkey ElastiCache security group" + vpcID: ${networkingStack.status.networkingInfo.vpcID} + ingressRules: + - fromPort: 6379 + toPort: 6379 + ipProtocol: tcp + ipRanges: + - cidrIP: 0.0.0.0/0 + - name: valkey + template: + apiVersion: elasticache.services.k8s.aws/v1alpha1 + kind: CacheCluster + metadata: + name: ${schema.spec.name}-valkey + spec: + cacheClusterID: vote-valkey-cluster + cacheNodeType: cache.t3.micro + cacheSubnetGroupName: ${schema.spec.name}-valkey-subnet-group + engine: valkey + engineVersion: "8.x" + numCacheNodes: 1 + port: 6379 + securityGroupIDs: + - ${sg.status.id} diff --git a/examples/cachecluster/simple-cachecluster.yaml b/examples/cachecluster/simple-cachecluster.yaml index a7997e5..1a55a87 100644 --- a/examples/cachecluster/simple-cachecluster.yaml +++ b/examples/cachecluster/simple-cachecluster.yaml @@ -1,11 +1,11 @@ apiVersion: kro.run/v1alpha1 kind: ResourceGroup metadata: - name: redis.kro.run + name: valkey.kro.run spec: schema: apiVersion: v1alpha1 - kind: Redis + kind: Valkey spec: name: string subnetIDs: "[]string" @@ -13,13 +13,13 @@ spec: csgARN: ${cacheSubnetGroup.status.ackResourceMetadata.arn} subnets: ${cacheSubnetGroup.status.subnets} resources: - - name: cacheSubnetGroup - template: - apiVersion: elasticache.services.k8s.aws/v1alpha1 - kind: CacheSubnetGroup - metadata: - name: ${schema.spec.name}-redis-subnet-group - spec: - cacheSubnetGroupDescription: "Redis ElastiCache subnet group" - cacheSubnetGroupName: ${schema.spec.name}-redis-subnet-group - subnetIDs: ${schema.spec.subnetIDs} \ No newline at end of file + - name: cacheSubnetGroup + template: + apiVersion: elasticache.services.k8s.aws/v1alpha1 + kind: CacheSubnetGroup + metadata: + name: ${schema.spec.name}-valkey-subnet-group + spec: + cacheSubnetGroupDescription: "Valkey ElastiCache subnet group" + cacheSubnetGroupName: ${schema.spec.name}-valkey-subnet-group + subnetIDs: ${schema.spec.subnetIDs} diff --git a/website/docs/docs/concepts/00-resource-groups.md b/website/docs/docs/concepts/00-resource-groups.md index f31e389..0a61f9d 100644 --- a/website/docs/docs/concepts/00-resource-groups.md +++ b/website/docs/docs/concepts/00-resource-groups.md @@ -4,131 +4,156 @@ sidebar_position: 1 # ResourceGroups -**ResourceGroups** are the fundamental building blocks in **kro**. They provide -a way to define, organize, and manage sets of related Kubernetes resources as a +ResourceGroups are the fundamental building blocks in **kro**. They provide a +way to define, organize, and manage sets of related Kubernetes resources as a single, reusable unit. -## What is a **ResourceGroup**? +## What is a ResourceGroup? -A **ResourceGroup** is a custom resource that serves as a blueprint for creating -and managing a collection of Kubernetes resources. It allows you to: +A **ResourceGroup** is a custom resource that lets you create new Kubernetes +APIs for deploying multiple resources together. It acts as a blueprint, +defining: -- Define multiple resources in a single, cohesive unit -- Establish relationships and dependencies between resources -- Create high-level abstractions of complex Kubernetes configurations -- Promote reusability and consistency across your infrastructure +- What users can configure (schema) +- What resources to create (resources) +- How resources reference each other (dependencies) +- When resources should be included (conditions) +- What status to expose (status) -## Anatomy of a **ResourceGroup** +When you create a **ResourceGroup**, kro generates a new API (a.k.a Custom +Resource Defintion) in your cluster that others can use to deploy resources in a +consistent, controlled way. -A **ResourceGroup**, like any Kubernetes resource, consists of three main parts: +## Anatomy of a ResourceGroup -1. **Metadata**: Name, namespace, labels, etc. +A ResourceGroup, like any Kubernetes resource, consists of three main parts: + +1. **Metadata**: name, namespace, labels, etc. 2. **Spec**: Defines the structure and properties of the ResourceGroup 3. **Status**: Reflects the current state of the ResourceGroup -The `spec` section of a ResourceGroup typically includes: +The `spec` section of a ResourceGroup contains two main components: + +- **Schema**: Defines what an instance of your API looks like: + - What users can configure during creation and update + - What status information they can view + - Default values and validation rules +- **Resources**: Specifies the Kubernetes resources to create: + - Resource templates + - Dependencies between resources + - Conditions for inclusion + - Readiness criteria -- **Parameters**: Define the customizable aspects of the ResourceGroup -- **Resources**: Specify the Kubernetes resources to be created -- The **kind** and **apiVersion** fields within the spec define the CRD that - will be generated for this ResourceGroup. Here's a simple example of a - ResourceGroup: +This structure translates to YAML as follows: -```yaml text title="simple-web-app.yaml" -apiVersion: kro.run/v1 +```yaml +apiVersion: kro.run/v1alpha1 kind: ResourceGroup metadata: - name: simple-web-app + name: my-resourcegroup # Metadata section spec: - kind: SimpleWebApp - apiVersion: v1alpha1 - parameters: - appName: string - image: string - replicas: int + schema: # Define your API + apiVersion: v1alpha1 # API version + kind: MyAPI # API kind + spec: {} # fields users can configure + status: {} # fields kro will populate + + # Define the resources kro will manage resources: - - name: deployment - definition: - apiVersion: apps/v1 - kind: Deployment - metadata: - name: ${schema.spec.appName}-deployment - spec: - replicas: ${schema.spec.replicas} - selector: - matchLabels: - app: ${schema.spec.appName} - template: - metadata: - labels: - app: ${schema.spec.appName} - spec: - containers: - - name: ${schema.spec.appName}-container - image: ${schema.spec.image} - - name: service - definition: - apiVersion: v1 - kind: Service - metadata: - name: ${schema.spec.appName}-service - spec: - selector: - app: ${schema.spec.appName} - ports: - - port: 80 - targetPort: 80 + - name: resource1 + # declare your resources along with default values and variables + template: {} ``` -In this example, the **ResourceGroup** defines a simple web application with a -Deployment and a Service. The appName, image, and replicas are parameters that -can be set when instantiating this ResourceGroup. +Let's look at each component in detail... + +## Understanding the Schema -## **ResourceGroup** Processing +The schema section defines your new API's structure. It determines: -When a **ResourceGroup** is submitted to the Kubernetes API server, the kro -controller processes it as follows: +- What fields users can configure when creating instances +- What status information they can view +- Type validation and default values -1. **Formal Verification**: The controller performs a comprehensive analysis of - the ResourceGroup definition. This includes: +Here's an example schema: - - **Syntax checking**: Ensuring all fields are correctly formatted. - - **Type checking**: Validating that parameter types match their definitions. - - **Semantic validation**: Verifying that resource relationships and - dependencies are logically sound. - - **Dry-run validation**: Simulating the creation of resources to detect - potential issues. +```yaml +schema: + apiVersion: v1alpha1 + kind: WebApplication # This becomes your new API type + spec: + # Fields users can configure using a simple, straightforward syntax + name: string + image: string | default="nginx" + replicas: integer | default=3 + ingress: + enabled: boolean | default=false + + status: + # Fields kro will populate automatically from your resources + # Types are inferred from these CEL expressions + availableReplicas: ${deployment.status.availableReplicas} + conditions: ${deployment.status.conditions} +``` -2. **CRD Generation**: The controller automatically generates a new **Custom - Resource Definition (CRD)** based on the ResourceGroup's specification. This - CRD represents the type for instances of this ResourceGroup. +**kro** follows a different approach for defining your API schema and shapes. It +leverages a human-friendly and readable syntax that is OpenAPI spec compatible. +No need to write complex OpenAPI schemas - just define your fields and types in +a straightforward way. For the complete specification of this format, check out +the [Simple Schema specification](./10-simple-schema.md). Status fields use CEL +expressions to reference fields from resources defined in your ResourceGroup. +kro automatically: -3. **CRD Registration**: It registers the newly generated CRD with the - Kubernetes API server, making it available for use in the cluster. +- Infers the correct types from your expressions +- Validates that referenced resources exist +- Updates these fields as your resources change -4. **Micro-Controller Deployment**: kro deploys a dedicated micro-controller for - this ResourceGroup. This micro-controller will listen for **"instance" - events** - instances of the CRD created in step 2. It will be responsible for - managing the **lifecycle of resources** defined in the ResourceGroup for each - instance. +## ResourceGroup Processing -5. **Status Update**: The controller updates the status of the ResourceGroup to - reflect that the corresponding CRD has been created and registered. +When you create a **ResourceGroup**, kro processes it in several steps to ensure +correctness and set up the necessary components: -For example, given our `simple-web-app` ResourceGroup, the controller would -create and register a CRD named `SimpleWebApps` (plural form of the -ResourceGroup name). This CRD defines the structure for creating instances of -the web application with customizable parameters. The deployed micro-controller -would then manage all **SimpleWebApps instances**, creating and managing the -associated **Deployments** and **Services** as defined in the ResourceGroup. +1. **Validation**: kro validates your **ResourceGroup** to ensure it's well + formed and follows the correct syntax, maximizing the chances of successful + deployment, and catching as many errors as possible early on. It: -The **kro** controller continues to monitor the **ResourceGroup** for any -changes, updating the corresponding CRD and micro-controller as necessary. + - Validates your schema definition follows the simple schema format + - Ensures all resource templates are valid Kubernetes manifests + - Checks that referenced values exist and are of the correct type + - Confirms resource dependencies form a valid Directed Acycled Graph(DAG) + without cycles + - Validates all CEL expressions in status fields and conditions -## **ResourceGroup** Instance Example +2. **API Generation**: kro generates and registers a new CRD in your cluster + based on your schema. For example, if your **ResourceGroup** defines a + `WebApplication` API, kro creates a CRD that: -After the **ResourceGroup** is processed, users can create instances of it. -Here's an example of how an instance for the `SimpleWebApp` might look: + - Provides API validation based on your schema definition + - Automatically applies default values you've defined + - Makes status information available to users and other systems + - Integrates seamlessly with kubectl and other Kubernetes tools + +3. **Controller Configuration**: kro configures itself to watch for instances of + your new API and: + + - Creates all required resources following the dependency order + - Manages references and value passing between resources + - Handles the complete lifecycle for create, update, and delete operations + - Keeps status information up to date based on actual resource states + +For instance, when you create a `WebApplication` ResourceGroup, kro generates +the `webapplications.kro.run` CRD. When users create instances of this API, kro +manages all the underlying resources (Deployments, Services, Custom Resources, +etc.) automatically. + +kro continuously monitors your ResourceGroup for changes, updating the API and +its behavior accordingly. + +## ResourceGroup Instance Example + +After the **ResourceGroup** is validated and registered in the cluster, users +can can create instances of it. Here's an example of how an instance for the +`SimpleWebApp` might look: ```yaml title="my-web-app-instance.yaml" apiVersion: kro.run/v1alpha1 @@ -140,6 +165,3 @@ spec: image: nginx:latest replicas: 3 ``` - -In the next section, we'll explore the `parameters` and `resources` sections of -a **ResourceGroup** in more detail. diff --git a/website/docs/docs/concepts/10-simple-schema.md b/website/docs/docs/concepts/10-simple-schema.md index cc8d701..10ed828 100644 --- a/website/docs/docs/concepts/10-simple-schema.md +++ b/website/docs/docs/concepts/10-simple-schema.md @@ -4,8 +4,9 @@ sidebar_position: 2 # Simple Schema -kro's Simple Schema provides a powerful yet intuitive way to define the -structure of your ResourceGroup. Here is comprehensive example: +**kro** follows a different approach for defining your API schema and shapes. It +leverages a human-friendly and readable syntax that is OpenAPI specification +compatible. Here's a comprehensive example: ```yaml apiVersion: kro.run/v1alpha1 @@ -13,174 +14,181 @@ kind: ResourceGroup metadata: name: web-application spec: - apiVersion: v1alpha1 - kind: WebApplication - parameters: + schema: + apiVersion: v1alpha1 + kind: WebApplication spec: - name: string | required=true description="Name of the web application" - replicas: integer | default=1 minimum=1 maximum=10 + # Basic types + name: string | required=true description="My Name" + replicas: integer | default=1 minimum=1 maximum=100 image: string | required=true - ports: - - port: integer | required=true - targetPort: integer - env: 'map[string]string' - config: ConfigType - configArray: []ConfigType - customTypes: - ConfigType: - logLevel: string | enum="debug,info,warn,error" default="info" - maxConnections: integer | minimum=1 maximum=1000 + + # Structured type + ingress: + enabled: boolean | default=false + host: string | default="example.com" + path: string | default="/" + + # Array type + ports: "[]integer" + + # Map type + env: "map[string]string" + status: - url: ${service.status.loadBalancer.ingress[0].hostname}" - resources: [] + # Status fields with auto-inferred types + availableReplicas: ${deployment.status.availableReplicas} + serviceEndpoint: ${service.status.loadBalancer.ingress[0].hostname} ``` -## Simple Schema Features Explained +## Type Definitions -### 1. Spec Field Definition +### Basic Types -#### Basic Types +kro supports these foundational types: -- `string`: Basic string type -- `integer`: Whole number -- `boolean`: True/False value +- `string`: Text values +- `integer`: Whole numbers +- `boolean`: True/False values +- `number`: Decimal numbers -for example to define a field that is a string, you can define it as follows: +For example: ```yaml name: string age: integer +enabled: boolean +price: number ``` -#### Structure types - -Structure types or object types are defined by specifying the fields within the -object. The fields can be of basic types or other structure types. +### Structure Types -for example to define a structure type for a person with name and age fields, -you can define it as follows: +You can create complex objects by nesting fields. Each field can use any type, +including other structures: ```yaml -person: +# Simple structure +address: + street: string + city: string + zipcode: string + +# Nested structures +user: name: string - age: integer + address: # Nested object + street: string + city: string + contacts: "[]string" # Array of strings ``` -#### Map Types +### Array Types -- Arrays: Denoted by `[]`, e.g., `'[]string'` -- Maps: Denoted by `map[keyType]valueType`, e.g., `'map[string]string'` and - `'map[string]Person'` +Arrays are denoted using `[]` syntax: -### 2. Validation and Documentation Markers +- Basic arrays: `[]string`, `[]integer`, `[]boolean` -In addition to the type, fields can also have markers for validation, -documentation and other purposes that are OpenAPISchema compatible. - -For example to define a field that is required, has a default value and a -description, you can define it as follows: +Examples: ```yaml -person: - name: - string | required=true default="Kylian Mbappé" description="Name of the - person" +tags: []string +ports: []integer ``` -Currently supported markers include: +### Map Types -- `required=true`: Field must be provided -- `default=value`: Default value if not provided -- `description="..."`: Provides documentation for the field -- `enum="value1,value2,..."`: Restricts to a set of values **NOT IMPLEMENTED** -- `minimum=value` and `maximum=value`: For numeric constraints **NOT - IMPLEMENTED** +Maps are key-value pairs denoted as `map[keyType]valueType`: + +- `map[string]string`: String to string mapping +- `map[string]integer`: String to integer mapping -### 3. Custom Types Definition +Examples: + +```yaml +labels: "map[string]string" +metrics: "map[string]number" +``` -Custom types are defined in the `customTypes` section, allowing for reusable -complex structures. They can be referenced by name in the spec or status fields. +## Validation and Documentation -Example: +Fields can have multiple markers for validation and documentation: ```yaml -customTypes: - ConfigType: - logLevel: string | enum="debug,info,warn,error" default="info" - maxConnections: integer | minimum=1 maximum=1000 -spec: - config: ConfigType | required=true +name: string | required=true default="app" description="Application name" +replicas: integer | default=3 minimum=1 maximum=10 +mode: string | enum="debug,info,warn,error" default="info" ``` -### 4. Status Field Definition +### Supported Markers + +- `required=true`: Field must be provided +- `default=value`: Default value if not specified +- `description="..."`: Field documentation +- `enum="value1,value2"`: Allowed values +- `minimum=value`: Minimum value for numbers +- `maximum=value`: Maximum value for numbers + +Multiple markers can be combined using the `|` separator. -Status fields are defined similarly to spec fields and can include validation -and documentation markers. However on top of that, status fields can also -include value markers: +For example: -#### Value Marker **NOT IMPLEMENTED** +```yaml +name: string | required=true default="app" description="Application name" +``` -- `value="${resource.status.field}"`: Specifies that this field's value should - be dynamically obtained from another resource. The value is a CEL expression - that is validated at ResourceGroup processing time and evaluated at runtime. +## Status Fields -:::tip Note that the value marker is a kro extension to the OpenAPISchema and is -not part of the official OpenAPISchema specification. ::: +Status fields use CEL expressions to reference values from resources. kro +automatically: -Example: +- Infers the correct types from the expressions +- Validates that referenced resources exist +- Updates values when the underlying resources change ```yaml status: - url: string | value="${service.status.loadBalancer.ingress[0].hostname}" + # Types are inferred from the referenced fields + availableReplicas: ${deployment.status.availableReplicas} + endpoint: ${service.status.loadBalancer.ingress[0].hostname} ``` -## Default status fields - -**kro** automatically injects two common fields into the status of all instances -generated from **ResourceGroups**: `conditions` and `state`. These fields -provide essential information about the current status of the instance and its -associated resources. +## Default Status Fields -:::tip `conditions` and `state` are reserved words in the status section. If a -user defines these fields in their **ResourceGroup**'s status schema, kro will -override them with its own values. ::: +kro automatically injects two fields to every instance's status: ### 1. Conditions -The `conditions` field is an array of condition objects, each representing a -specific aspect of the instance's state. kro automatically manages this field. +An array of condition objects tracking the instance's state: ```yaml status: - conditions: "[]condition" -customTypes: - condition: - type: string - status: string | enum="True,False,Unknown" - lastTransitionTime: string - reason: string - message: string + conditions: + - type: string # e.g., "Ready", "Progressing" + status: string # "True", "False", "Unknown" + lastTransitionTime: string + reason: string + message: string ``` -Common condition types include: +Common condition types: -- `Ready`: Indicates whether the instance is fully reconciled and operational. -- `Progressing`: Shows if the instance is in the process of reaching the desired - state. -- `Degraded`: Signals that the instance is operational but not functioning - optimally. -- `Error`: Indicates that an error has occurred during reconciliation. +- `Ready`: Instance is fully reconciled +- `Progressing`: Working towards desired state +- `Degraded`: Operational but not optimal +- `Error`: Reconciliation error occurred ### 2. State -The `state` field provides a high-level summary of the instance's current -status. +A high-level summary of the instance's status: ```yaml status: - state: string | enum="Ready,Progressing,Degraded,Error,Terminating,Unknown" + state: string # Ready, Progressing, Degraded, Unknown, Deleting ``` -> These default status fields are automatically added to every instance's -> status, providing a consistent way to check the health and state of resources -> across different **ResourceGroups**. +:::tip + +`conditions` and `state` are reserved words. If defined in your schema, kro will +override them with its own values. + +::: diff --git a/website/docs/docs/concepts/15-instances.md b/website/docs/docs/concepts/15-instances.md index 993b027..2f119b5 100644 --- a/website/docs/docs/concepts/15-instances.md +++ b/website/docs/docs/concepts/15-instances.md @@ -4,120 +4,71 @@ sidebar_position: 15 # Instances -Instances are a fundamental concept in **kro** that represent instances of -ResourceGroups. They define the desired state of a set of resources, which kro -continuously works to maintain. +Once **kro** processes your ResourceGroup, it creates a new API in your cluster. +Users can then create instances of this API to deploy resources in a consistent, +controlled way. -## What is an Instance? +## Understanding Instances -An Instance is a Kubernetes custom resource that: - -- References a specific ResourceGroup -- Provides values for the parameters defined in the ResourceGroup -- Represents the desired state of a set of Kubernetes resources - -## Anatomy of an Instance - -Here's an example of an Instance for a `WebApplication` ResourceGroup: +An instance represents your deployed application. When you create an instance, +you're telling kro "I want this set of resources running in my cluster". The +instance contains your configuration values and serves as the single source of +truth for your application's desired state. Here's an example instance of our +WebApplication API: ```yaml -apiVersion: kro.run/v1alpha1 +apiVersion: v1alpha1 kind: WebApplication metadata: - name: my-web-app + name: my-app spec: - name: awesome-app - replicas: 3 + name: web-app image: nginx:latest - ports: - - port: 80 - targetPort: 8080 - env: - DB_HOST: mydb.example.com - LOG_LEVEL: debug + ingress: + enabled: true ``` -::: - -tip The spec fields of an Instance correspond to the parameters defined in the -ResourceGroup. - -::: - -## The reconciliation loop - -kro manages Instances through a continuous reconciliation process: - -- **Desired state detection**: kro observes the Instance, which represents the - desired state of resources. -- **Current state assessment**: kro talks to the api-server and checks the - current state of resources in the cluster related to the Instance. -- **Difference identification**: Any differences between the desired state - (Instance) and the current state are identified. -- **State Reconciliation**: kro takes necessary actions to align the current - state with the desired state. This may involve creating, updating, or deleting - resources as needed. -- **Status Updates**: The Instance's status is updated to reflect the current - state of reconciliation and any issues encountered. -- **Continuous Loop**: This process repeats regularly, ensuring the cluster - state always converges towards the desired state defined in the Instance. - -## Advantages of declarative management [need better title] - -- **Declarative Management**: Users define what they want, not how to get there. -- **Self-healing**: The system continuously works to maintain the desired state. -- **Idempotency**: The same Instance always results in the same end state, - regardless of the current state. -- **Abstraction**: Complex resource relationships are managed behind the scenes. -- **Consistency**: All resources for an application are managed as a unit. -- **Auditability**: The Instance serves as a single source of truth for the - application's desired state. - -:::tip[Best Practices] +When you create this instance, kro: -- Treat instances as declarative definitions of your application's desired - state. Use version control for your Instances to track changes over time. -- Leverage labels and annotations in Instances for organization and filtering. -- Regularly review Instances to ensure they reflect current requirements. -- Use kro's dry-run feature to preview reconciliation actions before applying - changes to Instances. -- Monitor Instance statuses to understand the current state of your - applications. +- Creates all required resources (Deployment, Service, Ingress) +- Configures them according to your specification +- Manages them as a single unit +- Keeps their status up to date -::: +## How kro Manages Instances -## Common Status Fields +kro uses the standard Kubernetes reconciliation pattern to manage instances: -kro automatically injects two common fields into the status of all instances: -**Conditions** and **State**. These fields provide crucial information about the -current status of the instance and its associated resources. +1. **Observe**: Watches for changes to your instance or its resources +2. **Compare**: Checks if current state matches desired state +3. **Act**: Creates, updates, or deletes resources as needed +4. **Report**: Updates status to reflect current state -### 1. Conditions +This continuous loop ensures your resources stay in sync with your desired +state, providing features like: -Conditions are a standard Kubernetes concept that kro leverages to provide -detailed status information. Each condition represents a specific aspect of the -instance's state. Common conditions include: +- Self-healing +- Automatic updates +- Consistent state management +- Status tracking -- **Ready**: Indicates whether the instance is fully reconciled and operational. -- **Progressing**: Shows if the instance is in the process of reaching the - desired state. -- **Degraded**: Signals that the instance is operational but not functioning - optimally. -- **Error**: Indicates that an error has occurred during reconciliation. +## Monitoring Your Instances -Each condition typically includes the following properties: +KRO provides rich status information for every instance: -- **Type**: The name of the condition (e.g., "Ready"). -- **Status**: Either "True", "False", or "Unknown". -- **LastTransitionTime**: When the condition last changed. -- **Reason**: A brief, machine-readable explanation for the condition's state. -- **Message**: A human-readable description of the condition. +```bash +$ kubectl get webapplication my-app +NAME STATUS SYNCED AGE +my-app ACTIVE true 30s +``` -Example: +For detailed status, check the instance's YAML: ```yaml status: - conditions: + state: ACTIVE # High-level instance state + availableReplicas: 3 # Status from Deployment + conditions: # Detailed status conditions - type: Ready status: "True" lastTransitionTime: "2024-07-23T01:01:59Z" @@ -125,27 +76,42 @@ status: message: "All resources are available and configured correctly" ``` -### 2. State +### Understanding Status -The State field provides a high-level summary of the instance's current status. -It is typically one of the following values: +Every instance includes: -- **Pending**: The instance is being processed, but resources are not yet fully - created or configured. -- **Running**: All resources are created and the instance is operational. -- **Failed**: An error occurred and the instance could not be fully reconciled. -- **Terminating**: The instance is in the process of being deleted. -- **Degraded**: The instance is operational but not functioning optimally. -- **Unknown**: The instance's status cannot be determined. +1. **State**: High-level status -Example: + - `Running`: All resources are ready + - `Progressing`: Working towards desired state + - `Failed`: Error occurred + - `Terminating`: Being deleted -```yaml -status: - state: Running -``` +2. **Conditions**: Detailed status information + + - `Ready`: Instance is fully operational + - `Progressing`: Changes are being applied + - `Degraded`: Operating but not optimal + - `Error`: Problems detected + +3. **Resource Status**: Status from your resources + - Values you defined in your ResourceGroup's status section + - Automatically updated as resources change + +## Best Practices + +- **Version Control**: Keep your instance definitions in version control + alongside your application code. This helps track changes, rollback when + needed, and maintain configuration history. + +- **Use Labels Effectively**: Add meaningful labels to your instances for better + organization, filtering, and integration with other tools. kro propagates + labels to the sub resources for easy identification. + +- **Active Monitoring**: Regularly check instance status beyond just "Running". + Watch conditions, resource status, and events to catch potential issues early + and understand your application's health. -These common status fields provide users with a consistent and informative way -to check the health and state of their instances across different -ResourceGroups. They are essential for monitoring, troubleshooting, and -automating operations based on the status of kro-managed resources. +- **Regular Reviews**: Periodically review your instance configurations to + ensure they reflect current requirements and best practices. Update resource + requests, limits, and other configurations as your application needs evolve. diff --git a/website/docs/docs/getting-started/02-deploy-a-resource-group.md b/website/docs/docs/getting-started/02-deploy-a-resource-group.md index b235e0a..2e0827f 100644 --- a/website/docs/docs/getting-started/02-deploy-a-resource-group.md +++ b/website/docs/docs/getting-started/02-deploy-a-resource-group.md @@ -24,15 +24,6 @@ Under the hood, when you create a `ResourceGroup`, kro: 3. Creates a new API (CRD) in your cluster 4. Configures itself to watch and serve instances of this API -:::tip[info] - -**kro** is a Kubernetes-native tool that speaks **Kubernetes**! All you need to -get started is a Kubernetes cluster that supports CRDs, version 1.16 or later. -kro understands native Kubernetes resource as well as any custom resources your -cluster supports. - -::: - ## Prerequisites Before you begin, make sure you have the following: diff --git a/website/docs/docs/getting-started/img/docsVersionDropdown.png b/website/docs/docs/getting-started/img/docsVersionDropdown.png deleted file mode 100644 index 97e4164618b5f8beda34cfa699720aba0ad2e342..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25427 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 diff --git a/website/docs/docs/getting-started/img/localeDropdown.png b/website/docs/docs/getting-started/img/localeDropdown.png deleted file mode 100644 index e257edc1f932985396bf59584c7ccfaddf955779..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27841 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 JrC|B>e*nH7H{}2T diff --git a/website/docs/examples/ack-eks-cluster.md b/website/docs/examples/ack-eks-cluster.md new file mode 100644 index 0000000..651fe56 --- /dev/null +++ b/website/docs/examples/ack-eks-cluster.md @@ -0,0 +1,222 @@ +--- +sidebar_position: 10 +--- + +# EKS Cluster + +```yaml title="eks.yaml" +apiVersion: kro.run/v1alpha1 +kind: ResourceGroup +metadata: + name: ekscluster.kro.run +spec: + # CRD Schema + schema: + apiVersion: v1alpha1 + kind: EKSCluster + spec: + name: string + version: string + status: + networkingInfo: + vpcID: ${clusterVPC.status.vpcID} + subnetAZA: ${clusterSubnetA.status.subnetID} + subnetAZB: ${clusterSubnetB.status.subnetID} + clusterARN: ${cluster.status.ackResourceMetadata.arn} + # resources + resources: + - name: clusterVPC + readyWhen: + - ${clusterVPC.status.state == "available"} + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: VPC + metadata: + name: kro-cluster-vpc + spec: + cidrBlocks: + - 192.168.0.0/16 + enableDNSSupport: true + enableDNSHostnames: true + - name: clusterElasticIPAddress + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: ElasticIPAddress + metadata: + name: kro-cluster-eip + spec: {} + - name: clusterInternetGateway + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: InternetGateway + metadata: + name: kro-cluster-igw + spec: + vpc: ${clusterVPC.status.vpcID} + - name: clusterRouteTable + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: RouteTable + metadata: + name: kro-cluster-public-route-table + spec: + vpcID: ${clusterVPC.status.vpcID} + routes: + - destinationCIDRBlock: 0.0.0.0/0 + gatewayID: ${clusterInternetGateway.status.internetGatewayID} + - name: clusterSubnetA + readyWhen: + - ${clusterSubnetA.status.state == "available"} + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: Subnet + metadata: + name: kro-cluster-public-subnet1 + spec: + availabilityZone: us-west-2a + cidrBlock: 192.168.0.0/18 + vpcID: ${clusterVPC.status.vpcID} + routeTables: + - ${clusterRouteTable.status.routeTableID} + mapPublicIPOnLaunch: true + - name: clusterSubnetB + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: Subnet + metadata: + name: kro-cluster-public-subnet2 + spec: + availabilityZone: us-west-2b + cidrBlock: 192.168.64.0/18 + vpcID: ${clusterVPC.status.vpcID} + routeTables: + - ${clusterRouteTable.status.routeTableID} + mapPublicIPOnLaunch: true + - name: clusterNATGateway + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: NATGateway + metadata: + name: kro-cluster-natgateway1 + spec: + subnetID: ${clusterSubnetB.status.subnetID} + allocationID: ${clusterElasticIPAddress.status.allocationID} + - name: clusterRole + template: + apiVersion: iam.services.k8s.aws/v1alpha1 + kind: Role + metadata: + name: kro-cluster-role + spec: + name: kro-cluster-role + description: "kro created cluster cluster role" + policies: + - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy + assumeRolePolicyDocument: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "eks.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + } + - name: clusterNodeRole + template: + apiVersion: iam.services.k8s.aws/v1alpha1 + kind: Role + metadata: + name: kro-cluster-node-role + spec: + name: kro-cluster-node-role + description: "kro created cluster node role" + policies: + - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly + - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy + assumeRolePolicyDocument: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + } + - name: clusterAdminRole + template: + apiVersion: iam.services.k8s.aws/v1alpha1 + kind: Role + metadata: + name: kro-cluster-pia-role + spec: + name: kro-cluster-pia-role + description: "kro created cluster admin pia role" + policies: + - arn:aws:iam::aws:policy/AdministratorAccess + assumeRolePolicyDocument: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowEksAuthToAssumeRoleForPodIdentity", + "Effect": "Allow", + "Principal": { + "Service": "pods.eks.amazonaws.com" + }, + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ] + } + ] + } + - name: cluster + readyWhen: + - ${cluster.status.status == "ACTIVE"} + template: + apiVersion: eks.services.k8s.aws/v1alpha1 + kind: Cluster + metadata: + name: ${schema.spec.name} + spec: + name: ${schema.spec.name} + accessConfig: + authenticationMode: API_AND_CONFIG_MAP + roleARN: ${clusterRole.status.ackResourceMetadata.arn} + version: ${schema.spec.version} + resourcesVPCConfig: + endpointPrivateAccess: false + endpointPublicAccess: true + subnetIDs: + - ${clusterSubnetA.status.subnetID} + - ${clusterSubnetB.status.subnetID} + - name: clusterNodeGroup + template: + apiVersion: eks.services.k8s.aws/v1alpha1 + kind: Nodegroup + metadata: + name: kro-cluster-nodegroup + spec: + name: kro-cluster-ng + diskSize: 100 + clusterName: ${cluster.spec.name} + subnets: + - ${clusterSubnetA.status.subnetID} + - ${clusterSubnetB.status.subnetID} + nodeRole: ${clusterNodeRole.status.ackResourceMetadata.arn} + updateConfig: + maxUnavailable: 1 + scalingConfig: + minSize: 1 + maxSize: 1 + desiredSize: 1 +``` diff --git a/website/docs/examples/ack-networking-stack.md b/website/docs/examples/ack-networking-stack.md new file mode 100644 index 0000000..b86cc96 --- /dev/null +++ b/website/docs/examples/ack-networking-stack.md @@ -0,0 +1,79 @@ +--- +sidebar_position: 10 +--- + +# Networking Stack + +```yaml title="networking-stack.yaml" +apiVersion: kro.run/v1alpha1 +kind: ResourceGroup +metadata: + name: networkingstack.kro.run +spec: + # CRD Schema + schema: + apiVersion: v1alpha1 + kind: NetworkingStack + spec: + name: string + status: + networkingInfo: + vpcID: ${vpc.status.vpcID} + subnetAZA: ${subnetAZA.status.subnetID} + subnetAZB: ${subnetAZB.status.subnetID} + subnetAZC: ${subnetAZC.status.subnetID} + securityGroup: ${securityGroup.status.id} + # resources + resources: + - name: vpc + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: VPC + metadata: + name: vpc-${schema.spec.name} + spec: + cidrBlocks: + - 192.168.0.0/16 + enableDNSHostnames: false + enableDNSSupport: true + - name: subnetAZA + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: Subnet + metadata: + name: subnet-a-${schema.spec.name} + spec: + availabilityZone: us-west-2a + cidrBlock: 192.168.0.0/18 + vpcID: ${vpc.status.vpcID} + - name: subnetAZB + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: Subnet + metadata: + name: subnet-b-${schema.spec.name} + spec: + availabilityZone: us-west-2b + cidrBlock: 192.168.64.0/18 + vpcID: ${vpc.status.vpcID} + - name: subnetAZC + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: Subnet + metadata: + name: subnet-c-${schema.spec.name} + spec: + availabilityZone: us-west-2c + cidrBlock: 192.168.128.0/18 + vpcID: ${vpc.status.vpcID} + - name: securityGroup + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: SecurityGroup + metadata: + name: cluster-security-group-${schema.spec.name} + spec: + vpcID: ${vpc.status.vpcID} + name: my-sg-${schema.spec.name} + description: something something +``` diff --git a/website/docs/examples/ack-valkey-cachecluster.md b/website/docs/examples/ack-valkey-cachecluster.md new file mode 100644 index 0000000..38b02a4 --- /dev/null +++ b/website/docs/examples/ack-valkey-cachecluster.md @@ -0,0 +1,76 @@ +--- +sidebar_position: 10 +--- + +# Valkey cluster + +```yaml title="valkey-cachecluster.yaml" +apiVersion: kro.run/v1alpha1 +kind: ResourceGroup +metadata: + name: valkey.kro.run +spec: + schema: + apiVersion: v1alpha1 + kind: Valkey + spec: + name: string + status: + csgARN: ${cacheSubnetGroup.status.ackResourceMetadata.arn} + subnets: ${cacheSubnetGroup.status.subnets} + clusterARN: ${valkey.status.ackResourceMetadata.arn} + resources: + - name: networkingStack + template: + apiVersion: kro.run/v1alpha1 + kind: NetworkingStack + metadata: + name: ${schema.spec.name}-networking-stack + spec: + name: ${schema.spec.name}-networking-stack + - name: cacheSubnetGroup + template: + apiVersion: elasticache.services.k8s.aws/v1alpha1 + kind: CacheSubnetGroup + metadata: + name: ${schema.spec.name}-valkey-subnet-group + spec: + cacheSubnetGroupDescription: "Valkey ElastiCache subnet group" + cacheSubnetGroupName: ${schema.spec.name}-valkey-subnet-group + subnetIDs: + - ${networkingStack.status.networkingInfo.subnetAZA} + - ${networkingStack.status.networkingInfo.subnetAZB} + - ${networkingStack.status.networkingInfo.subnetAZC} + - name: sg + template: + apiVersion: ec2.services.k8s.aws/v1alpha1 + kind: SecurityGroup + metadata: + name: ${schema.spec.name}-valkey-sg + spec: + name: ${schema.spec.name}-valkey-sg + description: "Valkey ElastiCache security group" + vpcID: ${networkingStack.status.networkingInfo.vpcID} + ingressRules: + - fromPort: 6379 + toPort: 6379 + ipProtocol: tcp + ipRanges: + - cidrIP: 0.0.0.0/0 + - name: valkey + template: + apiVersion: elasticache.services.k8s.aws/v1alpha1 + kind: CacheCluster + metadata: + name: ${schema.spec.name}-valkey + spec: + cacheClusterID: vote-valkey-cluster + cacheNodeType: cache.t3.micro + cacheSubnetGroupName: ${schema.spec.name}-valkey-subnet-group + engine: valkey + engineVersion: "8.x" + numCacheNodes: 1 + port: 6379 + securityGroupIDs: + - ${sg.status.id} +``` diff --git a/website/docs/examples/deploying-controller.md b/website/docs/examples/deploying-controller.md new file mode 100644 index 0000000..084c324 --- /dev/null +++ b/website/docs/examples/deploying-controller.md @@ -0,0 +1,297 @@ +--- +sidebar_position: 10 +--- + +# Controller Deployment + +```yaml title="controller-rg.yaml" +apiVersion: kro.run/v1alpha1 +kind: ResourceGroup +metadata: + name: ekscontrollers.kro.run +spec: + schema: + apiVersion: v1alpha1 + kind: EKSController + spec: + name: string | default=eks-controller + namespace: string | default=default + values: + aws: + accountID: string | required=true + region: string | default=us-west-2 + deployment: + containerPort: integer | default=8080 + replicas: integer | default=1 + iamRole: + maxSessionDuration: integer | default=3600 + oidcProvider: string | required=true + roleDescription: string | default=IRSA role for ACK EKS controller deployement on EKS cluster using kro Resource group + iamPolicy: + # would prefer to add a policyDocument here, need to support multiline string here + description: string | default="policy for eks controller" + image: + deletePolicy: string | default=delete + repository: string | default=public.ecr.aws/aws-controllers-k8s/eks-controller + tag: string | default=1.4.7 + resources: + requests: + memory: string | default=64Mi + cpu: string | default=50m + limits: + memory: string | default=128Mi + cpu: string | default=100m + log: + enabled: boolean | default=false + level: string | default=info + serviceAccount: + name: string | default=eks-controller-sa + resources: + - name: eksCRDGroup + template: + apiVersion: kro.run/v1alpha1 + kind: EKSCRDGroup + metadata: + name: ${schema.spec.name}-crd-group + spec: + name: ${schema.spec.name}-crd-group + - name: eksControllerIamPolicy + template: + apiVersion: iam.services.k8s.aws/v1alpha1 + kind: Policy + metadata: + name: ${schema.spec.name}-iam-policy + spec: + name: ${schema.spec.name}-iam-policy + description: ${schema.spec.values.iamPolicy.description} + policyDocument: > + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "eks:*", + "iam:GetRole", + "iam:PassRole", + "iam:ListAttachedRolePolicies", + "ec2:DescribeSubnets" + ], + "Resource": "*" + } + ] + } + - name: eksControllerIamRole + template: + apiVersion: iam.services.k8s.aws/v1alpha1 + kind: Role + metadata: + name: ${schema.spec.name}-iam-role + namespace: ${schema.spec.namespace} + spec: + name: ${schema.spec.name}-iam-role + description: ${schema.spec.values.iamRole.roleDescription} + maxSessionDuration: ${schema.spec.values.iamRole.maxSessionDuration} + policies: + - ${eksControllerIamPolicy.status.ackResourceMetadata.arn} + assumeRolePolicyDocument: > + { + "Version":"2012-10-17", + "Statement": [{ + "Effect":"Allow", + "Principal": {"Federated": "arn:aws:iam::${schema.spec.values.aws.accountID}:oidc-provider/${schema.spec.values.iamRole.oidcProvider}"}, + "Action": ["sts:AssumeRoleWithWebIdentity"], + "Condition": { + "StringEquals": {"${schema.spec.values.iamRole.oidcProvider}:sub": "system:serviceaccount:${schema.spec.namespace}:${schema.spec.values.serviceAccount.name}"} + } + }] + } + - name: serviceAccount + template: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ${schema.spec.values.serviceAccount.name} + namespace: ${schema.spec.namespace} + annotations: + eks.amazonaws.com/role-arn : ${eksControllerIamRole.status.ackResourceMetadata.arn} + - name: deployment + template: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ${schema.spec.name}-deployment + namespace: ${schema.spec.namespace} + labels: + app.kubernetes.io.name: ${schema.spec.name}-deployment + app.kubernetes.io.instance: ${schema.spec.name} + spec: + replicas: ${schema.spec.values.deployment.replicas} + selector: + matchLabels: + app.kubernetes.io.name: ${schema.spec.name}-deployment + app.kubernetes.io.instance: ${schema.spec.name} + template: + metadata: + labels: + app.kubernetes.io.name: ${schema.spec.name}-deployment + app.kubernetes.io.instance: ${schema.spec.name} + spec: + serviceAccountName: ${serviceAccount.metadata.name} + containers: + - command: + - ./bin/controller + args: + - --aws-region + - ${schema.spec.values.aws.region} + - --enable-development-logging=${schema.spec.values.log.enabled} + - --log-level + - ${schema.spec.values.log.level} + - --deletion-policy + - ${schema.spec.values.image.deletePolicy} + - --watch-namespace + - ${schema.spec.namespace} + image: ${schema.spec.values.image.repository}:${schema.spec.values.image.tag} + name: controller + ports: + - name: http + containerPort: ${schema.spec.values.deployment.containerPort} + resources: + requests: + memory: ${schema.spec.values.image.resources.requests.memory} + cpu: ${schema.spec.values.image.resources.requests.cpu} + limits: + memory: ${schema.spec.values.image.resources.limits.memory} + cpu: ${schema.spec.values.image.resources.limits.cpu} + env: + - name: ACK_SYSTEM_NAMESPACE + value: ${schema.spec.namespace} + - name: AWS_REGION + value: ${schema.spec.values.aws.region} + - name: DELETE_POLICY + value: ${schema.spec.values.image.deletePolicy} + - name: ACK_LOG_LEVEL + value: ${schema.spec.values.log.level} + ports: + - containerPort: 80 + - name: clusterRoleBinding + template: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: ${schema.spec.name}-clusterrolebinding + roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: ${clusterRole.metadata.name} + subjects: + - kind: ServiceAccount + name: ${serviceAccount.metadata.name} + namespace: ${serviceAccount.metadata.namespace} + - name: clusterRole + template: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: ${schema.spec.name}-clusterrole + rules: + - apiGroups: + - "" + resources: + - configmaps + - secrets + verbs: + - get + - list + - patch + - watch + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch + - apiGroups: + - ec2.services.k8s.aws + resources: + - securitygroups + - securitygroups/status + - subnets + - subnets/status + verbs: + - get + - list + - apiGroups: + - eks.services.k8s.aws + resources: + - accessentries + - addons + - clusters + - fargateprofiles + - identityproviderconfigs + - nodegroups + - podidentityassociations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - eks.services.k8s.aws + resources: + - accessentries/status + - addons/status + - clusters/status + - fargateprofiles/status + - identityproviderconfigs/status + - nodegroups/status + - podidentityassociations/status + verbs: + - get + - patch + - update + - apiGroups: + - iam.services.k8s.aws + resources: + - roles + - roles/status + verbs: + - get + - list + - apiGroups: + - kms.services.k8s.aws + resources: + - keys + - keys/status + verbs: + - get + - list + - apiGroups: + - services.k8s.aws + resources: + - adoptedresources + - fieldexports + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - services.k8s.aws + resources: + - adoptedresources/status + - fieldexports/status + verbs: + - get + - patch + - update +``` diff --git a/website/docs/examples/deploying-coredns.md b/website/docs/examples/deploying-coredns.md new file mode 100644 index 0000000..aa483f0 --- /dev/null +++ b/website/docs/examples/deploying-coredns.md @@ -0,0 +1,189 @@ +--- +sidebar_position: 10 +--- + +# CoreDNS Deployment + +```yaml title="coredns-rg.yaml" +apiVersion: kro.run/v1alpha1 +kind: ResourceGroup +metadata: + name: coredns.kro.run +spec: + schema: + apiVersion: v1alpha1 + kind: CoreDNSDeployment + spec: + name: string | default=mycoredns + namespace: string | default=default + values: + clusterRole: + labels: 'map[string]string | default={"eks.amazonaws.com/component": "coredns", "k8s-app": "kube-dns", "kubernetes.io/bootstrapping": "rbac-defaults"}' + clusterRoleBinding: + annotations: 'map[string]string | default={"rbac.authorization.kubernetes.io/autoupdate": "\"true\""}' + configMap: + labels: 'map[string]string | default={"eks.amazonaws.com/component": "coredns", "k8s-app": "kube-dns"}' + deployment: + annotations: 'map[string]string | default={"deployment.kubernetes.io/revision": "\"1\""}' + labels: 'map[string]string | default={"eks.amazonaws.com/component": "coredns", "k8s-app": "kube-dns", "kubernetes.io/name": "CoreDNS"}' + replicas: integer | default=2 + image: + repository: string | default=coredns/coredns + tag: string | default=1.11.3 + resources: + limits: + cpu: string | default=100m + memory: string | default=128Mi + requests: + cpu: string | default=100m + memory: string | default=128Mi + service: + annotations: 'map[string]string | default={"prometheus.io/port": "9153", "prometheus.io/scrape": "true"}' + labels: 'map[string]string | default={"eks.amazonaws.com/component": "kube-dns", "k8s-app": "kube-dns", "kubernetes.io/cluster-service": "true", "kubernetes.io/name": "CoreDNS"}' + clusterIP: string | default=10.100.123.45 + clusterIPs: '[]string | default=["10.100.123.45"]' + ipFamilies: '[]string | default=["IPv4"]' + type: string | default=ClusterIP + serviceAccount: + secrets: 'map[string]string | default={"name": "coredns-token-pvcnf"}' + resources: + - name: clusterRole + template: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: ${schema.spec.name} + labels: ${schema.spec.values.clusterRole.labels} + rules: + - apiGroups: + - "" + resources: + - endpoints + - services + - pods + - namespaces + verbs: + - list + - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - apiGroups: + - "" + resources: + - nodes + verbs: + - get + - name: clusterRoleBinding + template: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: ${schema.spec.name} + labels: ${schema.spec.values.clusterRole.labels} + annotations: ${schema.spec.values.clusterRoleBinding.annotations} + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: ${clusterRole.metadata.name} + subjects: + - kind: ServiceAccount + name: ${serviceAccount.metadata.name} + namespace: ${serviceAccount.metadata.namespace} + - name: configMap + template: + apiVersion: v1 + kind: ConfigMap + metadata: + name: ${schema.spec.name} + labels: ${schema.spec.values.configMap.labels} + data: + Corefile: |- + .:53 { + errors + health + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + } + prometheus :9153 + forward . /etc/resolv.conf + cache 30 + loop + reload + loadbalance + } + - name: deployment + template: + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: ${schema.spec.values.deployment.annotations} + labels: ${schema.spec.values.deployment.labels} + name: ${schema.spec.name} + spec: + replicas: ${schema.spec.values.deployment.replicas} + selector: + matchLabels: ${schema.spec.values.configMap.labels} + template: + metadata: + labels: ${schema.spec.values.configMap.labels} + spec: + serviceAccountName: ${serviceAccount.metadata.name} + containers: + - name: "coredns" + image: ${schema.spec.values.image.repository}:${schema.spec.values.image.tag} + args: ["-conf", "/etc/coredns/Corefile"] + resources: ${schema.spec.values.resources} + volumeMounts: + - name: config-volume + mountPath: /etc/coredns + volumes: + - name: config-volume + configMap: + name: ${schema.spec.name} + items: + - key: Corefile + path: Corefile + - name: service + template: + apiVersion: v1 + kind: Service + metadata: + name: ${schema.spec.name} + labels: ${schema.spec.values.service.labels} + annotations: ${schema.spec.values.service.annotations} + spec: + selector: + k8s-app: kube-dns + clusterIP: ${schema.spec.values.service.clusterIP} + clusterIPs: ${schema.spec.values.service.clusterIPs} + internalTrafficPolicy: Cluster + ipFamilies: ${schema.spec.values.service.ipFamilies} + ports: + - name: dns + port: 53 + protocol: UDP + targetPort: 53 + - name: dns-tcp + port: 53 + protocol: TCP + targetPort: 53 + selector: + k8s-app: kube-dns + sessionAffinity: None + - name: serviceAccount + template: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ${schema.spec.name} + namespace: ${schema.spec.namespace} + labels: ${schema.spec.values.configMap.labels} + secrets: + - ${schema.spec.values.serviceAccount.secrets} +``` diff --git a/website/docs/examples/ekscluster.md b/website/docs/examples/ekscluster.md deleted file mode 100644 index af85093..0000000 --- a/website/docs/examples/ekscluster.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -sidebar_position: 10 ---- - -# EKSCluster - -```yaml title="ekscluster-rg.yaml" -apiVersion: kro.run/v1alpha1 -kind: ResourceGroup -metadata: - name: kro.run/v1alpha1 -spec: - # CRD Definition - apiVersion: v1alpha1 - kind: EKSCluster - - definition: - spec: - name: string - version: string - numNodes: integer - - # resources - resources: - - name: clusterVPC - definition: - apiVersion: ec2.services.k8s.aws/v1alpha1 - kind: VPC - metadata: - name: cluster-vpc-${schema.spec.name} - spec: - cidrBlocks: - - 192.168.0.0/16 - enableDNSHostnames: false - enableDNSSupport: true - - - name: subnetAZA - definition: - apiVersion: ec2.services.k8s.aws/v1alpha1 - kind: Subnet - metadata: - name: cluster-subnet-a-${schema.spec.name} - spec: - availabilityZone: us-west-2a - cidrBlock: 192.168.0.0/18 - vpcID: ${clusterVPC.status.vpcID} - - - name: securityGroup - definition: - apiVersion: ec2.services.k8s.aws/v1alpha1 - kind: SecurityGroup - metadata: - name: cluster-security-group-${schema.spec.name} - spec: - vpcID: ${clusterVPC.status.vpcID} - name: my-eks-cluster-sg-${schema.spec.name} - description: something something - - - name: subnetAZB - definition: - apiVersion: ec2.services.k8s.aws/v1alpha1 - kind: Subnet - metadata: - name: cluster-subnet-b-${schema.spec.name} - spec: - availabilityZone: us-west-2b - cidrBlock: 192.168.64.0/18 - vpcID: ${clusterVPC.status.vpcID} - - - name: clusterRole - definition: - apiVersion: iam.services.k8s.aws/v1alpha1 - kind: Role - metadata: - name: cluster-role-${schema.spec.name} - spec: - name: cluster-role-${schema.spec.name} - policies: - - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy - assumeRolePolicyDocument: | - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "eks.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - } - - - name: nodeRole - definition: - apiVersion: iam.services.k8s.aws/v1alpha1 - kind: Role - metadata: - name: cluster-node-role-${schema.spec.name} - spec: - name: cluster-node-role-${schema.spec.name} - policies: - - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy - - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly - - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy - assumeRolePolicyDocument: | - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "ec2.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - } - - - name: cluster - definition: - apiVersion: eks.services.k8s.aws/v1alpha1 - kind: Cluster - metadata: - name: cluster-${schema.spec.name} - spec: - name: cluster-${schema.spec.name} - roleARN: ${clusterRole.status.ackResourceMetadata.arn} - version: ${schema.spec.version} - resourcesVPCConfig: - subnetIDs: - - ${subnetAZA.status.subnetID} - - ${subnetAZB.status.subnetID} - - - name: nodegroup - definition: - apiVersion: eks.services.k8s.aws/v1alpha1 - kind: Nodegroup - metadata: - name: nodegroup-${schema.spec.name} - spec: - name: nodegroup-${schema.spec.name} - clusterName: cluster-${schema.spec.name} - subnets: - - ${subnetAZA.status.subnetID} - - ${subnetAZB.status.subnetID} - nodeRole: ${nodeRole.status.ackResourceMetadata.arn} - updateConfig: - maxUnavailable: 1 - scalingConfig: - minSize: ${schema.spec.numNodes} - maxSize: ${schema.spec.numNodes} - desiredSize: ${schema.spec.numNodes} -``` diff --git a/website/docs/examples/examples.md b/website/docs/examples/examples.md new file mode 100644 index 0000000..299e59b --- /dev/null +++ b/website/docs/examples/examples.md @@ -0,0 +1,49 @@ +--- +sidebar_position: 0 +--- + +# Examples + +This section provides a collection of examples demonstrating how to define and +use ResourceGroups in **kro** for various scenarios. Each example showcases a +specific use case and includes a detailed explanation along with the +corresponding YAML definitions. + +## Basic Examples + +- [Empty ResourceGroup (Noop)](./noop.md) Explore the simplest form of a + ResourceGroup that doesn't define any resources, serving as a reference for + the basic structure. + +- [Simple Web Application](./web-app.md) Deploy a basic web application with a + Deployment and Service. + +- [Web Application with Ingress](./web-app-ingress.md) Extend the basic web + application example to include an optional Ingress resource for external + access. + +## Advanced Examples + +- [Deploying CoreDNS](./deploying-coredns.md) Learn how to deploy CoreDNS in a + Kubernetes cluster using kro ResourceGroups, including the necessary + Deployment, Service, and ConfigMap. + +- [Deploying a Controller](./deploying-controller.md) Discover how to deploy a + Kubernetes controller using kro ResourceGroups, including the necessary + Deployment, ServiceAccount, and CRDs. + +- [AWS Networking Stack](./ack-networking-stack.md) Learn how to define and + manage an AWS networking stack using kro ResourceGroups, including VPCs, + subnets, and security groups. + +- [EKS Cluster with ACK CRDs](./ack-eks-cluster.md) Explore how to define and + manage an EKS cluster using AWS Controllers for Kubernetes (ACK) CRDs within a + kro ResourceGroup. + +- [Valkey CacheCluster with ACK CRDs](./ack-valkey-cachecluster.md) Learn how to + create and configure a Valkey CacheCluster using ACK CRDs in a kro + ResourceGroup. + +- [Pod and RDS DBInstance](./pod-rds-dbinstance.md) Deploy a Pod and an RDS + DBInstance in a kro ResourceGroup, showcasing the use of multiple resources + with dependencies. diff --git a/website/docs/examples/empty.md b/website/docs/examples/noop.md similarity index 76% rename from website/docs/examples/empty.md rename to website/docs/examples/noop.md index 26d9362..81da2c0 100644 --- a/website/docs/examples/empty.md +++ b/website/docs/examples/noop.md @@ -1,17 +1,17 @@ --- -sidebar_position: 0 +sidebar_position: 5 --- # Empty ResourceGroup -```yaml title="no-resources-rg.yaml" +```yaml title="noop.yaml" apiVersion: kro.run/v1alpha1 kind: ResourceGroup metadata: name: kro.run/v1alpha1 spec: apiVersion: v1alpha1 - kind: Noop + kind: NoOp definition: spec: name: string | required=true diff --git a/website/docs/examples/deploymentdbinstance.md b/website/docs/examples/pod-rds-dbinstance.md similarity index 97% rename from website/docs/examples/deploymentdbinstance.md rename to website/docs/examples/pod-rds-dbinstance.md index 88073c3..e200f72 100644 --- a/website/docs/examples/deploymentdbinstance.md +++ b/website/docs/examples/pod-rds-dbinstance.md @@ -2,7 +2,7 @@ sidebar_position: 20 --- -# DeploymentDBInstance +# Pod with RDS DBInstance ```yaml title="deploymentdbinstance-rg.yaml" apiVersion: kro.run/v1alpha1 diff --git a/website/docs/examples/web-app-ingress.md b/website/docs/examples/web-app-ingress.md new file mode 100644 index 0000000..4113aed --- /dev/null +++ b/website/docs/examples/web-app-ingress.md @@ -0,0 +1,92 @@ +--- +sidebar_position: 10 +--- + +# Web Application w/ Ingress + +```yaml title="webapp-ingress.yaml" +apiVersion: kro.run/v1alpha1 +kind: ResourceGroup +metadata: + name: my-application +spec: + # kro uses this simple schema to create your CRD schema and apply it + # The schema defines what users can provide when they instantiate the RG (create an instance). + schema: + apiVersion: v1alpha1 + kind: Application + spec: + # Spec fields that users can provide. + name: string + image: string | default="nginx" + ingress: + enabled: boolean | default=false + status: + # Fields the controller will inject into instances status. + deploymentConditions: ${deployment.status.conditions} + availableReplicas: ${deployment.status.availableReplicas} + + # Define the resources this API will manage. + resources: + - name: deployment + template: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ${schema.spec.name} # Use the name provided by user + spec: + replicas: 3 + selector: + matchLabels: + app: ${schema.spec.name} + template: + metadata: + labels: + app: ${schema.spec.name} + spec: + containers: + - name: ${schema.spec.name} + image: ${schema.spec.image} # Use the image provided by user + ports: + - containerPort: 80 + + - name: service + template: + apiVersion: v1 + kind: Service + metadata: + name: ${schema.spec.name}-service + spec: + selector: ${deployment.spec.selector.matchLabels} # Use the deployment selector + ports: + - protocol: TCP + port: 80 + targetPort: 80 + + - name: ingress + includeWhen: + - ${schema.spec.ingress.enabled} # Only include if the user wants to create an Ingress + template: + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: ${schema.spec.name}-ingress + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/healthcheck-path: /health + alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]' + alb.ingress.kubernetes.io/target-group-attributes: stickiness.enabled=true,stickiness.lb_cookie.duration_seconds=60 + spec: + rules: + - http: + paths: + - path: "/" + pathType: Prefix + backend: + service: + name: ${service.metadata.name} # Use the service name + port: + number: 80 +``` diff --git a/website/docs/examples/deploymentservice.md b/website/docs/examples/web-app.md similarity index 96% rename from website/docs/examples/deploymentservice.md rename to website/docs/examples/web-app.md index 56291d5..d8ee2b7 100644 --- a/website/docs/examples/deploymentservice.md +++ b/website/docs/examples/web-app.md @@ -1,8 +1,8 @@ --- -sidebar_position: 1 +sidebar_position: 10 --- -# DeploymentService +# Web Application ```yaml title="deploymentservice-rg.yaml" apiVersion: kro.run/v1alpha1 diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 6424798..4d02abd 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -90,12 +90,12 @@ const config: Config = { position: "left", label: "Examples", }, - { + /* { type: "docSidebar", sidebarId: "apisSidebar", position: "left", label: "API Reference", - }, + }, */ { type: "docsVersionDropdown", position: "right",