From 990dfca2684352511ebe445ac4b5d809fce9c365 Mon Sep 17 00:00:00 2001 From: Mark Needham Date: Sat, 9 Sep 2017 21:57:20 +0100 Subject: [PATCH] Add Neo4j chart (#1559) --- incubator/neo4j/Chart.yaml | 13 +++ incubator/neo4j/README.md | 75 +++++++++++++++++ incubator/neo4j/templates/NOTES.txt | 9 +++ incubator/neo4j/templates/core-dns.yaml | 19 +++++ .../neo4j/templates/core-statefulset.yaml | 80 +++++++++++++++++++ .../templates/readreplicas-deployment.yaml | 48 +++++++++++ incubator/neo4j/templates/tests/_helpers.tpl | 34 ++++++++ .../neo4j/templates/tests/test-config.yaml | 72 +++++++++++++++++ .../templates/tests/test-neo4j-cluster.yaml | 39 +++++++++ incubator/neo4j/values.yaml | 53 ++++++++++++ 10 files changed, 442 insertions(+) create mode 100644 incubator/neo4j/Chart.yaml create mode 100644 incubator/neo4j/README.md create mode 100644 incubator/neo4j/templates/NOTES.txt create mode 100644 incubator/neo4j/templates/core-dns.yaml create mode 100644 incubator/neo4j/templates/core-statefulset.yaml create mode 100644 incubator/neo4j/templates/readreplicas-deployment.yaml create mode 100644 incubator/neo4j/templates/tests/_helpers.tpl create mode 100644 incubator/neo4j/templates/tests/test-config.yaml create mode 100644 incubator/neo4j/templates/tests/test-neo4j-cluster.yaml create mode 100644 incubator/neo4j/values.yaml diff --git a/incubator/neo4j/Chart.yaml b/incubator/neo4j/Chart.yaml new file mode 100644 index 0000000000..a14075c300 --- /dev/null +++ b/incubator/neo4j/Chart.yaml @@ -0,0 +1,13 @@ +name: neo4j +home: https://www.neo4j.com +version: 0.1.0 +appVersion: 3.2.3 +description: Neo4j is the world's leading graph database +icon: http://info.neo4j.com/rs/773-GON-065/images/neo4j_logo.png +sources: + - https://github.com/neo4j/neo4j + - https://github.com/neo4j/docker-neo4j + - https://github.com/mneedham/k8s-kubectl +maintainers: + - name: mneedham + email: mark.needham@neo4j.com diff --git a/incubator/neo4j/README.md b/incubator/neo4j/README.md new file mode 100644 index 0000000000..73cd0fb282 --- /dev/null +++ b/incubator/neo4j/README.md @@ -0,0 +1,75 @@ +# Neo4j + +[Neo4j](https://neo4j.com/) is a highly scalable native graph database that leverages data relationships as first-class entities, helping enterprises build intelligent applications to meet today’s evolving data challenges. + +## TL;DR; + +```bash +$ helm install incubator/neo4j +``` + +## Introduction + +This chart bootstraps a [Neo4j](https://github.com/neo4j/docker-neo4j) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Kubernetes 1.6+ with Beta APIs enabled +- PV provisioner support in the underlying infrastructure + +## Installing the Chart + +To install the chart with the release name `neo4j-helm`: + +```bash +$ helm install --name neo4j-helm incubator/neo4j +``` + +The command deploys Neo4j on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `neo4j-helm` deployment: + +```bash +$ helm delete neo4j-helm --purge +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Configuration + +The following tables lists the configurable parameters of the Neo4j chart and their default values. + +| Parameter | Description | Default | +|--------------------------------------|------------------------------------------------|----------------------------------------------------------| +| `image` | Neo4j image | `neo4j` | +| `imageTag` | Neo4j version | `{VERSION}` | +| `imagePullPolicy` | Image pull policy | `IfNotPresent` | +| `core.numberOfServers` | Number of machines in CORE mode | `3` | +| `core.persistentVolume.storageClass` | Storage class of backing PVC | `standard` (uses beta storage class annotation) | +| `core.persistentVolume.size` | Size of data volume | `10Gi` | +| `core.persistentVolume.mountPath` | Persistent Volume mount root path | `/data` | +| `core.persistentVolume.annotations` | Persistent Volume Claim annotations | `{}` | +| `readReplica.numberOfServers` | Number of machines in READ_REPLICA mode | `0` | +| `resources` | Resources required (e.g. CPU, memory) | `{}` | + +The above parameters map to the env variables defined in the [Neo4j docker image](https://github.com/neo4j/docker-neo4j). + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +$ helm install --name neo4j-helm --set core.numberOfServers=5,readReplica.numberOfServers=3 incubator/neo4j +``` + +The above command creates a cluster containing 5 core servers and 3 read replicas. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +$ helm install --name neo4j-helm -f values.yaml incubator/neo4j +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) diff --git a/incubator/neo4j/templates/NOTES.txt b/incubator/neo4j/templates/NOTES.txt new file mode 100644 index 0000000000..c9a2f151a6 --- /dev/null +++ b/incubator/neo4j/templates/NOTES.txt @@ -0,0 +1,9 @@ +You can connect to the cluster using the Cypher shell by running: + + kubectl run -it --rm cypher-shell \ + --image=neo4j:3.2.1-enterprise \ + --restart=Never \ + --namespace {{ .Release.Namespace }} \ + --command -- ./bin/cypher-shell -u neo4j -p neo4j --a {{ printf "%s-%s" .Release.Name .Values.name | trunc 56 }}.default.svc.cluster.local "call dbms.cluster.overview()" + +This will print out the addresses of the members of the cluster. diff --git a/incubator/neo4j/templates/core-dns.yaml b/incubator/neo4j/templates/core-dns.yaml new file mode 100644 index 0000000000..1c9a5b9a4e --- /dev/null +++ b/incubator/neo4j/templates/core-dns.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "neo4j.fullname" . }} + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ template "neo4j.name" . }} + component: core +spec: + clusterIP: None + ports: + - port: 7474 + targetPort: 7474 + selector: + app: {{ template "neo4j.name" . }} + release: {{ .Release.Name }} + component: core diff --git a/incubator/neo4j/templates/core-statefulset.yaml b/incubator/neo4j/templates/core-statefulset.yaml new file mode 100644 index 0000000000..a22777ed3e --- /dev/null +++ b/incubator/neo4j/templates/core-statefulset.yaml @@ -0,0 +1,80 @@ +apiVersion: "apps/v1beta1" +kind: StatefulSet +metadata: + name: "{{ template "neo4j.core.fullname" . }}" +spec: + serviceName: {{ template "neo4j.fullname" . }} + replicas: {{ .Values.core.numberOfServers }} + template: + metadata: + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app: {{ template "neo4j.name" . }} + component: core + spec: + containers: + - name: {{ template "neo4j.fullname" . }} + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + env: + - name: NEO4J_dbms_mode + value: CORE + - name: NUMBER_OF_CORES + value: "{{ .Values.core.numberOfServers }}" + - name: NEO4J_dbms_security_auth__enabled + value: "false" + - name: NEO4J_causal__clustering_discovery__type + value: DNS + - name: NEO4J_causal__clustering_initial__discovery__members + value: "{{ template "neo4j.fullname" . }}.default.svc.cluster.local:5000" + command: + - "/bin/bash" + - "-ecx" + - | + export NEO4J_dbms_connectors_default__advertised__address=$(hostname -f) + export NEO4J_causal__clustering_discovery__advertised__address=$(hostname -f):5000 + export NEO4J_causal__clustering_transaction__advertised__address=$(hostname -f):6000 + export NEO4J_causal__clustering_raft__advertised__address=$(hostname -f):7000 + exec /docker-entrypoint.sh "neo4j" + ports: + - containerPort: 5000 + name: discovery + - containerPort: 7000 + name: raft + - containerPort: 6000 + name: tx + - containerPort: 7474 + name: browser + - containerPort: 7687 + name: bolt + securityContext: + privileged: true + volumeMounts: + - name: datadir + mountPath: "{{ .Values.core.persistentVolume.mountPath }}" + subPath: "{{ .Values.core.persistentVolume.subPath }}" + resources: +{{ toYaml .Values.resources | indent 10 }} + + volumeClaimTemplates: + - metadata: + name: datadir + annotations: + {{- if .Values.core.persistentVolume.annotations }} +{{ toYaml .Values.core.persistentVolume.annotations | indent 12 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- if .Values.core.persistentVolume.storageClass }} + {{- if (eq "-" .Values.core.persistentVolume.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.core.persistentVolume.storageClass }}" + {{- end }} + {{- end }} + resources: + requests: + storage: "{{ .Values.core.persistentVolume.size }}" diff --git a/incubator/neo4j/templates/readreplicas-deployment.yaml b/incubator/neo4j/templates/readreplicas-deployment.yaml new file mode 100644 index 0000000000..b041f2d992 --- /dev/null +++ b/incubator/neo4j/templates/readreplicas-deployment.yaml @@ -0,0 +1,48 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: "{{ template "neo4j.replica.fullname" . }}" + labels: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "neo4j.name" . }} + component: replica +spec: + replicas: {{ .Values.readReplica.numberOfServers }} + template: + metadata: + labels: + app: {{ template "neo4j.name" . }} + release: {{ .Release.Name | quote }} + component: replica + spec: + containers: + - name: neo4j + image: "{{ .Values.image }}:{{ .Values.imageTag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + env: + - name: NEO4J_dbms_mode + value: READ_REPLICA + - name: NEO4J_dbms_security_auth__enabled + value: "false" + - name: NEO4J_causal__clustering_discovery__type + value: DNS + - name: NEO4J_causal__clustering_initial__discovery__members + value: "{{ template "neo4j.fullname" . }}.default.svc.cluster.local:5000" + command: + - "/bin/bash" + - "-ecx" + - | + export NEO4J_dbms_connectors_default__advertised__address=$(hostname -f) + export NEO4J_causal__clustering_transaction__advertised__address=$(hostname -f):6000 + exec /docker-entrypoint.sh "neo4j" + ports: + - containerPort: 7474 + name: browser + - containerPort: 7687 + name: bolt + - containerPort: 6000 + name: tx + securityContext: + privileged: true diff --git a/incubator/neo4j/templates/tests/_helpers.tpl b/incubator/neo4j/templates/tests/_helpers.tpl new file mode 100644 index 0000000000..54be875cac --- /dev/null +++ b/incubator/neo4j/templates/tests/_helpers.tpl @@ -0,0 +1,34 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "neo4j.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). +*/}} +{{- define "neo4j.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name for core servers. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "neo4j.core.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s-core" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name for read replica servers. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "neo4j.replica.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s-replica" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/incubator/neo4j/templates/tests/test-config.yaml b/incubator/neo4j/templates/tests/test-config.yaml new file mode 100644 index 0000000000..783916c5ef --- /dev/null +++ b/incubator/neo4j/templates/tests/test-config.yaml @@ -0,0 +1,72 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "neo4j.fullname" . }}-tests + labels: + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "neo4j.name" . }} +data: + run.sh: |- + @test "Testing Neo4j cluster has quorum" { + echo "checking if the cluster is up" + check_machine() { + name=$1 + end="$((SECONDS+90))" + while true; do + echo "testing http://$name:7474/db/data" + out=`wget http://$name:7474/db/data -O - 2>&1` + echo "out: $out" + wget http://$name:7474/db/data -O - 2>&1 + response_code=$? + echo "response: $response_code" + [[ "0" = "$response_code" ]] && break + [[ "${SECONDS}" -ge "${end}" ]] && exit 1 + sleep 1 + done + } + + for id in {0..2}; do + name="${STATEFULSET_NAME}-core-$id.${STATEFULSET_NAME}.default.svc.cluster.local" + echo "checking $name" + run check_machine $name + echo $status + echo $output + [ "$status" -eq 0 ] + done + } + + @test "Testing we can get the cluster role of each server" { + check_role() { + name=$1 + end="$((SECONDS+90))" + while true; do + echo "checking cluster role: $name" + + kubectl exec $name -- bin/cypher-shell "call dbms.cluster.role()" + response_code=$? + echo "response: $response_code" + [[ "0" = "$response_code" ]] && break + [[ "${SECONDS}" -ge "${end}" ]] && exit 1 + sleep 1 + done + } + + for id in {0..2}; do + name="${STATEFULSET_NAME}-core-$id" + echo "checking role of $name" + run check_role $name + echo $status + echo $output + [ "$status" -eq 0 ] + done + + # kill a machine and make sure it comes back again + machine_to_kill="${STATEFULSET_NAME}-core-0" + kubectl delete pod ${machine_to_kill} + run check_role ${machine_to_kill} + echo $status + echo $output + [ "$status" -eq 0 ] + } diff --git a/incubator/neo4j/templates/tests/test-neo4j-cluster.yaml b/incubator/neo4j/templates/tests/test-neo4j-cluster.yaml new file mode 100644 index 0000000000..71a394b4bb --- /dev/null +++ b/incubator/neo4j/templates/tests/test-neo4j-cluster.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ template "neo4j.fullname" . }}-service-test-{{ randAlphaNum 5 | lower }}" + labels: + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "neo4j.name" . }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: {{ .Release.Name }}-ui-test + image: {{ .Values.testImage }}:{{ .Values.testImageTag }} + env: + - name: "STATEFULSET_NAME" + value: "{{ template "neo4j.fullname" . }}" + command: ["/tools/bats/bats", "-t", "/tests/run.sh"] + volumeMounts: + - mountPath: /tests + name: tests + readOnly: true + - mountPath: /tools + name: tools + initContainers: + - name: test-framework + image: "dduportal/bats:0.4.0" + command: ["bash", "-c", "set -ex\ncp -R /usr/local/libexec/ /tools/bats/"] + volumeMounts: + - mountPath: "/tools" + name: tools + volumes: + - name: tests + configMap: + name: {{ template "neo4j.fullname" . }}-tests + - name: tools + emptyDir: {} + restartPolicy: Never diff --git a/incubator/neo4j/values.yaml b/incubator/neo4j/values.yaml new file mode 100644 index 0000000000..dc96f5ab5e --- /dev/null +++ b/incubator/neo4j/values.yaml @@ -0,0 +1,53 @@ +# Default values for Neo4j. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value + +name: "neo4j" + +# Specs for the Neo4j docker image +image: "neo4j" +imageTag: "3.2.3-enterprise" +imagePullPolicy: "IfNotPresent" + +# Specs for the images used for running tests against the Helm package +testImage: "markhneedham/k8s-kubectl" +testImageTag: "master" + +# Cores +core: + numberOfServers: 3 + persistentVolume: + + ## core server data Persistent Volume mount root path + ## + mountPath: /data + + ## core server data Persistent Volume size + ## + size: 10Gi + + ## core server data Persistent Volume Storage Class + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ##storageClass: "-" + + ## Subdirectory of core server data Persistent Volume to mount + ## Useful if the volume's root directory is not empty + ## + subPath: "" + +# Read Replicas +readReplica: + numberOfServers: 0 + +resources: {} +# limits: +# cpu: 100m +# memory: 512Mi +# requests: +# cpu: 100m +# memory: 512Mi