Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/environments/enabled/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ DRYDOCK_PDB_MINAVAILABLE_PERCENTAGE_LMS_WORKER: 50
DRYDOCK_PDB_MINAVAILABLE_PERCENTAGE_CMS: 50
DRYDOCK_PDB_MINAVAILABLE_PERCENTAGE_CMS_WORKER: 50
DRYDOCK_MIGRATE_FROM: 13
DRYDOCK_EDGE_PROXY_ENABLED: true
LMS_HOST: local.edly.io
CMS_HOST: studio.local.edly.io
MFE_HOST: apps.local.edly.io
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/config-files-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ jobs:
- name: Setup Tutor environment
run: |
echo "TUTOR_ROOT=$GITHUB_WORKSPACE/.github/environments/${{ matrix.environment }}" >> $GITHUB_ENV
echo "TUTOR_PLUGINS_ROOT=$TUTOR_ROOT/plugins" >> $GITHUB_ENV

- name: Tutor config save
run: |
Expand All @@ -72,6 +71,16 @@ jobs:
caddy:2 \
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile

- name: Validate generated edge-proxy Caddyfile
run: |
EDGE_PROXY_CADDYFILE="$TUTOR_ROOT/env/plugins/drydock/edge-proxy/Caddyfile"
if [ -f "$EDGE_PROXY_CADDYFILE" ]; then
docker run --rm \
-v "$EDGE_PROXY_CADDYFILE:/etc/caddy/Caddyfile:ro" \
caddy:2 \
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
fi

- name: Print versions
run: |
echo "Kubectl version installed:"
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ See the fragment files in the [changelog.d/ directory](./changelog.d).

<!-- scriv-insert-here -->

<a id='changelog-22.2.0'></a>
## 22.2.0 — 2026-10-06

### Added

- DaemonSets can now use image secrets to pull private container images.

- Individual init jobs can now be excluded from execution.

- A customizable edge reverse proxy layer can be enabled by the
`DRYDOCK_EDGE_PROXY_ENABLED` setting. the proxy sits in front of OpenEdX's
Caddy instance and automatically updates Kubernetes Ingress routing when
enabled. It serves a built-in 503 maintenance page by default for scheduled
downtime and supports full Caddyfile overrides via the
`drydock-edge-proxy-caddyfile` Tutor patch for custom needs.

<a id='changelog-22.1.0'></a>
## 22.1.0 — 2026-08-13

Expand Down
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,62 @@ The following configuration options are available:
- `DRYDOCK_PDB_MINAVAILABLE_PERCENTAGE_CMS`: The minimum available percentage for the CMS's PodDisruptionBudget. To disable the PodDisruptionBudget, set `0`. Defaults to `0`.
- `DRYDOCK_PDB_MINAVAILABLE_PERCENTAGE_CMS_WORKER`: The minimum available percentage for the worker's PodDisruptionBudget. To disable the PodDisruptionBudget, set `0`. Defaults to `0`.
- `DRYDOCK_MIGRATE_FROM`: it allows defining the version of the OpenedX platform we are migrating from. It accepts the integer value mapping the origin release, for instance, `13`(maple) or `14`(nutmeg). When this variable is set, a group of `release-specific upgrade jobs` are added to the Kubernetes manifests. These jobs are applied to the cluster in a suitable order (thanks to the GitOps implementation with ArgoCD + sync waves) to guarantee the correct behavior of the platform in the new version. This brings the `tutor k8s upgrade <https://github.com/overhangio/tutor/blob/v15.3.7/tutor/commands/k8s.py#L484>`_ command to the GitOps pattern. The release-specific upgrade jobs are supported from release `13`(maple). Defaults to `0` (which disables release-specific upgrade jobs)
- `DRYDOCK_EDGE_PROXY_ENABLED`: Whether to deploy a dedicated edge proxy (Caddy) and redirect all Ingress backends to it. Defaults to `false`.

> **_NOTE:_** You also need to set `DRYDOCK_INIT_JOBS` to `true` to enable the
> release-specific upgrade jobs in the case of a platform migration.

Edge Proxy
----------

When `DRYDOCK_EDGE_PROXY_ENABLED` is `true` and `DRYDOCK_INGRESS` is enabled,
Drydock deploys an `edge-proxy` service (Caddy) and redirects **all** Ingress
backends (LMS, Studio, MFE, Notes, Meilisearch, and extra hosts) to it.

By default the edge proxy simply serves a static HTML page with a `503` status
code. The patch `drydock-edge-proxy-caddyfile` can be used to construct a
Caddyfile from scratch for the edge-proxy instead of the static HTML page with
custom routing rules.

An example on how to use the patch is as follows:

```python
from tutor import hooks

CADDYFILE_CONTENT = """
{
servers {
trusted_proxies static 10.0.0.0/8 private_ranges
}
}
:80 {
log {
output stdout
format json
}
@allowed client_ip 104.20.23.154
handle @allowed {
reverse_proxy caddy:80
}

@redirect_paths path /login /login/
handle @redirect_paths {
redir https://www.google.com permanent
}

handle {
respond "Under maintenance" 503
}
}
"""
hooks.Filters.ENV_PATCHES.add_items([("drydock-edge-proxy-caddyfile", CADDYFILE_CONTENT)])
```

> **_NOTE:_** You also need to set `DRYDOCK_INIT_JOBS` to `true` to enable the release-specific upgrade jobs in the case of a platform migration.
This configuration will show the maintenance page to everyone besides the user
with ip `104.20.23.154` and will redirect to google when accesing `/login` and
`/login/`. For ingress objects not handled by drydock and/or when
`DRYDOCK_INGRESS=False` the ingress will need to be modified to forward to the
edge-proxy service.

