Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kubernetes support #809

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.github/*
.tx/*
conf/*
doc/*
envs/*
res/*
tools/*
.gitignore
.travis
Procfile
run.sh
37 changes: 37 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
FROM python:3.9-buster as base

EXPOSE 8000
STOPSIGNAL SIGTERM
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# ENV DEBUG 1

RUN apt-get update
RUN apt-get install -y libpq-dev libjpeg-dev zlib1g-dev libwebp-dev libffi-dev

# copy source and install dependencies
RUN mkdir -p /app

WORKDIR /app

COPY . /app
RUN python -m pip install -r requirements.txt

FROM base as dev

RUN python -m pip install -r requirements-dev.txt
RUN make dev-config

CMD ["envdir", "envs/dev", "python", "manage.py", "runserver", "0.0.0.0:8000"]

FROM dev as test

RUN python -m pip install -r requirements-test.txt

CMD ["make", "test"]

FROM base as production

RUN python -m pip install -r requirements-kubernetes.txt

CMD ["python", "-m", "kubernetes_wsgi", "mygpo.wsgi", "--port", "8000"]
1 change: 1 addition & 0 deletions doc/dev/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Contents
:maxdepth: 1

installation
kubernetes
postgres-setup
libraries
configuration
Expand Down
29 changes: 29 additions & 0 deletions doc/dev/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,32 @@ directory with

If you want to run a production server, check out `Deploying Django
<https://docs.djangoproject.com/en/dev/howto/deployment/>`_.

Running with Docker
-------------------

There is a multi-stage docker definition. To build the image for local testing run

.. code-block:: bash

docker build --target dev -t gpodder.dev -f Dockerfile .

This will build the `dev` stage.

Next, you need to define the configuration to be passed into the container. The simplest is to run

.. code-block:: bash

make dev-config

Next, you need to run the migrations:

.. code-block:: bash

docker run -ti -v ${PWD}/envs/dev:/app/envs/dev gpodder.dev envdir envs/dev python manage.py migrate

Finally, you can start the development server:

.. code-block:: bash

docker run --rm -ti -v ${PWD}/envs/dev:/app/envs/dev gpodder.dev
212 changes: 212 additions & 0 deletions doc/dev/kubernetes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
Deploying to Kubernetes
========================

The provided docker file is ready to be built and deployed to Kubernetes.

At this stage, the project does not provide pre-build containers, but PR to add it to GitHub actions are welcome.

This section assumes that you built the `production` docker layer and have it in your container registry and you know how to deploy your YAML manifests to Kubernetes (e.g with FluxCD).

There are many ways to store the necessary secrets, configuration and everything in Kubernetes. This guide provides the bare minimum.

Configurations
---------------

```yaml
# Based on https://gpoddernet.readthedocs.io/en/latest/dev/configuration.html#configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: gpodder-app-config
data:
DJANGO_CONFIGURATION: Prod
ADMINS: 'Your Name <you@example.com>'
ALLOWED_HOSTS: '*'
DEFAULT_BASE_URL: 'https://gpodder.example.com'
# GOOGLE_ANALYTICS_PROPERTY_ID
# MAINTENANCE
DEBUG: "true"
DEFAULT_FROM_EMAIL: "daemon@example.com"
SERVER_EMAIL: "daemon@example.com"
BROKER_POOL_LIMIT: "10"
# CACHE_BACKEND: "django.core.cache.backends.db.DatabaseCache"
# ACCOUNT_ACTIVATION_DAYS
PODCAST_SLUG_SUBSCRIBER_LIMIT: "1"
MIN_SUBSCRIBERS_CATEGORY: "1"
# INTERNAL_IPS:
```

Secrets
--------

```yaml
apiVersion: v1
kind: Secret
metadata:
name: gpodder
spec:
encryptedData:
BROKER_URL: <base64 encoded data>
EMAIL_HOST: <base64 encoded data>
EMAIL_HOST_PASSWORD: <base64 encoded data>
EMAIL_HOST_USER: <base64 encoded data>
SECRET_KEY: <base64 encoded data>
STAFF_TOKEN: <base64 encoded data>
SUPPORT_URL: <base64 encoded data>
DATABASE_URL: <base64 encoded data>
AWS_ACCESS_KEY_ID: <base64 encoded data>
AWS_S3_ENDPOINT_URL: <base64 encoded data>
AWS_S3_ENDPOINT_URL: <base64 encoded data>

Jobs
----

As Kubernetes Jobs are immutable. It's up to you how you re-run them on changes. This guide does not help with it. A possible approach [using FluxCD is described here](https://fluxcd.io/flux/use-cases/running-jobs/.

```yaml
apiVersion: batch/v1
kind: Job
metadata:
name: collectstatics
labels:
app.kubernetes.io/component: collectstatics
spec:
template:
spec:
serviceAccountName: gpodder
containers:
- name: gpodder-migrate
image: registry.gitlab.com/nagyv/gpodder/gpodder:latest
command: ["python", "manage.py", "collectstatic", "--no-input"]
envFrom:
- secretRef:
name: gpodder
- configMapRef:
name: gpodder-app-config
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
restartPolicy: Never
```

```yaml
apiVersion: batch/v1
kind: Job
metadata:
name: gpodder-migrate
labels:
app.kubernetes.io/component: db-migrate
spec:
# ttlSecondsAfterFinished does not work well with GitOps as the Job is deleted
# ttlSecondsAfterFinished: 200
template:
metadata:
labels:
app.kubernetes.io/component: db-migrate
spec:
serviceAccountName: gpodder
containers:
- name: gpodder-migrate
image: registry.gitlab.com/nagyv/gpodder/gpodder:latest
command: ["python", "manage.py", "migrate"]
envFrom:
- secretRef:
name: gpodder
- configMapRef:
name: gpodder-app-config
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
restartPolicy: Never
```

Deployment
-----------

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpodder
labels:
app.kubernetes.io/component: webapp
spec:
selector:
matchLabels:
app.kubernetes.io/component: webapp
template:
metadata:
labels:
app.kubernetes.io/component: webapp
spec:
serviceAccountName: gpodder
containers:
- name: gpodder
image: registry.example.com/gpodder/gpodder:latest
imagePullPolicy: Always
resources: {}
# limits:
# memory: "128Mi"
# cpu: "500m"
# livenessProbe:
# httpGet:
# path: /ht/
# port: 8000
# httpHeaders:
# - name: Host
# value: gpodder.nagyv.com
# initialDelaySeconds: 15
# periodSeconds: 10
# successThreshold: 1
# failureThreshold: 2
# timeoutSeconds: 3
readinessProbe:
httpGet:
path: /ht/
port: 8000
httpHeaders:
- name: Host
value: gpodder.nagyv.com
initialDelaySeconds: 10
timeoutSeconds: 3
ports:
- containerPort: 8000
envFrom:
- secretRef:
name: gpodder
- configMapRef:
name: gpodder-app-config
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
securityContext:
{}
```

Service
-------

```yaml
apiVersion: v1
kind: Service
metadata:
name: gpodder
spec:
selector: {}
ports:
- port: 80
targetPort: 8000
protocol: TCP
```
20 changes: 15 additions & 5 deletions manage.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mygpo.settings")

from django.core.management import execute_from_command_line

def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mygpo.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'Base')
try:
from configurations.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

if __name__ == '__main__':
main()
3 changes: 2 additions & 1 deletion mygpo/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

import os

from django.core.asgi import get_asgi_application
from configurations.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mygpo.settings")
os.environ.setdefault('DJANGO_CONFIGURATION', 'Prod')

application = get_asgi_application()
4 changes: 4 additions & 0 deletions mygpo/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mygpo.settings")
os.environ.setdefault('DJANGO_CONFIGURATION', 'Prod')

import configurations
configurations.setup()

celery = Celery("mygpo.celery")
celery.config_from_object("django.conf:settings", namespace="CELERY")
Expand Down
Loading