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
go to your dashboard
minikube dashboard
you would see something like this
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
now to access the UI service you need to expose it outside our cluster,run the below command
cyctl serve
you would see the cyclops-ctrl and cyclops-ui pods running in your cluster
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
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
- 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
- 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 }}
- 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 }}
- _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 }}
- _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 }}
- 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 }}
- 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"
]
}
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 fromvalues.yaml
you want to expose through UI,the user will be able to edit these fields in cyclops UI which in turn will pass updatedvalues.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:
- add template to cyclops,go to templates in cyclopes ui.
2.click on add template,enter name,repo and path for your template and click ok.
3.now lets deploy dragonflyDB from our template.
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)
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?
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!)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.
Thanks for the additional information. Definitely see where this tool could be helpful