Skip to content

Commit

Permalink
feat: add the elasticsearch helm chart for cluster-level ES
Browse files Browse the repository at this point in the history
  • Loading branch information
keithgg committed Feb 26, 2023
1 parent 4dacc06 commit b8e4cbe
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ infra-*/.terraform*
infra-*/secrets.auto.tfvars
my-notes
values.yaml
tutor-multi-chart/charts/*.tgz
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ always upgrading to the corresponding version before updating the chart. This is
in the management of CRDs by Helm.
If you already installed cert-manager by different means, make sure set `cert-manager.enabled: false` for this chart.


## Central Database/Monitoring/etc

In the future, this chart could also be made to install monitoring, databases, ElasticSearch, or other shared resources
Expand Down Expand Up @@ -141,7 +142,27 @@ of the box.

----------------

## Appendix A: how to uninstall this chart
## Configuration

### Multi-tenant Elasticsearch

Tutor creates an Elasticsearch pod as part of the Kubernetes deployment. Depending on the number of instances
Memory and CPU use can be lowered by running a central ES cluster instead of an ES pod for every instance.

To enable set `elasticsearch.enabled=true` in your `values.yaml` and deploy the chart.

For each instance you would like to enable this on, set the configuration values in the respective `config.yml`:

```yaml
K8S_HARMONY_ENABLE_SHARED_ELASTICSEARCH: true
RUN_ELASTICSEARCH: false
```
- And create the user on the cluster with `k8s harmony create-elasticsearch-user`.
- Rebuild your Open edX image `tutor images build openedx`.
- Finally, redeploy your changes: `tutor k8s start && tutor k8s init`.

## Appendix : how to uninstall this chart

Just run `helm uninstall --namespace tutor-multi tutor-multi` to uninstall this.

Expand Down
2 changes: 1 addition & 1 deletion infra-example/k8s-cluster/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ resource "digitalocean_kubernetes_cluster" "cluster" {

node_pool {
name = "${var.cluster_name}-nodes"
size = "s-2vcpu-4gb"
size = "s-4vcpu-8gb"
# At this size, at least 4 nodes are recommended to run 2 Open edX instances using the default Tutor images, because
# resources like MySQL/MongoDB are not shared.
min_nodes = 4
Expand Down
44 changes: 44 additions & 0 deletions tutor-contrib-multi-plugin/tutor_multi_k8s_plugin/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os

import click
from tutor import config as tutor_config
from tutor import env as tutor_env
from tutor.commands.k8s import K8sContext, kubectl_exec
from .elasticsearch import ElasticSearchAPI

@click.group(help="Commands and subcommands of the openedx-k8s-harmony.")
@click.pass_context
def harmony(context: click.Context) -> None:
context.obj = K8sContext(context.obj.root)


@click.command(help="Create or update Elasticsearch users")
@click.pass_obj
def create_elasticsearch_user(context: click.Context):
"""
Creates or updates the Elasticsearch user
"""
config = tutor_config.load(context.root)
namespace = config["K8S_HARMONY_NAMESPACE"]
api = ElasticSearchAPI(namespace)
username, password = config["ELASTICSEARCH_HTTP_AUTH"].split(":", 1)
role_name = f"{username}_role"

prefix = config["ELASTICSEARCH_INDEX_PREFIX"]
api.post(
f"_security/role/{role_name}",
{"indices": [{"names": [f"{prefix}*"], "privileges": ["all"]}]},
)

api.post(
f"_security/user/{username}",
{
"password": password,
"enabled": True,
"roles": [role_name],
"full_name": username,
},
)


harmony.add_command(create_elasticsearch_user)
42 changes: 42 additions & 0 deletions tutor-contrib-multi-plugin/tutor_multi_k8s_plugin/elasticsearch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import json
from tutor import utils

class ElasticSearchAPI:
def __init__(self, namespace):
self._command_base = [
"kubectl",
"exec",
"--stdin",
"--tty",
"--namespace",
namespace,
"elasticsearch-master-0",
"--",
"bash",
"-c",
]
self._curl_base = ["curl", "--insecure", "-u", "elastic:${ELASTIC_PASSWORD}"]

def run_command(self, curl_options):
response = utils.check_output(
*self._command_base, " ".join(self._curl_base + curl_options)
)
try:
return json.loads(response)
except (TypeError, ValueError) as e:
return response

def get(self, url):
return self.run_command(["-XGET", f"https://localhost:9200/{url}"])

def post(self, endpoint, data):
return self.run_command(
[
"-XPOST",
f"https://localhost:9200/{endpoint}",
"-d",
f"'{json.dumps(data)}'",
"-H",
'"Content-Type: application/json"',
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ metadata:
annotations:
cert-manager.io/cluster-issuer: tutor-multi-letsencrypt-global
spec:
ingressClassName: {{ MULTI_K8S_INGRESS_CLASS_NAME }}
ingressClassName: {{ K8S_HARMONY_INGRESS_CLASS_NAME }}
rules:
{%- for host in HOSTS if host is defined %}
- host: "{{ host }}"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% if K8S_HARMONY_ENABLE_SHARED_ELASTICSEARCH %}
ELASTICSEARCH_INDEX_PREFIX = "{{ELASTICSEARCH_INDEX_PREFIX}}"
ELASTIC_SEARCH_CONFIG = [{
"use_ssl": True,
"host": "elasticsearch-master.{{K8S_HARMONY_NAMESPACE}}.svc.cluster.local",
"verify_certs": False,
"port": 9200,
"http_auth": "{{ ELASTICSEARCH_HTTP_AUTH }}"
}]
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% if K8S_HARMONY_ENABLE_SHARED_ELASTICSEARCH %}
# This is needed otherwise the previously installed edx-search
# package doesn't get replaced. Once the below branch is merged
# upstream it will no longer be needed.
RUN pip uninstall -y edx-search
RUN pip install --upgrade git+https://github.com/open-craft/edx-search.git@keith/prefixed-index-names
{% endif %}
19 changes: 14 additions & 5 deletions tutor-contrib-multi-plugin/tutor_multi_k8s_plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pkg_resources

from tutor import hooks
from . import commands

from .__about__ import __version__

Expand All @@ -28,16 +29,21 @@
# We are using HTTPS
"ENABLE_HTTPS": True,
},
"unique": {
"ELASTICSEARCH_HTTP_AUTH": "{{K8S_NAMESPACE}}:{{ 24|random_string }}",
"ELASTICSEARCH_INDEX_PREFIX": "{{K8S_NAMESPACE}}-{{ 4|random_string }}",
}
}

# Load all configuration entries
hooks.Filters.CONFIG_DEFAULTS.add_items(
[
(f"MULTI_K8S_{key}", value)
for key, value in config.get("defaults", {}).items()
]
[(f"K8S_HARMONY_{key}", value) for key, value in config["defaults"].items()]
)

hooks.Filters.CONFIG_OVERRIDES.add_items(list(config.get("overrides", {}).items()))
hooks.Filters.CONFIG_OVERRIDES.add_items(list(config["overrides"].items()))
hooks.Filters.CONFIG_UNIQUE.add_items(
list(config["unique"].items())
)

# Load all patches from the "patches" folder
for path in glob(
Expand All @@ -48,3 +54,6 @@
):
with open(path, encoding="utf-8") as patch_file:
hooks.Filters.ENV_PATCHES.add_item((os.path.basename(path), patch_file.read()))


hooks.Filters.CLI_COMMANDS.add_item(commands.harmony)
8 changes: 7 additions & 1 deletion tutor-multi-chart/Chart.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@ dependencies:
- name: cert-manager
repository: https://charts.jetstack.io
version: v1.11.0
- name: traefik
repository: https://traefik.github.io/charts
version: 20.3.0
- name: elasticsearch
repository: https://helm.elastic.co
version: 7.17.3
digest: sha256:7e80a648180d0fe471a9f45d2a47edd5da8894ef544b562911fac2e11d7fd411
generated: "2023-02-02T14:28:19.400979073-04:00"
generated: "2023-02-02T14:28:19.400979073-04:00"
4 changes: 4 additions & 0 deletions tutor-multi-chart/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ dependencies:
version: "1.11.0"
repository: https://charts.jetstack.io
condition: cert-manager.enabled

- name: elasticsearch
version: "7.17.3"
repository: https://helm.elastic.co
20 changes: 20 additions & 0 deletions tutor-multi-chart/templates/elasticsearch/secrets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
{{- $ca := genCA "elasticca" 1825 }}
{{- $cert := genSignedCert "elasticsearch-master.{{ Release.Namespace }}.local" nil (list "elasticsearch-master.{{ Release.Namespace }}.local") 1825 $ca }}
apiVersion: v1
kind: Secret
metadata:
name: elasticsearch-certificates
type: Opaque
data:
"ca.crt": {{ $ca.Cert | b64enc | toYaml | indent 4}}
"tls.key": {{ $cert.Key | b64enc | toYaml | indent 4}}
"tls.crt": {{ print $cert.Cert $ca.Cert | b64enc | toYaml | indent 4}}
---
apiVersion: v1
kind: Secret
metadata:
name: elasticsearch-credentials
type: Opaque
data:
"password": {{ randAlphaNum 32 | b64enc | quote }}
48 changes: 48 additions & 0 deletions tutor-multi-chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,51 @@ cert-manager:
# Email address associated with the ACME account. Used to notify about expiring
# certificates.
email: ""

# Multi-tenant ElasticSearch
elasticsearch:
enabled: false

# Operators will need to add/update the following setting in each
# of their instances by running the commands:
# ```
# tutor config save --set K8S_HARMONY_ENABLE_SHARED_ELASTICSEARCH=true --set RUN_ELASTICSEARCH=false
# tutor harmony create-elasticsearch-user
# ```
# RUN_ELASTICSEARCH: false
# ELASTICSEARCH_PREFIX_INDEX: "username-"
# MULTI_K8S_USE_SHARED_ELASTICSEARCH: true
# ELASTICSEARCH_AUTH: "username:actual_password"

# The chart will handle certificate creation so that the CA can be used
# in other pods/namespaces.
createCert: false
# Authentication is only available in https
protocol: https

# This secret will contain the http certificates.
secretMounts:
- name: elasticsearch-certificates
secretName: elasticsearch-certificates
path: /usr/share/elasticsearch/config/certs
defaultMode: 0777

# The password for the elastic user is stored in this secret
extraEnvs:
- name: ELASTIC_PASSWORD
valueFrom:
secretKeyRef:
name: elasticsearch-credentials
key: password

esConfig:
"elasticsearch.yml": |
xpack.security.enabled: true
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.key: /usr/share/elasticsearch/config/certs/tls.key
xpack.security.http.ssl.certificate: /usr/share/elasticsearch/config/certs/tls.crt
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.key: /usr/share/elasticsearch/config/certs/tls.key
xpack.security.transport.ssl.certificate: /usr/share/elasticsearch/config/certs/tls.crt
xpack.security.transport.ssl.certificate_authorities: /usr/share/elasticsearch/config/certs/ca.crt
xpack.security.transport.ssl.verification_mode: certificate
3 changes: 3 additions & 0 deletions values-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ ingress-nginx:
cert-manager:
# Set your email address here so auto-generated HTTPS certs will work:
email: "[email protected]"

elasticsearch:
enabled: false

0 comments on commit b8e4cbe

Please sign in to comment.