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
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"
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:
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:
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:
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:
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
Uses the custom WebApplication API created by our RGD
Creates a resource named carts in the carts namespace
Specifies the application name for resource naming
Sets single replica
Uses the retail store cart service container image
Exposes the application on port 8080
Configures environment variables for in-memory persistence mode
Enables the Kubernetes Service resource
Let's deploy the application:
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:
NAME AGE
carts 30s
Next, verify the deployment:
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.