diff --git a/README.rst b/README.rst index 7b0ba278..425904d6 100644 --- a/README.rst +++ b/README.rst @@ -1,20 +1,51 @@ drydock: a flexible manifest builder for Open edX ================================================= +**⚠️ Warning**: drydock is currently in an alpha stage and may suffer substantial changes +while it transitions in to a stable stage. + + Installation ------------ :: tvm plugins install -e git+https://github.com/edunext/drydock#egg=drydock + tutor plugins enable drydock -Usage ------ +Getting started +--------------- -:: +Drydock aims to offer flexibility on the kind of environment you want to generate, for that reason +you can chose different kind of implementations (of renderers?) depending on your needs. At the +moment Drydock ships with a basic manifest builder that wraps the Kubernetes generated files by tutor +in a Kustomize application with some useful extra resources. - tutor plugins enable drydock - tutor drydock save -r drydock/references/tutor_v13.yml +To use drydock just use the reference that defines the implementation you want drydock to use and run: + +.. code:: bash + + tutor drydock save -r reference.yml + +An example reference would look something like this: + +.. code:: yaml + + drydock: + builder_class: drydock.manifest_builder.application.manifest_builder.ManifestBuilder + config_class: drydock.manifest_builder.infrastructure.tutor_config.TutorConfig + manifest_class: drydock.manifest_builder.infrastructure.tutor_based_manifests.BaseManifests + manifest_options: + output: "drydock-environment" + +This will render the default drydock environment on the `drydock-environment` directory, allowing +you to check the files onto version control and use tools for continuous deployment such as +Flux or ArgoCD. The default environment is generated using Tutor and its templates, as a result +it should be compatible with all the plugins, variables and patches. + +**Note:** If you are using module or yaml plugins in tutor and set ``manifest_options.output=env`` +you will have to define your ``TUTOR_PLUGINS_ROOT`` outside of your ``TUTOR_ROOT`` because +drydock will override your tutor env and erase your plugins. Rationale diff --git a/drydock/manifest_builder/domain/config.py b/drydock/manifest_builder/domain/config.py index 809d05ec..af6f17ca 100644 --- a/drydock/manifest_builder/domain/config.py +++ b/drydock/manifest_builder/domain/config.py @@ -4,5 +4,9 @@ class DrydockConfig(ABC): @abstractmethod - def get_data() -> dict: + def get_data(self) -> dict: + pass + + @abstractmethod + def get_root(self) -> str: pass diff --git a/drydock/manifest_builder/domain/manifest_repository.py b/drydock/manifest_builder/domain/manifest_repository.py index 493c28b7..09662c6b 100644 --- a/drydock/manifest_builder/domain/manifest_repository.py +++ b/drydock/manifest_builder/domain/manifest_repository.py @@ -5,5 +5,5 @@ class ManifestRepository(ABC): @abstractmethod - def save(config: DrydockConfig) -> None: + def save(self, config: DrydockConfig) -> None: pass diff --git a/drydock/manifest_builder/infrastructure/tutor_based_manifests.py b/drydock/manifest_builder/infrastructure/tutor_based_manifests.py new file mode 100644 index 00000000..effdb15e --- /dev/null +++ b/drydock/manifest_builder/infrastructure/tutor_based_manifests.py @@ -0,0 +1,97 @@ +""" +Collection of tutor based renderers for Kubernetes manifests. +""" +import os +import shutil +import tempfile +from os.path import join as path_join + +import pkg_resources +from tutor import env as tutor_env +from tutor import fmt, hooks + +from drydock.manifest_builder.domain.config import DrydockConfig +from drydock.manifest_builder.domain.manifest_repository import ManifestRepository + + +class BaseManifests(ManifestRepository): + """Baseline of Kubernetes configuration repository. + + Generates an environment based on Tutor with the relevant Kubernetes configuration + to be tracked by version control. + """ + TEMPLATE_ROOT = "kustomized/tutor13" + TEMPLATE_TARGETS = [ + f"{TEMPLATE_ROOT}/base", + f"{TEMPLATE_ROOT}/extensions", + f"{TEMPLATE_ROOT}/kustomization.yml", + ] + + def __init__(self, options: dict) -> None: + """Initialize the class based on the `manifest_options` from the reference. + + Parameters + ---------- + options: dict + Defines additional configuration options for the generations of the + configuration repository. + + - ["output"]: Name of the directory to store the manifests. + """ + self.output_dir = options.get("output", "drydock-env") + + def render(self, root: str, config: DrydockConfig) -> None: + """Register drydock custom templates and render a tutor env.""" + with hooks.Contexts.APP("drydock-base").enter(): + hooks.Filters.ENV_TEMPLATE_ROOTS.add_item(pkg_resources.resource_filename("drydock", "templates")) + hooks.Filters.ENV_TEMPLATE_TARGETS.add_items( + [ + (target, "drydock") for target in self.TEMPLATE_TARGETS + ], + ) + tutor_env.save(root, config.get_data()) + hooks.Filters.ENV_TEMPLATE_ROOTS.clear(context=hooks.Contexts.APP("drydock-base").name) + hooks.Filters.ENV_TEMPLATE_TARGETS.clear(context=hooks.Contexts.APP("drydock-base").name) + + def relocate_env(self, src: str, dst: str) -> None: + """Moves the drydock rendered templates and tutor plugins to src. + + At the moment we render a full tutor env with our templates under + 'env/drydock/default'. The final drydock env is the one in 'env/drydock' + **plus** the plugin directory that has to be manually relocated to + 'env/drydock/base/plugins'. + + Parameters + ---------- + src: str + The inital path where the full tutor env was rendered. + dst: str + The path to save the final drydock env. + """ + base_dir = path_join(src, "env/drydock", f"{self.TEMPLATE_ROOT}") + plugins_dir = path_join(src, "env/plugins") + shutil.move(base_dir, dst) + if os.path.exists(plugins_dir): + shutil.move(plugins_dir, path_join(dst, "base")) + + def save(self, config: DrydockConfig) -> None: + """Creates an alternative tutor env with the base openedx installation. + + The generated env consists of a Kustomize application that uses the original + tutor env as a base in addition to an `extensions` overlay to include additional + resources. + + Parameters + ---------- + config: DrydockConfig + A Tutor configuration extension. + """ + tutor_root = config.get_root() + dst = path_join(tutor_root, self.output_dir) + with tempfile.TemporaryDirectory() as tmpdir: + src = path_join(tmpdir) + self.render(src, config) + shutil.rmtree(path=dst, ignore_errors=True) + self.relocate_env(src, dst) + + fmt.echo_info(f"Drydock environment generated in {dst}") diff --git a/drydock/manifest_builder/infrastructure/tutor_config.py b/drydock/manifest_builder/infrastructure/tutor_config.py index 56bf40f6..2328a19f 100644 --- a/drydock/manifest_builder/infrastructure/tutor_config.py +++ b/drydock/manifest_builder/infrastructure/tutor_config.py @@ -1,14 +1,65 @@ -from abc import abstractmethod -from tutor import config as tutor_config +import pkg_resources +import tutor +import yaml + from drydock.manifest_builder.domain.config import DrydockConfig class TutorConfig(DrydockConfig): - def __init__(self, context, options: dict): self.context = context self.options = options def get_data(self) -> dict: - config = tutor_config.load_full(self.context.obj.root) + config = tutor.config.load_full(self.context.obj.root) return config + + def get_root(self) -> dict: + return self.context.obj.root + + +class TutorExtendedConfig(DrydockConfig): + """Drydock configuration based on tutor.""" + DEFAULT_TEMPLATE_SET = "kustomized/tutor13" + + def __init__(self, context, options: dict): + """Initialize the class based on the `config_options` from the manifest. + + Parameters + ---------- + context: clic.core.Context + context of the current Tutor command. + options: dict + + - ["template_set"]: template set to render default values from. + """ + self.context = context + self.options = options + + def get_data(self) -> dict: + """Return tutor config values using a template set defaults as fallback. + + Retrieve the Tutor configuration values for the current TUTOR_ROOT and + use the the values in `defaults.yml` of the chosen template set + + Returns + ------- + base: dict + Tutor configuration values taken from the config.yml at the TUTOR_ROOT + using the defaults from the template set as a fallback. + """ + template_set = self.options.get("template_set", self.DEFAULT_TEMPLATE_SET) + + try: + defaults_path = pkg_resources.resource_filename("drydock", f"templates/{template_set}/defaults.yml") + with open(defaults_path, encoding="utf-8") as file: + base = yaml.safe_load(file) + except FileNotFoundError: + base = {} + + config = tutor.config.load_full(self.get_root()) + base.update(config) + return base + + def get_root(self) -> str: + return self.context.obj.root diff --git a/drydock/references/kustomized_v13.yml b/drydock/references/kustomized_v13.yml new file mode 100644 index 00000000..2306f1c1 --- /dev/null +++ b/drydock/references/kustomized_v13.yml @@ -0,0 +1,10 @@ +# The kustomized_v13 reference is a Kustomize based instalation that uses the environment +# generated by tutor v13.3.1 as a Base with additional resources such as an ingress and +# a flower deployment used as overlays. +# +drydock: + builder_class: drydock.manifest_builder.application.manifest_builder.ManifestBuilder + config_class: drydock.manifest_builder.infrastructure.tutor_config.TutorConfig + manifest_class: drydock.manifest_builder.infrastructure.tutor_based_manifests.BaseManifests + manifest_options: + output: "drydock-environment" diff --git a/drydock/templates/README.md b/drydock/templates/README.md new file mode 100644 index 00000000..544f0e0a --- /dev/null +++ b/drydock/templates/README.md @@ -0,0 +1,32 @@ +# Drydock Templates + +The inventory of template sets used by drydock. + +## Template list + +1. `kustomized/tutor13`: Kubernetes focused template set. Uses the templates from Tutor v13 as a Kustomize + base (rendered in the `base` directory) and adds additional resources as an overlay in the + `extensions` directory. + The additional resources include: + + ```yaml + - flowers.yml # A flowers deployment to track celery tasks. + - hpa.yml # An Horizontal Pod Autoscaler for lms, cms and workers. + - ingress.yml # An ingress with certmanager to use instead of caddy as web proxy. + ``` + + The list of variables with their default values can be found in [defaults.yml](kustomized/tutor13/defaults.yml) + +
+ List of patches + + ``` + {{ patch("drydock-kustomization-resources")}} + {{ patch("drydock-kustomization-patches")}} + {{ patch("drydock-overrides") }} + ``` +
+ +2. `tutor/v13`: Small subset of templates from Tutor v13. Mostly as a proof con concept. + +3. `tutor/v13`: Small subset of templates from Tutor v12. Mostly as a proof con concept. diff --git a/drydock/templates/kustomized/tutor13/base/apps/caddy/Caddyfile b/drydock/templates/kustomized/tutor13/base/apps/caddy/Caddyfile new file mode 100644 index 00000000..cfb82fd0 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/caddy/Caddyfile @@ -0,0 +1,62 @@ +# Global configuration +{ + {{ patch("caddyfile-global")|indent(4) }} +} + +# proxy directive snippet (with logging) to be used as follows: +# +# import proxy "containername:port" +(proxy) { + log { + output stdout + format filter { + wrap json + fields { + common_log delete + request>headers delete + resp_headers delete + tls delete + } + } + } + + reverse_proxy {args.0} { + header_up X-Forwarded-Port {{ 443 if ENABLE_HTTPS else 80 }} + } +} + +{{ LMS_HOST }}{$default_site_port}, {{ PREVIEW_LMS_HOST }}{$default_site_port} { + @favicon_matcher { + path_regexp ^/favicon.ico$ + } + rewrite @favicon_matcher /theming/asset/images/favicon.ico + + # Limit profile image upload size + request_body /api/profile_images/*/*/upload { + max_size 1MB + } + request_body { + max_size 4MB + } + + import proxy "lms:8000" + + {{ patch("caddyfile-lms")|indent(4) }} +} + +{{ CMS_HOST }}{$default_site_port} { + @favicon_matcher { + path_regexp ^/favicon.ico$ + } + rewrite @favicon_matcher /theming/asset/images/favicon.ico + + request_body { + max_size 250MB + } + + import proxy "cms:8000" + + {{ patch("caddyfile-cms")|indent(4) }} +} + +{{ patch("caddyfile") }} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/config/cms.env.json b/drydock/templates/kustomized/tutor13/base/apps/openedx/config/cms.env.json new file mode 100644 index 00000000..68aca49f --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/config/cms.env.json @@ -0,0 +1,84 @@ +{ + "SITE_NAME": "{{ CMS_HOST }}", + "BOOK_URL": "", + "LOG_DIR": "/openedx/data/logs", + "LOGGING_ENV": "sandbox", + "OAUTH_OIDC_ISSUER": "{{ JWT_COMMON_ISSUER }}", + "PLATFORM_NAME": "{{ PLATFORM_NAME }}", + "FEATURES": { + {{ patch("common-env-features", separator=",\n", suffix=",")|indent(4) }} + {{ patch("cms-env-features", separator=",\n", suffix=",")|indent(4) }} + "CERTIFICATES_HTML_VIEW": true, + "PREVIEW_LMS_BASE": "{{ PREVIEW_LMS_HOST }}", + "ENABLE_COURSEWARE_INDEX": true, + "ENABLE_CSMH_EXTENDED": false, + "ENABLE_LEARNER_RECORDS": false, + "ENABLE_LIBRARY_INDEX": true, + "MILESTONES_APP": true, + "ENABLE_PREREQUISITE_COURSES": true + }, + "LMS_ROOT_URL": "{{ "https" if ENABLE_HTTPS else "http" }}://{{ LMS_HOST }}", + "CMS_ROOT_URL": "{{ "https" if ENABLE_HTTPS else "http" }}://{{ CMS_HOST }}", + "CMS_BASE": "{{ CMS_HOST }}", + "LMS_BASE": "{{ LMS_HOST }}", + "CONTACT_EMAIL": "{{ CONTACT_EMAIL }}", + "CELERY_BROKER_TRANSPORT": "redis", + "CELERY_BROKER_HOSTNAME": "{{ REDIS_HOST }}:{{ REDIS_PORT }}", + "CELERY_BROKER_VHOST": "{{ OPENEDX_CELERY_REDIS_DB }}", + "CELERY_BROKER_USER": "{{ REDIS_USERNAME }}", + "CELERY_BROKER_PASSWORD": "{{ REDIS_PASSWORD }}", + "ALTERNATE_WORKER_QUEUES": "lms", + "ENABLE_COMPREHENSIVE_THEMING": true, + "COMPREHENSIVE_THEME_DIRS": ["/openedx/themes"], + "STATIC_ROOT_BASE": "/openedx/staticfiles", + "EMAIL_BACKEND": "django.core.mail.backends.smtp.EmailBackend", + "EMAIL_HOST": "{{ SMTP_HOST }}", + "EMAIL_PORT": {{ SMTP_PORT }}, + "EMAIL_USE_TLS": {{ "true" if SMTP_USE_TLS else "false" }}, + "HTTPS": "{{ "on" if ENABLE_HTTPS else "off" }}", + "LANGUAGE_CODE": "{{ LANGUAGE_CODE }}", + "SESSION_COOKIE_DOMAIN": "{{ CMS_HOST }}", + {{ patch("cms-env", separator=",\n", suffix=",")|indent(2) }} + "CACHES": { + "default": { + "KEY_PREFIX": "default", + "VERSION": "1", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "general": { + "KEY_PREFIX": "general", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "mongo_metadata_inheritance": { + "KEY_PREFIX": "mongo_metadata_inheritance", + "TIMEOUT": 300, + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "staticfiles": { + "KEY_PREFIX": "staticfiles_cms", + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "staticfiles_cms" + }, + "configuration": { + "KEY_PREFIX": "configuration", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "celery": { + "KEY_PREFIX": "celery", + "TIMEOUT": "7200", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "course_structure_cache": { + "KEY_PREFIX": "course_structure", + "TIMEOUT": "7200", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + } + }, +{% include "apps/openedx/config/partials/auth.json" %} +} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/config/lms.env.json b/drydock/templates/kustomized/tutor13/base/apps/openedx/config/lms.env.json new file mode 100644 index 00000000..b3188f94 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/config/lms.env.json @@ -0,0 +1,97 @@ +{ + "SITE_NAME": "{{ LMS_HOST }}", + "BOOK_URL": "", + "LOG_DIR": "/openedx/data/logs", + "LOGGING_ENV": "sandbox", + "OAUTH_OIDC_ISSUER": "{{ JWT_COMMON_ISSUER }}", + "PLATFORM_NAME": "{{ PLATFORM_NAME }}", + "FEATURES": { + {{ patch("common-env-features", separator=",\n", suffix=",")|indent(4) }} + {{ patch("lms-env-features", separator=",\n", suffix=",")|indent(4) }} + "CERTIFICATES_HTML_VIEW": true, + "PREVIEW_LMS_BASE": "{{ PREVIEW_LMS_HOST }}", + "ENABLE_CORS_HEADERS": true, + "ENABLE_COURSE_DISCOVERY": true, + "ENABLE_COURSEWARE_SEARCH": true, + "ENABLE_CSMH_EXTENDED": false, + "ENABLE_DASHBOARD_SEARCH": true, + "ENABLE_COMBINED_LOGIN_REGISTRATION": true, + "ENABLE_GRADE_DOWNLOADS": true, + "ENABLE_LEARNER_RECORDS": false, + "ENABLE_MOBILE_REST_API": true, + "ENABLE_OAUTH2_PROVIDER": true, + "ENABLE_THIRD_PARTY_AUTH": true, + "MILESTONES_APP": true, + "ENABLE_PREREQUISITE_COURSES": true + }, + "LMS_ROOT_URL": "{{ "https" if ENABLE_HTTPS else "http" }}://{{ LMS_HOST }}", + "CMS_ROOT_URL": "{{ "https" if ENABLE_HTTPS else "http" }}://{{ CMS_HOST }}", + "CMS_BASE": "{{ CMS_HOST }}", + "LMS_BASE": "{{ LMS_HOST }}", + "CONTACT_EMAIL": "{{ CONTACT_EMAIL }}", + "CELERY_BROKER_TRANSPORT": "redis", + "CELERY_BROKER_HOSTNAME": "{{ REDIS_HOST }}:{{ REDIS_PORT }}", + "CELERY_BROKER_VHOST": "{{ OPENEDX_CELERY_REDIS_DB }}", + "CELERY_BROKER_USER": "{{ REDIS_USERNAME }}", + "CELERY_BROKER_PASSWORD": "{{ REDIS_PASSWORD }}", + "ALTERNATE_WORKER_QUEUES": "cms", + "ENABLE_COMPREHENSIVE_THEMING": true, + "COMPREHENSIVE_THEME_DIRS": ["/openedx/themes"], + "STATIC_ROOT_BASE": "/openedx/staticfiles", + "EMAIL_BACKEND": "django.core.mail.backends.smtp.EmailBackend", + "EMAIL_HOST": "{{ SMTP_HOST }}", + "EMAIL_PORT": {{ SMTP_PORT }}, + "EMAIL_USE_TLS": {{ "true" if SMTP_USE_TLS else "false" }}, + "ACE_ROUTING_KEY": "edx.lms.core.default", + "HTTPS": "{{ "on" if ENABLE_HTTPS else "off" }}", + "LANGUAGE_CODE": "{{ LANGUAGE_CODE }}", + "SESSION_COOKIE_DOMAIN": "{{ LMS_HOST }}", + {{ patch("lms-env", separator=",\n", suffix=",")|indent(2) }} + "CACHES": { + "default": { + "KEY_PREFIX": "default", + "VERSION": "1", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "general": { + "KEY_PREFIX": "general", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "mongo_metadata_inheritance": { + "KEY_PREFIX": "mongo_metadata_inheritance", + "TIMEOUT": 300, + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "staticfiles": { + "KEY_PREFIX": "staticfiles_lms", + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "staticfiles_lms" + }, + "configuration": { + "KEY_PREFIX": "configuration", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "celery": { + "KEY_PREFIX": "celery", + "TIMEOUT": "7200", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "course_structure_cache": { + "KEY_PREFIX": "course_structure", + "TIMEOUT": "7200", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + }, + "ora2-storage": { + "KEY_PREFIX": "ora2-storage", + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": "redis://{% if REDIS_USERNAME and REDIS_PASSWORD %}{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}{% endif %}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CACHE_REDIS_DB }}" + } + }, +{% include "apps/openedx/config/partials/auth.json" %} +} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/config/partials/auth.json b/drydock/templates/kustomized/tutor13/base/apps/openedx/config/partials/auth.json new file mode 100644 index 00000000..66e9422b --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/config/partials/auth.json @@ -0,0 +1,26 @@ + "SECRET_KEY": "{{ OPENEDX_SECRET_KEY }}", + "AWS_ACCESS_KEY_ID": "{{ OPENEDX_AWS_ACCESS_KEY }}", + "AWS_SECRET_ACCESS_KEY": "{{ OPENEDX_AWS_SECRET_ACCESS_KEY }}", + "CONTENTSTORE": null, + "DOC_STORE_CONFIG": null, + {{ patch("openedx-auth", separator=",\n", suffix=",")|indent(2) }} + "XQUEUE_INTERFACE": { + "django_auth": null, + "url": null + }, + "DATABASES": { + "default": { + "ENGINE": "django.db.backends.mysql", + "HOST": "{{ MYSQL_HOST }}", + "PORT": {{ MYSQL_PORT }}, + "NAME": "{{ OPENEDX_MYSQL_DATABASE }}", + "USER": "{{ OPENEDX_MYSQL_USERNAME }}", + "PASSWORD": "{{ OPENEDX_MYSQL_PASSWORD }}", + "ATOMIC_REQUESTS": true, + "OPTIONS": { + "init_command": "SET sql_mode='STRICT_TRANS_TABLES'" + } + } + }, + "EMAIL_HOST_USER": "{{ SMTP_USERNAME }}", + "EMAIL_HOST_PASSWORD": "{{ SMTP_PASSWORD }}" \ No newline at end of file diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/__init__.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/development.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/development.py new file mode 100644 index 00000000..5ba365da --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/development.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +import os +from cms.envs.devstack import * + +LMS_BASE = "{{ LMS_HOST }}:8000" +LMS_ROOT_URL = "http://" + LMS_BASE + +# Authentication +SOCIAL_AUTH_EDX_OAUTH2_KEY = "{{ CMS_OAUTH2_KEY_SSO_DEV }}" +SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT = LMS_ROOT_URL + +FEATURES["PREVIEW_LMS_BASE"] = "{{ PREVIEW_LMS_HOST }}:8000" + +{% include "apps/openedx/settings/partials/common_cms.py" %} + +# Setup correct webpack configuration file for development +WEBPACK_CONFIG_PATH = "webpack.dev.config.js" + +{{ patch("openedx-development-settings") }} +{{ patch("openedx-cms-development-settings") }} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/production.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/production.py new file mode 100644 index 00000000..d09456e3 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/production.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +import os +from cms.envs.production import * + +{% include "apps/openedx/settings/partials/common_cms.py" %} + +ALLOWED_HOSTS = [ + ENV_TOKENS.get("CMS_BASE"), + "cms", +] + +# Authentication +SOCIAL_AUTH_EDX_OAUTH2_KEY = "{{ CMS_OAUTH2_KEY_SSO }}" +SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}" + +{{ patch("openedx-cms-production-settings") }} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/test.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/test.py new file mode 100644 index 00000000..14a3e3db --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/cms/test.py @@ -0,0 +1,3 @@ +from cms.envs.test import * + +{% include "apps/openedx/settings/partials/common_test.py" %} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/__init__.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/development.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/development.py new file mode 100644 index 00000000..e30aabb6 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/development.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +import os +from lms.envs.devstack import * + +{% include "apps/openedx/settings/partials/common_lms.py" %} + +# Setup correct webpack configuration file for development +WEBPACK_CONFIG_PATH = "webpack.dev.config.js" + +LMS_BASE = "{{ LMS_HOST}}:8000" +LMS_ROOT_URL = "http://{}".format(LMS_BASE) +LMS_INTERNAL_ROOT_URL = LMS_ROOT_URL +SITE_NAME = LMS_BASE +CMS_BASE = "{{ CMS_HOST}}:8001" +CMS_ROOT_URL = "http://{}".format(CMS_BASE) +LOGIN_REDIRECT_WHITELIST.append(CMS_BASE) + +# Session cookie +SESSION_COOKIE_DOMAIN = "{{ LMS_HOST }}" +SESSION_COOKIE_SECURE = False +CSRF_COOKIE_SECURE = False +SESSION_COOKIE_SAMESITE = "Lax" + +# CMS authentication +IDA_LOGOUT_URI_LIST.append("http://{{ CMS_HOST }}:8001/logout/") + +FEATURES['ENABLE_COURSEWARE_MICROFRONTEND'] = False + +LOGGING["loggers"]["oauth2_provider"] = { + "handlers": ["console"], + "level": "DEBUG" +} + +{{ patch("openedx-development-settings") }} +{{ patch("openedx-lms-development-settings") }} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/production.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/production.py new file mode 100644 index 00000000..b859b5fe --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/production.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +import os +from lms.envs.production import * + +{% include "apps/openedx/settings/partials/common_lms.py" %} + +ALLOWED_HOSTS = [ + ENV_TOKENS.get("LMS_BASE"), + FEATURES["PREVIEW_LMS_BASE"], + "lms", +] + +{% if ENABLE_HTTPS %} +# Properly set the "secure" attribute on session/csrf cookies. This is required in +# Chrome to support samesite=none cookies. +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +SESSION_COOKIE_SAMESITE = "None" +{% else %} +# When we cannot provide secure session/csrf cookies, we must disable samesite=none +SESSION_COOKIE_SECURE = False +CSRF_COOKIE_SECURE = False +SESSION_COOKIE_SAMESITE = "Lax" +{% endif %} + +# CMS authentication +IDA_LOGOUT_URI_LIST.append("{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ CMS_HOST }}/logout/") + +# Required to display all courses on start page +SEARCH_SKIP_ENROLLMENT_START_DATE_FILTERING = True + +{{ patch("openedx-lms-production-settings") }} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/test.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/test.py new file mode 100644 index 00000000..a5fc3518 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/lms/test.py @@ -0,0 +1,3 @@ +from lms.envs.test import * + +{% include "apps/openedx/settings/partials/common_test.py" %} diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_all.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_all.py new file mode 100644 index 00000000..d6a92914 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_all.py @@ -0,0 +1,174 @@ +####### Settings common to LMS and CMS +import json +import os + +from xmodule.modulestore.modulestore_settings import update_module_store_settings + +# Mongodb connection parameters: simply modify `mongodb_parameters` to affect all connections to MongoDb. +mongodb_parameters = { + "host": "{{ MONGODB_HOST }}", + "port": {{ MONGODB_PORT }}, + {% if MONGODB_USERNAME and MONGODB_PASSWORD %} + "user": "{{ MONGODB_USERNAME }}", + "password": "{{ MONGODB_PASSWORD }}", + {% else %} + "user": None, + "password": None, + {% endif %} + "db": "{{ MONGODB_DATABASE }}", +} +DOC_STORE_CONFIG = mongodb_parameters +CONTENTSTORE = { + "ENGINE": "xmodule.contentstore.mongo.MongoContentStore", + "ADDITIONAL_OPTIONS": {}, + "DOC_STORE_CONFIG": DOC_STORE_CONFIG +} +# Load module store settings from config files +update_module_store_settings(MODULESTORE, doc_store_settings=DOC_STORE_CONFIG) +DATA_DIR = "/openedx/data/modulestore" + +for store in MODULESTORE["default"]["OPTIONS"]["stores"]: + store["OPTIONS"]["fs_root"] = DATA_DIR + +# Behave like memcache when it comes to connection errors +DJANGO_REDIS_IGNORE_EXCEPTIONS = True + +# Elasticsearch connection parameters +ELASTIC_SEARCH_CONFIG = [{ + {% if ELASTICSEARCH_SCHEME == "https" %}"use_ssl": True,{% endif %} + "host": "{{ ELASTICSEARCH_HOST }}", + "port": {{ ELASTICSEARCH_PORT }}, +}] + +CONTACT_MAILING_ADDRESS = "{{ PLATFORM_NAME }} - {% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}" + +DEFAULT_FROM_EMAIL = ENV_TOKENS.get("DEFAULT_FROM_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS.get("DEFAULT_FEEDBACK_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +SERVER_EMAIL = ENV_TOKENS.get("SERVER_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +TECH_SUPPORT_EMAIL = ENV_TOKENS.get("TECH_SUPPORT_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +CONTACT_EMAIL = ENV_TOKENS.get("CONTACT_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +BUGS_EMAIL = ENV_TOKENS.get("BUGS_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +UNIVERSITY_EMAIL = ENV_TOKENS.get("UNIVERSITY_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +PRESS_EMAIL = ENV_TOKENS.get("PRESS_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +PAYMENT_SUPPORT_EMAIL = ENV_TOKENS.get("PAYMENT_SUPPORT_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +BULK_EMAIL_DEFAULT_FROM_EMAIL = ENV_TOKENS.get("BULK_EMAIL_DEFAULT_FROM_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +API_ACCESS_MANAGER_EMAIL = ENV_TOKENS.get("API_ACCESS_MANAGER_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) +API_ACCESS_FROM_EMAIL = ENV_TOKENS.get("API_ACCESS_FROM_EMAIL", ENV_TOKENS["CONTACT_EMAIL"]) + +# Get rid completely of coursewarehistoryextended, as we do not use the CSMH database +INSTALLED_APPS.remove("lms.djangoapps.coursewarehistoryextended") +DATABASE_ROUTERS.remove( + "openedx.core.lib.django_courseware_routers.StudentModuleHistoryExtendedRouter" +) + +# Set uploaded media file path +MEDIA_ROOT = "/openedx/media/" + +# Add your MFE and third-party app domains here +CORS_ORIGIN_WHITELIST = [] + +# Video settings +VIDEO_IMAGE_SETTINGS["STORAGE_KWARGS"]["location"] = MEDIA_ROOT +VIDEO_TRANSCRIPTS_SETTINGS["STORAGE_KWARGS"]["location"] = MEDIA_ROOT + +GRADES_DOWNLOAD = { + "STORAGE_TYPE": "", + "STORAGE_KWARGS": { + "base_url": "/media/grades/", + "location": "/openedx/media/grades", + }, +} + +ORA2_FILEUPLOAD_BACKEND = "filesystem" +ORA2_FILEUPLOAD_ROOT = "/openedx/data/ora2" +ORA2_FILEUPLOAD_CACHE_NAME = "ora2-storage" + +# Change syslog-based loggers which don't work inside docker containers +LOGGING["handlers"]["local"] = { + "class": "logging.handlers.WatchedFileHandler", + "filename": os.path.join(LOG_DIR, "all.log"), + "formatter": "standard", +} +LOGGING["handlers"]["tracking"] = { + "level": "DEBUG", + "class": "logging.handlers.WatchedFileHandler", + "filename": os.path.join(LOG_DIR, "tracking.log"), + "formatter": "standard", +} +LOGGING["loggers"]["tracking"]["handlers"] = ["console", "local", "tracking"] +# Silence some loggers (note: we must attempt to get rid of these when upgrading from one release to the next) + +import warnings +from django.utils.deprecation import RemovedInDjango40Warning, RemovedInDjango41Warning +warnings.filterwarnings("ignore", category=RemovedInDjango40Warning) +warnings.filterwarnings("ignore", category=RemovedInDjango41Warning) +warnings.filterwarnings("ignore", category=DeprecationWarning, module="lms.djangoapps.course_wiki.plugins.markdownedx.wiki_plugin") +warnings.filterwarnings("ignore", category=DeprecationWarning, module="wiki.plugins.links.wiki_plugin") + +# Email +EMAIL_USE_SSL = {{ SMTP_USE_SSL }} +# Forward all emails from edX's Automated Communication Engine (ACE) to django. +ACE_ENABLED_CHANNELS = ["django_email"] +ACE_CHANNEL_DEFAULT_EMAIL = "django_email" +ACE_CHANNEL_TRANSACTIONAL_EMAIL = "django_email" +EMAIL_FILE_PATH = "/tmp/openedx/emails" + +# Language/locales +LOCALE_PATHS.append("/openedx/locale/contrib/locale") +LOCALE_PATHS.append("/openedx/locale/user/locale") +LANGUAGE_COOKIE_NAME = "openedx-language-preference" + +# Allow the platform to include itself in an iframe +X_FRAME_OPTIONS = "SAMEORIGIN" + +{% set jwt_rsa_key = rsa_import_key(JWT_RSA_PRIVATE_KEY) %} +JWT_AUTH["JWT_ISSUER"] = "{{ JWT_COMMON_ISSUER }}" +JWT_AUTH["JWT_AUDIENCE"] = "{{ JWT_COMMON_AUDIENCE }}" +JWT_AUTH["JWT_SECRET_KEY"] = "{{ JWT_COMMON_SECRET_KEY }}" +JWT_AUTH["JWT_PRIVATE_SIGNING_JWK"] = json.dumps( + { + "kid": "openedx", + "kty": "RSA", + "e": "{{ jwt_rsa_key.e|long_to_base64 }}", + "d": "{{ jwt_rsa_key.d|long_to_base64 }}", + "n": "{{ jwt_rsa_key.n|long_to_base64 }}", + "p": "{{ jwt_rsa_key.p|long_to_base64 }}", + "q": "{{ jwt_rsa_key.q|long_to_base64 }}", + } +) +JWT_AUTH["JWT_PUBLIC_SIGNING_JWK_SET"] = json.dumps( + { + "keys": [ + { + "kid": "openedx", + "kty": "RSA", + "e": "{{ jwt_rsa_key.e|long_to_base64 }}", + "n": "{{ jwt_rsa_key.n|long_to_base64 }}", + } + ] + } +) +JWT_AUTH["JWT_ISSUERS"] = [ + { + "ISSUER": "{{ JWT_COMMON_ISSUER }}", + "AUDIENCE": "{{ JWT_COMMON_AUDIENCE }}", + "SECRET_KEY": "{{ OPENEDX_SECRET_KEY }}" + } +] + +# Enable/Disable some features globally +FEATURES["ENABLE_DISCUSSION_SERVICE"] = False +FEATURES["PREVENT_CONCURRENT_LOGINS"] = False + +# Disable codejail support +# explicitely configuring python is necessary to prevent unsafe calls +import codejail.jail_code +codejail.jail_code.configure("python", "nonexistingpythonbinary", user=None) +# another configuration entry is required to override prod/dev settings +CODE_JAIL = { + "python_bin": "nonexistingpythonbinary", + "user": None, +} + +{{ patch("openedx-common-settings") }} +######## End of settings common to LMS and CMS diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_cms.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_cms.py new file mode 100644 index 00000000..d6093459 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_cms.py @@ -0,0 +1,25 @@ +{% include "apps/openedx/settings/partials/common_all.py" %} + +######## Common CMS settings + +STUDIO_NAME = u"{{ PLATFORM_NAME }} - Studio" + +# Authentication +SOCIAL_AUTH_EDX_OAUTH2_SECRET = "{{ CMS_OAUTH2_SECRET }}" +SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT = "http://lms:8000" +SOCIAL_AUTH_REDIRECT_IS_HTTPS = False # scheme is correctly included in redirect_uri +SESSION_COOKIE_NAME = "studio_session_id" + +MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB = 100 + +FRONTEND_LOGIN_URL = LMS_ROOT_URL + '/login' +FRONTEND_REGISTER_URL = LMS_ROOT_URL + '/register' + +# Create folders if necessary +for folder in [LOG_DIR, MEDIA_ROOT, STATIC_ROOT_BASE]: + if not os.path.exists(folder): + os.makedirs(folder) + +{{ patch("openedx-cms-common-settings") }} + +######## End of common CMS settings diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_lms.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_lms.py new file mode 100644 index 00000000..2398a7e9 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_lms.py @@ -0,0 +1,31 @@ +{% include "apps/openedx/settings/partials/common_all.py" %} + +######## Common LMS settings +LOGIN_REDIRECT_WHITELIST = ["{{ CMS_HOST }}"] + +# Better layout of honor code/tos links during registration +REGISTRATION_EXTRA_FIELDS["terms_of_service"] = "required" +REGISTRATION_EXTRA_FIELDS["honor_code"] = "hidden" + +# Fix media files paths +PROFILE_IMAGE_BACKEND["options"]["location"] = os.path.join( + MEDIA_ROOT, "profile-images/" +) + +COURSE_CATALOG_VISIBILITY_PERMISSION = "see_in_catalog" +COURSE_ABOUT_VISIBILITY_PERMISSION = "see_about_page" + +# Allow insecure oauth2 for local interaction with local containers +OAUTH_ENFORCE_SECURE = False + +# Email settings +DEFAULT_EMAIL_LOGO_URL = LMS_ROOT_URL + "/theming/asset/images/logo.png" + +# Create folders if necessary +for folder in [DATA_DIR, LOG_DIR, MEDIA_ROOT, STATIC_ROOT_BASE, ORA2_FILEUPLOAD_ROOT]: + if not os.path.exists(folder): + os.makedirs(folder, exist_ok=True) + +{{ patch("openedx-lms-common-settings") }} + +######## End of common LMS settings diff --git a/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_test.py b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_test.py new file mode 100644 index 00000000..290ac292 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/openedx/settings/partials/common_test.py @@ -0,0 +1,3 @@ +# Fix MongoDb connection credentials +DOC_STORE_CONFIG["user"] = None +DOC_STORE_CONFIG["password"] = None diff --git a/drydock/templates/kustomized/tutor13/base/apps/redis/redis.conf b/drydock/templates/kustomized/tutor13/base/apps/redis/redis.conf new file mode 100644 index 00000000..cfdf709d --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/apps/redis/redis.conf @@ -0,0 +1,41 @@ +# https://raw.githubusercontent.com/redis/redis/6.0/redis.conf +port 6379 + +tcp-backlog 511 +timeout 0 +tcp-keepalive 300 +daemonize no +supervised no +pidfile /var/run/redis_6379.pid +loglevel notice +logfile "" +databases 16 + +################################ SNAPSHOTTING ################################ +# +# Save the DB on disk: +# +# save + +save 900 1 +save 300 10 +save 60 10000 + +stop-writes-on-bgsave-error yes +rdbcompression yes +rdbchecksum yes +dir /openedx/redis/data/ +dbfilename dump.rdb +rdb-del-sync-files no + +############################## APPEND ONLY MODE ############################### + +# http://redis.io/topics/persistence +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec +no-appendfsync-on-rewrite no +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb +aof-load-truncated yes +aof-use-rdb-preamble yes diff --git a/drydock/templates/kustomized/tutor13/base/k8s/deployments.yml b/drydock/templates/kustomized/tutor13/base/k8s/deployments.yml new file mode 100644 index 00000000..6f8098c2 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/k8s/deployments.yml @@ -0,0 +1,467 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: caddy + labels: + app.kubernetes.io/name: caddy +spec: + selector: + matchLabels: + app.kubernetes.io/name: caddy + template: + metadata: + labels: + app.kubernetes.io/name: caddy + spec: + {%- if ENABLE_WEB_PROXY %} + # This Deployment uses a persistent volume claim. This requires + # that in order to enable rolling updates (i.e. use a deployment + # strategy other than Replace), we schedule the new Pod to the + # same node as the original Pod. + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - caddy + topologyKey: "kubernetes.io/hostname" + {%- endif %} + containers: + - name: caddy + image: {{ DOCKER_IMAGE_CADDY }} + env: + - name: default_site_port + value: "{% if not ENABLE_HTTPS or not ENABLE_WEB_PROXY %}:80{% endif %}" + volumeMounts: + - mountPath: /etc/caddy/ + name: config + {%- if ENABLE_WEB_PROXY %} + - mountPath: /data/ + name: data + {%- endif %} + ports: + - containerPort: 80 + {%- if ENABLE_WEB_PROXY %} + - containerPort: 443 + {%- endif %} + volumes: + - name: config + configMap: + name: caddy-config + {%- if ENABLE_WEB_PROXY %} + - name: data + persistentVolumeClaim: + claimName: caddy + {%- endif %} +{% if RUN_CMS %} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cms + labels: + app.kubernetes.io/name: cms +spec: + selector: + matchLabels: + app.kubernetes.io/name: cms + template: + metadata: + labels: + app.kubernetes.io/name: cms + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + containers: + - name: cms + image: {{ DOCKER_IMAGE_OPENEDX }} + env: + - name: SERVICE_VARIANT + value: cms + - name: DJANGO_SETTINGS_MODULE + value: cms.envs.tutor.production + ports: + - containerPort: 8000 + volumeMounts: + - mountPath: /openedx/edx-platform/lms/envs/tutor/ + name: settings-lms + - mountPath: /openedx/edx-platform/cms/envs/tutor/ + name: settings-cms + - mountPath: /openedx/config + name: config + resources: + requests: + memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + volumes: + - name: settings-lms + configMap: + name: openedx-settings-lms + - name: settings-cms + configMap: + name: openedx-settings-cms + - name: config + configMap: + name: openedx-config +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cms-worker + labels: + app.kubernetes.io/name: cms-worker +spec: + selector: + matchLabels: + app.kubernetes.io/name: cms-worker + template: + metadata: + labels: + app.kubernetes.io/name: cms-worker + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + containers: + - name: cms-worker + image: {{ DOCKER_IMAGE_OPENEDX }} + args: ["celery", "worker", "--app=cms.celery", "--loglevel=info", "--hostname=edx.cms.core.default.%%h", "--maxtasksperchild", "100", "--exclude-queues=edx.lms.core.default"] + env: + - name: SERVICE_VARIANT + value: cms + - name: DJANGO_SETTINGS_MODULE + value: cms.envs.tutor.production + volumeMounts: + - mountPath: /openedx/edx-platform/lms/envs/tutor/ + name: settings-lms + - mountPath: /openedx/edx-platform/cms/envs/tutor/ + name: settings-cms + - mountPath: /openedx/config + name: config + securityContext: + allowPrivilegeEscalation: false + volumes: + - name: settings-lms + configMap: + name: openedx-settings-lms + - name: settings-cms + configMap: + name: openedx-settings-cms + - name: config + configMap: + name: openedx-config +{% endif %} +{% if RUN_LMS %} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lms + labels: + app.kubernetes.io/name: lms +spec: + selector: + matchLabels: + app.kubernetes.io/name: lms + template: + metadata: + labels: + app.kubernetes.io/name: lms + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + containers: + - name: lms + image: {{ DOCKER_IMAGE_OPENEDX }} + env: + - name: SERVICE_VARIANT + value: lms + - name: DJANGO_SETTINGS_MODULE + value: lms.envs.tutor.production + ports: + - containerPort: 8000 + volumeMounts: + - mountPath: /openedx/edx-platform/lms/envs/tutor/ + name: settings-lms + - mountPath: /openedx/edx-platform/cms/envs/tutor/ + name: settings-cms + - mountPath: /openedx/config + name: config + resources: + requests: + memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + volumes: + - name: settings-lms + configMap: + name: openedx-settings-lms + - name: settings-cms + configMap: + name: openedx-settings-cms + - name: config + configMap: + name: openedx-config +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lms-worker + labels: + app.kubernetes.io/name: lms-worker +spec: + selector: + matchLabels: + app.kubernetes.io/name: lms-worker + template: + metadata: + labels: + app.kubernetes.io/name: lms-worker + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + containers: + - name: lms-worker + image: {{ DOCKER_IMAGE_OPENEDX }} + args: ["celery", "worker", "--app=lms.celery", "--loglevel=info", "--hostname=edx.lms.core.default.%%h", "--maxtasksperchild=100", "--exclude-queues=edx.cms.core.default"] + env: + - name: SERVICE_VARIANT + value: lms + - name: DJANGO_SETTINGS_MODULE + value: lms.envs.tutor.production + volumeMounts: + - mountPath: /openedx/edx-platform/lms/envs/tutor/ + name: settings-lms + - mountPath: /openedx/edx-platform/cms/envs/tutor/ + name: settings-cms + - mountPath: /openedx/config + name: config + securityContext: + allowPrivilegeEscalation: false + volumes: + - name: settings-lms + configMap: + name: openedx-settings-lms + - name: settings-cms + configMap: + name: openedx-settings-cms + - name: config + configMap: + name: openedx-config +{% endif %} +{% if RUN_ELASTICSEARCH %} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: elasticsearch + labels: + app.kubernetes.io/name: elasticsearch +spec: + selector: + matchLabels: + app.kubernetes.io/name: elasticsearch + strategy: + type: Recreate + template: + metadata: + labels: + app.kubernetes.io/name: elasticsearch + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + containers: + - name: elasticsearch + image: {{ DOCKER_IMAGE_ELASTICSEARCH }} + env: + - name: cluster.name + value: "openedx" + - name: bootstrap.memory_lock + value: "true" + - name: discovery.type + value: "single-node" + - name: ES_JAVA_OPTS + value: "-Xms{{ ELASTICSEARCH_HEAP_SIZE }} -Xmx{{ ELASTICSEARCH_HEAP_SIZE }}" + - name: TAKE_FILE_OWNERSHIP + value: "1" + ports: + - containerPort: 9200 + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - mountPath: /usr/share/elasticsearch/data + name: data + volumes: + - name: data + persistentVolumeClaim: + claimName: elasticsearch +{% endif %} +{% if RUN_MONGODB %} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mongodb + labels: + app.kubernetes.io/name: mongodb +spec: + selector: + matchLabels: + app.kubernetes.io/name: mongodb + strategy: + type: Recreate + template: + metadata: + labels: + app.kubernetes.io/name: mongodb + spec: + securityContext: + runAsUser: 999 + runAsGroup: 999 + fsGroup: 999 + fsGroupChangePolicy: "OnRootMismatch" + containers: + - name: mongodb + image: {{ DOCKER_IMAGE_MONGODB }} + args: ["mongod", "--nojournal", "--storageEngine", "wiredTiger"] + ports: + - containerPort: 27017 + volumeMounts: + - mountPath: /data/db + name: data + securityContext: + allowPrivilegeEscalation: false + volumes: + - name: data + persistentVolumeClaim: + claimName: mongodb +{% endif %} +{% if RUN_MYSQL %} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql + labels: + app.kubernetes.io/name: mysql +spec: + selector: + matchLabels: + app.kubernetes.io/name: mysql + strategy: + type: Recreate + template: + metadata: + labels: + app.kubernetes.io/name: mysql + spec: + securityContext: + runAsUser: 999 + runAsGroup: 999 + fsGroup: 999 + fsGroupChangePolicy: "OnRootMismatch" + containers: + - name: mysql + image: {{ DOCKER_IMAGE_MYSQL }} + # Note the ignore-db-dir: this is because ext4 volumes are created with a lost+found directory in them, which causes mysql + # initialisation to fail + args: ["mysqld", "--character-set-server=utf8", "--collation-server=utf8_general_ci", "--ignore-db-dir=lost+found"] + env: + - name: MYSQL_ROOT_PASSWORD + value: "{{ MYSQL_ROOT_PASSWORD }}" + ports: + - containerPort: 3306 + volumeMounts: + - mountPath: /var/lib/mysql + name: data + securityContext: + allowPrivilegeEscalation: false + volumes: + - name: data + persistentVolumeClaim: + claimName: mysql +{% endif %} +{% if RUN_SMTP %} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: smtp + labels: + app.kubernetes.io/name: smtp +spec: + selector: + matchLabels: + app.kubernetes.io/name: smtp + template: + metadata: + labels: + app.kubernetes.io/name: smtp + spec: + securityContext: + runAsUser: 100 + runAsGroup: 101 + containers: + - name: smtp + image: {{ DOCKER_IMAGE_SMTP }} + ports: + - containerPort: 8025 +{% endif %} +{% if RUN_REDIS %} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app.kubernetes.io/name: redis +spec: + selector: + matchLabels: + app.kubernetes.io/name: redis + strategy: + type: Recreate + template: + metadata: + labels: + app.kubernetes.io/name: redis + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + fsGroupChangePolicy: "OnRootMismatch" + containers: + - name: redis + image: {{ DOCKER_IMAGE_REDIS }} + args: ["redis-server", "/openedx/redis/config/redis.conf"] + workingDir: /openedx/redis/data + ports: + - containerPort: {{ REDIS_PORT }} + volumeMounts: + - mountPath: /openedx/redis/config/ + name: config + - mountPath: /openedx/redis/data + name: data + securityContext: + allowPrivilegeEscalation: false + volumes: + - name: config + configMap: + name: redis-config + - name: data + persistentVolumeClaim: + claimName: redis +{% endif %} +{{ patch("k8s-deployments") }} diff --git a/drydock/templates/kustomized/tutor13/base/k8s/jobs.yml b/drydock/templates/kustomized/tutor13/base/k8s/jobs.yml new file mode 100644 index 00000000..7b751360 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/k8s/jobs.yml @@ -0,0 +1,88 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: lms-job + labels: + app.kubernetes.io/component: job +spec: + template: + spec: + restartPolicy: Never + containers: + - name: lms + image: {{ DOCKER_IMAGE_OPENEDX }} + env: + - name: SERVICE_VARIANT + value: lms + - name: DJANGO_SETTINGS_MODULE + value: lms.envs.tutor.production + volumeMounts: + - mountPath: /openedx/edx-platform/lms/envs/tutor/ + name: settings-lms + - mountPath: /openedx/edx-platform/cms/envs/tutor/ + name: settings-cms + - mountPath: /openedx/config + name: config + volumes: + - name: settings-lms + configMap: + name: openedx-settings-lms + - name: settings-cms + configMap: + name: openedx-settings-cms + - name: config + configMap: + name: openedx-config +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: cms-job + labels: + app.kubernetes.io/component: job +spec: + template: + spec: + restartPolicy: Never + containers: + - name: cms + image: {{ DOCKER_IMAGE_OPENEDX }} + env: + - name: SERVICE_VARIANT + value: cms + - name: DJANGO_SETTINGS_MODULE + value: cms.envs.tutor.production + volumeMounts: + - mountPath: /openedx/edx-platform/lms/envs/tutor/ + name: settings-lms + - mountPath: /openedx/edx-platform/cms/envs/tutor/ + name: settings-cms + - mountPath: /openedx/config + name: config + volumes: + - name: settings-lms + configMap: + name: openedx-settings-lms + - name: settings-cms + configMap: + name: openedx-settings-cms + - name: config + configMap: + name: openedx-config +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: mysql-job + labels: + app.kubernetes.io/component: job +spec: + template: + spec: + restartPolicy: Never + containers: + - name: mysql + image: {{ DOCKER_IMAGE_MYSQL }} + +{{ patch("k8s-jobs") }} diff --git a/drydock/templates/kustomized/tutor13/base/k8s/namespace.yml b/drydock/templates/kustomized/tutor13/base/k8s/namespace.yml new file mode 100644 index 00000000..d1b35d97 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/k8s/namespace.yml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: {{ K8S_NAMESPACE }} + labels: + app.kubernetes.io/component: namespace \ No newline at end of file diff --git a/drydock/templates/kustomized/tutor13/base/k8s/services.yml b/drydock/templates/kustomized/tutor13/base/k8s/services.yml new file mode 100644 index 00000000..180032ba --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/k8s/services.yml @@ -0,0 +1,149 @@ +{% if ENABLE_WEB_PROXY %} +--- +apiVersion: v1 +kind: Service +metadata: + name: caddy + labels: + app.kubernetes.io/name: caddy + app.kubernetes.io/component: loadbalancer +spec: + type: LoadBalancer + ports: + - port: 80 + name: http + {%- if ENABLE_HTTPS %} + - port: 443 + name: https + {%- endif %} + selector: + app.kubernetes.io/name: caddy +{% else %} +--- +apiVersion: v1 +kind: Service +metadata: + name: caddy + labels: + app.kubernetes.io/name: caddy +spec: + type: ClusterIP + ports: + - port: {{ CADDY_HTTP_PORT }} + name: http + selector: + app.kubernetes.io/name: caddy +{% endif %} +{% if RUN_CMS %} +--- +apiVersion: v1 +kind: Service +metadata: + name: cms + labels: + app.kubernetes.io/name: cms +spec: + type: ClusterIP + ports: + - port: 8000 + protocol: TCP + selector: + app.kubernetes.io/name: cms +{% endif %} +{% if RUN_LMS %} +--- +apiVersion: v1 +kind: Service +metadata: + name: lms + labels: + app.kubernetes.io/name: lms +spec: + type: ClusterIP + ports: + - port: 8000 + protocol: TCP + selector: + app.kubernetes.io/name: lms +{% endif %} +{% if RUN_ELASTICSEARCH %} +--- +apiVersion: v1 +kind: Service +metadata: + name: elasticsearch + labels: + app.kubernetes.io/name: elasticsearch +spec: + type: ClusterIP + ports: + - port: 9200 + protocol: TCP + selector: + app.kubernetes.io/name: elasticsearch +{% endif %} +{% if RUN_MONGODB %} +--- +apiVersion: v1 +kind: Service +metadata: + name: mongodb + labels: + app.kubernetes.io/name: mongodb +spec: + type: ClusterIP + ports: + - port: 27017 + protocol: TCP + selector: + app.kubernetes.io/name: mongodb +{% endif %} +{% if RUN_MYSQL %} +--- +apiVersion: v1 +kind: Service +metadata: + name: mysql + labels: + app.kubernetes.io/name: mysql +spec: + type: ClusterIP + ports: + - port: 3306 + protocol: TCP + selector: + app.kubernetes.io/name: mysql +{% endif %} +{% if RUN_REDIS %} +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + labels: + app.kubernetes.io/name: redis +spec: + type: ClusterIP + ports: + - port: {{ REDIS_PORT }} + protocol: TCP + selector: + app.kubernetes.io/name: redis +{% endif %} +{% if RUN_SMTP %} +--- +apiVersion: v1 +kind: Service +metadata: + name: smtp + labels: + app.kubernetes.io/name: smtp +spec: + type: ClusterIP + ports: + - port: 8025 + protocol: TCP + selector: + app.kubernetes.io/name: smtp +{% endif %} +{{ patch("k8s-services") }} diff --git a/drydock/templates/kustomized/tutor13/base/k8s/volumes.yml b/drydock/templates/kustomized/tutor13/base/k8s/volumes.yml new file mode 100644 index 00000000..ffb4b664 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/k8s/volumes.yml @@ -0,0 +1,81 @@ +{% if ENABLE_WEB_PROXY %} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: caddy + labels: + app.kubernetes.io/component: volume + app.kubernetes.io/name: caddy +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +{% endif %} +{% if RUN_ELASTICSEARCH %} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: elasticsearch + labels: + app.kubernetes.io/component: volume + app.kubernetes.io/name: elasticsearch +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi +{% endif %} +{% if RUN_MONGODB %} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mongodb + labels: + app.kubernetes.io/component: volume + app.kubernetes.io/name: mongodb +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +{% endif %} +{% if RUN_MYSQL %} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mysql + labels: + app.kubernetes.io/component: volume + app.kubernetes.io/name: mysql +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +{% endif %} +{% if RUN_REDIS %} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis + labels: + app.kubernetes.io/component: volume + app.kubernetes.io/name: redis +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +{% endif %} +{{ patch("k8s-volumes") }} \ No newline at end of file diff --git a/drydock/templates/kustomized/tutor13/base/kustomization.yml b/drydock/templates/kustomized/tutor13/base/kustomization.yml new file mode 100644 index 00000000..7d807284 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/base/kustomization.yml @@ -0,0 +1,65 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- k8s/namespace.yml +- k8s/deployments.yml +- k8s/services.yml +- k8s/volumes.yml +{%- if not DRYDOCK_DISABLE_JOBS | default(false) %} +- k8s/jobs.yml +{%- endif %} + +{{ patch("kustomization-resources") }} + +# namespace to deploy all Resources to +namespace: {{ K8S_NAMESPACE }} + +# annotations added to all Resources +# https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/commonannotations/ +commonAnnotations: + app.kubernetes.io/version: {{ TUTOR_VERSION }} + +# labels (and label selectors) added to all Resources +# https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +# https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/commonlabels/ +commonLabels: + app.kubernetes.io/instance: openedx-{{ ID }} + app.kubernetes.io/part-of: openedx + app.kubernetes.io/managed-by: tutor + {{ patch("kustomization-commonlabels")|indent(2) }} + +configMapGenerator: +- name: caddy-config + files: + - apps/caddy/Caddyfile + options: + labels: + app.kubernetes.io/name: caddy +- name: openedx-settings-lms + files:{% for file in "apps/openedx/settings/lms"|walk_templates %} + - {{ file }}{% endfor %} + options: + labels: + app.kubernetes.io/name: openedx +- name: openedx-settings-cms + files:{% for file in "apps/openedx/settings/cms"|walk_templates %} + - {{ file }}{% endfor %} + options: + labels: + app.kubernetes.io/name: openedx +- name: openedx-config + files:{% for file in "apps/openedx/config"|walk_templates %} + - {{ file }}{% endfor %} + options: + labels: + app.kubernetes.io/name: openedx +- name: redis-config + files: + - apps/redis/redis.conf + options: + labels: + app.kubernetes.io/name: redis +{{ patch("kustomization-configmapgenerator") }} + +{{ patch("kustomization") }} diff --git a/drydock/templates/kustomized/tutor13/defaults.yml b/drydock/templates/kustomized/tutor13/defaults.yml new file mode 100644 index 00000000..9d2ce2a1 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/defaults.yml @@ -0,0 +1,35 @@ +DRYDOCK_CMS_LIMIT_CPU: 1 +DRYDOCK_CMS_LIMIT_MEMORY: "2Gi" +DRYDOCK_CMS_MAX_REPLICAS: 3 +DRYDOCK_CMS_MIN_REPLICAS: 3 +DRYDOCK_CMS_REQUEST_CPU: "600m" +DRYDOCK_CMS_REQUEST_MEMORY: "1Gi" +DRYDOCK_CMS_TARGET_CPU: 90 +DRYDOCK_CMS_WORKERS_MAX_REPLICAS: 3 +DRYDOCK_CMS_WORKERS_MIN_REPLICAS: 1 +DRYDOCK_CMS_WORKERS_TARGET_CPU: 90 +DRYDOCK_CMS_WORKER_LIMIT_CPU: 1 +DRYDOCK_CMS_WORKER_LIMIT_MEMORY: "2Gi" +DRYDOCK_CMS_WORKER_REQUEST_CPU: "600m" +DRYDOCK_CMS_WORKER_REQUEST_MEMORY: "1Gi" +DRYDOCK_DISABLE_JOBS: False +DRYDOCK_ENABLE_OVERRIDES: False +DRYDOCK_FLOWER: False +DRYDOCK_HPA: False +DRYDOCK_ENABLE_RESOURCE_MANAGEMENT: False +DRYDOCK_INGRESS: False +DRYDOCK_INGRESS_EXTRA_HOSTS: [] +DRYDOCK_LMS_LIMIT_CPU: 1 +DRYDOCK_LMS_LIMIT_MEMORY: "2Gi" +DRYDOCK_LMS_MAX_REPLICAS: 1 +DRYDOCK_LMS_MIN_REPLICAS: 1 +DRYDOCK_LMS_REQUEST_CPU: "600m" +DRYDOCK_LMS_REQUEST_MEMORY: "1Gi" +DRYDOCK_LMS_TARGET_CPU: 90 +DRYDOCK_LMS_WORKERS_MAX_REPLICAS: 3 +DRYDOCK_LMS_WORKERS_MIN_REPLICAS: 1 +DRYDOCK_LMS_WORKERS_TARGET_CPU: 90 +DRYDOCK_LMS_WORKER_LIMIT_CPU: 1 +DRYDOCK_LMS_WORKER_LIMIT_MEMORY: "2Gi" +DRYDOCK_LMS_WORKER_REQUEST_CPU: "600m" +DRYDOCK_LMS_WORKER_REQUEST_MEMORY: "1Gi" diff --git a/drydock/templates/kustomized/tutor13/extensions/flowers.yml b/drydock/templates/kustomized/tutor13/extensions/flowers.yml new file mode 100644 index 00000000..13172355 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/extensions/flowers.yml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: flower-edxapp + labels: + app.kubernetes.io/name: flower-edxapp +spec: + selector: + matchLabels: + app.kubernetes.io/name: flower-edxapp + template: + metadata: + labels: + app.kubernetes.io/name: flower-edxapp + spec: + containers: + - name: flower-edxapp + image: docker.io/mher/flower:0.9.5 + ports: + - containerPort: 5555 + env: + - name: CELERY_BROKER_URL + value: redis://{{ REDIS_USERNAME }}:{{ REDIS_PASSWORD }}@{{ REDIS_HOST }}:{{ REDIS_PORT }}/{{ OPENEDX_CELERY_REDIS_DB }} + - name: FLOWER_PORT + value: "5555" diff --git a/drydock/templates/kustomized/tutor13/extensions/hpa.yml b/drydock/templates/kustomized/tutor13/extensions/hpa.yml new file mode 100644 index 00000000..640a36ed --- /dev/null +++ b/drydock/templates/kustomized/tutor13/extensions/hpa.yml @@ -0,0 +1,57 @@ +--- +apiVersion: v1 +kind: List +metadata: {} +items: +- apiVersion: autoscaling/v1 + kind: HorizontalPodAutoscaler + metadata: + name: hpa-lms-deployment + namespace: {{ K8S_NAMESPACE }} + spec: + maxReplicas: {{ DRYDOCK_LMS_MAX_REPLICAS }} + minReplicas: {{ DRYDOCK_LMS_MIN_REPLICAS }} + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: lms + targetCPUUtilizationPercentage: {{ DRYDOCK_LMS_TARGET_CPU }} +- apiVersion: autoscaling/v1 + kind: HorizontalPodAutoscaler + metadata: + name: hpa-cms-deployment + namespace: {{ K8S_NAMESPACE }} + spec: + maxReplicas: {{ DRYDOCK_CMS_MAX_REPLICAS }} + minReplicas: {{ DRYDOCK_CMS_MIN_REPLICAS }} + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: cms + targetCPUUtilizationPercentage: {{ DRYDOCK_CMS_TARGET_CPU }} +- apiVersion: autoscaling/v1 + kind: HorizontalPodAutoscaler + metadata: + name: hpa-lms-worker-deployment + namespace: {{ K8S_NAMESPACE }} + spec: + maxReplicas: {{ DRYDOCK_LMS_WORKERS_MAX_REPLICAS }} + minReplicas: {{ DRYDOCK_LMS_WORKERS_MIN_REPLICAS }} + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: lms-worker + targetCPUUtilizationPercentage: {{ DRYDOCK_LMS_WORKERS_TARGET_CPU }} +- apiVersion: autoscaling/v1 + kind: HorizontalPodAutoscaler + metadata: + name: hpa-cms-worker-deployment + namespace: {{ K8S_NAMESPACE }} + spec: + maxReplicas: {{ DRYDOCK_CMS_WORKERS_MAX_REPLICAS }} + minReplicas: {{ DRYDOCK_CMS_WORKERS_MIN_REPLICAS }} + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: cms-worker + targetCPUUtilizationPercentage: {{ DRYDOCK_CMS_WORKERS_TARGET_CPU }} diff --git a/drydock/templates/kustomized/tutor13/extensions/ingress.yml b/drydock/templates/kustomized/tutor13/extensions/ingress.yml new file mode 100644 index 00000000..8332e54e --- /dev/null +++ b/drydock/templates/kustomized/tutor13/extensions/ingress.yml @@ -0,0 +1,70 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress + namespace: {{ K8S_NAMESPACE }} + annotations: + kubernetes.io/ingress.class: nginx + {%- if ENABLE_HTTPS %} + cert-manager.io/issuer: letsencrypt + {%- endif %} +spec: + rules: + {%- for host in [LMS_HOST, PREVIEW_LMS_HOST, CMS_HOST] %} + - host: {{ host }} + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: caddy + port: + number: 80 + {%- endfor %} + {%- for host in DRYDOCK_INGRESS_EXTRA_HOSTS %} + - host: {{ host }} + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: caddy + port: + number: 80 + {%- endfor %} + {% if ENABLE_HTTPS -%} + tls: + - hosts: + {%- for host in [LMS_HOST, PREVIEW_LMS_HOST, CMS_HOST] %} + - {{ host }} + {%- endfor %} + {%- for host in DRYDOCK_INGRESS_EXTRA_HOSTS %} + - {{ host }} + {%- endfor %} + secretName: {{ K8S_NAMESPACE }}-tls + {%- endif %} +{% if ENABLE_HTTPS -%} +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: letsencrypt + namespace: {{ K8S_NAMESPACE }} + labels: + app.kubernetes.io/name: letsencrypt +spec: + acme: + # Let's Encrypt will use this to contact you about expiring + # certificates, and issues related to your account. + email: {{ DRYDOCK_LETSENCRYPT_EMAIL|default(CONTACT_EMAIL) }} + # Secret resource that will be used to store the account's private key. + privateKeySecretRef: + name: {{ K8S_NAMESPACE }}-letsencrypt-account-key + server: https://acme-v02.api.letsencrypt.org/directory + solvers: + - http01: + ingress: + class: nginx +{% endif -%} diff --git a/drydock/templates/kustomized/tutor13/extensions/kustomization.yml b/drydock/templates/kustomized/tutor13/extensions/kustomization.yml new file mode 100644 index 00000000..40d01510 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/extensions/kustomization.yml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +{% if DRYDOCK_FLOWERS|default(false) -%} +- flowers.yml +{% endif -%} +{% if DRYDOCK_INGRESS|default(false) -%} +- ingress.yml +{% endif -%} +{% if DRYDOCK_HPA|default(false) and DRYDOCK_ENABLE_OVERRIDES|default(false) -%} +- hpa.yml +{% endif -%} diff --git a/drydock/templates/kustomized/tutor13/extensions/overrides.yml b/drydock/templates/kustomized/tutor13/extensions/overrides.yml new file mode 100644 index 00000000..b24d51e2 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/extensions/overrides.yml @@ -0,0 +1,71 @@ +{%- if DRYDOCK_ENABLE_RESOURCE_MANAGEMENT -%} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lms +spec: + template: + spec: + containers: + - name: lms + resources: + limits: + cpu: "{{ DRYDOCK_LMS_LIMIT_CPU }}" + memory: "{{ DRYDOCK_LMS_LIMIT_MEMORY }}" + requests: + cpu: "{{ DRYDOCK_LMS_REQUEST_CPU }}" + memory: "{{ DRYDOCK_LMS_REQUEST_MEMORY }}" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cms +spec: + template: + spec: + containers: + - name: cms + resources: + limits: + cpu: "{{ DRYDOCK_CMS_LIMIT_CPU }}" + memory: "{{ DRYDOCK_CMS_LIMIT_MEMORY }}" + requests: + cpu: "{{ DRYDOCK_CMS_REQUEST_CPU }}" + memory: "{{ DRYDOCK_CMS_REQUEST_MEMORY }}" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: lms-worker +spec: + template: + spec: + containers: + - name: lms-worker + resources: + limits: + cpu: "{{ DRYDOCK_LMS_WORKER_LIMIT_CPU }}" + memory: "{{ DRYDOCK_LMS_WORKER_LIMIT_MEMORY }}" + requests: + cpu: "{{ DRYDOCK_LMS_WORKER_REQUEST_CPU }}" + memory: "{{ DRYDOCK_LMS_WORKER_REQUEST_MEMORY }}" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cms-worker +spec: + template: + spec: + containers: + - name: cms-worker + resources: + limits: + cpu: "{{ DRYDOCK_CMS_WORKER_LIMIT_CPU }}" + memory: "{{ DRYDOCK_CMS_WORKER_LIMIT_MEMORY }}" + requests: + cpu: "{{ DRYDOCK_CMS_WORKER_REQUEST_CPU }}" + memory: "{{ DRYDOCK_CMS_WORKER_REQUEST_MEMORY }}" +--- +{% endif -%} +{{ patch("drydock-overrides") }} diff --git a/drydock/templates/kustomized/tutor13/kustomization.yml b/drydock/templates/kustomized/tutor13/kustomization.yml new file mode 100644 index 00000000..0d104bb0 --- /dev/null +++ b/drydock/templates/kustomized/tutor13/kustomization.yml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- base +- extensions +{{ patch("drydock-kustomization-resources") }} + +{%- if DRYDOCK_ENABLE_OVERRIDES %} +patchesStrategicMerge: +{%- if DRYDOCK_ENABLE_RESOURCE_MANAGEMENT or patch("drydock-overrides") %} +- extensions/overrides.yml +{%- endif %} +{{ patch("drydock-kustomization-patches") }} +{%- endif %}