Skip to main content

Provisioning resources with kro

Now that kro has been installed, we will deploy the Carts component using a kro WebApplication ResourceGraphDefinitions. First, let's examine the ResourceGraphDefinition template that defines the reusable WebApplication API:

Expand for full RGD manifest
~/environment/eks-workshop/modules/automation/controlplanes/kro/rgds/webapp-rgd.yaml
apiVersion: kro.run/v1alpha1
kind: ResourceGraphDefinition
metadata:
name: web-application
spec:
schema:
apiVersion: v1alpha1
kind: WebApplication
spec:
appName: string | required=true description="Web Application Name"
replicas: integer | default=1 minimum=1 maximum=100
image: string | default=nginx
port: integer | default=8080
healthcheck:
readinessPath: string | default="/actuator/health/readiness"
readinessPort: integer | default=8080
livenessPath: string | default="/actuator/health/liveness"
livenessPort: integer | default=8080

service:
enabled: boolean | default=true
iamRole: string | default=""

env: map[string]string | default={}

ingress:
enabled: boolean | default=false
path: string | default="/"
healthcheckPath: string | default="/health"
groupname: string | default="eks-workshop"

resources:
- id: serviceAccount
template:
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${schema.spec.appName}
namespace: ${schema.spec.appName}

- id: configMap
template:
apiVersion: v1
kind: ConfigMap
metadata:
name: ${schema.spec.appName}
namespace: ${schema.spec.appName}
data: ${schema.spec.env}

- id: deployment
template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${schema.spec.appName}
namespace: ${schema.spec.appName}
labels:
app.kubernetes.io/created-by: "eks-workshop"
app.kubernetes.io/type: app
spec:
replicas: ${schema.spec.replicas}
revisionHistoryLimit: 3
selector:
matchLabels:
app.kubernetes.io/name: ${schema.spec.appName}
app.kubernetes.io/instance: ${schema.spec.appName}
app.kubernetes.io/component: service
template:
metadata:
annotations:
prometheus.io/path: /actuator/prometheus
prometheus.io/port: "8080"
prometheus.io/scrape: "true"
labels:
app.kubernetes.io/name: ${schema.spec.appName}
app.kubernetes.io/instance: ${schema.spec.appName}
app.kubernetes.io/component: service
app.kubernetes.io/created-by: eks-workshop
spec:
serviceAccountName: ${schema.spec.appName}
securityContext:
fsGroup: 1000
containers:
- name: ${schema.spec.appName}
env:
- name: JAVA_OPTS
value: -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/urandom
envFrom:
- configMapRef:
name: ${schema.spec.appName}
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
image: ${schema.spec.image}
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: ${schema.spec.port}
protocol: TCP
readinessProbe:
httpGet:
path: ${schema.spec.healthcheck.readinessPath}
port: ${schema.spec.healthcheck.readinessPort}
initialDelaySeconds: 15
periodSeconds: 3
livenessProbe:
httpGet:
path: ${schema.spec.healthcheck.livenessPath}
port: ${schema.spec.healthcheck.livenessPort}
initialDelaySeconds: 45
periodSeconds: 3
resources:
limits:
memory: 1Gi
requests:
cpu: 250m
memory: 1Gi
volumeMounts:
- mountPath: /tmp
name: tmp-volume
volumes:
- name: tmp-volume
emptyDir:
medium: Memory

- id: service
template:
apiVersion: v1
kind: Service
metadata:
name: ${schema.spec.appName}
namespace: ${schema.spec.appName}
labels:
app.kubernetes.io/created-by: eks-workshop
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: ${schema.spec.appName}
app.kubernetes.io/instance: ${schema.spec.appName}
app.kubernetes.io/component: service
includeWhen:
- ${schema.spec.service.enabled}

- id: ingress
template:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ${schema.spec.appName}
namespace: ${schema.spec.appName}
labels:
app.kubernetes.io/created-by: eks-workshop
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/healthcheck-path: ${schema.spec.ingress.healthcheckPath}
alb.ingress.kubernetes.io/group.name: ${schema.spec.ingress.groupname}
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: ${schema.spec.ingress.path}
pathType: Prefix
backend:
service:
name: ${schema.spec.appName}
port:
number: 80
includeWhen:
- ${schema.spec.ingress.enabled}

