diff --git a/charts/rstudio-workbench/Chart.lock b/charts/rstudio-workbench/Chart.lock new file mode 100644 index 00000000..349ac0d5 --- /dev/null +++ b/charts/rstudio-workbench/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: rstudio-library + repository: file://../rstudio-library + version: 0.1.13-rc01 +digest: sha256:a730e6eebdb0313c93ffbeabfcee19e785fca64482daf794345d8a59d9a38c67 +generated: "2021-08-03T10:36:57.881257-04:00" diff --git a/charts/rstudio-workbench/Chart.yaml b/charts/rstudio-workbench/Chart.yaml new file mode 100644 index 00000000..b266a8e2 --- /dev/null +++ b/charts/rstudio-workbench/Chart.yaml @@ -0,0 +1,14 @@ +name: rstudio-workbench +description: Kubernetes deployment for RStudio Workbench +version: 0.4.0-rc09 +apiVersion: v2 +appVersion: 1.4.1717-3 +icon: https://rstudio.com/wp-content/uploads/2018/10/RStudio-Logo-Flat.png +maintainers: + - name: sol-eng + email: docker@rstudio.com + url: https://github.com/sol-eng +dependencies: + - name: rstudio-library + version: 0.1.13-rc01 + repository: file://../rstudio-library diff --git a/charts/rstudio-workbench/LICENSE.md b/charts/rstudio-workbench/LICENSE.md new file mode 100644 index 00000000..27a72c80 --- /dev/null +++ b/charts/rstudio-workbench/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2021 RStudio PBC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/charts/rstudio-workbench/Makefile b/charts/rstudio-workbench/Makefile new file mode 100644 index 00000000..58c53def --- /dev/null +++ b/charts/rstudio-workbench/Makefile @@ -0,0 +1,14 @@ +lint: + helm lint --strict --set service.name=example --set service.version=0.0.1 -f ./ci/complex-values.yaml . + helm lint --strict --set service.name=example --set service.version=0.0.1 -f ./ci/simple-values.yaml . + helm lint --strict --set service.name=example --set service.version=0.0.1 -f ./ci/empty-values.yaml . + helm lint --strict --set service.name=example --set service.version=0.0.1 -f ./ci/overrides-values.yaml . + +template: + helm template -f ci/simple-values.yaml . + +template-complex: + helm template -f ci/complex-values.yaml . + +template-debug: + helm template -f ci/simple-values.yaml --debug . diff --git a/charts/rstudio-workbench/NEWS.md b/charts/rstudio-workbench/NEWS.md new file mode 100644 index 00000000..6c8d089e --- /dev/null +++ b/charts/rstudio-workbench/NEWS.md @@ -0,0 +1,184 @@ +# 0.4.0 + +- Breaking: Licensing configuration now uses a `license` section. For example, + `license: my-key` should be changed to + ```yaml + license: + key: my-key + ``` +- Added support for floating licenses and license files. +- BREAKING: defaults have changed for `config.server.launcher\.kubernetes\.profiles\.conf`. + - To avoid the breaking change, add the defaults to your explicitly enumerated values + - See why this happened and an alternative forward-looking pattern below + - The previous defaults: +```yaml +config: + server: + launcher.kubernetes.profiles.conf: + "*": + default-container-image: rstudio/r-session-complete:bionic-1.4.1106-5 + container-images: rstudio/r-session-complete:bionic-1.4.1106-5 + allow-unknown-images: 1 +``` + +- BREAKING: we now automatically mount session configuration into the session pod + - This adds default `job-json-overrides` using the mechanism above + - This can be disabled by setting `session.defaultConfigMount=false` + - This is useful for things like `repos.conf`, `rsession.conf` (default Connect server, etc.), etc. + +- Switch to using the `rstudio-library` chart for configuration generation + - This enables putting verbatim files in place if that is preferred to values-interpolation (converting values into a config file dynamically by the chart) + - i.e. passing a string to the configuration value will short-circuit configuration generation +```yaml +config: + server: + some-config-file: | + interpret-verbatim-please +``` + +- Update appVersion to 1.4.1717-3 + +- Add a new `config.profiles` option for configuring profiles files more naturally. + - This will only be used if the `launcher.kubernetes.profiles.conf` key is not in `config.server` (testing for key + duplication is tricky in helm, so we pick the most common key) + - Before, we would have something like this in `values.yaml`: +```yaml +jobJsonOverridesFiles: + some.json: + "text" + other.json: + - an + - array +config: + server: + launcher.kubernetes.profiles.conf: + "*": + job-json-overrides: '"some/target:some.json","other/target:other.json"' + container-images: "one-image:tag,two-image:tag" +``` +- Now, we can do something like the following. A bit more verbose, but much easier to read and understand: +```yaml +config: + profiles: + launcher.kubernetes.profiles.conf: + "*": + job-json-overrides: + - target: "some/target" + json: "text" + name: some + - target: "other/target" + json: + - an + - array + name: other + container-images: + - "one-image:tag" + - "two-image:tag" +``` + +- Moreover, job-json-overrides defined under `config.profiles` now have inheritance within the chart. That is, `*` + job-json-overrides are appended to everyone else's configuration. Documentation and possible extension of this pattern + to container images, etc. to follow. + +- Now hiding the rstudio-workbench container's configuration files under `/etc/rstudio` as we are mounting + them in different directories as defined by the `XDG_CONFIG_DIRS` environment variable. This is to prevent confusion + that can occur when someone edits `/etc/rstudio` configuration files and then sees no changes after reloading the + server configuration. + +- When specifying a `server` for floating licensing, the RSW chart will now automatically be configured to set + `server-licensing-type=remote` in the `rserver.conf` configuration file. + +# 0.3.7 + +- Make `secure-cookie-key` and `launcher.pem` autogeneration static + - This means that the auto-generated values will persist across helm upgrades + - It is still safest to define these values yourself + +# 0.3.6 + +- Fix small reference issue in the prestart.bash script + +# 0.3.5 + +- Decouple securityContext values from the main RSW container and the sidecar container + +# 0.3.4 + +- remove "privileged: true", which is not necessary for rstudio-workbench server or sessions +- Add ingress as an option +- Add annotations to deployment so that the pods roll when config changes +- Switch the "secret" configurations to being an actual Secret + +# 0.3.3 + +- Bump `load-balancer-manager` again (to `2.2`) +- Allow customization of load-balancer-manager env vars + +# 0.3.2 + +- Fix a bug in the `load-balancer-manager` (`sidecar` container) + - The helm chart (as a result of previous changes) no longer defines an `app` label, but an `app.kubernetes.io/name` label. + - update the selector, make error handling better, etc. This requires version 2.0 of the load-balancer-manager + +# 0.3.1 + +- allow `global.secureCookieKey` as an option along with `secureCookieKey` +- ensure that no empty `launcher.pub` file is generated by default +- default image.tag to Chart.AppVersion + +# 0.3.0 +- BREAKING: changed `rstudio` container `command` and `args` to tell `tini` how to supervise processes and run a differently named prestart script. Also made `/usr/local/bin/startup.sh` script execution a part of the `args`. + +# 0.2.2 + +- Update Workbench version to 1.4.1106-5 +- Update docs + +# 0.2.1 + +- rename to `rstudio-workbench` corresponding to upcoming `rstudio-server-pro` rebranding +- fix bug that was creating a test user by default +- add other licensing options (via `server`, `file`, and `secret` values) + +# 0.2.0 + +- Change naming convention + - Fix issues with namespacing + - However, this will damage backwards compatibility, particularly for PVCs if using `sharedStorage.create = true` + - If you need to migrate data, set `replicas: 0`, upgrade, and then copy the data to the new PVC + - Alternatively, you can set `fullnameOverride: "previous-release-name"` to force backwards compatibility + - Finally, deployment selectors have changed, so you will need to delete the current deployment manually, then put back with `helm upgrade --install` + - Use `helm diff upgrade` to ensure things are working as you expect before upgrading + +# 0.0.8 + +- add `jobJsonOverridesFiles` value option + +# 0.0.7 + +- Made HA functional + +# 0.0.5 + +- BREAKING: move storage\* values to a sharedStorage map +- Add homeStorage +- Add logging.conf + +# 0.0.4 + +- Add a secret configmap for pem and pub keys + +# 0.0.3 + +- BREAKING: Restructure the image values object +- Add image.pullPolicy +- Switch to image.repository and image.tag from image +- Allow customizing pod command and args + +# 0.0.2 + +- Add database.conf and notifications.conf + +# 0.0.1 + +- Initial pass! diff --git a/charts/rstudio-workbench/README.md b/charts/rstudio-workbench/README.md index b7cb4445..6ef7ef57 100644 --- a/charts/rstudio-workbench/README.md +++ b/charts/rstudio-workbench/README.md @@ -33,19 +33,19 @@ This chart requires the following in order to function: * A license key, license file, or address of a running license server. See the `license` configuration below. * A Kubernetes [PersistentVolume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) that contains the home directory for users. - * If `homeStorage.create` is set, a PVC that relies on the default storage class will be created to generate the PersistentVolume. - Most Kubernetes environments do not have a default storage class that you can use with `ReadWriteMany` access mode out-of-the-box. - In this case, we recommend you disable `homeStorage.create` and create your own `PersistentVolume` and `PersistentVolumeClaim`, then mount them + * If `homeStorage.create` is set, a PVC that relies on the default storage class will be created to generate the PersistentVolume. + Most Kubernetes environments do not have a default storage class that you can use with `ReadWriteMany` access mode out-of-the-box. + In this case, we recommend you disable `homeStorage.create` and create your own `PersistentVolume` and `PersistentVolumeClaim`, then mount them into the container by specifying the `pod.volumes` and `pod.volumeMounts` parameters. * If you cannot use a `PersistentVolume` to properly mount your users' home directories, you'll need to mount your data in the container by using a regular [Kubernetes Volume](https://kubernetes.io/docs/concepts/storage/volumes/#nfs), specified in `pod.volumes` and `pod.volumeMounts`. - * If you cannot use a `Volume` to mount the directories, you'll need to manually mount them during container startup with a mechanism similar to what + * If you cannot use a `Volume` to mount the directories, you'll need to manually mount them during container startup with a mechanism similar to what is described below for joining to auth domains. * If not using `homeStorage.create`, you'll need to configure `config.serverDcf.launcher-mounts` to ensure that the correct mounts are used when users create new sessions. * If using load balancing (by setting `replicas > 1`), you will need similar storage defined for `sharedStorage` to store shared project configuration. * A method to join the deployed `rstudio-workbench` container to your auth domain. The default `rstudio/rstudio-server-pro` image does not contain a way to join domains. We recommend creating your own Docker image that derives from this base image to provide domain joining that fits your needs. Your image can then use a process supervisor - like [supervisord](http://supervisord.org/) to run multiple processes: in the most common case, `rstudio-server`, `rstudio-launcher`, and `sssd`. See + like [supervisord](http://supervisord.org/) to run multiple processes: in the most common case, `rstudio-server`, `rstudio-launcher`, and `sssd`. See [here](https://github.com/rstudio/sol-eng-demo-server/tree/main/helper/workbench) for an example of this. ## Recommended Configuration @@ -53,7 +53,7 @@ This chart requires the following in order to function: In addition to the above required configuration, we recommend setting the following to ensure a reliable deployment: * Set the `launcherPem` value to ensure that it stays the same between releases. - This will ensure that users can continue to properly connect to older sessions even after a redeployment of the chart. See the + This will ensure that users can continue to properly connect to older sessions even after a redeployment of the chart. See the [RSW Admin Guide](https://docs.rstudio.com/ide/server-pro/job-launcher.html#authentication) for details on generating the file. * Set the `global.secureCookieKey` so that user authentication continues to work between deployments. A valid value can be obtained by simply running the `uuid` command. diff --git a/charts/rstudio-workbench/ci/complex-values.yaml b/charts/rstudio-workbench/ci/complex-values.yaml new file mode 100644 index 00000000..e0b83c24 --- /dev/null +++ b/charts/rstudio-workbench/ci/complex-values.yaml @@ -0,0 +1,126 @@ +image: + repository: rstudio/rstudio-server-pro + tag: daily +license: + key: test +service: + nodePort: 31878 + type: NodePort + annotations: + key: value +replicas: 2 + +pod: + env: + - name: TEST_ENV_WORKS + value: "true" + volumeMounts: + - name: key + mountPath: /tmp/somepath + volumes: + - name: key + emptyDir: {} + annotations: + testannotation2: three + sidecar: + - name: test + image: "busybox" + imagePullPolicy: "IfNotPresent" + +xdgConfigDirs: '/tmp/base/' +xdgConfigDirsExtra: + - '/tmp/one/' + - '/tmp/two/' + +rbac.create: true +serviceAccountName: rstudio-server-job-launcher + +sharedStorage: + create: true + path: "/var/lib/awesome" + storageClassName: nfs + requests: + storage: "200Gi" + +homeStorage: + create: true + path: "/mnt/home" + storageClassName: nfs + requests: + storage: "500Gi" + +jobJsonOverridesFiles: + test: + one: two + three: four + array: + - one + - two: + - three + - 4 + - false + two: + three: four + +loadBalancer: + env: + - name: FAIL_ON_ERROR + value: "false" + +config: + session: + repos.conf: + RSPM: https://packagemanager.rstudio.com/cran/__linux__/bionic/latest + CRAN: https://packagemanager.rstudio.com/cran/__linux__/bionic/latest + rsession.conf: {} + notifications.conf: {} + r-versions: + - Label: test + Path: /opt/R/3.6.3 + - Label: other + Path: /opt/R/4.0.2 + secret: + "database.conf": {} + server: + rserver.conf: + server-health-check-enabled: 1 + admin-enabled: 1 + www-port: 8787 + server-project-sharing: 1 + launcher-address: 127.0.0.1 + launcher-port: 5559 + launcher-sessions-enabled: 1 + launcher-default-cluster: Kubernetes + launcher-sessions-callback-address: http://127.0.0.1:8787 + launcher.conf: + server: + address: 127.0.0.1 + port: 5559 + server-user: rstudio-server + admin-group: rstudio-server + authorization-enabled: 1 + thread-pool-size: 4 + enable-debug-logging: 1 + cluster: + name: Kubernetes + type: Kubernetes + "jupyter.conf": + jupyter-exe: /opt/python/3.6.5/bin/jupyter + notebooks-enabled: 1 + labs-enabled: 1 + default-session-cluster: Kubernetes + logging.conf: {} + serverDcf: + launcher-mounts: + - MountType: KubernetesPersistentVolumeClaim + MountPath: /home + ClaimName: rstudio-server-home-storage + launcher-env: + - JobType: session + Environment: + TEST: 1 + TEST2: 2 + - JobType: adhoc + Environment: + TESTA: A + TESTB: B diff --git a/charts/rstudio-workbench/ci/empty-values.yaml b/charts/rstudio-workbench/ci/empty-values.yaml new file mode 100644 index 00000000..e69de29b diff --git a/charts/rstudio-workbench/ci/license-file-secret-values.yaml b/charts/rstudio-workbench/ci/license-file-secret-values.yaml new file mode 100644 index 00000000..a5a90feb --- /dev/null +++ b/charts/rstudio-workbench/ci/license-file-secret-values.yaml @@ -0,0 +1,6 @@ +license: + file: + secret: some-secret + secretKey: my-license.lic + mountPath: "/etc/license/license.lic" + mountSubPath: "my-license.lic" diff --git a/charts/rstudio-workbench/ci/license-file-values.yaml b/charts/rstudio-workbench/ci/license-file-values.yaml new file mode 100644 index 00000000..cec022ab --- /dev/null +++ b/charts/rstudio-workbench/ci/license-file-values.yaml @@ -0,0 +1,4 @@ +license: + file: + contents: | + some-license-file diff --git a/charts/rstudio-workbench/ci/license-server-values.yaml b/charts/rstudio-workbench/ci/license-server-values.yaml new file mode 100644 index 00000000..915aa5ba --- /dev/null +++ b/charts/rstudio-workbench/ci/license-server-values.yaml @@ -0,0 +1,2 @@ +license: + server: http://my-license-server.example.com:9999 diff --git a/charts/rstudio-workbench/ci/license-values.yaml b/charts/rstudio-workbench/ci/license-values.yaml new file mode 100644 index 00000000..95dbda2a --- /dev/null +++ b/charts/rstudio-workbench/ci/license-values.yaml @@ -0,0 +1,2 @@ +license: + key: test-license diff --git a/charts/rstudio-workbench/ci/overrides-values-new.yaml b/charts/rstudio-workbench/ci/overrides-values-new.yaml new file mode 100644 index 00000000..c2cd6a08 --- /dev/null +++ b/charts/rstudio-workbench/ci/overrides-values-new.yaml @@ -0,0 +1,36 @@ +license: + key: real +replicas: 1 +rbac: + create: true +homeStorage: + create: true +userCreate: true + +config: + profiles: + launcher.kubernetes.profiles.conf: + "*": + container-images: + - some-image:tag + - another-image:tag + job-json-overrides: + - target: "/spec/template/spec/volumes/-" + name: volumes + json: + configMap: + defaultMode: 0755 + name: session-config + items: + - path: startup.sh + key: startup.sh + name: session-config + - target: "/spec/template/spec/containers/0/volumeMounts/-" + name: volumeMounts + json: + mountPath: /usr/local/bin/startup.sh + name: session-config + subPath: startup.sh + - target: "/spec/template/spec/containers/0/command/0" + name: entrypoint + json: "/usr/local/bin/startup.sh" diff --git a/charts/rstudio-workbench/ci/overrides-values.yaml b/charts/rstudio-workbench/ci/overrides-values.yaml new file mode 100644 index 00000000..43a2b7ce --- /dev/null +++ b/charts/rstudio-workbench/ci/overrides-values.yaml @@ -0,0 +1,29 @@ +license: + key: real +replicas: 1 +rbac: + create: true +homeStorage: + create: true +userCreate: true +jobJsonOverridesFiles: + entrypoint.json: "/usr/local/bin/startup.sh" + volumes.json: + configMap: + defaultMode: 0755 + name: session-config + items: + - path: startup.sh + key: startup.sh + name: session-config + volumeMounts.json: + mountPath: /usr/local/bin/startup.sh + name: session-config + subPath: startup.sh + +config: + server: + launcher.kubernetes.profiles.conf: + "*": + job-json-overrides: '"/spec/template/spec/volumes/-":"/mnt/job-json-overrides/volumes.json","/spec/template/spec/containers/0/volumeMounts/-":"/mnt/job-json-overrides/volumeMounts.json","/spec/template/spec/containers/0/command/0":"/mnt/job-json-overrides/entrypoint.json"' + # job-json-overrides: '"/spec/template/spec/volumes/-":"/mnt/job-json-overrides/volumes.json","/spec/template/spec/containers/0/volumeMounts/-":"/mnt/job-json-overrides/volumeMounts.json"' diff --git a/charts/rstudio-workbench/ci/simple-profiles-values.yaml b/charts/rstudio-workbench/ci/simple-profiles-values.yaml new file mode 100644 index 00000000..be06a15b --- /dev/null +++ b/charts/rstudio-workbench/ci/simple-profiles-values.yaml @@ -0,0 +1,19 @@ +license: + key: real +replicas: 1 +rbac: + create: true +homeStorage: + create: true +userCreate: true + +securityContext: + privileged: false + +config: + profiles: + launcher.kubernetes.profiles.conf: + "*": + allow-unknown-images: 0 + "cole": + allow-unknown-images: 1 diff --git a/charts/rstudio-workbench/ci/simple-values.yaml b/charts/rstudio-workbench/ci/simple-values.yaml new file mode 100644 index 00000000..aa189c04 --- /dev/null +++ b/charts/rstudio-workbench/ci/simple-values.yaml @@ -0,0 +1,11 @@ +license: + key: real +replicas: 1 +rbac: + create: true +homeStorage: + create: true +userCreate: true + +securityContext: + privileged: false diff --git a/charts/rstudio-workbench/prestart.bash b/charts/rstudio-workbench/prestart.bash new file mode 100644 index 00000000..44aff387 --- /dev/null +++ b/charts/rstudio-workbench/prestart.bash @@ -0,0 +1,122 @@ +#!/bin/bash +set -o errexit +set -o pipefail + +main() { + local startup_script="${1:-/usr/local/bin/startup.sh}" + local dyn_dir='/mnt/dynamic/rstudio' + + local cacert='/var/run/secrets/kubernetes.io/serviceaccount/ca.crt' + local k8s_url="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}" + local launcher_k8s_conf="${dyn_dir}/launcher.kubernetes.conf" + local launcher_pem='/mnt/secret-configmap/rstudio/launcher.pem' + local launcher_pub="${dyn_dir}/launcher.pub" + local launcher_ns="${RSTUDIO_LAUNCHER_NAMESPACE:-rstudio}" + local lb_conf='/mnt/load-balancer/rstudio/load-balancer' + + _logf 'Loading service account token' + local sa_token + sa_token="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" + + _logf 'Loading service account ca.crt' + local ca_string + ca_string="$(tr -d '\n' <"${cacert}" | base64 | tr -d '\n')" + + _logf 'Ensuring %s exists' "${dyn_dir}" + mkdir -p "${dyn_dir}" + + if [[ "${PRESTART_LOAD_BALANCER_CONFIGURATION}" == enabled ]]; then + _logf 'Generating %s' "${lb_conf}" + cat >"${lb_conf}" <&1 | _indent + chmod -v 600 "${launcher_pub}" 2>&1 | _indent + else + _logf 'Ensuring %s does not exist' "${launcher_pub}" + rm -vf "${launcher_pub}" 2>&1 | _indent + fi + + _logf 'Checking kubernetes health via %s' "${k8s_url}" + curl -fsSL \ + -H "Authorization: Bearer ${sa_token}" \ + --cacert "${cacert}" \ + "${k8s_url}/healthz" 2>&1 | _indent + printf '\n' + + _logf 'Generating %s' "${launcher_k8s_conf}" + cat >"${launcher_k8s_conf}" <&1 | _indent + mkdir -p /usr/local/share/ca-certificates/Kubernetes + cp -v \ + ${dyn_dir}/k8s-cert \ + /usr/local/share/ca-certificates/Kubernetes/cert-Kubernetes.crt 2>&1 | _indent + + _logf 'Updating CA certificates' + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + update-ca-certificates 2>&1 | _indent + + _logf 'Preparing dirs' + mkdir -p \ + /var/lib/rstudio-server/monitor/log \ + /var/lib/rstudio-launcher/Local \ + /var/lib/rstudio-launcher/Kubernetes + chown -v -R \ + rstudio-server:rstudio-server \ + /var/lib/rstudio-server \ + /var/lib/rstudio-launcher 2>&1 | _indent + + _writeEtcRstudioReadme + + _logf 'Replacing process with %s' "${startup_script}" + exec "${startup_script}" +} + +_logf() { + local msg="${1}" + shift + local now + now="$(date -u +%Y-%m-%dT%H:%M:%S)" + local format_string + format_string="$(printf '#----> prestart.bash %s: %s' "${now}" "${msg}")\\n" + # shellcheck disable=SC2059 + printf "${format_string}" "${@}" +} + +_indent() { + sed -u 's/^/ /' +} + +_writeEtcRstudioReadme() { + _logf 'Writing README to empty /etc/rstudio directory' + (cat <<$HERE$ +The contents of this configuration directory have been moved to other directories +in order to facilitate running in Kubernetes. The directories are specified via +the XDG_CONFIG_DIRS environment variable defined in the Helm chart. The currently +defined directories are: + +$(echo "$XDG_CONFIG_DIRS" | sed 's/:/\n/g') +$HERE$ + ) > /etc/rstudio/README +} + +main "${@}" diff --git a/charts/rstudio-workbench/templates/NOTES.txt b/charts/rstudio-workbench/templates/NOTES.txt new file mode 100644 index 00000000..5e77b491 --- /dev/null +++ b/charts/rstudio-workbench/templates/NOTES.txt @@ -0,0 +1,40 @@ + +{{ include "rstudio-workbench.fullname" . }} successfully deployed to namespace {{ $.Release.Namespace }} + +{{- if eq .Values.launcherPem "" }} + +NOTE: Using auto-generated value for "launcher.pem" + - We recommend making this value persistent by setting `.Values.launcherPem` + - If the value changes, sessions started before the change will not be accessible + - You can get the current value with: +``` +kubectl -n {{ $.Release.Namespace }} get secret {{ include "rstudio-workbench.fullname" . }}-secret --template='{{print "{{" }}index .data "launcher.pem" | base64decode {{print "}}" }}' +``` +{{- end }} +{{- if eq (default .Values.secureCookieKey .Values.global.secureCookieKey) "" }} + +NOTE: Using an auto-generated value for "secure-cookie-key" + - We recommend making this value persistent by setting `.Values.global.secureCookieKey` + - If the value changes, authenticated sessions will be invalidated (all users will be logged out) and some old sessions will not be accessible + - You can get the current value with: +``` +kubectl -n {{ $.Release.Namespace }} get secret {{ include "rstudio-workbench.fullname" . }}-secret --template='{{print "{{" }}index .data "secure-cookie-key" | base64decode{{ print "}}" }}' +``` +{{- end }} + +{{- if hasKey .Values.config.server "launcher.kubernetes.profiles.conf" }} + +NOTE: `.Values.config.server.launcher\.kubernetes\.profiles\.conf` is superseded + - Instead, we recommend using `.Values.config.profiles.launcher\.kubernetes\.profiles\.conf + - Please note, `job-json-overrides` behaves differently for this new location. See NEWS.md for details. + +{{- end }} + +{{- if hasKey (get .Values.config.server "rserver.conf") "server-license-type" }} + +NOTE: `.Values.config.server.rserver\.conf.server-license-type` is configured manually. Normally, we automatically configure this value: + - When `license.server` is set, we set `server-license-type` to `remote`. + - Otherwise, `server-license-type` uses the default of `local`. +Please consider removing this configuration value. + +{{- end }} diff --git a/charts/rstudio-workbench/templates/_helpers.tpl b/charts/rstudio-workbench/templates/_helpers.tpl new file mode 100644 index 00000000..e9065e6e --- /dev/null +++ b/charts/rstudio-workbench/templates/_helpers.tpl @@ -0,0 +1,139 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "rstudio-workbench.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 "rstudio-workbench.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 "rstudio-workbench.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "rstudio-workbench.labels" -}} +helm.sh/chart: {{ include "rstudio-workbench.chart" . }} +{{ include "rstudio-workbench.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "rstudio-workbench.selectorLabels" -}} +app.kubernetes.io/name: {{ include "rstudio-workbench.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* + Defines the default launcherMounts (if not defined) + Precedence: + - If .Values.config.serverDcf.launcher-mounts is defined, use that verbatim + - If not, and if homestorage.create is true, provision home storage and launcher-mounts automatically + - Otherwise use .Values.config.serverDcf.launcher-mounts +*/}} +{{- define "rstudio-workbench.config.launcherMounts" -}} +{{ $currentMounts := index $.Values.config.serverDcf "launcher-mounts" }} +{{- if and ( empty $currentMounts ) ( $.Values.homeStorage.create ) -}} +{{- $claimName := printf "%s-home-storage" (include "rstudio-workbench.fullname" . ) }} +{{- $defaultMounts := (dict "launcher-mounts" (dict "MountType" "KubernetesPersistentVolumeClaim" "MountPath" $.Values.homeStorage.path "ClaimName" $claimName ) ) }} +{{ include "rstudio-library.config.dcf" $defaultMounts }} +{{- else }} +{{ include "rstudio-library.config.dcf" (dict "launcher-mounts" $currentMounts) }} +{{- end }} +{{- end }} + +{{/* + Precedence: + - .Values.launcherPem + - auto-generated value + - we check to see if the secret is already created + - if it is, we warn and leave it alone +*/}} +{{- define "rstudio-workbench.launcherPem" -}} +{{- $pemVar := $.Values.launcherPem -}} +{{- if eq ($.Values.launcherPem) ("") -}} +{{- $secretName := print (include "rstudio-workbench.fullname" .) "-secret" }} +{{- $currentSecret := lookup "v1" "Secret" $.Release.Namespace $secretName }} +{{- if and $currentSecret (not .Values.dangerRegenerateAutomatedValues) }} +{{- $pemVar = get $currentSecret.data "launcher.pem" | b64dec }} +{{- else }} +{{- $pemVar = genPrivateKey "rsa" -}} +{{- end }} +{{- end -}} +{{ print $pemVar }} +{{ end }} + +{{/* + Precedence: + - .Values.global.secureCookieKey + - .Values.secureCookieKey + - auto-generated value + - we check to see if the secret is already created + - if it is, we warn and leave it alone +*/}} +{{- define "rstudio-workbench.secureCookieKey" -}} +{{- $cookieVar := default .Values.secureCookieKey .Values.global.secureCookieKey -}} +{{- if eq ($cookieVar) ("") -}} +{{- $secretName := print (include "rstudio-workbench.fullname" .) "-secret" }} +{{- $currentSecret := lookup "v1" "Secret" $.Release.Namespace $secretName }} +{{- if and $currentSecret (not .Values.dangerRegenerateAutomatedValues ) }} +{{- $cookieVar = get $currentSecret.data "secure-cookie-key" | b64dec }} +{{- else }} +{{- $cookieVar = uuidv4 -}} +{{- end }} +{{- end -}} +{{ print $cookieVar }} +{{ end }} + +{{- define "rstudio-workbench.launcherNamespace" -}} +{{- $myvar := $.Release.Namespace -}} +{{- if $.Values.launcherNamespace -}} +{{- $myvar = $.Values.launcherNamespace -}} +{{- end -}} +{{ print $myvar }} +{{ end }} + +{{- define "rstudio-workbench.annotations" -}} +{{- range $key,$value := $.Values.service.annotations -}} +{{ $key }}: {{ $value | quote }} +{{ end }} +{{- end -}} + +{{- define "rstudio-workbench.pod.annotations" -}} +{{- range $key,$value := $.Values.pod.annotations -}} +{{ $key }}: {{ $value | quote }} +{{ end }} +{{- end -}} + + +{{- define "rstudio-workbench.xdg-config-dirs" -}} +{{ trimSuffix ":" ( join ":" (list .Values.xdgConfigDirs (join ":" .Values.xdgConfigDirsExtra) ) ) }} +{{- end -}} diff --git a/charts/rstudio-workbench/templates/configmap-general.yaml b/charts/rstudio-workbench/templates/configmap-general.yaml new file mode 100644 index 00000000..69d3e853 --- /dev/null +++ b/charts/rstudio-workbench/templates/configmap-general.yaml @@ -0,0 +1,75 @@ +{{/* Define the default values that will be merged over */}} +{{- $defaultVersion := .Values.versionOverride | default $.Chart.AppVersion }} +{{- $sessionTag := .Values.session.image.tag | default (printf "%s%s" .Values.session.image.tagPrefix $defaultVersion ) }} +{{- $defaultImages := list (printf "%s:%s" .Values.session.image.repository $sessionTag) }} +{{- $defaultOverrides := list }} +{{- if .Values.session.defaultConfigMount }} + {{/* default session config mount */}} + {{- $sessionVolume := dict "configMap" ( dict "name" (printf "%s-session" ( include "rstudio-workbench.fullname" . ) ) ) "name" "session-config" }} + {{- $sessionVolumeMount := dict "mountPath" "/mnt/session-configmap" "name" "session-config" }} + {{- $sessionVolumeOverride := dict "name" "defaultSessionVolume" "target" "/spec/template/spec/volumes/-" "json" $sessionVolume }} + {{- $sessionVolumeMountOverride := dict "name" "defaultSessionVolumeMount" "target" "/spec/template/spec/containers/0/volumeMounts/-" "json" $sessionVolumeMount }} + {{- $defaultOverrides = concat $defaultOverrides ( list $sessionVolumeOverride $sessionVolumeMountOverride ) }} +{{- end }} +{{- $defaultProfiles := dict "default-container-image" (first $defaultImages) "container-images" $defaultImages "allow-unknown-images" 1 }} +{{- if $defaultOverrides }} + {{/* if defaultOverrides exist - add them */}} + {{- $defaultProfiles = mergeOverwrite $defaultProfiles ( dict "job-json-overrides" $defaultOverrides ) }} +{{- end }} +{{- $defaultProfilesConfig := dict "*" $defaultProfiles }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-config + namespace: {{ $.Release.Namespace }} +data: +{{/* generate the server configuration, setting remote licensing if applicable */}} +{{- $overrideDict := .Values.config.server | deepCopy }} +{{- if .Values.license.server }} + {{- $licenseParam := dict "server-license-type" "remote" }} + {{- $licenseServerConf := dict "rserver.conf" $licenseParam}} + {{- $overrideDict = mergeOverwrite $overrideDict $licenseServerConf}} +{{- end }} +{{ include "rstudio-library.config.ini" $overrideDict | indent 2 }} +{{/* helper variables to make things here a bit more sane */}} +{{- $profilesConfig := .Values.config.profiles }} +{{- $profilesConfig = mergeOverwrite (dict "launcher.kubernetes.profiles.conf" $defaultProfilesConfig) $profilesConfig }} +{{- $useLegacyProfiles := hasKey .Values.config.server "launcher.kubernetes.profiles.conf" }} +{{- $jobJsonFilePath := "/mnt/job-json-overrides-new/" }} +{{- if not $useLegacyProfiles }} + {{- $profilesDict := dict "data" ($profilesConfig | deepCopy) "filePath" $jobJsonFilePath }} + {{/* generate the profiles configuration */}} + {{- include "rstudio-library.profiles.ini.advanced" $profilesDict | nindent 2 }} +{{- end }} +{{/* generate the server configuration (dcf files) minus launcher-mounts */}} +{{ include "rstudio-library.config.dcf" ( omit .Values.config.serverDcf "launcher-mounts" ) | indent 2 }} +{{/* generate the launcher-mounts file */}} +{{ include "rstudio-workbench.config.launcherMounts" . | indent 2 }} +--- +{{/* The old pattern for job-json-overrides "json" files */}} +{{- if .Values.jobJsonOverridesFiles }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-overrides-old + namespace: {{ $.Release.Namespace }} +data: +{{- range $file,$content := $.Values.jobJsonOverridesFiles }} + {{ $file }}: | + {{- $content | toPrettyJson | nindent 4 }} +{{- end }} +--- +{{- end }} +{{/* The new pattern for job-json-overrides "json" files */}} +{{- if not $useLegacyProfiles }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-overrides-new + namespace: {{ $.Release.Namespace }} +data: + {{/* TODO: need to make the json-from-overrides-config take more than one file... only using launcher.kubernetes.profiles.conf today */}} + {{- $configValue := dict "data" (get $profilesConfig "launcher.kubernetes.profiles.conf" | deepCopy) "default" (list) }} + {{- include "rstudio-library.profiles.json-from-overrides-config" $configValue | indent 2 }} +{{- end }} diff --git a/charts/rstudio-workbench/templates/configmap-graphite-exporter.yaml b/charts/rstudio-workbench/templates/configmap-graphite-exporter.yaml new file mode 100644 index 00000000..36721b5c --- /dev/null +++ b/charts/rstudio-workbench/templates/configmap-graphite-exporter.yaml @@ -0,0 +1,22 @@ +{{- if .Values.prometheusExporter.enabled }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-graphite + namespace: {{ $.Release.Namespace }} +data: + graphite-mapping.yaml: |- + mappings: + - match: "rstudio\\.(\\w+)\\.system\\.load\\.(.*)" + match_type: regex + name: "rstudio_system_load" + labels: + host: "$1" + duration: "$2" + - match: "rstudio\\.(\\w+)\\.(.*)" + match_type: regex + name: "rstudio_$2" + labels: + host: "$1" +{{- end }} diff --git a/charts/rstudio-workbench/templates/configmap-prestart.yaml b/charts/rstudio-workbench/templates/configmap-prestart.yaml new file mode 100644 index 00000000..0da81b63 --- /dev/null +++ b/charts/rstudio-workbench/templates/configmap-prestart.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-prestart + namespace: {{ $.Release.Namespace }} +data: + prestart.bash: | + {{- .Files.Get "prestart.bash" | nindent 4 }} diff --git a/charts/rstudio-workbench/templates/configmap-secret.yaml b/charts/rstudio-workbench/templates/configmap-secret.yaml new file mode 100644 index 00000000..846701c5 --- /dev/null +++ b/charts/rstudio-workbench/templates/configmap-secret.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-secret + namespace: {{ $.Release.Namespace }} +stringData: +{{ include "rstudio-library.config.ini" .Values.config.secret | indent 2 }} + launcher.pem: | +{{ include "rstudio-workbench.launcherPem" . | indent 4 }} + secure-cookie-key: | +{{ include "rstudio-workbench.secureCookieKey" . | indent 4 }} +{{- if .Values.launcherPub }} +# TODO: would ideally be able to generate launcher.pub as well + launcher.pub: | +{{ .Values.launcherPub | indent 4 }} +{{- end }} +--- diff --git a/charts/rstudio-workbench/templates/configmap-session.yaml b/charts/rstudio-workbench/templates/configmap-session.yaml new file mode 100644 index 00000000..bd42ec00 --- /dev/null +++ b/charts/rstudio-workbench/templates/configmap-session.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-session + namespace: {{ $.Release.Namespace }} +data: +{{ include "rstudio-library.config.ini" .Values.config.session | indent 2 }} +--- diff --git a/charts/rstudio-workbench/templates/deployment.yaml b/charts/rstudio-workbench/templates/deployment.yaml new file mode 100644 index 00000000..63141ff2 --- /dev/null +++ b/charts/rstudio-workbench/templates/deployment.yaml @@ -0,0 +1,255 @@ +{{- $useLegacyProfiles := hasKey .Values.config.server "launcher.kubernetes.profiles.conf" }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "rstudio-workbench.fullname" . }} + namespace: {{ $.Release.Namespace }} +spec: + strategy: + type: {{ .Values.strategy.type }} + {{- if eq .Values.strategy.type "RollingUpdate" }} + rollingUpdate: + maxUnavailable: {{ .Values.strategy.rollingUpdate.maxUnavailable }} + maxSurge: {{ .Values.strategy.rollingUpdate.maxSurge }} + {{- end }} + replicas: {{ .Values.replicas }} + selector: + matchLabels: + {{- include "rstudio-workbench.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config-general: {{ include (print $.Template.BasePath "/configmap-general.yaml") . | sha256sum }} + checksum/config-graphite: {{ include (print $.Template.BasePath "/configmap-graphite-exporter.yaml") . | sha256sum }} + checksum/config-prestart: {{ include (print $.Template.BasePath "/configmap-prestart.yaml") . | sha256sum }} + checksum/config-secret: {{ include (print $.Template.BasePath "/configmap-secret.yaml") . | sha256sum }} + checksum/config-session: {{ include (print $.Template.BasePath "/configmap-session.yaml") . | sha256sum }} + {{- if .Values.prometheusExporter.enabled }} + prometheus.io/scrape: "true" + prometheus.io/path: "/metrics" + prometheus.io/port: "9109" + {{- end }} +{{ include "rstudio-workbench.pod.annotations" . | indent 8 }} + labels: + {{- include "rstudio-workbench.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.rbac.create }} + serviceAccountName: {{ include "rstudio-workbench.fullname" . }}-job-launcher + {{- end }} + {{- if and (not .Values.rbac.create) (.Values.serviceAccountName) }} + serviceAccountName: {{ .Values.serviceAccountName }} + {{- end }} + shareProcessNamespace: {{ .Values.shareProcessNamespace }} + {{- if .Values.initContainers }} + initContainers: +{{ toYaml .Values.initContainers | indent 8 }} + {{- end }} + containers: + - name: rstudio + {{- $defaultVersion := .Values.versionOverride | default $.Chart.AppVersion }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default $defaultVersion }}" + env: + - name: RSTUDIO_LAUNCHER_NAMESPACE + value: "{{ $.Release.Namespace }}" +{{ include "rstudio-library.license-env" (dict "license" ( .Values.license ) "product" ("rstudio-workbench") "envVarPrefix" ("RSW") "fullName" (include "rstudio-workbench.fullname" .)) | indent 8 }} + - name: RSP_LAUNCHER + value: "{{ .Values.launcher }}" + {{- if .Values.userCreate }} + - name: RSP_TESTUSER + value: "{{ .Values.userName }}" + - name: RSP_TESTUSER_UID + value: "{{ .Values.userUid }}" + - name: RSP_TESTUSER_PASSWD + value: "{{ .Values.userPassword }}" + {{- else }} + - name: RSP_TESTUSER + value: "" + {{- end }} + - name: XDG_CONFIG_DIRS + value: "{{ template "rstudio-workbench.xdg-config-dirs" .}}" + {{- if or ( gt (int .Values.replicas) 1 ) ( .Values.loadBalancer.forceEnabled ) }} + - name: PRESTART_LOAD_BALANCER_CONFIGURATION + value: enabled + {{- end }} + {{- if .Values.pod.env }} +{{ toYaml .Values.pod.env | indent 8 }} + {{- end }} + {{- if .Values.command }} + command: +{{ toYaml .Values.command | indent 10 }} + {{- end }} + {{- if .Values.args }} + args: +{{ toYaml .Values.args | indent 10 }} + {{- end }} + imagePullPolicy: "{{ .Values.image.imagePullPolicy }}" + ports: + - containerPort: 8787 + securityContext: +{{ toYaml .Values.securityContext | indent 10 }} + volumeMounts: + {{- if .Values.sharedStorage.create }} + - name: rstudio-shared-storage + mountPath: "{{ .Values.sharedStorage.path }}" + {{- end }} + {{- if .Values.homeStorage.create }} + - name: rstudio-home-storage + mountPath: "{{ .Values.homeStorage.path }}" + {{- end }} + - name: rstudio-prestart + mountPath: "/scripts/" + - name: rstudio-config + mountPath: "/mnt/configmap/rstudio/" + - name: rstudio-session-config + mountPath: "/mnt/session-configmap/rstudio/" + - name: rstudio-secret + mountPath: "/mnt/secret-configmap/rstudio/" + - name: etc-rstudio + mountPath: "/etc/rstudio" + - name: shared-data + mountPath: "/mnt/load-balancer/rstudio" + {{ include "rstudio-library.license-mount" (dict "license" ( .Values.license )) | indent 10 }} + {{/* TODO: path collision problems... would be ideal to not have to maintain both long term */}} + {{- if .Values.jobJsonOverridesFiles }} + - name: rstudio-job-overrides-old + mountPath: "/mnt/job-json-overrides" + {{- end }} + {{- if not $useLegacyProfiles }} + - name: rstudio-job-overrides-new + mountPath: "/mnt/job-json-overrides-new" + {{- end }} + {{- if .Values.pod.volumeMounts }} +{{ toYaml .Values.pod.volumeMounts | indent 10 }} + {{- end }} + resources: + {{- if .Values.resources.requests.enabled }} + requests: + memory: "{{ .Values.resources.requests.memory }}" + cpu: "{{ .Values.resources.requests.cpu }}" + ephemeral-storage: "{{ .Values.resources.requests.ephemeralStorage }}" + {{- end }} + limits: + {{- if .Values.resources.limits.enabled }} + memory: "{{ .Values.resources.limits.memory }}" + cpu: "{{ .Values.resources.limits.cpu }}" + ephemeral-storage: "{{ .Values.resources.limits.ephemeralStorage }}" + {{- end }} + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: /health-check + port: 8787 + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.startupProbe.enabled }} + startupProbe: + httpGet: + path: /health-check + port: 8787 + initialDelaySeconds: {{ .Values.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.startupProbe.periodSeconds }} + timeoutSeconds: {{ .Values.startupProbe.timeoutSeconds }} + failureThreshold: {{ .Values.startupProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: /health-check + port: 8787 + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + {{- if or (gt (int .Values.replicas) 1) (.Values.loadBalancer.forceEnabled) }} + - name: sidecar + image: "{{ .Values.loadBalancer.image.repository }}:{{ .Values.loadBalancer.image.tag }}" + imagePullPolicy: "{{ .Values.loadBalancer.image.imagePullPolicy }}" + {{- if .Values.loadBalancer.env }} + env: + {{- toYaml .Values.loadBalancer.env | nindent 8 }} + {{- end }} + args: + - "{{ include "rstudio-workbench.fullname" . }}" + - "{{ $.Release.Namespace }}" + - "/mnt/load-balancer/rstudio/" + - "{{ .Values.loadBalancer.sleepDuration }}" + - "{{ .Values.loadBalancer.appLabelKey }}" + {{- if .Values.loadBalancer.securityContext }} + securityContext: + {{- toYaml .Values.loadBalancer.securityContext | nindent 10 }} + {{- end }} + volumeMounts: + - name: shared-data + mountPath: "/mnt/load-balancer/rstudio/" + {{- end }} + {{- if .Values.prometheusExporter.enabled }} + - name: exporter + image: "{{ .Values.prometheusExporter.image.repository }}:{{ .Values.prometheusExporter.image.tag }}" + imagePullPolicy: "{{ .Values.prometheusExporter.image.imagePullPolicy }}" + args: + - "--graphite.mapping-config=/mnt/graphite/graphite-mapping.yaml" + volumeMounts: + - name: graphite-exporter-config + mountPath: "/mnt/graphite/" + {{- end }} + {{- if .Values.pod.sidecar }} +{{ toYaml .Values.pod.sidecar | indent 6 }} + {{- end }} + volumes: + {{- if .Values.sharedStorage.create }} + - name: rstudio-shared-storage + persistentVolumeClaim: + claimName: {{ include "rstudio-workbench.fullname" . }}-shared-storage + {{- end }} + {{- if .Values.homeStorage.create }} + - name: rstudio-home-storage + persistentVolumeClaim: + claimName: {{ include "rstudio-workbench.fullname" . }}-home-storage + {{- end }} + {{- if .Values.jobJsonOverridesFiles }} + - name: rstudio-job-overrides-old + configMap: + name: {{ include "rstudio-workbench.fullname" . }}-overrides-old + defaultMode: 0644 + {{- end }} + {{- if not $useLegacyProfiles }} + - name: rstudio-job-overrides-new + configMap: + name: {{ include "rstudio-workbench.fullname" . }}-overrides-new + defaultMode: 0644 + {{- end }} + - name: etc-rstudio + emptyDir: {} + - name: shared-data + emptyDir: {} + - name: rstudio-config + configMap: + name: {{ include "rstudio-workbench.fullname" . }}-config + defaultMode: 0644 + - name: rstudio-session-config + configMap: + name: {{ include "rstudio-workbench.fullname" . }}-session + defaultMode: 0644 + - name: rstudio-prestart + configMap: + name: {{ include "rstudio-workbench.fullname" . }}-prestart + defaultMode: 0755 + - name: rstudio-secret + secret: + secretName: {{ include "rstudio-workbench.fullname" . }}-secret + defaultMode: 0600 + {{ include "rstudio-library.license-volume" (dict "license" ( .Values.license ) "fullName" (include "rstudio-workbench.fullname" .)) | indent 6 }} + {{- if .Values.prometheusExporter.enabled }} + - name: graphite-exporter-config + configMap: + name: {{ include "rstudio-workbench.fullname" . }}-graphite + defaultMode: 0755 + {{- end }} + {{- if .Values.pod.volumes }} +{{ toYaml .Values.pod.volumes | indent 6 }} + {{- end }} diff --git a/charts/rstudio-workbench/templates/ingress.yaml b/charts/rstudio-workbench/templates/ingress.yaml new file mode 100644 index 00000000..5caf07f7 --- /dev/null +++ b/charts/rstudio-workbench/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "rstudio-workbench.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "rstudio-workbench.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ . }} + backend: + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} diff --git a/charts/rstudio-workbench/templates/license-secret.yaml b/charts/rstudio-workbench/templates/license-secret.yaml new file mode 100644 index 00000000..1db2db86 --- /dev/null +++ b/charts/rstudio-workbench/templates/license-secret.yaml @@ -0,0 +1 @@ +{{ include "rstudio-library.license-secret" (dict "license" ( .Values.license ) "fullName" (include "rstudio-workbench.fullname" .) "product" ("rstudio-workbench") "namespace" $.Release.Namespace) }} diff --git a/charts/rstudio-workbench/templates/pvc.yaml b/charts/rstudio-workbench/templates/pvc.yaml new file mode 100644 index 00000000..4728721f --- /dev/null +++ b/charts/rstudio-workbench/templates/pvc.yaml @@ -0,0 +1,40 @@ +{{- if .Values.sharedStorage.create }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-shared-storage + namespace: {{ $.Release.Namespace }} + annotations: + "helm.sh/resource-policy": keep +spec: + accessModes: +{{ .Values.sharedStorage.accessModes | toYaml | indent 4 }} + volumeMode: Filesystem + {{- if .Values.sharedStorage.storageClassName }} + storageClassName: {{ .Values.sharedStorage.storageClassName }} + {{- end }} + resources: + requests: + storage: {{ .Values.sharedStorage.requests.storage }} +--- +{{- end }} +{{- if .Values.homeStorage.create }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-home-storage + namespace: {{ $.Release.Namespace }} + annotations: + "helm.sh/resource-policy": keep +spec: + accessModes: +{{ .Values.homeStorage.accessModes | toYaml | indent 4 }} + volumeMode: Filesystem + {{- if .Values.homeStorage.storageClassName }} + storageClassName: {{ .Values.homeStorage.storageClassName }} + {{- end }} + resources: + requests: + storage: {{ .Values.homeStorage.requests.storage }} +--- +{{- end }} diff --git a/charts/rstudio-workbench/templates/rbac.yaml b/charts/rstudio-workbench/templates/rbac.yaml new file mode 100644 index 00000000..069a10d9 --- /dev/null +++ b/charts/rstudio-workbench/templates/rbac.yaml @@ -0,0 +1,39 @@ +{{- if .Values.rbac.create }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-job-launcher + namespace: {{ .Release.Namespace }} + + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-api-access + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-api-access + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "rstudio-workbench.fullname" . }}-api-access +subjects: + - kind: ServiceAccount + name: {{ include "rstudio-workbench.fullname" . }}-job-launcher + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/rstudio-workbench/templates/svc.yaml b/charts/rstudio-workbench/templates/svc.yaml new file mode 100644 index 00000000..042aa858 --- /dev/null +++ b/charts/rstudio-workbench/templates/svc.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "rstudio-workbench.fullname" . }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "rstudio-workbench.labels" . | nindent 4 }} + annotations: +{{ include "rstudio-workbench.annotations" . | indent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "rstudio-workbench.selectorLabels" . | nindent 4 }} + ports: + - protocol: TCP + port: 80 + {{- if .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + targetPort: 8787 +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "rstudio-workbench.fullname" . }}-launcher + namespace: {{ $.Release.Namespace }} + labels: + {{- include "rstudio-workbench.labels" . | nindent 4 }} +spec: + type: NodePort + selector: + {{- include "rstudio-workbench.selectorLabels" . | nindent 4 }} + ports: + - protocol: TCP + port: 80 + targetPort: 5559 +--- diff --git a/charts/rstudio-workbench/templates/tests/test-verify-installation.yaml b/charts/rstudio-workbench/templates/tests/test-verify-installation.yaml new file mode 100644 index 00000000..372ab885 --- /dev/null +++ b/charts/rstudio-workbench/templates/tests/test-verify-installation.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-verify-installation-test" + annotations: + "helm.sh/hook": test +spec: + restartPolicy: Never + containers: + - name: {{ include "rstudio-workbench.fullname" . }} + image: {{ .Values.image.repository }} + securityContext: + privileged: true + env: + - name: RSW_TESTUSER + value: {{ .Values.userName }} + command: ["/bin/bash", "-c", "rstudio-launcher start ; useradd -m rstudio ; rstudio-server verify-installation --verify-user=$RSW_TESTUSER"] diff --git a/charts/rstudio-workbench/values.yaml b/charts/rstudio-workbench/values.yaml new file mode 100644 index 00000000..793a8d33 --- /dev/null +++ b/charts/rstudio-workbench/values.yaml @@ -0,0 +1,278 @@ +# -- the name of the chart deployment (can be overridden) +nameOverride: "" +# -- the full name of the release (can be overridden) +fullnameOverride: "" + +# -- A Workbench version to override the "tag" for the RStudio Workbench image and the session images. Necessary until https://github.com/helm/helm/issues/8194 +versionOverride: "" + +session: + defaultConfigMount: true + image: + # -- A tag prefix for session images (common selections: bionic-, centos-). Only used if tag is not defined + tagPrefix: bionic- + # -- The repository to use for the session image + repository: "rstudio/r-session-complete" + # -- A tag override for the session image. Overrides the "tagPrefix" above, if set. Default tag is `{{ tagPrefix }}{{ version }}` + tag: "" + +sharedStorage: + # -- whether to create the persistentVolumeClaim for shared storage + create: false + # -- the path to mount the sharedStorage claim within the pod + path: /var/lib/rstudio-server + # -- storageClassName - the type of storage to use. Must allow ReadWriteMany + storageClassName: false + # -- accessModes defined for the storage PVC (represented as YAML) + accessModes: + - ReadWriteMany + requests: + # -- the volume of storage to request for this persistent volume claim + storage: "10Gi" + +strategy: + type: "RollingUpdate" + rollingUpdate: + maxSurge: "100%" + maxUnavailable: 0 + +# -- allow customizing the namespace that sessions are launched into. Note RBAC and some config issues today +launcherNamespace: false + +homeStorage: + # -- whether to create the persistentVolumeClaim for homeStorage + create: false + # -- the path to mount the homeStorage claim within the pod + path: /home + # -- storageClassName - the type of storage to use. Must allow ReadWriteMany + storageClassName: false + # -- accessModes defined for the storage PVC (represented as YAML) + accessModes: + - ReadWriteMany + requests: + # -- the volume of storage to request for this persistent volume claim + storage: "10Gi" + +image: + # -- the repository to use for the main pod image + repository: rstudio/rstudio-server-pro + # -- Overrides the image tag whose default is the chart appVersion. + tag: '' + # -- the imagePullPolicy for the main pod image + imagePullPolicy: IfNotPresent + +# -- the initContainer spec that will be used verbatim +initContainers: false + +ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + # - host: chart-example.local + # paths: [] + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +# -- whether to provide `shareProcessNamespace` to the pod. Important for HA environments for the sidecar +shareProcessNamespace: true + +service: + # -- annotations for the service definition + annotations: {} + # -- the service type (i.e. NodePort, LoadBalancer, etc.) + type: NodePort + # -- the nodePort to use when using service type NodePort. If not defined, Kubernetes will provide one automatically + nodePort: false + +# -- resources define requests and limits for the rstudio-server pod +resources: + requests: + enabled: false + memory: "2Gi" + cpu: "100m" + ephemeralStorage: "100Mi" + limits: + enabled: false + memory: "4Gi" + cpu: "2000m" + ephemeralStorage: "200Mi" + +# -- livenessProbe is used to configure the container's livenessProbe +livenessProbe: + enabled: false + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 2 + failureThreshold: 10 +# -- startupProbe is used to configure the container's startupProbe +startupProbe: + enabled: false + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 1 + # -- failureThreshold * periodSeconds should be strictly > worst case startup time + failureThreshold: 30 +# -- readinessProbe is used to configure the container's readinessProbe +readinessProbe: + enabled: true + initialDelaySeconds: 3 + periodSeconds: 3 + timeoutSeconds: 1 + successThreshold: 1 + failureThreshold: 3 + +loadBalancer: + # -- whether to force the loadBalancer to be enabled. Otherwise requires replicas > 1. Worth setting if you are HA but may only have one node + forceEnabled: false + appLabelKey: "app.kubernetes.io/name" + sleepDuration: 15 + # -- env is an array of maps that is injected as-is into the "env:" component of the loadBalancer sidecar spec + env: [] + securityContext: + capabilities: + add: + # necessary for sidecar container to function properly in HA + - SYS_PTRACE + image: + # -- the repository to use for the side-car pod image + repository: rstudio/rstudio-server-load-balancer-manager + # -- the tag to use for the side-car pod image + tag: "2.2" + # -- the imagePullPolicy to use for the side-car pod image + imagePullPolicy: IfNotPresent + +# -- command is the pod container's run command. +command: [tini, -s, --] +# -- args is the pod container's run arguments. By default, it uses a kubernetes-specific prestart script that exec's the default container startup script. +args: + - /scripts/prestart.bash + - /usr/local/bin/startup.sh + +license: + # -- key is the license to use + key: null + # -- server is the : for a license server + server: false + # -- the file section is used for licensing with a license file + file: + # -- contents is an in-line license file + contents: false + # -- mountPath is the place the license file will be mounted into the container + mountPath: "/etc/rstudio-licensing" + # -- mountSubPath is whether to mount the subPath for the file secret. + # -- It can be preferable _not_ to enable this, because then updates propagate automatically + mountSubPath: false + # -- secretKey is the key for the secret to use for the license file + secretKey: "license.lic" + # -- secret is an existing secret with a license file in it + secret: false + +# -- replicas is the number of replica pods to maintain for this service. Use 2 or more to enable HA +replicas: 1 + +# -- launcher determines whether the launcher should be started in the container +launcher: "true" + +# -- userCreate determines whether a user should be created at startup (if true) +userCreate: false +# -- userName determines the username of the created user +userName: "rstudio" +# -- userUid determines the UID of the created user +userUid: "10000" +# -- userPassword determines the password of the created user +userPassword: "rstudio" + +# -- The XDG config dirs (directories where configuration will be read from). Do not change without good reason. +xdgConfigDirs: "/mnt/dynamic:/mnt/session-configmap:/mnt/secret-configmap:/mnt/configmap:/mnt/load-balancer/" + +# -- A list of additional XDG config dir paths +xdgConfigDirsExtra: [] + +securityContext: {} + +pod: + # -- env is an array of maps that is injected as-is into the "env:" component of the pod.container spec + env: [] + # -- volumes is injected as-is into the "volumes:" component of the pod.container spec + volumes: [] + # -- volumeMounts is injected as-is into the "volumeMounts:" component of the pod.container spec + volumeMounts: [] + # -- podAnnotations is a map of keys / values that will be added as annotations to the rstudio-pm pods + annotations: {} + # -- sidecar is an array of containers that will be run alongside the main container + sidecar: false + +prometheusExporter: + # -- whether the prometheus exporter sidecar should be enabled + enabled: true + image: + repository: "prom/graphite-exporter" + tag: "v0.9.0" + imagePullPolicy: IfNotPresent + +rbac: + # -- rbac.create specifies whether to create the ServiceAccount required for the Job Launcher to have appropriate permissions + create: true +# -- serviceAccountName is the service account used to launch pods into Kubernetes (into launcherNamespace) +serviceAccountName: false + +# -- jobJsonOverridesFiles is a map of maps. Each item in the map will become a file (named by the key), and the underlying object will be converted to JSON as the file's contents +jobJsonOverridesFiles: {} + +launcherPem: '' +launcherPub: false +secureCookieKey: '' +dangerRegenerateAutomatedValues: false + +global: + secureCookieKey: '' + +config: + session: + repos.conf: + RSPM: https://packagemanager.rstudio.com/cran/__linux__/bionic/latest + CRAN: https://packagemanager.rstudio.com/cran/__linux__/bionic/latest + rsession.conf: {} + notifications.conf: {} + secret: + "database.conf": {} + server: + rserver.conf: + server-health-check-enabled: 1 + admin-enabled: 1 + www-port: 8787 + server-project-sharing: 1 + launcher-address: 127.0.0.1 + launcher-port: 5559 + launcher-sessions-enabled: 1 + launcher-default-cluster: Kubernetes + launcher-sessions-callback-address: http://127.0.0.1:8787 + monitor-graphite-enabled: 1 + monitor-graphite-host: 127.0.0.1 + monitor-graphite-port: 9109 + monitor-graphite-client-id: rstudio + launcher.conf: + server: + address: 127.0.0.1 + port: 5559 + server-user: rstudio-server + admin-group: rstudio-server + authorization-enabled: 1 + thread-pool-size: 4 + enable-debug-logging: 1 + cluster: + name: Kubernetes + type: Kubernetes + "jupyter.conf": + jupyter-exe: /opt/python/3.6.5/bin/jupyter + notebooks-enabled: 1 + labs-enabled: 1 + default-session-cluster: Kubernetes + logging.conf: {} + profiles: {} + serverDcf: + launcher-mounts: []