diff --git a/incubator/cassandra/.helmignore b/incubator/cassandra/.helmignore new file mode 100644 index 0000000000..998fef7406 --- /dev/null +++ b/incubator/cassandra/.helmignore @@ -0,0 +1,16 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/incubator/cassandra/Chart.yaml b/incubator/cassandra/Chart.yaml new file mode 100644 index 0000000000..75ea05a6f9 --- /dev/null +++ b/incubator/cassandra/Chart.yaml @@ -0,0 +1,13 @@ +name: cassandra +version: 0.1.0 +description: Apache Cassandra is a free and open-source distributed database management system designed to handle large amounts of data across many commodity servers, providing high availability with no single point of failure. +icon: https://upload.wikimedia.org/wikipedia/commons/5/5e/Cassandra_logo.svg +keywords: +- cassandra +- database +- nosql +home: http://cassandra.apache.org +maintainers: +- name: Siri C + email: goonohc@gmail.com +engine: gotpl diff --git a/incubator/cassandra/README.md b/incubator/cassandra/README.md new file mode 100644 index 0000000000..2ccd3dea97 --- /dev/null +++ b/incubator/cassandra/README.md @@ -0,0 +1,112 @@ +# Cassandra +A Cassandra Chart for Kubernetes + +## Install Chart +To install the Cassandra Chart into your Kubernetes cluster (This Chart requires persistent volume by default, you may need to create a storage class before install chart. To create storage class, see [Persist data](#persist_data) section) + +```bash +helm install --namespace "cassandra" -n "cassandra" incubator/cassandra +``` + +After installation succuess, you can get a status of Chart + +```bash +helm status "cassandra" +``` + +If you want to delete your Chart, use this command +```bash +helm delete --purge "cassandra" +``` + +## Persist data +You need to create `StorageClass` before able to persist data in persistent volume. +To create a `StorageClass` on Google Cloud, run the following + +```bash +kubectl create -f sample/create-storage-gce.yaml +``` + +And set the following values in `values.yaml` + +```yaml +persistence: + enabled: true +``` + +If you want to create a `StorageClass` on other platform, please see documentation here [https://kubernetes.io/docs/user-guide/persistent-volumes/](https://kubernetes.io/docs/user-guide/persistent-volumes/) + + +## Install Chart with specific cluster size +By default, this Chart will create a cassandra with 3 nodes. If you want to change the cluster size during installation, you can use `--set config.cluster_size={value}` argument. Or edit `values.yaml` + +For example: +Set cluster size to 5 + +```bash +helm install --namespace "cassandra" -n "cassandra" --set config.cluster_size=5 incubator/cassandra/ +``` + +## Install Chart with specific resource size +By default, this Chart will create a cassandra with CPU 2 vCPU and 4Gi of memery which is suitable for development environment. +If you want to use this Chart for production, I would recommend to update the CPU to 4 vCPU and 16Gi. Also increase size of `max_heap_size` and `heap_new_size`. +To update the settings, edit `values.yaml` + +## Install Chart with specific node +Sometime you may need to deploy your cassandra to specific nodes to allocate resources. You can use node selector by edit `nodes.enabled=true` in `values.yaml` +For example, you have 6 vms in node pools and you want to deploy cassandra to node which labeled as `cloud.google.com/gke-nodepool: pool-db` + +Set the following values in `values.yaml` + +```yaml +nodes: + enabled: true + selector: + nodeSelector: + cloud.google.com/gke-nodepool: pool-db +``` + +## Scale cassandra +When you want to change the cluser size of your cassandra, you can use the helm upgrade command. + +```bash +helm upgrade --set config.cluster_size=5 cassandra incubator/cassandra +``` + +## Get cassandra status +You can get your cassandra cluster status by running the command + +```bash +kubectl exec -it --namespace cassandra $(kubectl get pods --namespace cassandra -l app=cassandra-cassandra -o jsonpath='{.items[0].metadata.name}') nodetool status +``` + +Output +```bash +Datacenter: asia-east1 +====================== +Status=Up/Down +|/ State=Normal/Leaving/Joining/Moving +-- Address Load Tokens Owns (effective) Host ID Rack +UN 10.8.1.11 108.45 KiB 256 66.1% 410cc9da-8993-4dc2-9026-1dd381874c54 a +UN 10.8.4.12 84.08 KiB 256 68.7% 96e159e1-ef94-406e-a0be-e58fbd32a830 c +UN 10.8.3.6 103.07 KiB 256 65.2% 1a42b953-8728-4139-b070-b855b8fff326 b +``` + +## Benchmark +You can use [cassandra-stress](https://docs.datastax.com/en/cassandra/3.0/cassandra/tools/toolsCStress.html) tool to run the benchmark on the cluster by the following command + +```bash +kubectl exec -it --namespace cassandra $(kubectl get pods --namespace cassandra -l app=cassandra-cassandra -o jsonpath='{.items[0].metadata.name}') cassandra-stress +``` + +Example of `cassandra-stress` argument + - Run both read and write with ration 9:1 + - Operator total 1 million keys with uniform distribution + - Use QUORUM for read/write + - Generate 50 threads + - Generate result in graph + - Use NetworkTopologyStrategy with replica factor 2 + +```bash +cassandra-stress mixed ratio\(write=1,read=9\) n=1000000 cl=QUORUM -pop dist=UNIFORM\(1..1000000\) -mode native cql3 -rate threads=50 -log file=~/mixed_autorate_r9w1_1M.log -graph file=test2.html title=test revision=test2 -schema "replication(strategy=NetworkTopologyStrategy, factor=2)" +``` \ No newline at end of file diff --git a/incubator/cassandra/sample/create-storage-gce.yaml b/incubator/cassandra/sample/create-storage-gce.yaml new file mode 100644 index 0000000000..b5960ae482 --- /dev/null +++ b/incubator/cassandra/sample/create-storage-gce.yaml @@ -0,0 +1,7 @@ +kind: StorageClass +apiVersion: storage.k8s.io/v1beta1 +metadata: + name: generic +provisioner: kubernetes.io/gce-pd +parameters: + type: pd-ssd diff --git a/incubator/cassandra/templates/NOTES.txt b/incubator/cassandra/templates/NOTES.txt new file mode 100644 index 0000000000..5c4ae91e33 --- /dev/null +++ b/incubator/cassandra/templates/NOTES.txt @@ -0,0 +1,27 @@ +Cassandra CQL can be accessed via port {{ .Values.config.ports.cql }} on the following DNS name from within your cluster: +Cassandra Thrift can be accessed via port {{ .Values.config.ports.thrift }} on the following DNS name from within your cluster: + +If you want to connect to the remote instance with your local Cassandra CQL cli. To forward the API port to localhost:9042 run the following: +- kubectl port-forward --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l app={{ template "fullname" . }} -o jsonpath='{ .items[0].metadata.name }') 9042:{{ .Values.config.ports.cql }} + +If you want to connect to the Cassandra CQL run the following: +{{- if contains "NodePort" .Values.service.type }} +- export CQL_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }}) +- export CQL_HOST=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") +- cqlsh $CQL_HOST $CQL_PORT + +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "fullname" . }}' +- export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') +- echo cqlsh $SERVICE_IP +{{- else if contains "ClusterIP" .Values.service.type }} +- kubectl port-forward --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l "app={{ template "fullname" . }}" -o jsonpath="{.items[0].metadata.name}") 9042:{{ .Values.config.ports.cql }} + echo cqlsh 127.0.0.1 9042 +{{- end }} + +You can also see the cluster status by run the following: +- kubectl exec -it --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l app={{ template "fullname" . }} -o jsonpath='{.items[0].metadata.name}') nodetool status + +To tail the logs for the Cassandra pod run the following: +- kubectl logs -f --namespace {{ .Release.Namespace }} $(kubectl get pods --namespace {{ .Release.Namespace }} -l app={{ template "fullname" . }} -o jsonpath='{ .items[0].metadata.name }') diff --git a/incubator/cassandra/templates/_helpers.tpl b/incubator/cassandra/templates/_helpers.tpl new file mode 100644 index 0000000000..0a4d57d2c5 --- /dev/null +++ b/incubator/cassandra/templates/_helpers.tpl @@ -0,0 +1,17 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 21 -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 21 chars because Kubernetes name fields are limited to 24 (by the DNS naming spec) +and Statefulset will append -xx at the end of name. +*/}} +{{- define "fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 21 -}} +{{- end -}} diff --git a/incubator/cassandra/templates/cassandra-statefulset.yaml b/incubator/cassandra/templates/cassandra-statefulset.yaml new file mode 100644 index 0000000000..f4dc45e816 --- /dev/null +++ b/incubator/cassandra/templates/cassandra-statefulset.yaml @@ -0,0 +1,128 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + clusterIP: None + type: {{ .Values.service.type }} + ports: + - name: intra + port: 7000 + targetPort: 7000 + - name: tls + port: 7001 + targetPort: 7001 + - name: jmx + port: 7199 + targetPort: 7199 + - name: cql + port: {{ default 9042 .Values.config.ports.cql }} + targetPort: {{ default 9042 .Values.config.ports.cql }} + - name: thrift + port: {{ default 9160 .Values.config.ports.thrift }} + targetPort: {{ default 9160 .Values.config.ports.thrift }} + selector: + app: {{ template "fullname" . }} +--- +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: {{ template "fullname" . }} + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + serviceName: {{ template "fullname" . }} + replicas: {{ .Values.config.cluster_size }} + template: + metadata: + labels: + app: {{ template "fullname" . }} + spec: +{{- if .Values.selector }} +{{ toYaml .Values.selector | indent 6 }} +{{- end }} + containers: + - name: {{ template "fullname" . }} + image: "{{ .Values.image.repo }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + resources: +{{ toYaml .Values.resources | indent 10 }} + env: + {{- $seed_size := default 1 .Values.config.seed_size | int -}} + {{- $global := . }} + - name: CASSANDRA_SEEDS + value: "{{- range $i, $e := until $seed_size }}{{ template "fullname" $global }}-{{ $i }}.{{ template "fullname" $global }}.{{ $global.Release.Namespace }}.svc.cluster.local,{{- end }}" + - name: MAX_HEAP_SIZE + value: {{ default "8192M" .Values.config.max_heap_size | quote }} + - name: HEAP_NEWSIZE + value: {{ default "200M" .Values.config.heap_new_size | quote }} + - name: CASSANDRA_ENDPOINT_SNITCH + value: {{ default "SimpleSnitch" .Values.config.endpoint_snitch | quote }} + - name: CASSANDRA_CLUSTER_NAME + value: {{ default "Cassandra" .Values.config.cluster_name | quote }} + - name: CASSANDRA_DC + value: {{ default "DC1" .Values.config.dc_name | quote }} + - name: CASSANDRA_RACK + value: {{ default "RAC1" .Values.config.rack_name | quote }} + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + livenessProbe: + exec: + command: [ "/bin/sh", "-c", "nodetool status | grep -E \"^UN\\s+${POD_IP}\"" ] + initialDelaySeconds: 90 + periodSeconds: 30 + readinessProbe: + exec: + command: [ "/bin/sh", "-c", "nodetool status | grep -E \"^UN\\s+${POD_IP}\"" ] + initialDelaySeconds: 90 + periodSeconds: 30 + ports: + - name: intra + containerPort: 7000 + - name: tls + containerPort: 7001 + - name: jmx + containerPort: 7199 + - name: cql + containerPort: {{ default 9042 .Values.config.ports.cql }} + - name: thrift + containerPort: {{ default 9160 .Values.config.ports.thrift }} + volumeMounts: + - name: data + {{- if .Values.persistence.enabled }} + mountPath: /var/lib/cassandra + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + annotations: + {{- if .Values.persistence.storageClass }} + volume.beta.kubernetes.io/storage-class: {{ .Values.persistence.storageClass | quote }} + {{- else }} + volume.alpha.kubernetes.io/storage-class: default + {{- end }} + spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- end }} diff --git a/incubator/cassandra/values.yaml b/incubator/cassandra/values.yaml new file mode 100644 index 0000000000..42c88bea7d --- /dev/null +++ b/incubator/cassandra/values.yaml @@ -0,0 +1,58 @@ +## Cassandra image version +## ref: https://hub.docker.com/r/library/cassandra/ +image: + repo: "cassandra" + tag: "3" + pullPolicy: IfNotPresent + +## Specify a service type +## ref: http://kubernetes.io/docs/user-guide/services/ +service: + type: ClusterIP + +## Persist data to a persitent volume +persistence: + enabled: true + ## If defined, volume.beta.kubernetes.io/storage-class: + ## Default: volume.alpha.kubernetes.io/storage-class: default + # storageClass: generic + accessMode: ReadWriteOnce + size: 10Gi + +## Configure resource requests and limits +## ref: http://kubernetes.io/docs/user-guide/compute-resources/ +## Minimum memory for development is 4GB and 2 CPU cores +## Minimum memory for production is 8GB and 4 CPU cores +## ref: http://docs.datastax.com/en/archived/cassandra/2.0/cassandra/architecture/architecturePlanningHardware_c.html +resources: + requests: + memory: 4Gi + cpu: 2 + limits: + memory: 4Gi + cpu: 2 + +## Change cassandra configuration paramaters below: +## ref: http://docs.datastax.com/en/cassandra/3.0/cassandra/configuration/configCassandra_yaml.html +## Recommended max heap size is 1/2 of system memory +## Recommeneed heap new size is 1/4 of max heap size +## ref: http://docs.datastax.com/en/cassandra/3.0/cassandra/operations/opsTuneJVM.html +config: + cluster_name: cassandra + cluster_size: 3 + seed_size: 2 + num_tokens: 256 + dc_name: DC1 + rack_name: RAC1 + endpoint_snitch: SimpleSnitch + max_heap_size: 2048M + heap_new_size: 512M + ports: + cql: 9042 + thrift: 9160 + +## Configure node selector. Edit code below for adding selector to pods +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +# selector: + # nodeSelector: + # cloud.google.com/gke-nodepool: pool-db \ No newline at end of file