Let's understand OAM and Napptive
Introduction
Important question: What is OAM and Napptive?
OAM stands for Open Application Model its a method to simplify the Kubernetes manifest to be easily understandable, and maintainable. The key component why companies have started using these is because it provides a layer of abstraction to the Kubernetes objects and provides development teams with a template for their Kubernetes manifest without the need to gain expertise on Kubernetes, also due to its simplistic approach it's far better maintainable then Kubernetes objects and the operations/platform teams can tailor-made the template to their specific or custom Kubernetes objects still being same for the development teams
It aims to be app-centric means the manifests are more towards the application rather than infrastructure
Napptive is a company that provides anyone to deploy their OAM templates to their SaaS offering having a catalog so that you can pick and choose which app to deploy
- it is one of the company among many who have started to use OAM for their products like (Kubevela, Crossplane, etc.)
How does OAM work
It provides a standard way to define and describe applications and their components, making it easier for developers and operators to work together.
It can deployed in any existing Kubernetes cluster, any resource with specific apiVersion core.oam.dev/v1alphaXY
is first translated to equavalent kubernetes resources and is deployed to the kubernetes cluster, so you may think how can that be possible.
I bet you know the answer, CRD
is almost similar just that using it you can create your own kubernetes resources by manipulating different kubernetes components
but for this something called as cue
is used to transform the OAM template ==>> K8s manifests
OAM works by using a few key concepts:
- Components: These are the building blocks of an application. Components can be anything from a microservice to a database. In OAM, components are defined using a ComponentSchematic, which describes the properties and characteristics of the component. For example:
apiVersion: core.oam.dev/v1alpha2
kind: Component
metadata:
name: my-web-app
spec:
type: webservice
settings:
image: example/my-web-app:1.0.0
port: 8080
- ApplicationConfigurations: These are used to assemble and configure components into a complete application. ApplicationConfigurations define how components are connected, scaled, and updated. For example:
# it is used to create custom applicationConfiguration
apiVersion: core.oam.dev/v1alpha2
kind: ApplicationConfiguration
metadata:
name: my-web-app-config
spec:
components:
- componentName: my-web-app
instanceName: my-web-app-instance
parameterValues:
- name: port
value: 8080
- Traits: These are optional, reusable attributes that can be attached to components to modify their behavior or configuration. Traits can be used to specify things like auto-scaling, ingress, and resource limits. For example:
# it defines custom traits to the existing components
# this trait maps to HorizontalPodAutoscaler
apiVersion: core.oam.dev/v1alpha2
kind: TraitDefinition
metadata:
name: manualscalertraits.core.oam.dev
spec:
appliesToWorkloads:
- core.oam.dev/v1alpha2.Server
schematic:
cue:
template: |
output: {
"apiVersion": "autoscaling/v2beta2"
"kind": "HorizontalPodAutoscaler"
"metadata": {
"name": context.name
}
"spec": {
"scaleTargetRef": {
"apiVersion": "apps/v1"
"kind": "Deployment"
"name": context.name
}
"minReplicas": parameter.min
"maxReplicas": parameter.max
}
}
parameter: {
min: *1 | int
max: *10 | int
}
The basic flow of OAM templates
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: hackathon-application
spec:
components:
- name: Component1
type: XyZ
...
properties:
....
traits:
- trait 1
....
- trait n
- name: Component2
type: XyZ
....
workflows:
steps:
- deployment step 1
- deployment step 2
...
policies:
- policy 1
- policy 2
....
Here
the application acts like groups of various components of a single application
components helps us to specify what kind of component we want like deployment ?, StatefulSet ? and many more
traits help us to modify each of the existing components
workflows helps us to make deployment of each component in some defined stages like first database should be deployed before the application
policies it provides the ways to modify the default properties like interval of health checks and more
Let's show how we can convert an existing Kubernetes manifest to OAM based
using the GitHub link below as a reference
through this, we will go together through the various component conversion
Simple Redis instance
how to solve when the Kubernetes object you want to use is not available
well well I know many people will say why Go but yes we are using Go gin server as an API server with some specific route that can be used to add, get, and delete data to/from the Redis instance
Need for custom Component for configMap also for secrets
In Cue their 2 main components
parameter
we define what all input user has to specify
output
is the resultant kubernetes manifest in json format
its kind of a mapping
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: configmap # it will be the type of component
spec:
schematic:
cue:
template: |
parameter: {
// +usage=Name of the ConfigMap
name: string
// +usage=Key-value pairs to be stored in the ConfigMap
data: [string]: string
// +usage=Key-value pairs for labels
labels: [string]: string // is a map[string]: string
}
output: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: {
name: parameter.name
labels: parameter.labels
}
data: parameter.data
}
---
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: secrets # it will be the type of component
spec:
schematic:
cue:
template: |
parameter: {
// +usage=Name of the ConfigMap
name: string
// +usage=Key-value pairs to be stored in the ConfigMap
data: [string]: string
}
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: parameter.name
}
data: parameter.data
}
now using these ComponentDefinition
components:
- name: redis-configuration
type: configmap
properties:
name: redis-config
labels:
role: redis
data:
redis.conf: |-
bind 0.0.0.0
port 6379
tcp-backlog 511
masterauth OKz6eZYrkIZQSjtFb1
requirepass OKz6eZYrkIZQSjtFb1
protected-mode no
timeout 0
tcp-keepalive 300
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
dbfilename dump.rdb
dir /data/
replica-read-only yes
appendfsync everysec
# and similarly for the secrets
now you get it how simple the things are
another component of the Redis DB
- name: redis
type: statefulservice
properties:
replicas: 1
image: redis:6.2.3-alpine
cmd: ["redis-server"]
args: ["/etc/redis/redis.conf"]
name: redis
ports:
- port: 6379
protocol: TCP
expose: true
volumeMounts:
pvc:
- name: data
mountPath: /data
size: 500Mi
claimName: redis-pvc
configMap:
- name: config
mountPath: "/etc/redis/redis.conf"
subPath: redis.conf
cmName: redis-config
and similarly the http server
- name: http-server
type: webservice
properties:
image: docker.io/dipugodocker/hackathon-napptive:3.0@sha256:514c510cae88324263c170bc265bd45cf28a605568b90fed0ebb32afb9ead401
ports:
- port: 8080
expose: true
env:
- name: REDIS_PORT
value: "6379"
- name: REDIS_HOST
value: "redis-0.redis-headless"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-password
key: password
traits: # used to add ingress
- type: napptive-ingress
properties:
port: 8080
path: /
- type: scaler # used to have deployment having 2 pods
properties:
replicas: 2
so how to manage which one first gets deployed
workflows 🧐
first the config map and secrets then the database and finally the webserver
so here is the final OAM template ready to be deployed
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: configmap
spec:
schematic:
cue:
template: |
parameter: {
// +usage=Name of the ConfigMap
name: string
// +usage=Key-value pairs to be stored in the ConfigMap
data: [string]: string
// +usage=Key-value pairs for labels
labels: [string]: string
}
output: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: {
name: parameter.name
labels: parameter.labels
}
data: parameter.data
}
---
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: secrets
spec:
schematic:
cue:
template: |
parameter: {
// +usage=Name of the ConfigMap
name: string
// +usage=Key-value pairs to be stored in the ConfigMap
data: [string]: string
}
output: {
apiVersion: "v1"
kind: "Secret"
metadata: {
name: parameter.name
}
data: parameter.data
}
---
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: hackathon-application
spec:
components:
- name: redis-configuration
type: configmap
properties:
name: redis-config
labels:
role: redis
data:
redis.conf: |-
bind 0.0.0.0
port 6379
tcp-backlog 511
masterauth OKz6eZYrkIZQSjtFb1
requirepass OKz6eZYrkIZQSjtFb1
protected-mode no
timeout 0
tcp-keepalive 300
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
dbfilename dump.rdb
dir /data/
replica-read-only yes
appendfsync everysec
- name: redis-secrets
type: secrets
properties:
name: redis-password
data:
password: T0t6NmVaWXJrSVpRU2p0RmIxCg==
- name: redis
type: statefulservice
properties:
replicas: 1
image: redis:6.2.3-alpine
cmd: ["redis-server"]
args: ["/etc/redis/redis.conf"]
name: redis
ports:
- port: 6379
protocol: TCP
expose: true
volumeMounts:
pvc:
- name: data
mountPath: /data
size: 500Mi
claimName: redis-pvc
configMap:
- name: config
mountPath: "/etc/redis/redis.conf"
subPath: redis.conf
cmName: redis-config
- name: http-server
type: webservice
properties:
image: docker.io/dipugodocker/hackathon-napptive:3.0@sha256:514c510cae88324263c170bc265bd45cf28a605568b90fed0ebb32afb9ead401
ports:
- port: 8080
expose: true
env:
- name: REDIS_PORT
value: "6379"
- name: REDIS_HOST
value: "redis-0.redis-headless"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-password
key: password
traits:
- type: napptive-ingress
properties:
port: 8080
path: /
- type: scaler
properties:
replicas: 2
workflows:
steps:
- name: redis-config
type: apply-component
properties:
component: redis-configuration
- name: redis-secrets
type: apply-component
properties:
component: redis-secrets
- name: redis-server
type: apply-component
properties:
component: redis
- name: http-server
type: apply-component
properties:
component: http-server
how to deploy this?
Step 1: Click login to playground
Step 2: Click on deploy app
Step 3: Paste the above yaml
🎉You have deployed your first OAM app
click on the endpoint and enjoy the demo
References
Conclusion
By using these concepts, OAM enables developers to focus on writing code and defining components, while operators can focus on managing the deployment and runtime aspects of the application. This separation of concerns helps to streamline the development and operation process, making it easier to build, deploy, and manage cloud-native applications.
I hope you all enjoyed it, if I have made mistake do let me know
Subscribe to my newsletter
Read articles from Dipankar Das directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Dipankar Das
Dipankar Das
DevOps, Development and CLoud related stuff Kubesimplify ambassador and a OSS contributor to various CNCF projects