In a previous article, we discussed KUDO and the benefits of it when you want to create or manage Operators.
In this article we will focus on how to start to work with KUDO: Installation, using a predefined Operator and create your own one.
Installing KUDO
To install KUDO the first step is to install the CLI plugin in order to manage KUDO via CLI. Depending on your OS you can use a package manager like Brew or Krew, however installing the binary is a straightforward option to proceed.
You can download binary from the Kudo GitHub repo, going to the last release and checking the related assets to pick the right one for you.
In this example we are using a x86-64 Linux distribution and the related package is kudo_0.11.0-rc1_darwin_x86_64.tar.gz. After uncompressing the file we should copy the binary to a directory included in our path like /usr/local/bin
.
$ tar xvfz kudo_0.10.1_linux_x86_64.tar.gz $ sudo cp kubectl-kudo /usr/local/bin/
Following that, we would be able to use kubectl kudo
in our CLI to manage all our objects, including installing KUDO. The first step is to proceed with the installation doing a kubectl kudo init
To install KUDO automatically:
$ kubectl kudo init
To check the required actions prior to applying them you can also create a yaml file and apply it manually:
$ kubectl kudo init --dry-run -o=yaml > kudo.yaml
The installation process will create some CRD, ClusterRoleBinding, Namespace, ServiceAccount, StatefulSet and a Service in order to prepare the cluster to manage KUDO and the instance of KUDO controller. If everything runs fine, you will receive this output:
✅ installed crds ✅ installed service accounts and other requirements for controller to run ✅ installed kudo controller
Deploying a KUDO Operator
There is a list of predefined Operators ready to go. As an example, we will install Kafka in our cluster using the official KUDO Kafka Operator
Installation is simple. You only have to set the Operator’s name and the latest version will pulled (though you can include a specific version as per your requirement):
$ kubectl kudo install kafka --operator-version=1.2.0
An Operator, OperatorVersion and an Instance will be created:
operator.kudo.dev/v1beta1/kafka created operatorversion.kudo.dev/v1beta1/kafka-1.2.0 created instance.kudo.dev/v1beta1/kafka-instance created
With a kubectl get instances
we can see a new instance has been created:
$ kubectl get instances NAME AGE kafka-instance 93s
A kubectl get pods
command shows a single pod was created but it terminated with an error:
$ kubectl get pods NAME READY STATUS RESTARTS AGE kafka-instance-kafka-0 1/2 CrashLoopBackOff 4 2m50s
The reason is Kafka has dependencies with Zookeeper, so the deployment needs a Zookeper instance in order to start Kafka. Dependencies have to be solved manually but an automatic management of them is part of the KUDO’s roadmap.
To uninstall an existing KUDO Operator you can use a kubectl kudo uninstall --instance name_of_the_instance
:
$ kubectl kudo uninstall --instance kafka-instance instance.kudo.dev/v1beta1/kafka-instance deleted
You can proceed now to install sequentially both Operators. We will install Zookeeper using kubectl kudo install zookeeper
with the default version and later, the Kafka Operator:
$ kubectl kudo install zookeeper operator.kudo.dev/v1beta1/zookeeper created operatorversion.kudo.dev/v1beta1/zookeeper-0.3.0 created instance.kudo.dev/v1beta1/zookeeper-instance created $ kubectl kudo install kafka --operator-version=1.2.0 operator.kudo.dev/v1beta1/kafka created operatorversion.kudo.dev/v1beta1/kafka-1.2.0 created instance.kudo.dev/v1beta1/kafka-instance created
Note that both applications (Zookeeper and Kafka) are generating a Persistent Volume Claim (PVC) for every pod they are creating. You will need 6 available Persistent Volumes (PV) with at least 5Gi and ReadWriteOnce for access mode:
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE kafka-instance-datadir-kafka-instance-kafka-0 Bound volume1 5Gi RWO 20m kafka-instance-datadir-kafka-instance-kafka-1 Bound volume2 5Gi RWO 20m kafka-instance-datadir-kafka-instance-kafka-2 Bound volume3 5Gi RWO 20m zookeeper-instance-datadir-zookeeper-instance-zookeeper-0 Bound volume4 5Gi RWO 16m zookeeper-instance-datadir-zookeeper-instance-zookeeper-1 Bound volume5 5Gi RWO 16m zookeeper-instance-datadir-zookeeper-instance-zookeeper-2 Bound volume6 5Gi RWO 16m
If a PV has already been created on your cluster, you can deploy it using this YAML file (you will need to create the directory referenced in the path on all of your worker nodes and assign them the right permissions):
apiVersion: v1 kind: PersistentVolume metadata: name: volume1 labels: type: local spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce hostPath: path: "/volumes/volume1"
After successfully deploying both applications you will get a bunch of pods:
$ kubectl get pods NAME READY STATUS RESTARTS AGE kafka-instance-kafka-0 2/2 Running 0 25s kafka-instance-kafka-1 2/2 Running 0 25s kafka-instance-kafka-2 2/2 Running 0 25s zookeeper-instance-zookeeper-0 1/1 Running 0 4m54s zookeeper-instance-zookeeper-1 1/1 Running 0 4m54s zookeeper-instance-zookeeper-2 1/1 Running 0 4m54s
All of them being part of a StatefulSet as you can see running kubectl get statefulsets
:
$ kubectl get statefulsets NAME READY AGE kafka-instance-kafka 3/3 3m21s zookeeper-instance-zookeeper 3/3 7m49s
Creating your own Operator
Using predefined Operators is easy and very useful but sometimes we have the limitation of a specific list of available Operators. If we need to create our own Operator we can still use all KUDO functionalities.
A specific structure is required when we are creating our own Operator. First of all we will create a directory named as our Operator and we will set a couple of files: operator.yaml
and params.yaml
. The first one will be definition of the Operator; while the second one will contain parameters to be used by our tasks.
Additionally, we need to create a subdirectory called templates where we will create a YAML file for every defined task. Assuming an Operator with just one task named deploy, the file structure looks like:
├── operator.yaml ├── params.yaml └── templates └── deploy.yaml
To define an Operator we only need to declare a YAML file when we can include different plans and tasks. Every plan could include a bunch of phases and tasks to be executed sequentially or in parallel. This example includes just one plan with a single task named init. All tasks have to be defined in a separate YAML file into the directory templates. If we need a complex operational logic we can just add more plans, phases, steps and tasks according our needs.
Our operator.yaml
:
apiVersion: kudo.dev/v1beta1 name: "our-own-operator" operatorVersion: "1.0.0" appVersion: "3.0.1" kubernetesVersion: 1.17.0 maintainers: - name: Your name email: <name@domain.com> url: https://domain.com tasks: - name: init kind: Apply spec: resources: - deploy.yaml plans: deploy: strategy: serial phases: - name: main strategy: serial steps: - name: onlyone tasks: - init
The task has an associated YAML file where we defined all required actions. Here we are just creating a deployment using a certain number of replicas and an image, both defined in our params.yaml
file. As you can see the syntax to use a defined parameter is {{ .Params.name_of_your_param }}
Our deploy.yaml
(task init)
apiVersion: apps/v1 kind: Deployment metadata: name: our-own-operator spec: replicas: {{ .Params.replicas }} selector: matchLabels: app: our-own-operator template: metadata: labels: app: our-own-operator spec: containers: - name: our-own-operator image: {{ .Params.image }} imagePullPolicy: Always
Parameters are defined in our params.yaml
file and the syntax is pretty straightforward. Here we are defining the number of replicas (4) and the specific image we are going to use (httpd version 2.4.41). If any change is required on both values we only need to update the params file which will save the effort and time of modifying every file.
apiVersion: kudo.dev/v1beta1 parameters: - name: replicas description: Number of replicas that should be run as part of the deployment default: 4 - name: image description: Container image to be used default: "httpd:2.4.41"
To install our Operator we just need to run the same command kubectl kudo install
and specifying the directory instead of the Operator name:
$ kubectl kudo install ./our-own-operator/ operator.kudo.dev/v1beta1/our-own-operator created operatorversion.kudo.dev/v1beta1/our-own-operator-1.0.0 created instance.kudo.dev/v1beta1/our-own-operator-instance created
Finally the Operator will be running with all related replicas up (4 pods as defined):
$ kubectl get pods NAME READY STATUS RESTARTS AGE our-own-operator-588b647986-9kgf8 1/1 Running 0 3m48s our-own-operator-588b647986-kq2jf 1/1 Running 0 3m48s our-own-operator-588b647986-lhrpz 1/1 Running 0 3m48s our-own-operator-588b647986-mj9jt 1/1 Running 0 3m48s
Adding more steps and tasks will allow you to accomplish your application requirements like init, backup, testing or define the specific behavior until some changes.