This ResourceGraphDefinition creates a custom WebApplication API that abstracts the complexity of deploying:

  • ServiceAccount
  • ConfigMap
  • Deployment
  • Service
  • Ingress (optional)

The schema provides sensible defaults while allowing customization of key parameters like the application image, replica count, environment variables, and health check configuration as shown:

    spec:
appName: string | required=true description="Web Application Name"
replicas: integer | default=1 minimum=1 maximum=100
image: string | default=nginx
port: integer | default=8080
healthcheck:
readinessPath: string | default="/actuator/health/readiness"
readinessPort: integer | default=8080
livenessPath: string | default="/actuator/health/liveness"
livenessPort: integer | default=8080

service:
enabled: boolean | default=true
iamRole: string | default=""

env: map[string]string | default={}

ingress:
enabled: boolean | default=false
path: string | default="/"
healthcheckPath: string | default="/health"
groupname: string | default="eks-workshop"
info

Notice how the schema uses default values and type definitions to create a developer-friendly API that hides the underlying Kubernetes complexity.

We will use this WebApplication ResourceGraphDefinition to create an instance of the Carts component which uses an in-memory database. To do this, let's first clean up the existing carts deployment:

~$kubectl delete all --all -n carts
pod "carts-68d496fff8-9lcpc" deleted
pod "carts-dynamodb-995f7768c-wtsbr" deleted
service "carts" deleted
service "carts-dynamodb" deleted
deployment.apps "carts" deleted
deployment.apps "carts-dynamodb" deleted

Next, apply the ResourceGraphDefinition to register the WebApplication API:

~$kubectl apply -f ~/environment/eks-workshop/modules/automation/controlplanes/kro/rgds/webapp-rgd.yaml
resourcegraphdefinition.kro.run/web-application created

This registers the WebApplication API. kro automatically creates the Custom Resource Definition (CRD) based on the RGD schema. Verify the CRD:

~$kubectl get crd webapplications.kro.run
NAME                       CREATED AT
webapplications.kro.run    2024-01-15T10:30:00Z

Now let's examine the carts.yaml file that will use the WebApplication API to create an instance of the Carts component:

~/environment/eks-workshop/modules/automation/controlplanes/kro/app/carts.yaml
apiVersion: kro.run/v1alpha1
kind: WebApplication
metadata:
name: carts
namespace: carts
spec:
# Basic types
appName: carts
replicas: 1
image: "public.ecr.aws/aws-containers/retail-store-sample-cart:1.2.1"
port: 8080
env:
RETAIL_CART_PERSISTENCE_PROVIDER: "in-memory"
RETAIL_CART_PERSISTENCE_DYNAMODB_TABLE_NAME: "Items"
RETAIL_CART_PERSISTENCE_DYNAMODB_CREATE_TABLE: "false"

service:
enabled: true
A

Uses the custom WebApplication API created by our RGD

B

Creates a resource named carts in the carts namespace

C

Specifies the application name for resource naming

D

Sets single replica

E

Uses the retail store cart service container image

F

Exposes the application on port 8080

G

Configures environment variables for in-memory persistence mode

H

Enables the Kubernetes Service resource

Let's deploy the application:

~$kubectl apply -f ~/environment/eks-workshop/modules/automation/controlplanes/kro/app/carts.yaml
webapplication.kro.run/carts created

kro will process this custom resource and create all the underlying Kubernetes resources. Let's verify the custom resource was created:

~$kubectl get webapplication -n carts
NAME    AGE
carts   30s

Next, verify the deployment:

~$kubectl get all -n carts
NAME                         READY   STATUS    RESTARTS   AGE
pod/carts-7d58cfb7c9-xyz12   1/1     Running   0          30s
 
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/carts   ClusterIP   172.20.123.45   <none>        80/TCP    30s
 
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/carts   1/1     1            1           30s
 
NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/carts-7d58cfb7c9   1         1         1       30s

kro has successfully orchestrated the deployment of all Kubernetes resources required by the Carts component as a single unit. By using kro, we've transformed what would typically require applying multiple YAML files into a single, declarative API call. This demonstrates kro's power in simplifying complex resource orchestration.

In the next section, we'll replace the in-memory database that is currently being used by carts with an Amazon DynamoDB table.