DEV Community

Cover image for simplifying Kubernetes with cyclops Templates
Ashish Kumar
Ashish Kumar

Posted on • Edited on

simplifying Kubernetes with cyclops Templates

what is cyclops?

cyclops is a tool that simplifies Kubernetes for developers,IT teams and newcomers with no experience in Kubernetes. Cyclops has a easy to UI interface which translates to yaml manifests files abstracting away the complexity from developers.

let's start

to work with cyclops you first need a kubernetes cluster ,welcome minikube easiest way to get a working local cluster.
I got mine ready make sure to install it in your machine first

start your cluster

minikube start
Enter fullscreen mode Exit fullscreen mode

go to your dashboard

minikube dashboard
Enter fullscreen mode Exit fullscreen mode

you would see something like this
Image description

now let's install cyclops in our cluster

below command will add cyclops-ctrl and cyclops-ui to our cluster make sure you have cyctl installed checkout cyclops docs otherwise you can use kubectl

cyctl init 
Enter fullscreen mode Exit fullscreen mode

now to access the UI service you need to expose it outside our cluster,run the below command

cyctl serve
Enter fullscreen mode Exit fullscreen mode

you would see the cyclops-ctrl and cyclops-ui pods running in your cluster

Image description

let's create our own template from scratch.

In this blog I will be creating a dragonflyDb(a fasy memory datastore) template and then we will deploy it using cyclops.

1.create a new folder

dragonfly
│   Chart.yaml
│   values.schema.json
│   values.yaml
│
└───templates
        deployment.yaml
        statefulset.yaml
        service.yaml
        _helpers.tpl
        _pod.tpl
Enter fullscreen mode Exit fullscreen mode

let's write some code now :0

  • Chart.yaml chart.yaml has contains basic info and data about dragonfly helm chart.
apiVersion: v2
name: dragonfly
description: "a modern superfast in-memory datastore, fully compatible with Redis and Memcached APIs"
version: 0.0.0
Enter fullscreen mode Exit fullscreen mode
  • values.yaml this file specifies default configuration for dragonflDB that will be used by helm
# default configuration for dragonfly

# no of dragonfly replicas to deploy
replicaCount: 1

image:
  repository: docker.dragonflydb.io/dragonflydb/dragonfly



# default resource requirements, you can change it according to your needs.
resources: 
  requests:
    # min cpu millisecs
    cpu:  100m
    # min memory for dragonfly deployment
    memory: 128Mi

  limits:
    #max cpu millisecs for dragonfly deployment
    cpu: 1000m
    # max memory limit for dragonfly deployment 
    memory: 512Mi



service:

  type: ClusterIP
  # Load balancer static ip to use when service type is set to LoadBalancer
  loadBalancerIP: ""
  # Dragonfly default service port
  port: 6379

  metrics:
  # name for the metrics port
    portName: metrics
  # serviceType for the metrics service
    serviceType: ClusterIP


serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template
  name: ""



storage:
  # you can set this to true for production use cases,if you want dragonfly to persist data.
  enabled: false 

  storageClassName: ""
  #  Volume size to request for the PVC,default set to 128Mb
  requests: 128Mi



# extra  arguments to pass to dragonfly binary
extraArgs:[]



securityContexts:
  capabilities:
    drop:
      - ALL
  # sets file system permission as read-only for dragonfly container
  readOnlyRootFilesystem: true
  # sets the dragon container to run as non-root 
  runAsNonRoot: true
  # run dragonfly container with user-Id as 1000
  runAsUser: 1000



# the below fields serviceMonitor and promtheiumsRule requires you have prometheus operator running in your cluster.

serviceMonitor:
  # If true, a ServiceMonitor CRD is created for a prometheus operator
  enabled: false
  # namespace in which to deploy the ServiceMonitor CR. defaults to the application namespace
  namespace: ""
  # additional labels to apply to the metrics
  labels: {}
  # additional annotations to apply to the metrics
  annotations: {}
  # scrape interval
  interval: 10s
  # scrape timeout
  scrapeTimeout: 10s

prometheusRule:
  # Deploy a PrometheusRule
  enabled: false
  # PrometheusRule.Spec
  # https://awesome-prometheus-alerts.grep.to/rules
  spec: []