Job generation
--------------
Expand Down
29 changes: 0 additions & 29 deletions changelog.d/20260924_163701_piotr_daemonset_image_secrets.md

This file was deleted.

This file was deleted.

30 changes: 30 additions & 0 deletions drydock/patches/k8s-deployments
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{% if DRYDOCK_EDGE_PROXY_ENABLED %}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: edge-proxy
labels:
app.kubernetes.io/name: edge-proxy
spec:
selector:
matchLabels:
app.kubernetes.io/name: edge-proxy
template:
metadata:
labels:
app.kubernetes.io/name: edge-proxy
spec:
containers:
- name: caddy
image: {{ DOCKER_IMAGE_CADDY }}
volumeMounts:
- mountPath: /etc/caddy/
name: config
ports:
- containerPort: 80
volumes:
- name: config
configMap:
name: edge-proxy-config
{% endif %}
16 changes: 16 additions & 0 deletions drydock/patches/k8s-services
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% if DRYDOCK_EDGE_PROXY_ENABLED %}
---
apiVersion: v1
kind: Service
metadata:
name: edge-proxy
labels:
app.kubernetes.io/name: edge-proxy
spec:
type: ClusterIP
ports:
- port: 80
name: http
selector:
app.kubernetes.io/name: edge-proxy
{% endif %}
8 changes: 8 additions & 0 deletions drydock/patches/kustomization-configmapgenerator
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@
labels:
app.kubernetes.io/name: openedx
{% endif -%}
{%- if DRYDOCK_EDGE_PROXY_ENABLED and DRYDOCK_INGRESS %}
- name: edge-proxy-config
files:
- plugins/drydock/edge-proxy/Caddyfile
options:
labels:
app.kubernetes.io/name: edge-proxy
{%- endif %}
2 changes: 2 additions & 0 deletions drydock/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
("DRYDOCK_INGRESS_LMS_EXTRA_HOSTS", []),
("DRYDOCK_NEWRELIC_LICENSE_KEY", ""),
("DRYDOCK_CUSTOM_CERTS", {}),
("DRYDOCK_EDGE_PROXY_ENABLED", False),
("DRYDOCK_LETSENCRYPT_EMAIL", "{{ CONTACT_EMAIL }}"),
("DRYDOCK_ENABLE_MULTITENANCY", True),
("DRYDOCK_ENABLE_SCORM", True),
Expand Down Expand Up @@ -189,6 +190,7 @@ def get_sync_waves_for_resource(resource_name: str) -> int:
("drydock/build", "plugins"),
("drydock/apps", "plugins"),
("drydock/k8s", "plugins"),
("drydock/edge-proxy", "plugins"),
],
)
# Load all patches from the "patches" folder
Expand Down
50 changes: 50 additions & 0 deletions drydock/templates/drydock/edge-proxy/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{% if patch("drydock-edge-proxy-caddyfile") -%}
{{ patch("drydock-edge-proxy-caddyfile") }}
{%- else -%}
:80 {
handle {
header Content-Type "text/html; charset=utf-8"
respond 503 {
body <<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Under maintenance</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
background: #f4f4f9;
color: #333;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
text-align: center;
}
.container {
background: #fff;
padding: 2.5rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, .1);
max-width: 480px;
width: 90%;
}
h1 { margin: 0 0 .5rem; }
p { color: #666; line-height: 1.5; }
</style>
</head>
<body>
<div class="container">
<h1>Under maintenance</h1>
<p>We are performing scheduled maintenance and will be back online shortly.</p>
</div>
</body>
</html>
HTML
}
}
}
{%- endif %}
6 changes: 3 additions & 3 deletions drydock/templates/drydock/k8s/ingress/cms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ spec:
path: "/course-authoring"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
- pathType: Prefix
path: "/{{app_name}}"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{%- endif %}
Expand All @@ -38,7 +38,7 @@ spec:
path: "/"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{%- if DRYDOCK_AUTO_TLS or DRYDOCK_CUSTOM_CERTS %}
Expand Down
2 changes: 1 addition & 1 deletion drydock/templates/drydock/k8s/ingress/extra-hosts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ spec:
path: "/"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{%- if DRYDOCK_AUTO_TLS or DRYDOCK_CUSTOM_CERTS %}
Expand Down
12 changes: 6 additions & 6 deletions drydock/templates/drydock/k8s/ingress/lms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ spec:
path: "/learning"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{%- if MFE_DOCKER_IMAGE is defined %}
Expand All @@ -28,7 +28,7 @@ spec:
path: "/{{app_name}}"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{%- endif %}
Expand All @@ -38,7 +38,7 @@ spec:
path: "/"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{{ patch("drydock-lms-extra-paths")|indent(6) }}
Expand All @@ -50,7 +50,7 @@ spec:
path: "/learning"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{%- if MFE_DOCKER_IMAGE is defined %}
Expand All @@ -59,7 +59,7 @@ spec:
path: "/{{app_name}}"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{%- endfor %}
Expand All @@ -68,7 +68,7 @@ spec:
path: "/"
backend:
service:
name: caddy
name: {{ "edge-proxy" if DRYDOCK_EDGE_PROXY_ENABLED else "caddy" }}
port:
number: 80
{{ patch("drydock-lms-extra-paths")|indent(6) }}
Expand Down
Loading
Loading