Creating your first service API
This is Part 1 of a series illustrating how Kratix works.
👉🏾 Next: Delivering your service on demand
In this tutorial, you will
- Learn more about a Promise
- Design an API for your service
- Implement the design with validations and versioning
- Deploy your API using Kratix
- Experience requesting a Resource
- Summary
- Clean up environment
What is a Promise?
In Part I, you learned that Kratix is a framework used by platform teams to build platforms tailored to their organisation.
Kratix Promises are the encapsulation of platform offerings. They provide a structure to manage the complexities of providing something-as-a-service, including the definition of how a user can request the Resource, the steps to provision the requested Resource, and how to provide access to the Resource.
Promises allow platform teams to:
- Define an API, including versioning and validation
- Execute imperative commands per user request
- Use GitOps to continuously reconcile delivered services
- Install and configure (or verify) dependencies for delivering a service
- Manage where services are deployed across the infrastructure (on and off-Kubernetes, on Cloud providers, etc)
Promise Architecture
Before jumping into writing a Promise, here is a quick review of the Promise Architecture.
At a very high-level, a Promise is made up of four parts:
- The Promise API: The Promise API is what the users of the Platform will interact with when requesting a new Resource from the Promised Service
- The Imperative Workflow: A series of steps where Platform teams can codify all of their business requirements.
- The Declarative State: The Workflow executes a series of imperative steps top generate a declarative state that's then persisted into the State Store. Other systems will then converge on that state.
- The Dependencies: A dependency is anything that must be installed or made available on Worker Destinations to enable the promised service to run.
As you go through building your own Promise, you will explore each of these four sections in detail. This specific section will focus on the API.
API in detail
When building an API, you have a lot of design considerations to include. For example, you will want to:
- require certain parameters
- make some parameters dependent on values in other parameters
- provide client-side validation
- run server-side validation
- ensure API versioning to manage updates
These and many other features are all provided standard with a Kubernetes Custom Resource Definition (CRD). This is why Kratix uses CRDs as the mechanism to codify your Promise API.
In a Promise, the API is stored in a key called api
🤔 Want to learn more about CRDs?
A Custom Resource (CR) is an extension of the Kubernetes API that is not necessarily available in a default Kubernetes installation. It represents a customisation of a particular Kubernetes installation.
A Custom Resource Definition (CRD) is the object you create to teach your Kubernetes cluster about this new resource type.
To read more about CRDs please refer to the Kubernetes documentation.
This tutorial will not go through details on how to write a Kubernetes CRD, but check out the Kubernetes documentation for further details on the topic.
Design an API for your service
Imagine that your users have requested a monitoring system for their applications. You on the platform team have investigated potential options and determined that Elastic Cloud on Kubernetes (ECK) is the best fit for your users needs.
In addition, you have found that ECK provides an Kubernetes Operator which can help your team manage the configuration of different deployments. There's also a Helm chart, which can be used to request fully configured Resources.
You have a few concerns though about how to expose the Operator to your users. While some of your users have a deep knowledge of monitoring, the majority of them are not bothered by the details and just want access to the Kibana interface.
Your first job is to decide what options to expose to the Application teams.
Start with the interface
A Kratix Promise allows platform teams to fine-tune how much application teams need to know about low-level details while still providing the levers they need to get the service they want.
You can start by defining an interface for the applications teams to request their monitoring system. This interface is the contract of what can be configured and within what parameters.
Explore possible parameters
When designing the API for your new Elastic Promise, one option would be to review the Helm chart configuration options and expose all of them to the users to provide maximum autonomy and configurability. However, it is a lot to understand and misconfiguration of services can cause both cost and security issues in the future.
You could go to the other end of the spectrum, hiding all the options, making it impossible for users to change anything on the deployed ECK Resource. You could have, for example, a set of strict business rules for how to configure a secure and reliable ECK. However, this API design would likely cause a lot of support requests from users who need slight variations to the stock deployment.
While both extremes are possible using Kratix, the recommendation is to design your API with one key principle in mind: what do your users (need to) care about? Answering that question is fundamental to designing a useful API.
Define your interface
Keeping in mind your users, it is easy to dismiss the "all levers" option as a first implementation. The ElasticSearch chart provides a lot of important configurations for the platform but most, if not all, are too low level for the application developer to care about.
Instead, you can focus your energy on the top level values file, which provides the ability to turn on and off specific services.
Upon review of the available services that make up the Elastic Stack, it seems sensible that both ElasticSearch and Kibana are always delivered. You have also decided that Fleet Server and Agents should not be included in your offering, as they will not fulfil any current needs and risk operational overhead. However, you have seen that data collection is less consistent: some people want to use the provided collectors whereas others will use language specific libraries in their applications.
Therefore, a truly user-centric API for your platform may be as simple as:
- enable data collection: true or false (false by default)
While only a single parameter, you can notice two key aspects in this final API design:
- The parameter is named on your internal domain language
- Your API codifies the platform teams opinion (i.e. Fleet Server and Agents are never included)
With your API defined, you can start writing your Promise.
Implement your Promise API
Start by creating a directory on your computer. This workshop will include a number of other files and directories so it is best to start organising your Promise now.
mkdir -p elastic-cloud-promise
cd elastic-cloud-promise
touch promise.yaml
The first file you need to create is the Promise with its API defined.
At the root of your newly created directory, create a promise.yaml
file with
the following contents:
apiVersion: platform.kratix.io/v1alpha1
kind: Promise
metadata:
name: elastic-cloud
spec:
api:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: elastic-clouds.workshop.kratix.io
spec:
group: workshop.kratix.io
names:
kind: elastic-cloud
plural: elastic-clouds
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
enableDataCollection:
type: boolean
default: false
description: |
If enabled, you will receive tools for
metric, log, and trace collection that
can be used to populate the elastic
cloud instance.
Notice that in this file there is a single key called api
under the Promise spec.
This is where a complete and valid Kubernetes CRD is provided that contains the data
collection parameter as decided above along with a default value and description that
can later be used for documentation (check highlighted block).
🤔 How did the design result in this CRD?
Custom Resource Definitions have a specification that must be followed. The two main areas of interest for your CRD will be how it is named and how to manage the fields.
Naming:
Your API will be referred to as a combination of the CRD defined group
and plural
name. The group can be shared across all of your APIs or be further subdivided.
In this case, the group will be workshop.kratix.io
and the plural name will be
elastic-clouds
. This means the full name will be
elastic-clouds.workshop.kratix.io
.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: elastic-clouds.workshop.kratix.io
spec:
group: workshop.kratix.io
names:
kind: elastic-cloud
plural: elastic-clouds
Field definition:
Your API only has a single field, but it requires a number of validations. Specifically you need to:
- Define a new key within the
spec.properties
field with the name of the field - Set the property type as
boolean
- Provide a default value of
false
- Optionally add a description that can later be used for automated documentation
This is all added to the CRD as a schema for a single version:
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
enableDataCollection:
type: boolean
default: false
description: |
If enabled, you will receive tools for
metric, log, and trace collection that
can be used to populate the elastic
cloud instance.
Deploy your API using Kratix
Prerequisite setup
For this tutorial, you will need a fresh installation of Kratix.
You can use the following command if you are unsure about your state.
If you are participating in a facilitated workshop using Instruqt, this is not necessary.
👉🏾 commands to create a fresh installation 👈🏾
git clone https://github.com/syntasso/kratix.git /tmp/kratix
/tmp/kratix/scripts/quick-start.sh --recreate --no-labels
export PLATFORM=kind-platform
export WORKER=kind-worker
Once the script completes, you should have Kratix up and running. Verify:
kubectl --context $PLATFORM get deployments --namespace kratix-platform-system
The above command will give an output similar to:
NAME READY UP-TO-DATE AVAILABLE AGE
kratix-platform-controller-manager 1/1 1 1 1h
minio 1/1 1 1 1h
You will also have a single worker cluster which you can verify:
kubectl --context $PLATFORM get destinations.platform.kratix.io
The above command will give an output similar to:
NAME AGE
worker-cluster-1 10s
And you can see that you have two separate Kubectl contexts, one for each KinD cluster you created:
kubectl config get-contexts
Which will result in output similar to:
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* kind-platform kind-platform kind-platform
kind-worker kind-worker kind-worker
More details on how the Kratix installation and configuration works can be found in Part I
Teaching Kratix about your service
Run the following command to teach Kratix about your newest ECK platform offering:
kubectl --context $PLATFORM create --filename promise.yaml
Since a Kratix Promise is a Kubernetes Custom Resource, you can use the Kubernetes API to manage them both individually and as a group.
This provides the benefit of being able to catalog all platform offerings by just listing all Promises in your cluster.
To see that the Promise has been installed, run:
kubectl --context kind-platform get promises
Your output will show the elastic-cloud
Promise:
NAME AGE
elastic-cloud 10s
More importantly, you will also be able to see the elastic-cloud
API you defined:
kubectl --context kind-platform get crds | grep workshop
The above command will give an output similar to:
elastic-cloud.workshop.kratix.io 2023-01-01T12:00:00Z
Experience requesting a Resource
Now that your platform is offering ECK, you will switch hats for a minute and become a user who will request an on-demand elastic-cloud Resource.
To do this, you will create a Kubernetes resource that matches the API defined in the Promise.
Create a file at the root of your promise
directory, called
resource-request.yaml
:
touch resource-request.yaml
Then copy the following contents into that new file:
apiVersion: workshop.kratix.io/v1alpha1
kind: elastic-cloud
metadata:
name: example
namespace: default
spec:
enableDataCollection: true
🤔 How can this make a request to the Promise API?
This resource apiVersion
and kind
match the properties configured on the
CRD.
In addition, the spec
is validated against the OpenAPIv3 Schema. In this
case the spec only contains the single property that was exposed in the API
schema.
You can now make a request for an Elastic Cloud Resource:
kubectl --context $PLATFORM apply --filename resource-request.yaml
You should get the following output:
elastic-cloud.workshop.kratix.io/example created
For this particular Promise, the resources it creates are also Kubernetes Resources. So just as you did for Promises, you can list all the elastic cloud Resources using the following command:
kubectl --context $PLATFORM get elastic-cloud
The above command will give an output similar to:
NAME STATUS
example Pending
You may see the status as Resource requested
.
In future steps of this workshop you will get a chance to edit and update this output.
At this stage you can explore the API validation if you would like.
For example, you can try to:
- remove the
enableDataCollection
field - setting the
enableDataCollection
field to a non-boolean value - introducing an addition field that you did not define in your CRD
These are just a few examples of simple validations that are being applied to each user request to the API.
Summary
You have delivered your first Kratix Promise!
However, while Kratix has received and accepted the request, there's no defined provisioning process for your Promise just yet. That means Elastic Stack will not be delivered until you codify the provisioning process.
That is not to say that what you just did is useless! The current Promise is, in a way, very similar to platforms based on tickets: a resource is requested, which can notify a platform team to go and manually create the service, updating the request once the service is instantiated.
In the next section, you will build on top of this Promise and actually deliver your first Elastic Stack on-demand.
To recap the steps you took:
- ✅ Identify a service to provide on demand
- ✅ Design a user experience for requesting the service
- ✅ Define an API to match the user experience
- ✅ Install a Kratix Promise with that API definition
- ✅ Request an on-demand Resource from a Promise
Clean up environment
Before moving on, please remove the ECK Promise from your cluster.
To delete all the Promises:
kubectl --context $PLATFORM delete promises --all
🎉 Congratulations
✅ Your Promise has an API.
👉🏾 Next you will deliver a service on each user request.