tls:
  # -enable TLS
  enabled: false
  # use cert-manager to automatically create the certificate
  createCerts: false
  # duration or ttl of the validity of the created certificate
  duration: 87600h0m0s
  issuer:
    # - cert-manager issuer kind. Usually Issuer or ClusterIssuer
    kind: ClusterIssuer
    # -name of the referenced issuer
    name: selfsigned
    # - group of the referenced issuer
    # if you are using an external issuer, change this to that issuer group.
    group: cert-manager.io
  # - use TLS certificates from existing secret
  existing_secret: ""
  # - TLS certificate
  cert: ""
  # cert: |
  #   -----BEGIN CERTIFICATE-----
  #   MIIDazCCAlOgAwIBAgIUfV3ygaaVW3+yzK5Dq6Aw6TsZ494wDQYJKoZIhvcNAQEL
  #   ...
  #   BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
  #   zJAL4hNw4Tr6E52fqdmX
  #   -----END CERTIFICATE-----
  # -- TLS private key
  key: ""




passwordFromSecret:
  enable: false
  existingSecret:
    name: ""
    key: ""



# for more fields check dragon fly helm chart
Enter fullscreen mode Exit fullscreen mode
  • deployment.yaml this file generates the deployment resource for dragonflDB
{{- if not .Values.storage.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "dragonfly.fullname" .}}
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "dragonfly.labels" . | nindent 4 }}


spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "dragonfly.selectorLabels" . | nindent 6 }}

  template:
    metadata:
      annotations:
        {{- if and (.Values.tls.enabled) (not .Values.tls.existing_secret) }}
        checksum/tls-secret: {{ include (print $.Template.BasePath "/tls-secret.yaml") . | sha256sum }}
        {{- end }}
        {{- with .Values.podAnnotations }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
      labels:
        {{- include "dragonfly.selectorLabels" . | nindent 8 }}
    spec:
      {{- include "dragonfly.pod" . | trim | nindent 6 }}
{{- end }}

Enter fullscreen mode Exit fullscreen mode
  • service.yaml This file creates a resource of kind service for dragonflyDB.
apiVersion: v1
kind: Service
metadata:
  name: {{ include "dragonfly.fullname" . }}
  namespace: {{ .Release.Namespace }}
  {{- with .Values.service.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
  labels:
    {{- with .Values.service.labels }}
    {{- toYaml . | nindent 4 }}
    {{- end }}
    {{- include "dragonfly.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  {{- if and (eq .Values.service.type "LoadBalancer") (ne .Values.service.loadBalancerIP "") }}
  loadBalancerIP: {{ .Values.service.loadBalancerIP }}
  {{- end }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: dragonfly
      protocol: TCP
      name: dragonfly
  selector:
    {{- include "dragonfly.selectorLabels" . | nindent 4 }}
Enter fullscreen mode Exit fullscreen mode
  • _helpers.tpl this file define template vaiables to be used by helm to generate yaml manifests.
{{/*
Expand the name of the chart.
*/}}
{{- define "dragonfly.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "dragonfly.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "dragonfly.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "dragonfly.labels" -}}
helm.sh/chart: {{ include "dragonfly.chart" . }}
{{ include "dragonfly.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "dragonfly.selectorLabels" -}}
app.kubernetes.io/name: {{ include "dragonfly.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "dragonfly.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "dragonfly.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

Enter fullscreen mode Exit fullscreen mode
  • _pod.tpl this files defines template variables for pvc resource and are used when storage and tls are enabled for dragonfly.
{{- define "dragonfly.volumemounts" -}}
{{- if or (.Values.storage.enabled) (.Values.extraVolumeMounts) (.Values.tls.enabled) }}
volumeMounts:
  {{- if .Values.storage.enabled }}
  - mountPath: /data
    name: "{{ .Release.Name }}-data"
  {{- end }}
  {{- if and .Values.tls .Values.tls.enabled }}
  - mountPath: /etc/dragonfly/tls
    name: tls
  {{- end }}
  {{- with .Values.extraVolumeMounts }}
    {{- toYaml . | trim | nindent 2 }}
  {{- end }}
{{- end }}
{{- end }}

{{- define "dragonfly.pod" -}}
{{- if ne .Values.priorityClassName "" }}
priorityClassName: {{ .Values.priorityClassName }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
  {{- toYaml . | trim | nindent 2 -}}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
  {{- toYaml . | trim | nindent 2 -}}
{{- end }}
{{- with .Values.affinity }}
affinity:
  {{- toYaml . | trim | nindent 2 -}}
{{- end }}
serviceAccountName: {{ include "dragonfly.serviceAccountName" . }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
  {{- toYaml . | trim | nindent 2 }}
{{- end }}
{{- with .Values.podSecurityContext }}
securityContext:
  {{- toYaml . | trim | nindent 2 }}
{{- end }}
{{- with .Values.initContainers }}
initContainers:
  {{- if eq (typeOf .) "string" }}
  {{- tpl . $ | trim | nindent 2 }}
  {{- else }}
  {{- toYaml . | trim | nindent 2 }}
  {{- end }}
{{- end }}
containers:
  {{- with .Values.extraContainers }}
  {{- if eq (typeOf .) "string" -}}
  {{- tpl . $ | trim | nindent 2 }}
  {{- else }}
  {{- toYaml . | trim | nindent 2 }}
  {{- end }}
  {{- end }}
  - name: {{ .Chart.Name }}
    {{- with .Values.securityContext }}
    securityContext:
      {{- toYaml . | trim | nindent 6 }}
    {{- end }}
    image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
    imagePullPolicy: {{ .Values.image.pullPolicy }}
    ports:
      - name: dragonfly
        containerPort: 6379
        protocol: TCP
    {{- with .Values.probes }}
    {{- toYaml . | trim | nindent 4 }}
    {{- end }}
    {{- with .Values.command }}
    command:
      {{- toYaml . | trim | nindent 6 }}
    {{- end }}
    args:
      - "--alsologtostderr"
    {{- with .Values.extraArgs }}
      {{- toYaml . | trim | nindent 6 }}
    {{- end }}
    {{- if .Values.tls.enabled }}
      - "--tls"
      - "--tls_cert_file=/etc/dragonfly/tls/tls.crt"
      - "--tls_key_file=/etc/dragonfly/tls/tls.key"
    {{- end }}
    {{- with .Values.resources }}
    resources:
      {{- toYaml . | trim | nindent 6 }}
    {{- end }}
    {{- include "dragonfly.volumemounts" . | trim | nindent 4 }}
    {{- if .Values.passwordFromSecret.enable }}
    {{- $appVersion := .Chart.AppVersion | trimPrefix "v" }}
    {{- $imageTag := .Values.image.tag | trimPrefix "v" }}
    {{- $effectiveVersion := $appVersion }}
    {{- if and $imageTag (ne $imageTag "") }}
      {{- $effectiveVersion = $imageTag }}
    {{- end }}
    env:
    {{- if semverCompare ">=1.14.0" $effectiveVersion }}
      - name: DFLY_requirepass
    {{- else }}
      - name: DFLY_PASSWORD
    {{- end }}
        valueFrom:
          secretKeyRef:
            name: {{ .Values.passwordFromSecret.existingSecret.name }}
            key: {{ .Values.passwordFromSecret.existingSecret.key }}
    {{- end }}

{{- if or (.Values.tls.enabled) (.Values.extraVolumes) }}
volumes:
{{- if and .Values.tls .Values.tls.enabled }}
  {{- if .Values.tls.existing_secret }}
  - name: tls
    secret:
      secretName: {{ .Values.tls.existing_secret }}
  {{- else if .Values.tls.createCerts }}
  - name: tls
    secret:
      secretName: '{{ include "dragonfly.fullname" . }}-server-tls'
  {{- else }}
  - name: tls
    secret:
      secretName: {{ include "dragonfly.fullname" . }}-tls
  {{- end }}
{{- end }}
{{- with .Values.extraVolumes }}
  {{- toYaml . | trim | nindent 2 }}
{{- end }}
{{- end }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode
  • statefulset.yaml If we want dragonflyDB to persist data kubernetes will create a statefulset using statefulset.yaml.
{{- if .Values.storage.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ include "dragonfly.fullname" . }}
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "dragonfly.labels" . | nindent 4 }}
spec:
  serviceName: {{ .Release.Name }}
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "dragonfly.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        {{- if and (.Values.tls.enabled) (not .Values.tls.existing_secret) }}
        checksum/tls-secret: {{ include (print $.Template.BasePath "/tls-secret.yaml") . | sha256sum }}
        {{- end }}
        {{- with .Values.podAnnotations }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
      labels:
        {{- include "dragonfly.selectorLabels" . | nindent 8 }}
    spec:
      {{- include "dragonfly.pod" . | trim | nindent 6 }}
  volumeClaimTemplates:
    - metadata:
        name: "{{ .Release.Name }}-data"
      spec:
        accessModes: [ "ReadWriteOnce" ]
        storageClassName: {{ .Values.storage.storageClassName }}
        resources:
          requests:
            storage: {{ .Values.storage.requests }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode
  • schema.values.json this is the file needed to render the ui in cyclops,it defines which fields will be shown to user in UI,values are edited for values.yaml,this file is super necessary for cyclops UI.
{

    "properties": {
        "replicaCount":{
            "description": "number of replicas of dragonflyDb",
            "type":"integer"
        },
        "storage":{

            "description": "storage for dragonflydb,default is 128Mi",
            "type":"object",
            "properties": {
                "enabled":{
                    "description": "enables storage if set to true ,default is false",
                    "type":"boolean"

                },
                "requests":{
                    "description":"requests storage,default 128Mi",
                    "type":"string"
            }
            }

        },
        "serviceMonitor":{
            "description": "enables service monitor ,requires kube-promtheus installed in your cluster",
            "type":"boolean"

        },
        "resources":{

            "description": "allocate cpu and ram for dragonfly",
            "title": "resources",
            "type":"object",
            "properties": {

                "requests":{

                    "title": "Request resoures",
                    "type":"object",
                    "properties": {


                        "cpu":{

                            "description":"request to allocates  cpu resource,default is 100m",
                            "type":"string"

                    },
                        "memory":{

                            "description":"requests memory for dragonfly, default 128Mi(Megabyte)",
                            "type":"string"
                    }
                    }
                },
                "limits":{

                    "title":"resource limits",
                    "description": "sets upper limits for resources",
                    "type":"object",
                    "properties": {

                        "cpu":{
                            "description":"cpu resource limit,deafult is 1000m",
                            "type":"string"
                        },
                        "memory":{
                            "description": "memory limit for dragonfly,default 512Mi",
                            "type":"string"
                        }
                    }


                }

            }

        }


    },
    "order":[

        "replicaCount",
        "storage",
        "resources",
        "serviceMonitor"

    ],
    "required": [
        "replicaCount",
        "storage",
        "resources",
        "serviceMonitor"
    ]



}
Enter fullscreen mode Exit fullscreen mode

I know this might be hard to digest if you are new to helm.
let me explain you the big picture :-
our root contains three files values.yaml
values.schema.json Chart.yaml

let's deal with these three first

  • Chart.yaml: this ones very basic it just contains info about our helm chart.

  • values.yaml :now this one is important,it is from this file helm generates yaml manifests,this file contains default value of fields like resources,service etc.it is basically this file your editing using Cyclops UI then helm uses these values to create your applications, if you dont pass any values in cyclops UI the default values of fields insidevalues.yaml will be used to generate manifests.

  • values.schema.json : cyclops needs this file to render UI,this doesn't come with your helm chart you need to create it for cyclops so that it can render UI for your template,in this file you can specify which fields from values.yamlyou want to expose through UI,the user will be able to edit these fields in cyclops UI which in turn will pass updated values.yaml to helm and then helm will create your application.

I hope it's a bit clear now.

underneath cyclops uses helm chart under to to render UI refer to dragonflyDb Helm Chart

Now lets deploy dragon fly using our cyclops template that we just created,first we need to add our templates to cyclops:

  1. add template to cyclops,go to templates in cyclopes ui.

Image description

2.click on add template,enter name,repo and path for your template and click ok.

Image description

3.now lets deploy dragonflyDB from our template.

Image description

4.enjoy :)

contributing to cyclops

I learned a lot while contributing to cyclops in this quira quest,I got to know the basics of kubernetes and managed to solve two issues in cyclops repo,This was my first contribution to a major opensource project the maintainers are very welcoming to newcomers.

Quira cyclops quest feats

-solved the update command issue PR

-solved printer-column issue PR

-added dragonflyDB template to cyclops/templates repo PR

HUNGRY TO CONTRIBUTE MORE

Top comments (4)

Collapse
 
whimsicalbison profile image
Jack

Thanks for writing this article!

Was wondering what workflow most people use with Cyclops. When you generate a manifest, is it stored anywhere, can resources be automatically generated? Or is the idea that you take the generated manifest and copy and paste it somewhere in a repository?

Collapse
 
whimsicalbison profile image
Jack

Asked ChatGPT... and apparently Cyclops doesn't save the generated templates for you. You can use cyclops generate > manifest.yaml to store it in a file. You could then use a CI/CD tool to automatically apply the template, but Cyclops doesn't have a built in way of saving manifests or automatically applying them (at least... according to ChatGPT, please let me know if this is wrong!)

Collapse
 
ashish_kumar_4dc2a7ee7693 profile image
Ashish Kumar

Hi jack ,Thanks for your queries.

let me clarify ,Cyclops uses Helm charts behind the scenes and provides you a UI to configure it, So yeah it's not storing manifests it's just using helm. It comes with some prebuilt templates Redis,Postgres ...also you can create your own but your would need a helm chart for that application and make cyclops refrence that template repo.

The main aim of cyclops tool is to simplify deploying applications to k8s for developers ,it's basically providing a UI abstraction for people who don't want to deal with inner working of k8s. This tool is still under development,Cyclops aims to be the standard way of deploying applications on k8s.

hope I answered your query.

Collapse
 
whimsicalbison profile image
Jack

Thanks for the additional information. Definitely see where this tool could be helpful